@@ -4,14 +4,38 @@ import (
44 "context"
55 "errors"
66 "fmt"
7- "slices "
7+ "sync "
88
99 "github.com/cloudquery/plugin-sdk/v4/schema"
1010 "github.com/cloudquery/plugin-sdk/v4/transformers"
1111 "github.com/yandex-cloud/cq-source-yc/client"
1212 "github.com/yandex-cloud/go-genproto/yandex/cloud/quotamanager/v1"
1313)
1414
15+ type servicesCacher struct {
16+ responses sync.Map
17+ }
18+
19+ func (sc * servicesCacher ) Get (ctx context.Context , client * client.Client , resourceType client.ResourceType ) ([]* quotamanager.Service , error ) {
20+ if val , ok := sc .responses .Load (resourceType ); ok {
21+ return val .([]* quotamanager.Service ), nil
22+ }
23+
24+ result , err := client .SDK .QuotaManager ().QuotaLimit ().ListServices (ctx , & quotamanager.ListServicesRequest {
25+ ResourceType : string (resourceType ),
26+ // HACK: if services > 1000 this would work incorrectly
27+ PageSize : 1000 ,
28+ })
29+ if err != nil {
30+ return nil , err
31+ }
32+
33+ sc .responses .Store (resourceType , result .Services )
34+ return result .Services , nil
35+ }
36+
37+ var cacher = servicesCacher {}
38+
1539type QuotaLimit struct {
1640 Resource * quotamanager.Resource
1741 * quotamanager.QuotaLimit
@@ -26,59 +50,51 @@ func QuotaLimits() *schema.Table {
2650 transformers .WithUnwrapStructFields ("Resource" , "QuotaLimit" ),
2751 transformers .WithPrimaryKeys ("Resource.Id" , "QuotaId" ),
2852 ),
53+ Multiplex : client .CombineMultiplex (client .OrganizationMultiplex , client .CloudMultiplex ),
2954 }
3055}
3156
3257func fetchQuotaLimits (ctx context.Context , meta schema.ClientMeta , parent * schema.Resource , res chan <- interface {}) error {
33- service , ok := parent .Item .(* Service )
34- if ! ok {
35- return fmt .Errorf ("failed to extract service id from parent %T (%s)" , parent .Item , parent .Item )
58+ c := meta .(* client.Client )
59+
60+ if c .MultiplexedResourceId == "" {
61+ return fmt .Errorf ("client must be multiplexed by resource hierarchy entity (multiplexed resource id is empty)" )
62+ }
63+ if c .MultiplexedResourceType == "" {
64+ return fmt .Errorf ("client must be multiplexed by resource hierarchy entity (multiplexed resource type is empty) " )
3665 }
3766
38- var joinedErr error
39- // HACK: artificial child + multiplex: service (from parent) -> resource (from multiplex).
40- // When using `schema.Tables/Relates`, the child table doesn't use multiplexing (feature), so we are doing it manually.
41- // e.g. we want to resolve all `Service{Id: "vpc"}` quotas for `quotamanager.Resource{Id: "bg...", Type: "resource-manager.cloud"}`
42- for _ , cmeta := range client .CombineMultiplex (client .OrganizationMultiplex , client .CloudMultiplex )(meta ) {
43- c := cmeta .(* client.Client )
44-
45- resourceId := c .MultiplexedResourceId
46- if resourceId == "" {
47- return fmt .Errorf ("client must be multiplexed by resource hierarchy entity (multiplexed resource id is empty)" )
48- }
67+ // HACK: artificial (resource, service) multiplex: (Organizations + Clouds) ✕ service (from API call).
68+ // Bad concurrency as CQ SDK limits apply per multiplexer entity – resource in our case,
69+ // so concurrency key would be (resource), hence if services count is big, it would be slow
4970
50- resourceType := c .MultiplexedResourceType
51- if resourceType == "" {
52- return fmt .Errorf ("client must be multiplexed by resource hierarchy entity (multiplexed resource type is empty) " )
53- }
71+ // Don't want to call rarely-changing response API per each org or cloud
72+ services , err := cacher .Get (ctx , c , c .MultiplexedResourceType )
73+ if err != nil {
74+ return fmt .Errorf ("failed to fetch quota-enabled services for %s" , c .MultiplexedResourceType )
75+ }
5476
55- if slices .Index (quotaEnabledResourceTypes , resourceType ) == - 1 {
56- return fmt .Errorf ("%T (%s) is not a quota-enabled service" , resourceType , resourceType )
57- }
77+ resource := & quotamanager.Resource {
78+ Id : c .MultiplexedResourceId ,
79+ Type : string (c .MultiplexedResourceType ),
80+ }
5881
82+ var joinedErr error
83+ for _ , service := range services {
5984 it := c .SDK .QuotaManager ().QuotaLimit ().QuotaLimitIterator (ctx , & quotamanager.ListQuotaLimitsRequest {
60- Resource : & quotamanager.Resource {
61- Id : resourceId ,
62- Type : string (resourceType ),
63- },
64- Service : service .Id ,
85+ Resource : resource ,
86+ Service : service .Id ,
6587 })
6688
6789 for it .Next () {
68- ql := it .Value ()
69-
7090 res <- & QuotaLimit {
71- Resource : & quotamanager.Resource {
72- Id : resourceId ,
73- Type : string (resourceType ),
74- },
75- QuotaLimit : ql ,
91+ Resource : resource ,
92+ QuotaLimit : it .Value (),
7693 }
7794 }
7895
7996 if err := it .Error (); err != nil {
8097 // continue iterating because of artificial multiplex
81- c .Logger .Err (err )
8298 joinedErr = errors .Join (joinedErr , err )
8399 }
84100 }
0 commit comments