Skip to content

Commit 007421a

Browse files
authored
feat(azure-resources): Add Consumption Resources (#9117)
#### Summary Fixes #8929. ### Notes - The consumption APIs require a `scope` argument that can be a subscription, billing account, billing account, enrollment account, and more. See example in https://learn.microsoft.com/en-us/rest/api/consumption/usage-details/list?tabs=HTTP#uri-parameters. We could support all scopes, but I'm starting with only billing account, profiles and subscriptions, based on the API so the result will have the "Modern" or "Legacy" struct returned (where applicable). - Some APIs return 404 or 204 (no content) where there's no data, so I'm logging these errors instead of reporting them <!--
1 parent 53cbd9a commit 007421a

File tree

73 files changed

+2389
-78
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2389
-78
lines changed

plugins/source/azure/client/client.go

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
1212
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
1313
"github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
14+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
1415
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
16+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billing/armbilling"
1517
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
1618
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription"
1719
"github.com/cloudquery/plugin-sdk/v2/plugins/source"
@@ -42,7 +44,12 @@ type Client struct {
4244
Creds azcore.TokenCredential
4345
Options *arm.ClientOptions
4446

45-
pluginSpec *Spec
47+
pluginSpec *Spec
48+
BillingAccounts []*armbilling.Account
49+
BillingAccount *armbilling.Account
50+
BillingProfile *armbilling.Profile
51+
BillingPeriods map[string][]*armbilling.Period
52+
BillingPeriod *armbilling.Period
4653
}
4754

4855
func (c *Client) discoverSubscriptions(ctx context.Context) error {
@@ -114,6 +121,63 @@ func (c *Client) getRegisteredProvidersForSubscription(ctx context.Context, subs
114121
return providers, nil
115122
}
116123

124+
func (c *Client) discoverBillingAccounts(ctx context.Context) error {
125+
accounts := make([]*armbilling.Account, 0)
126+
svc, err := armbilling.NewAccountsClient(c.Creds, c.Options)
127+
if err != nil {
128+
return err
129+
}
130+
pager := svc.NewListPager(&armbilling.AccountsClientListOptions{Expand: to.Ptr("soldTo,billingProfiles,billingProfiles/invoiceSections")})
131+
for pager.More() {
132+
p, err := pager.NextPage(ctx)
133+
if err != nil {
134+
return err
135+
}
136+
accounts = append(accounts, p.Value...)
137+
}
138+
c.BillingAccounts = accounts
139+
return nil
140+
}
141+
142+
func (c *Client) discoverBillingPeriods(ctx context.Context) error {
143+
billingPeriods := make(map[string][]*armbilling.Period, len(c.subscriptions))
144+
errorGroup, gtx := errgroup.WithContext(ctx)
145+
errorGroup.SetLimit(c.pluginSpec.DiscoveryConcurrency)
146+
147+
periodsLock := sync.Mutex{}
148+
149+
for _, subID := range c.subscriptions {
150+
subID := subID
151+
errorGroup.Go(func() error {
152+
periods := make([]*armbilling.Period, 0)
153+
svc, err := armbilling.NewPeriodsClient(subID, c.Creds, c.Options)
154+
if err != nil {
155+
return err
156+
}
157+
pager := svc.NewListPager(nil)
158+
for pager.More() {
159+
p, err := pager.NextPage(gtx)
160+
if err != nil {
161+
return err
162+
}
163+
periods = append(periods, p.Value...)
164+
}
165+
166+
periodsLock.Lock()
167+
defer periodsLock.Unlock()
168+
billingPeriods[subID] = periods
169+
170+
return nil
171+
})
172+
}
173+
err := errorGroup.Wait()
174+
if err != nil {
175+
return err
176+
}
177+
c.BillingPeriods = billingPeriods
178+
return nil
179+
}
180+
117181
func (c *Client) discoverResourceGroups(ctx context.Context) error {
118182
c.ResourceGroups = make(map[string][]*armresources.ResourceGroup, len(c.subscriptions))
119183
c.registeredNamespaces = make(map[string]map[string]bool, len(c.subscriptions))
@@ -233,6 +297,14 @@ func New(ctx context.Context, logger zerolog.Logger, s specs.Source, _ source.Op
233297
return nil, err
234298
}
235299

300+
if err := c.discoverBillingAccounts(ctx); err != nil {
301+
c.logger.Warn().Err(err).Msg("failed to discover billing accounts (skipping)")
302+
}
303+
304+
if err := c.discoverBillingPeriods(ctx); err != nil {
305+
c.logger.Warn().Err(err).Msg("failed to discover billing periods (skipping)")
306+
}
307+
236308
return c, nil
237309
}
238310

@@ -244,6 +316,15 @@ func (c *Client) ID() string {
244316
if c.ResourceGroup != "" {
245317
return fmt.Sprintf("subscriptions/%s/resourceGroups/%s", c.SubscriptionId, c.ResourceGroup)
246318
}
319+
if c.BillingProfile != nil {
320+
return fmt.Sprintf("billingAccounts/%s/billingProfiles/%s", *c.BillingAccount.Name, *c.BillingProfile.Name)
321+
}
322+
if c.BillingAccount != nil {
323+
return fmt.Sprintf("billingAccounts/%s", *c.BillingAccount.Name)
324+
}
325+
if c.BillingPeriod != nil {
326+
return fmt.Sprintf("subscriptions/%s/billingPeriods/%s", c.SubscriptionId, *c.BillingPeriod.Name)
327+
}
247328
return fmt.Sprintf("subscriptions/%s", c.SubscriptionId)
248329
}
249330

@@ -261,3 +342,24 @@ func (c *Client) withResourceGroup(resourceGroup string) *Client {
261342
newC.ResourceGroup = resourceGroup
262343
return &newC
263344
}
345+
346+
func (c *Client) withBillingAccount(billingAccount *armbilling.Account) *Client {
347+
newC := *c
348+
newC.logger = c.logger.With().Str("billing_account", *billingAccount.ID).Logger()
349+
newC.BillingAccount = billingAccount
350+
return &newC
351+
}
352+
353+
func (c *Client) withBillingProfile(billingProfile *armbilling.Profile) *Client {
354+
newC := *c
355+
newC.logger = c.logger.With().Str("billing_profile", *billingProfile.ID).Logger()
356+
newC.BillingProfile = billingProfile
357+
return &newC
358+
}
359+
360+
func (c *Client) withBillingPeriod(billingPeriod *armbilling.Period) *Client {
361+
newC := *c
362+
newC.logger = c.logger.With().Str("billing_period", *billingPeriod.ID).Logger()
363+
newC.BillingPeriod = billingPeriod
364+
return &newC
365+
}

plugins/source/azure/client/multiplexer.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package client
22

33
import (
4+
"strings"
5+
6+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billing/armbilling"
47
"github.com/cloudquery/plugin-sdk/v2/schema"
58
)
69

@@ -52,3 +55,64 @@ func SubscriptionMultiplex(meta schema.ClientMeta) []schema.ClientMeta {
5255
}
5356
return c
5457
}
58+
59+
func BillingAccountMultiplex(meta schema.ClientMeta) []schema.ClientMeta {
60+
client := meta.(*Client)
61+
var c = make([]schema.ClientMeta, len(client.BillingAccounts))
62+
for i := range client.BillingAccounts {
63+
c[i] = client.withBillingAccount(client.BillingAccounts[i])
64+
}
65+
return c
66+
}
67+
func isModernAccount(account *armbilling.Account) bool {
68+
return strings.Contains(*account.Name, ":")
69+
}
70+
71+
func LegacyBillingAccountMultiplex(meta schema.ClientMeta) []schema.ClientMeta {
72+
client := meta.(*Client)
73+
var c = make([]schema.ClientMeta, 0)
74+
for i := range client.BillingAccounts {
75+
if !isModernAccount(client.BillingAccounts[i]) {
76+
c = append(c, client.withBillingAccount(client.BillingAccounts[i]))
77+
}
78+
}
79+
return c
80+
}
81+
82+
func ModernBillingAccountMultiplex(meta schema.ClientMeta) []schema.ClientMeta {
83+
client := meta.(*Client)
84+
var c = make([]schema.ClientMeta, 0)
85+
for i := range client.BillingAccounts {
86+
if isModernAccount(client.BillingAccounts[i]) {
87+
c = append(c, client.withBillingAccount(client.BillingAccounts[i]))
88+
}
89+
}
90+
return c
91+
}
92+
93+
func BillingAccountProfileMultiplex(meta schema.ClientMeta) []schema.ClientMeta {
94+
client := meta.(*Client)
95+
var c = make([]schema.ClientMeta, 0)
96+
for i := range client.BillingAccounts {
97+
if client.BillingAccounts[i].Properties.BillingProfiles == nil {
98+
continue
99+
}
100+
profiles := client.BillingAccounts[i].Properties.BillingProfiles.Value
101+
for j := range profiles {
102+
c = append(c, client.withBillingAccount(client.BillingAccounts[i]).withBillingProfile(profiles[j]))
103+
}
104+
}
105+
return c
106+
}
107+
108+
func SubscriptionBillingPeriodMultiplex(meta schema.ClientMeta) []schema.ClientMeta {
109+
client := meta.(*Client)
110+
var c = make([]schema.ClientMeta, 0)
111+
for _, subID := range client.subscriptions {
112+
periodsForSubscription := client.BillingPeriods[subID]
113+
for _, period := range periodsForSubscription {
114+
c = append(c, client.withSubscription(subID).withBillingPeriod(period))
115+
}
116+
}
117+
return c
118+
}

plugins/source/azure/client/namespaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
Namespacemicrosoft_containerinstance = "microsoft.containerinstance"
2323
Namespacemicrosoft_containerregistry = "microsoft.containerregistry"
2424
Namespacemicrosoft_containerservice = "microsoft.containerservice"
25+
Namespacemicrosoft_consumption = "microsoft.consumption"
2526
Namespacemicrosoft_customerinsights = "microsoft.customerinsights"
2627
Namespacemicrosoft_dashboard = "microsoft.dashboard"
2728
Namespacemicrosoft_databox = "microsoft.databox"
@@ -106,6 +107,7 @@ var namespaces = []string{
106107
Namespacemicrosoft_containerinstance,
107108
Namespacemicrosoft_containerregistry,
108109
Namespacemicrosoft_containerservice,
110+
Namespacemicrosoft_consumption,
109111
Namespacemicrosoft_customerinsights,
110112
Namespacemicrosoft_dashboard,
111113
Namespacemicrosoft_databox,

plugins/source/azure/client/testing.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,24 @@ import (
99
"testing"
1010
"time"
1111

12-
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
13-
"github.com/cloudquery/plugin-sdk/v2/faker"
1412
"github.com/gorilla/mux"
1513

1614
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1715
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
1816
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
17+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
18+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billing/armbilling"
19+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
20+
"github.com/cloudquery/plugin-sdk/v2/faker"
1921
"github.com/cloudquery/plugin-sdk/v2/plugins/source"
2022
"github.com/cloudquery/plugin-sdk/v2/schema"
2123
"github.com/cloudquery/plugin-sdk/v2/specs"
2224
"github.com/rs/zerolog"
2325
)
2426

2527
const TestSubscription = "12345678-1234-1234-1234-123456789000"
28+
const LegacyAccountName = "9971ccb0-02bb-45e2-bd6a-9e340372dcba"
29+
const ModernAccountName = "7c05a543-80ff-571e-9f98-1063b3b53cf2:99ad03ad-2d1b-4889-a452-090ad407d25f_2019-05-31"
2630

2731
var testResourceGroup = "test-resource-group"
2832

@@ -73,6 +77,29 @@ func MockTestHelper(t *testing.T, table *schema.Table, createServices func(*mux.
7377
defer h.Close()
7478
mockClient := NewMockHttpClient(h.Client(), h.URL)
7579

80+
var legacyAccount armbilling.Account
81+
if err := faker.FakeObject(&legacyAccount); err != nil {
82+
t.Fatal(err)
83+
}
84+
legacyAccount.ID = to.Ptr("/providers/Microsoft.Billing/billingAccounts/" + LegacyAccountName)
85+
legacyAccount.Name = to.Ptr(LegacyAccountName)
86+
legacyAccount.Properties.BillingProfiles = nil
87+
88+
var modernAccount armbilling.Account
89+
if err := faker.FakeObject(&modernAccount); err != nil {
90+
t.Fatal(err)
91+
}
92+
modernAccount.ID = to.Ptr("/providers/Microsoft.Billing/billingAccounts/" + ModernAccountName)
93+
modernAccount.Name = to.Ptr(ModernAccountName)
94+
modernAccount.Properties.BillingProfiles.Value[0].ID = to.Ptr("/providers/Microsoft.Billing/billingAccounts/account-id/billingProfiles/profile-id")
95+
modernAccount.Properties.BillingProfiles.Value[0].Name = to.Ptr("profile-id")
96+
97+
var billingPeriod armbilling.Period
98+
if err := faker.FakeObject(&billingPeriod); err != nil {
99+
t.Fatal(err)
100+
}
101+
billingPeriod.ID = to.Ptr("/subscriptions/" + TestSubscription + "/providers/Microsoft.Billing/billingPeriods/202205-1")
102+
76103
l := zerolog.New(zerolog.NewTestWriter(t)).Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.StampMicro}).Level(zerolog.DebugLevel).With().Timestamp().Logger()
77104
newTestExecutionClient := func(ctx context.Context, logger zerolog.Logger, spec specs.Source, _ source.Options) (schema.ClientMeta, error) {
78105
err := createServices(router)
@@ -105,6 +132,10 @@ func MockTestHelper(t *testing.T, table *schema.Table, createServices func(*mux.
105132
ResourceGroups: map[string][]*armresources.ResourceGroup{
106133
TestSubscription: {resourceGroup},
107134
},
135+
BillingAccounts: []*armbilling.Account{&legacyAccount, &modernAccount},
136+
BillingPeriods: map[string][]*armbilling.Period{
137+
TestSubscription: {&billingPeriod},
138+
},
108139
}
109140

110141
return c, nil

plugins/source/azure/docs/tables/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,26 @@
7676
- [azure_connectedvmware_virtual_machine_templates](../../../../../website/tables/azure/azure_connectedvmware_virtual_machine_templates.md)
7777
- [azure_connectedvmware_virtual_machines](../../../../../website/tables/azure/azure_connectedvmware_virtual_machines.md)
7878
- [azure_connectedvmware_virtual_networks](../../../../../website/tables/azure/azure_connectedvmware_virtual_networks.md)
79+
- [azure_consumption_billing_account_balances](../../../../../website/tables/azure/azure_consumption_billing_account_balances.md)
80+
- [azure_consumption_billing_account_budgets](../../../../../website/tables/azure/azure_consumption_billing_account_budgets.md)
81+
- [azure_consumption_billing_account_charges](../../../../../website/tables/azure/azure_consumption_billing_account_charges.md)
82+
- [azure_consumption_billing_account_events](../../../../../website/tables/azure/azure_consumption_billing_account_events.md)
83+
- [azure_consumption_billing_account_legacy_usage_details](../../../../../website/tables/azure/azure_consumption_billing_account_legacy_usage_details.md)
84+
- [azure_consumption_billing_account_lots](../../../../../website/tables/azure/azure_consumption_billing_account_lots.md)
85+
- [azure_consumption_billing_account_marketplaces](../../../../../website/tables/azure/azure_consumption_billing_account_marketplaces.md)
86+
- [azure_consumption_billing_account_modern_usage_details](../../../../../website/tables/azure/azure_consumption_billing_account_modern_usage_details.md)
87+
- [azure_consumption_billing_account_reservation_recommendations](../../../../../website/tables/azure/azure_consumption_billing_account_reservation_recommendations.md)
88+
- [azure_consumption_billing_account_tags](../../../../../website/tables/azure/azure_consumption_billing_account_tags.md)
89+
- [azure_consumption_billing_profile_reservation_details](../../../../../website/tables/azure/azure_consumption_billing_profile_reservation_details.md)
90+
- [azure_consumption_billing_profile_reservation_recommendations](../../../../../website/tables/azure/azure_consumption_billing_profile_reservation_recommendations.md)
91+
- [azure_consumption_billing_profile_reservation_summaries](../../../../../website/tables/azure/azure_consumption_billing_profile_reservation_summaries.md)
92+
- [azure_consumption_billing_profile_reservation_transactions](../../../../../website/tables/azure/azure_consumption_billing_profile_reservation_transactions.md)
93+
- [azure_consumption_subscription_budgets](../../../../../website/tables/azure/azure_consumption_subscription_budgets.md)
94+
- [azure_consumption_subscription_legacy_usage_details](../../../../../website/tables/azure/azure_consumption_subscription_legacy_usage_details.md)
95+
- [azure_consumption_subscription_marketplaces](../../../../../website/tables/azure/azure_consumption_subscription_marketplaces.md)
96+
- [azure_consumption_subscription_price_sheets](../../../../../website/tables/azure/azure_consumption_subscription_price_sheets.md)
97+
- [azure_consumption_subscription_reservation_recommendations](../../../../../website/tables/azure/azure_consumption_subscription_reservation_recommendations.md)
98+
- [azure_consumption_subscription_tags](../../../../../website/tables/azure/azure_consumption_subscription_tags.md)
7999
- [azure_containerinstance_container_groups](../../../../../website/tables/azure/azure_containerinstance_container_groups.md)
80100
- [azure_containerregistry_registries](../../../../../website/tables/azure/azure_containerregistry_registries.md)
81101
- [azure_containerservice_managed_clusters](../../../../../website/tables/azure/azure_containerservice_managed_clusters.md)

plugins/source/azure/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0
2424
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/confluent/armconfluent v1.0.0
2525
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/connectedvmware/armconnectedvmware v0.1.0
26+
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption v1.0.0
2627
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance/v2 v2.1.0
2728
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.0.0
2829
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.3.0

plugins/source/azure/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/confluent/armconfluent v1.
7474
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/confluent/armconfluent v1.0.0/go.mod h1:HPMdvtu1bwSsVSQ1wRLTUoe7TUlNGRbmdcRo384ekXE=
7575
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/connectedvmware/armconnectedvmware v0.1.0 h1:km3FTKovs/UyF3qJpsBFPSXuS75bVbzDsE00nUUTdFA=
7676
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/connectedvmware/armconnectedvmware v0.1.0/go.mod h1:eFPy/X3ZpHjOGolySu+h+AIboGtguBv0z16N/oEenZc=
77+
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption v1.0.0 h1:DSK/n3SPssFsqEn15KMksdbjEBlBNNgn4IGkvWM4nt0=
78+
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption v1.0.0/go.mod h1:0PHlFYAfcqh0IKo+50hEPUNzuMrcyvRakDKzJTonchY=
7779
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance/v2 v2.1.0 h1:BhiBk1oFqFQxcVCUXoMNUmXJyOD9Jy2EobM0fL/e2rI=
7880
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance/v2 v2.1.0/go.mod h1:nqIVnU22IacbrniShrveGMTMHdVozaqfzVFVygR/g/k=
7981
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.0.0 h1:UuDNFPr44qr9eFQF2sv2WpwSGJlvZroSMsI3A4YtQMc=

plugins/source/azure/resources/plugin/tables.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/compute"
2020
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/confluent"
2121
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/connectedvmware"
22+
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/consumption"
2223
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/containerinstance"
2324
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/containerregistry"
2425
"github.com/cloudquery/cloudquery/plugins/source/azure/resources/services/containerservice"
@@ -165,6 +166,26 @@ func tables() []*schema.Table {
165166
containerservice.Snapshots(),
166167
cosmos.Locations(),
167168
cosmos.RestorableDatabaseAccounts(),
169+
consumption.BillingAccountBalances(),
170+
consumption.BillingAccountBudgets(),
171+
consumption.BillingAccountCharges(),
172+
consumption.BillingAccountEvents(),
173+
consumption.BillingAccountReservationRecommendations(),
174+
consumption.BillingAccountLots(),
175+
consumption.BillingAccountMarketplaces(),
176+
consumption.BillingProfileReservationDetails(),
177+
consumption.BillingProfileReservationRecommendations(),
178+
consumption.BillingProfileReservationSummaries(),
179+
consumption.BillingProfileReservationTransactions(),
180+
consumption.BillingAccountLegacyUsageDetails(),
181+
consumption.BillingAccountModernUsageDetails(),
182+
consumption.BillingAccountTags(),
183+
consumption.SubscriptionBudgets(),
184+
consumption.SubscriptionMarketplaces(),
185+
consumption.SubscriptionPriceSheets(),
186+
consumption.SubscriptionReservationRecommendations(),
187+
consumption.SubscriptionLegacyUsageDetails(),
188+
consumption.SubscriptionTags(),
168189
customerinsights.Hubs(),
169190
dashboard.Grafana(),
170191
databox.Jobs(),

0 commit comments

Comments
 (0)