From f6e6a3d1e6840eaecb80fee3df76c533241e70c4 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 4 Nov 2021 09:36:56 +0000 Subject: [PATCH] New resource and data source: azurerm_aadb2c_directory --- .teamcity/components/generated/services.kt | 1 + internal/clients/client.go | 3 + internal/provider/services.go | 2 + .../aadb2c/aadb2c_directory_data_source.go | 158 +++++++++ .../aadb2c_directory_data_source_test.go | 37 ++ .../aadb2c/aadb2c_directory_resource.go | 319 ++++++++++++++++++ .../aadb2c/aadb2c_directory_resource_test.go | 202 +++++++++++ internal/services/aadb2c/client/client.go | 19 ++ internal/services/aadb2c/registration.go | 47 +++ .../sdk/2021-04-01-preview/tenants/client.go | 15 + .../2021-04-01-preview/tenants/constants.go | 124 +++++++ .../tenants/id_b2cdirectory.go | 124 +++++++ .../tenants/id_b2cdirectory_test.go | 279 +++++++++++++++ .../tenants/id_resourcegroup.go | 109 ++++++ .../tenants/id_resourcegroup_test.go | 204 +++++++++++ .../tenants/id_subscription.go | 96 ++++++ .../tenants/id_subscription_test.go | 159 +++++++++ .../method_checknameavailability_autorest.go | 66 ++++ .../tenants/method_create_autorest.go | 75 ++++ .../tenants/method_delete_autorest.go | 73 ++++ .../tenants/method_get_autorest.go | 64 ++++ .../method_listbyresourcegroup_autorest.go | 183 ++++++++++ .../method_listbysubscription_autorest.go | 183 ++++++++++ .../tenants/method_update_autorest.go | 65 ++++ .../tenants/model_billingconfig.go | 6 + .../model_checknameavailabilityrequest.go | 6 + .../model_checknameavailabilityresult.go | 7 + .../tenants/model_createtenant.go | 8 + .../tenants/model_createtenantproperties.go | 6 + .../2021-04-01-preview/tenants/model_sku.go | 6 + .../tenants/model_tenant.go | 11 + .../tenants/model_tenantproperties.go | 8 + .../model_tenantpropertiesforcreate.go | 5 + .../tenants/model_updatetenant.go | 7 + .../tenants/model_updatetenantproperties.go | 5 + .../2021-04-01-preview/tenants/predicates.go | 24 ++ .../sdk/2021-04-01-preview/tenants/version.go | 9 + .../services/aadb2c/validate/directory_id.go | 21 ++ website/docs/d/aadb2c_directory.html.markdown | 58 ++++ website/docs/r/aadb2c_directory.html.markdown | 71 ++++ 40 files changed, 2865 insertions(+) create mode 100644 internal/services/aadb2c/aadb2c_directory_data_source.go create mode 100644 internal/services/aadb2c/aadb2c_directory_data_source_test.go create mode 100644 internal/services/aadb2c/aadb2c_directory_resource.go create mode 100644 internal/services/aadb2c/aadb2c_directory_resource_test.go create mode 100644 internal/services/aadb2c/client/client.go create mode 100644 internal/services/aadb2c/registration.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/client.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/constants.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory_test.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup_test.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription_test.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_checknameavailability_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_create_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_delete_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_get_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbyresourcegroup_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbysubscription_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_update_autorest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_billingconfig.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityrequest.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityresult.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenant.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenantproperties.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_sku.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenant.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantproperties.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantpropertiesforcreate.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenant.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenantproperties.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/predicates.go create mode 100644 internal/services/aadb2c/sdk/2021-04-01-preview/tenants/version.go create mode 100644 internal/services/aadb2c/validate/directory_id.go create mode 100644 website/docs/d/aadb2c_directory.html.markdown create mode 100644 website/docs/r/aadb2c_directory.html.markdown diff --git a/.teamcity/components/generated/services.kt b/.teamcity/components/generated/services.kt index 27926dcfb314e..9a3a2ba4693fd 100644 --- a/.teamcity/components/generated/services.kt +++ b/.teamcity/components/generated/services.kt @@ -1,6 +1,7 @@ // NOTE: this is Generated from the Service Definitions - manual changes will be lost // to re-generate this file, run 'make generate' in the root of the repository var services = mapOf( + "aadb2c" to "AAD B2C", "apimanagement" to "API Management", "advisor" to "Advisor", "analysisservices" to "Analysis Services", diff --git a/internal/clients/client.go b/internal/clients/client.go index e4d453c715aea..74859a4c078b3 100644 --- a/internal/clients/client.go +++ b/internal/clients/client.go @@ -7,6 +7,7 @@ import ( "github.com/Azure/go-autorest/autorest/validation" "github.com/hashicorp/terraform-provider-azurerm/internal/common" "github.com/hashicorp/terraform-provider-azurerm/internal/features" + aadb2c "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/client" advisor "github.com/hashicorp/terraform-provider-azurerm/internal/services/advisor/client" analysisServices "github.com/hashicorp/terraform-provider-azurerm/internal/services/analysisservices/client" apiManagement "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/client" @@ -113,6 +114,7 @@ type Client struct { Account *ResourceManagerAccount Features features.UserFeatures + AadB2c *aadb2c.Client Advisor *advisor.Client AnalysisServices *analysisServices.Client ApiManagement *apiManagement.Client @@ -222,6 +224,7 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error client.Features = o.Features client.StopContext = ctx + client.AadB2c = aadb2c.NewClient(o) client.Advisor = advisor.NewClient(o) client.AnalysisServices = analysisServices.NewClient(o) client.ApiManagement = apiManagement.NewClient(o) diff --git a/internal/provider/services.go b/internal/provider/services.go index 71ed0ab5922ac..f0c966f5cd3f1 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -2,6 +2,7 @@ package provider import ( "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c" "github.com/hashicorp/terraform-provider-azurerm/internal/services/advisor" "github.com/hashicorp/terraform-provider-azurerm/internal/services/analysisservices" "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement" @@ -106,6 +107,7 @@ import ( func SupportedTypedServices() []sdk.TypedServiceRegistration { return []sdk.TypedServiceRegistration{ + aadb2c.Registration{}, apimanagement.Registration{}, appconfiguration.Registration{}, appservice.Registration{}, diff --git a/internal/services/aadb2c/aadb2c_directory_data_source.go b/internal/services/aadb2c/aadb2c_directory_data_source.go new file mode 100644 index 0000000000000..c5189e37d306d --- /dev/null +++ b/internal/services/aadb2c/aadb2c_directory_data_source.go @@ -0,0 +1,158 @@ +package aadb2c + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/sdk/2021-04-01-preview/tenants" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type AadB2cDirectoryDataSourceModel struct { + BillingType string `tfschema:"billing_type"` + DataResidencyLocation string `tfschema:"data_residency_location"` + DomainName string `tfschema:"domain_name"` + EffectiveStartDate string `tfschema:"effective_start_date"` + ResourceGroup string `tfschema:"resource_group_name"` + Sku string `tfschema:"sku_name"` + Tags map[string]string `tfschema:"tags"` + TenantId string `tfschema:"tenant_id"` +} + +type AadB2cDirectoryDataSource struct{} + +var _ sdk.DataSource = AadB2cDirectoryDataSource{} + +func (r AadB2cDirectoryDataSource) ResourceType() string { + return "azurerm_aadb2c_directory" +} + +func (r AadB2cDirectoryDataSource) ModelObject() interface{} { + return &AadB2cDirectoryModel{} +} + +func (r AadB2cDirectoryDataSource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.B2CDirectoryID +} + +func (r AadB2cDirectoryDataSource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "domain_name": { + Description: "Domain name of the B2C tenant, including onmicrosoft.com suffix.", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), + } +} + +func (r AadB2cDirectoryDataSource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "billing_type": { + Description: "The type of billing for the B2C tenant. Possible values include: `MAU` or `Auths`.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "data_residency_location": { + Description: "Location in which the B2C tenant is hosted and data resides.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "effective_start_date": { + Description: "The date from which the billing type took effect. May not be populated until after the first billing cycle.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tenant_id": { + Description: "The Tenant ID for the B2C tenant.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "sku_name": { + Description: "Billing SKU for the B2C tenant.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tags": tags.SchemaDataSource(), + } +} + +func (r AadB2cDirectoryDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AadB2c.TenantsClient + subscriptionId := metadata.Client.Account.SubscriptionId + + var state AadB2cDirectoryDataSourceModel + if err := metadata.Decode(&state); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + id := tenants.NewB2CDirectoryID(subscriptionId, state.ResourceGroup, state.DomainName) + + metadata.Logger.Infof("Reading %s", id) + resp, err := client.Get(ctx, id) + if err != nil { + if resp.HttpResponse.StatusCode == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %v", id, err) + } + + model := resp.Model + if model == nil { + return fmt.Errorf("retrieving %s: model was nil", id) + } + + state.DomainName = id.DirectoryName + state.ResourceGroup = id.ResourceGroup + + if model.Location != nil { + state.DataResidencyLocation = string(*model.Location) + } + + if model.Sku != nil { + state.Sku = string(model.Sku.Name) + } + + if model.Tags != nil { + state.Tags = *model.Tags + } + + if properties := model.Properties; properties != nil { + if billingConfig := properties.BillingConfig; billingConfig != nil { + if billingConfig.BillingType != nil { + state.BillingType = string(*billingConfig.BillingType) + } + if billingConfig.EffectiveStartDateUtc != nil { + state.EffectiveStartDate = *billingConfig.EffectiveStartDateUtc + } + } + + if properties.TenantId != nil { + state.TenantId = *properties.TenantId + } + } + + metadata.SetID(id) + + return metadata.Encode(&state) + }, + } +} diff --git a/internal/services/aadb2c/aadb2c_directory_data_source_test.go b/internal/services/aadb2c/aadb2c_directory_data_source_test.go new file mode 100644 index 0000000000000..53547f1b4c0d6 --- /dev/null +++ b/internal/services/aadb2c/aadb2c_directory_data_source_test.go @@ -0,0 +1,37 @@ +package aadb2c_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type AadB2cDirectoryDataSource struct{} + +func TestAccAadB2cDirectoryDataSource_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_aadb2c_directory", "test") + d := AadB2cDirectoryDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: d.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("data_residency_location").HasValue("United States"), + check.That(data.ResourceName).Key("sku_name").HasValue("PremiumP1"), + ), + }, + }) +} + +func (d AadB2cDirectoryDataSource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azurerm_aadb2c_directory" "test" { + domain_name = azurerm_aadb2c_directory.test.domain_name + resource_group_name = azurerm_aadb2c_directory.test.resource_group_name +} +`, AadB2cDirectoryResource{}.basic(data)) +} diff --git a/internal/services/aadb2c/aadb2c_directory_resource.go b/internal/services/aadb2c/aadb2c_directory_resource.go new file mode 100644 index 0000000000000..04f81f0df4d33 --- /dev/null +++ b/internal/services/aadb2c/aadb2c_directory_resource.go @@ -0,0 +1,319 @@ +package aadb2c + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/sdk/2021-04-01-preview/tenants" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type AadB2cDirectoryModel struct { + BillingType string `tfschema:"billing_type"` + CountryCode string `tfschema:"country_code"` + DataResidencyLocation string `tfschema:"data_residency_location"` + DisplayName string `tfschema:"display_name"` + DomainName string `tfschema:"domain_name"` + EffectiveStartDate string `tfschema:"effective_start_date"` + ResourceGroup string `tfschema:"resource_group_name"` + Sku string `tfschema:"sku_name"` + Tags map[string]string `tfschema:"tags"` + TenantId string `tfschema:"tenant_id"` +} + +type AadB2cDirectoryResource struct{} + +var _ sdk.Resource = AadB2cDirectoryResource{} +var _ sdk.ResourceWithUpdate = AadB2cDirectoryResource{} + +func (r AadB2cDirectoryResource) ResourceType() string { + return "azurerm_aadb2c_directory" +} + +func (r AadB2cDirectoryResource) ModelObject() interface{} { + return &AadB2cDirectoryModel{} +} + +func (r AadB2cDirectoryResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.B2CDirectoryID +} + +func (r AadB2cDirectoryResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "domain_name": { + Description: "Domain name of the B2C tenant, including onmicrosoft.com suffix.", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "country_code": { + Description: "Country code of the B2C tenant. See https://aka.ms/B2CDataResidency for valid country codes.", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "data_residency_location": { + Description: "Location in which the B2C tenant is hosted and data resides. See https://aka.ms/B2CDataResidency for more information.", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(tenants.LocationAsiaPacific), + string(tenants.LocationAustralia), + string(tenants.LocationEurope), + string(tenants.LocationGlobal), + string(tenants.LocationUnitedStates), + }, false), + }, + + "display_name": { + Description: "The initial display name of the B2C tenant.", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "sku_name": { + Description: "Billing SKU for the B2C tenant. See https://aka.ms/b2cBilling for more information.", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(tenants.SkuNamePremiumP1), + string(tenants.SkuNamePremiumP2), + //string(tenants.SkuNameStandard), // API doesn't seem to support "Standard", it's ignored and "PremiumP1" is used instead, even when patching + }, false), + }, + + "tags": tags.Schema(), + } +} + +func (r AadB2cDirectoryResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "billing_type": { + Description: "The type of billing for the B2C tenant. Possible values include: `MAU` or `Auths`.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "effective_start_date": { + Description: "The date from which the billing type took effect. May not be populated until after the first billing cycle.", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "tenant_id": { + Description: "The Tenant ID for the B2C tenant.", + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (r AadB2cDirectoryResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AadB2c.TenantsClient + subscriptionId := metadata.Client.Account.SubscriptionId + + var model AadB2cDirectoryModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + id := tenants.NewB2CDirectoryID(subscriptionId, model.ResourceGroup, model.DomainName) + + metadata.Logger.Infof("Import check for %s", id) + existing, err := client.Get(ctx, id) + if err != nil && existing.HttpResponse.StatusCode != http.StatusNotFound { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + + if existing.Model != nil && existing.Model.Id != nil && *existing.Model.Id != "" { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + metadata.Logger.Infof("Domain name availability check for %s", id) + availabilityResult, err := client.CheckNameAvailability(ctx, tenants.NewSubscriptionID(subscriptionId), tenants.CheckNameAvailabilityRequest{ + Name: &model.DomainName, + CountryCode: &model.CountryCode, + }) + if err != nil { + return fmt.Errorf("checking availability of `domain_name`: %v", err) + } + + if availabilityResult.Model.NameAvailable == nil || !*availabilityResult.Model.NameAvailable { + reason := "unknown reason" + if availabilityResult.Model.Reason != nil { + reason = *availabilityResult.Model.Reason + } + if availabilityResult.Model.Message != nil { + reason = fmt.Sprintf("%s (%s)", reason, *availabilityResult.Model.Message) + } + return fmt.Errorf("checking availability of `domain_name`: the specified domain %q is unavailable: %s", model.DomainName, reason) + } + + metadata.Logger.Infof("Creating %s", id) + + properties := tenants.CreateTenant{ + Location: tenants.Location(model.DataResidencyLocation), + Properties: tenants.TenantPropertiesForCreate{ + CreateTenantProperties: tenants.CreateTenantProperties{ + CountryCode: model.CountryCode, + DisplayName: model.DisplayName, + }, + }, + Sku: tenants.Sku{ + Name: tenants.SkuName(model.Sku), + Tier: tenants.SkuTierA0, + }, + Tags: &model.Tags, + } + + if err := client.CreateThenPoll(ctx, id, properties); err != nil { + return err + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r AadB2cDirectoryResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AadB2c.TenantsClient + + id, err := tenants.ParseB2CDirectoryID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + metadata.Logger.Infof("Decoding state for %s", id) + var state AadB2cDirectoryModel + if err := metadata.Decode(&state); err != nil { + return err + } + + metadata.Logger.Infof("Updating %s", id) + + properties := tenants.UpdateTenant{ + Sku: tenants.Sku{ + Name: tenants.SkuName(state.Sku), + Tier: tenants.SkuTierA0, + }, + Tags: &state.Tags, + } + + if _, err := client.Update(ctx, *id, properties); err != nil { + return err + } + + return nil + }, + } +} + +func (r AadB2cDirectoryResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AadB2c.TenantsClient + + id, err := tenants.ParseB2CDirectoryID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + metadata.Logger.Infof("Reading %s", id) + resp, err := client.Get(ctx, *id) + if err != nil { + if resp.HttpResponse.StatusCode == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %v", id, err) + } + + model := resp.Model + if model == nil { + return fmt.Errorf("retrieving %s: model was nil", id) + } + + state := AadB2cDirectoryModel{ + DomainName: id.DirectoryName, + ResourceGroup: id.ResourceGroup, + CountryCode: metadata.ResourceData.Get("country_code").(string), + DisplayName: metadata.ResourceData.Get("display_name").(string), + } + + if model.Location != nil { + state.DataResidencyLocation = string(*model.Location) + } + + if model.Sku != nil { + state.Sku = string(model.Sku.Name) + } + + if model.Tags != nil { + state.Tags = *model.Tags + } + + if properties := model.Properties; properties != nil { + if billingConfig := properties.BillingConfig; billingConfig != nil { + if billingConfig.BillingType != nil { + state.BillingType = string(*billingConfig.BillingType) + } + if billingConfig.EffectiveStartDateUtc != nil { + state.EffectiveStartDate = *billingConfig.EffectiveStartDateUtc + } + } + + if properties.TenantId != nil { + state.TenantId = *properties.TenantId + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r AadB2cDirectoryResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AadB2c.TenantsClient + + id, err := tenants.ParseB2CDirectoryID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + metadata.Logger.Infof("Deleting %s", id) + + if err := client.DeleteThenPoll(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + return nil + }, + } +} diff --git a/internal/services/aadb2c/aadb2c_directory_resource_test.go b/internal/services/aadb2c/aadb2c_directory_resource_test.go new file mode 100644 index 0000000000000..ce0ef27d051cb --- /dev/null +++ b/internal/services/aadb2c/aadb2c_directory_resource_test.go @@ -0,0 +1,202 @@ +package aadb2c_test + +import ( + "context" + "fmt" + "net/http" + "regexp" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/sdk/2021-04-01-preview/tenants" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type AadB2cDirectoryResource struct{} + +func TestAccAadB2cDirectoryResource_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_aadb2c_directory", "test") + r := AadB2cDirectoryResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("country_code", "display_name"), + }) +} + +func TestAccAadB2cDirectoryResource_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_aadb2c_directory", "test") + r := AadB2cDirectoryResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("country_code", "display_name"), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("country_code", "display_name"), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("country_code", "display_name"), + }) +} + +func TestAccAadB2cDirectoryResource_domainNameUnavailable(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_aadb2c_directory", "test") + r := AadB2cDirectoryResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.domainNameUnavailable(data), + ExpectError: regexp.MustCompile("checking availability of `domain_name`: the specified domain \"[^\"]+\" is unavailable"), + }, + }) +} + +func TestAccAadB2cDirectoryResource_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_aadb2c_directory", "test") + r := AadB2cDirectoryResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.requiresImport(data), + ExpectError: acceptance.RequiresImportError(data.ResourceType), + }, + }) +} + +func (r AadB2cDirectoryResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := tenants.ParseB2CDirectoryID(state.ID) + if err != nil { + return nil, err + } + + resp, err := client.AadB2c.TenantsClient.Get(ctx, *id) + if err != nil { + if resp.HttpResponse.StatusCode == http.StatusNotFound { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return utils.Bool(true), nil +} + +func (r AadB2cDirectoryResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + client_id = "" + client_certificate_path = "" + client_secret = "" + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (r AadB2cDirectoryResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_aadb2c_directory" "test" { + country_code = "US" + data_residency_location = "United States" + display_name = "acctest%[2]d" + domain_name = "acctest%[2]d.onmicrosoft.com" + resource_group_name = azurerm_resource_group.test.name + sku_name = "PremiumP1" +} +`, r.template(data), data.RandomInteger) +} + +func (r AadB2cDirectoryResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_aadb2c_directory" "test" { + country_code = "US" + data_residency_location = "United States" + display_name = "acctest%[2]d" + domain_name = "acctest%[2]d.onmicrosoft.com" + resource_group_name = azurerm_resource_group.test.name + sku_name = "PremiumP2" + + tags = { + "Environment" : "Test", + "Project" : "Locksmith", + } +} +`, r.template(data), data.RandomInteger) +} + +func (r AadB2cDirectoryResource) domainNameUnavailable(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_resource_group" "duplicate" { + name = "acctestRG-duplicate-%[2]d" + location = "%[3]s" +} + +resource "azurerm_aadb2c_directory" "duplicate" { + country_code = azurerm_aadb2c_directory.test.country_code + data_residency_location = azurerm_aadb2c_directory.test.data_residency_location + display_name = "acctest-duplicate-%[2]d" + domain_name = azurerm_aadb2c_directory.test.domain_name + resource_group_name = azurerm_resource_group.duplicate.name + sku_name = "PremiumP1" +} +`, r.basic(data), data.RandomInteger, data.Locations.Secondary) +} + +func (r AadB2cDirectoryResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_aadb2c_directory" "import" { + country_code = azurerm_aadb2c_directory.test.country_code + data_residency_location = azurerm_aadb2c_directory.test.data_residency_location + display_name = azurerm_aadb2c_directory.test.display_name + domain_name = azurerm_aadb2c_directory.test.domain_name + resource_group_name = azurerm_aadb2c_directory.test.resource_group_name + sku_name = azurerm_aadb2c_directory.test.sku_name +} +`, r.basic(data)) +} diff --git a/internal/services/aadb2c/client/client.go b/internal/services/aadb2c/client/client.go new file mode 100644 index 0000000000000..5783e0551cc59 --- /dev/null +++ b/internal/services/aadb2c/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/hashicorp/terraform-provider-azurerm/internal/common" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/sdk/2021-04-01-preview/tenants" +) + +type Client struct { + TenantsClient *tenants.TenantsClient +} + +func NewClient(o *common.ClientOptions) *Client { + tenantsClient := tenants.NewTenantsClientWithBaseURI(o.ResourceManagerEndpoint) + o.ConfigureClient(&tenantsClient.Client, o.ResourceManagerAuthorizer) + + return &Client{ + TenantsClient: &tenantsClient, + } +} diff --git a/internal/services/aadb2c/registration.go b/internal/services/aadb2c/registration.go new file mode 100644 index 0000000000000..4b241c926b0c9 --- /dev/null +++ b/internal/services/aadb2c/registration.go @@ -0,0 +1,47 @@ +package aadb2c + +import ( + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type Registration struct{} + +var _ sdk.TypedServiceRegistration = Registration{} +var _ sdk.UntypedServiceRegistration = Registration{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "AAD B2C" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "AAD B2C", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { + return map[string]*pluginsdk.Resource{} +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { + return map[string]*pluginsdk.Resource{} +} + +// DataSources returns the typed DataSources supported by this service +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{ + AadB2cDirectoryDataSource{}, + } +} + +// Resources returns the typed Resources supported by this service +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + AadB2cDirectoryResource{}, + } +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/client.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/client.go new file mode 100644 index 0000000000000..4db63e86e6634 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/client.go @@ -0,0 +1,15 @@ +package tenants + +import "github.com/Azure/go-autorest/autorest" + +type TenantsClient struct { + Client autorest.Client + baseUri string +} + +func NewTenantsClientWithBaseURI(endpoint string) TenantsClient { + return TenantsClient{ + Client: autorest.NewClientWithUserAgent(userAgent()), + baseUri: endpoint, + } +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/constants.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/constants.go new file mode 100644 index 0000000000000..735197e778ad6 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/constants.go @@ -0,0 +1,124 @@ +package tenants + +import "strings" + +type BillingType string + +const ( + BillingTypeAuths BillingType = "auths" + BillingTypeMonthlyActiveUsers BillingType = "mau" +) + +func PossibleValuesForBillingType() []string { + return []string{ + string(BillingTypeAuths), + string(BillingTypeMonthlyActiveUsers), + } +} + +func parseBillingType(input string) (*BillingType, error) { + vals := map[string]BillingType{ + "auths": BillingTypeAuths, + "mau": BillingTypeMonthlyActiveUsers, + } + if v, ok := vals[strings.ToLower(input)]; ok { + return &v, nil + } + + // otherwise presume it's an undefined value and best-effort it + out := BillingType(input) + return &out, nil +} + +type Location string + +const ( + LocationAsiaPacific Location = "Asia Pacific" + LocationAustralia Location = "Australia" + LocationEurope Location = "Europe" + LocationGlobal Location = "Global" + LocationUnitedStates Location = "United States" +) + +func PossibleValuesForLocation() []string { + return []string{ + string(LocationAsiaPacific), + string(LocationAustralia), + string(LocationEurope), + string(LocationGlobal), + string(LocationUnitedStates), + } +} + +func parseLocation(input string) (*Location, error) { + vals := map[string]Location{ + "asia pacific": LocationAsiaPacific, + "australia": LocationAustralia, + "europe": LocationEurope, + "global": LocationGlobal, + "united states": LocationUnitedStates, + } + if v, ok := vals[strings.ToLower(input)]; ok { + return &v, nil + } + + // otherwise presume it's an undefined value and best-effort it + out := Location(input) + return &out, nil +} + +type SkuName string + +const ( + SkuNamePremiumP1 SkuName = "PremiumP1" + SkuNamePremiumP2 SkuName = "PremiumP2" + SkuNameStandard SkuName = "Standard" +) + +func PossibleValuesForSkuName() []string { + return []string{ + string(SkuNamePremiumP1), + string(SkuNamePremiumP2), + string(SkuNameStandard), + } +} + +func parseSkuName(input string) (*SkuName, error) { + vals := map[string]SkuName{ + "premiump1": SkuNamePremiumP1, + "premiump2": SkuNamePremiumP2, + "standard": SkuNameStandard, + } + if v, ok := vals[strings.ToLower(input)]; ok { + return &v, nil + } + + // otherwise presume it's an undefined value and best-effort it + out := SkuName(input) + return &out, nil +} + +type SkuTier string + +const ( + SkuTierA0 SkuTier = "A0" +) + +func PossibleValuesForSkuTier() []string { + return []string{ + string(SkuTierA0), + } +} + +func parseSkuTier(input string) (*SkuTier, error) { + vals := map[string]SkuTier{ + "a0": SkuTierA0, + } + if v, ok := vals[strings.ToLower(input)]; ok { + return &v, nil + } + + // otherwise presume it's an undefined value and best-effort it + out := SkuTier(input) + return &out, nil +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory.go new file mode 100644 index 0000000000000..e04fcd3ed5b98 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory.go @@ -0,0 +1,124 @@ +package tenants + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.ResourceId = B2CDirectoryId{} + +// B2CDirectoryId is a struct representing the Resource ID for a B 2 C Directory +type B2CDirectoryId struct { + SubscriptionId string + ResourceGroup string + DirectoryName string +} + +// NewB2CDirectoryID returns a new B2CDirectoryId struct +func NewB2CDirectoryID(subscriptionId string, resourceGroup string, directoryName string) B2CDirectoryId { + return B2CDirectoryId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + DirectoryName: directoryName, + } +} + +// ParseB2CDirectoryID parses 'input' into a B2CDirectoryId +func ParseB2CDirectoryID(input string) (*B2CDirectoryId, error) { + parser := resourceids.NewParserFromResourceIdType(B2CDirectoryId{}) + parsed, err := parser.Parse(input, false) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := B2CDirectoryId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, fmt.Errorf("the segment 'subscriptionId' was not found in the resource id %q", input) + } + + if id.ResourceGroup, ok = parsed.Parsed["resourceGroup"]; !ok { + return nil, fmt.Errorf("the segment 'resourceGroup' was not found in the resource id %q", input) + } + + if id.DirectoryName, ok = parsed.Parsed["directoryName"]; !ok { + return nil, fmt.Errorf("the segment 'directoryName' was not found in the resource id %q", input) + } + + return &id, nil +} + +// ParseB2CDirectoryIDInsensitively parses 'input' case-insensitively into a B2CDirectoryId +// note: this method should only be used for API response data and not user input +func ParseB2CDirectoryIDInsensitively(input string) (*B2CDirectoryId, error) { + parser := resourceids.NewParserFromResourceIdType(B2CDirectoryId{}) + parsed, err := parser.Parse(input, true) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := B2CDirectoryId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, fmt.Errorf("the segment 'subscriptionId' was not found in the resource id %q", input) + } + + if id.ResourceGroup, ok = parsed.Parsed["resourceGroup"]; !ok { + return nil, fmt.Errorf("the segment 'resourceGroup' was not found in the resource id %q", input) + } + + if id.DirectoryName, ok = parsed.Parsed["directoryName"]; !ok { + return nil, fmt.Errorf("the segment 'directoryName' was not found in the resource id %q", input) + } + + return &id, nil +} + +// ValidateB2CDirectoryID checks that 'input' can be parsed as a B 2 C Directory ID +func ValidateB2CDirectoryID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := ParseB2CDirectoryID(v); err != nil { + errors = append(errors, err) + } + + return +} + +// ID returns the formatted B 2 C Directory ID +func (id B2CDirectoryId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.AzureActiveDirectory/b2cDirectories/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.DirectoryName) +} + +// Segments returns a slice of Resource ID Segments which comprise this B 2 C Directory ID +func (id B2CDirectoryId) Segments() []resourceids.Segment { + return []resourceids.Segment{ + resourceids.StaticSegment("subscriptions", "subscriptions", "subscriptions"), + resourceids.SubscriptionIdSegment("subscriptionId", "12345678-1234-9876-4563-123456789012"), + resourceids.StaticSegment("resourceGroups", "resourceGroups", "resourceGroups"), + resourceids.ResourceGroupSegment("resourceGroup", "example-resource-group"), + resourceids.StaticSegment("providers", "providers", "providers"), + resourceids.StaticSegment("microsoftAzureActiveDirectory", "Microsoft.AzureActiveDirectory", "Microsoft.AzureActiveDirectory"), + resourceids.StaticSegment("b2cDirectories", "b2cDirectories", "b2cDirectories"), + resourceids.UserSpecifiedSegment("directoryName", "directoryValue"), + } +} + +// String returns a human-readable description of this B 2 C Directory ID +func (id B2CDirectoryId) String() string { + components := []string{ + fmt.Sprintf("Subscription: %q", id.SubscriptionId), + fmt.Sprintf("Resource Group: %q", id.ResourceGroup), + fmt.Sprintf("Directory Name: %q", id.DirectoryName), + } + return fmt.Sprintf("B 2 C Directory (%s)", strings.Join(components, "\n")) +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory_test.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory_test.go new file mode 100644 index 0000000000000..d8635f29675e7 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_b2cdirectory_test.go @@ -0,0 +1,279 @@ +package tenants + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.ResourceId = B2CDirectoryId{} + +func TestNewB2CDirectoryID(t *testing.T) { + id := NewB2CDirectoryID("12345678-1234-9876-4563-123456789012", "example-resource-group", "directoryValue") + + if id.SubscriptionId != "12345678-1234-9876-4563-123456789012" { + t.Fatalf("Expected %q but got %q for Segment 'SubscriptionId'", id.SubscriptionId, "12345678-1234-9876-4563-123456789012") + } + + if id.ResourceGroup != "example-resource-group" { + t.Fatalf("Expected %q but got %q for Segment 'ResourceGroup'", id.ResourceGroup, "example-resource-group") + } + + if id.DirectoryName != "directoryValue" { + t.Fatalf("Expected %q but got %q for Segment 'DirectoryName'", id.DirectoryName, "directoryValue") + } +} + +func TestFormatB2CDirectoryID(t *testing.T) { + actual := NewB2CDirectoryID("12345678-1234-9876-4563-123456789012", "example-resource-group", "directoryValue").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directoryValue" + if actual != expected { + t.Fatalf("Expected the Formatted ID to be %q but got %q", expected, actual) + } +} + +func TestParseB2CDirectoryID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *B2CDirectoryId + }{ + { + // Incomplete URI + Input: "", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories", + Error: true, + }, + { + // Valid URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directoryValue", + Expected: &B2CDirectoryId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "example-resource-group", + DirectoryName: "directoryValue", + }, + }, + { + // Invalid (Valid Uri with Extra segment) + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directoryValue/extra", + Error: true, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ParseB2CDirectoryID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %+v", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.DirectoryName != v.Expected.DirectoryName { + t.Fatalf("Expected %q but got %q for DirectoryName", v.Expected.DirectoryName, actual.DirectoryName) + } + + } +} + +func TestParseB2CDirectoryIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *B2CDirectoryId + }{ + { + // Incomplete URI + Input: "", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP/pRoViDeRs", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP/pRoViDeRs/mIcRoSoFt.aZuReAcTiVeDiReCtOrY", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP/pRoViDeRs/mIcRoSoFt.aZuReAcTiVeDiReCtOrY/b2cDiReCtOrIeS", + Error: true, + }, + { + // Valid URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directoryValue", + Expected: &B2CDirectoryId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "example-resource-group", + DirectoryName: "directoryValue", + }, + }, + { + // Invalid (Valid Uri with Extra segment) + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directoryValue/extra", + Error: true, + }, + { + // Valid URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP/pRoViDeRs/mIcRoSoFt.aZuReAcTiVeDiReCtOrY/b2cDiReCtOrIeS/dIrEcToRyVaLuE", + Expected: &B2CDirectoryId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "eXaMpLe-rEsOuRcE-GrOuP", + DirectoryName: "dIrEcToRyVaLuE", + }, + }, + { + // Invalid (Valid Uri with Extra segment - mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP/pRoViDeRs/mIcRoSoFt.aZuReAcTiVeDiReCtOrY/b2cDiReCtOrIeS/dIrEcToRyVaLuE/extra", + Error: true, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ParseB2CDirectoryIDInsensitively(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %+v", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.DirectoryName != v.Expected.DirectoryName { + t.Fatalf("Expected %q but got %q for DirectoryName", v.Expected.DirectoryName, actual.DirectoryName) + } + + } +} + +func TestSegmentsForB2CDirectoryId(t *testing.T) { + segments := B2CDirectoryId{}.Segments() + if len(segments) == 0 { + t.Fatalf("B2CDirectoryId has no segments") + } + + uniqueNames := make(map[string]struct{}, 0) + for _, segment := range segments { + uniqueNames[segment.Name] = struct{}{} + } + if len(uniqueNames) != len(segments) { + t.Fatalf("Expected the Segments to be unique but got %q unique segments and %d total segments", len(uniqueNames), len(segments)) + } +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup.go new file mode 100644 index 0000000000000..12a29d402feda --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup.go @@ -0,0 +1,109 @@ +package tenants + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.ResourceId = ResourceGroupId{} + +// ResourceGroupId is a struct representing the Resource ID for a Resource Group +type ResourceGroupId struct { + SubscriptionId string + ResourceGroup string +} + +// NewResourceGroupID returns a new ResourceGroupId struct +func NewResourceGroupID(subscriptionId string, resourceGroup string) ResourceGroupId { + return ResourceGroupId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + } +} + +// ParseResourceGroupID parses 'input' into a ResourceGroupId +func ParseResourceGroupID(input string) (*ResourceGroupId, error) { + parser := resourceids.NewParserFromResourceIdType(ResourceGroupId{}) + parsed, err := parser.Parse(input, false) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := ResourceGroupId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, fmt.Errorf("the segment 'subscriptionId' was not found in the resource id %q", input) + } + + if id.ResourceGroup, ok = parsed.Parsed["resourceGroup"]; !ok { + return nil, fmt.Errorf("the segment 'resourceGroup' was not found in the resource id %q", input) + } + + return &id, nil +} + +// ParseResourceGroupIDInsensitively parses 'input' case-insensitively into a ResourceGroupId +// note: this method should only be used for API response data and not user input +func ParseResourceGroupIDInsensitively(input string) (*ResourceGroupId, error) { + parser := resourceids.NewParserFromResourceIdType(ResourceGroupId{}) + parsed, err := parser.Parse(input, true) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := ResourceGroupId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, fmt.Errorf("the segment 'subscriptionId' was not found in the resource id %q", input) + } + + if id.ResourceGroup, ok = parsed.Parsed["resourceGroup"]; !ok { + return nil, fmt.Errorf("the segment 'resourceGroup' was not found in the resource id %q", input) + } + + return &id, nil +} + +// ValidateResourceGroupID checks that 'input' can be parsed as a Resource Group ID +func ValidateResourceGroupID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := ParseResourceGroupID(v); err != nil { + errors = append(errors, err) + } + + return +} + +// ID returns the formatted Resource Group ID +func (id ResourceGroupId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup) +} + +// Segments returns a slice of Resource ID Segments which comprise this Resource Group ID +func (id ResourceGroupId) Segments() []resourceids.Segment { + return []resourceids.Segment{ + resourceids.StaticSegment("subscriptions", "subscriptions", "subscriptions"), + resourceids.SubscriptionIdSegment("subscriptionId", "12345678-1234-9876-4563-123456789012"), + resourceids.StaticSegment("resourceGroups", "resourceGroups", "resourceGroups"), + resourceids.ResourceGroupSegment("resourceGroup", "example-resource-group"), + } +} + +// String returns a human-readable description of this Resource Group ID +func (id ResourceGroupId) String() string { + components := []string{ + fmt.Sprintf("Subscription: %q", id.SubscriptionId), + fmt.Sprintf("Resource Group: %q", id.ResourceGroup), + } + return fmt.Sprintf("Resource Group (%s)", strings.Join(components, "\n")) +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup_test.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup_test.go new file mode 100644 index 0000000000000..9a3cae34236e4 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_resourcegroup_test.go @@ -0,0 +1,204 @@ +package tenants + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.ResourceId = ResourceGroupId{} + +func TestNewResourceGroupID(t *testing.T) { + id := NewResourceGroupID("12345678-1234-9876-4563-123456789012", "example-resource-group") + + if id.SubscriptionId != "12345678-1234-9876-4563-123456789012" { + t.Fatalf("Expected %q but got %q for Segment 'SubscriptionId'", id.SubscriptionId, "12345678-1234-9876-4563-123456789012") + } + + if id.ResourceGroup != "example-resource-group" { + t.Fatalf("Expected %q but got %q for Segment 'ResourceGroup'", id.ResourceGroup, "example-resource-group") + } +} + +func TestFormatResourceGroupID(t *testing.T) { + actual := NewResourceGroupID("12345678-1234-9876-4563-123456789012", "example-resource-group").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group" + if actual != expected { + t.Fatalf("Expected the Formatted ID to be %q but got %q", expected, actual) + } +} + +func TestParseResourceGroupID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ResourceGroupId + }{ + { + // Incomplete URI + Input: "", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups", + Error: true, + }, + { + // Valid URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "example-resource-group", + }, + }, + { + // Invalid (Valid Uri with Extra segment) + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/extra", + Error: true, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ParseResourceGroupID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %+v", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + } +} + +func TestParseResourceGroupIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ResourceGroupId + }{ + { + // Incomplete URI + Input: "", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS", + Error: true, + }, + { + // Valid URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "example-resource-group", + }, + }, + { + // Invalid (Valid Uri with Extra segment) + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/extra", + Error: true, + }, + { + // Valid URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "eXaMpLe-rEsOuRcE-GrOuP", + }, + }, + { + // Invalid (Valid Uri with Extra segment - mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/rEsOuRcEgRoUpS/eXaMpLe-rEsOuRcE-GrOuP/extra", + Error: true, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ParseResourceGroupIDInsensitively(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %+v", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + } +} + +func TestSegmentsForResourceGroupId(t *testing.T) { + segments := ResourceGroupId{}.Segments() + if len(segments) == 0 { + t.Fatalf("ResourceGroupId has no segments") + } + + uniqueNames := make(map[string]struct{}, 0) + for _, segment := range segments { + uniqueNames[segment.Name] = struct{}{} + } + if len(uniqueNames) != len(segments) { + t.Fatalf("Expected the Segments to be unique but got %q unique segments and %d total segments", len(uniqueNames), len(segments)) + } +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription.go new file mode 100644 index 0000000000000..6930ba6ad66ad --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription.go @@ -0,0 +1,96 @@ +package tenants + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.ResourceId = SubscriptionId{} + +// SubscriptionId is a struct representing the Resource ID for a Subscription +type SubscriptionId struct { + SubscriptionId string +} + +// NewSubscriptionID returns a new SubscriptionId struct +func NewSubscriptionID(subscriptionId string) SubscriptionId { + return SubscriptionId{ + SubscriptionId: subscriptionId, + } +} + +// ParseSubscriptionID parses 'input' into a SubscriptionId +func ParseSubscriptionID(input string) (*SubscriptionId, error) { + parser := resourceids.NewParserFromResourceIdType(SubscriptionId{}) + parsed, err := parser.Parse(input, false) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := SubscriptionId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, fmt.Errorf("the segment 'subscriptionId' was not found in the resource id %q", input) + } + + return &id, nil +} + +// ParseSubscriptionIDInsensitively parses 'input' case-insensitively into a SubscriptionId +// note: this method should only be used for API response data and not user input +func ParseSubscriptionIDInsensitively(input string) (*SubscriptionId, error) { + parser := resourceids.NewParserFromResourceIdType(SubscriptionId{}) + parsed, err := parser.Parse(input, true) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := SubscriptionId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, fmt.Errorf("the segment 'subscriptionId' was not found in the resource id %q", input) + } + + return &id, nil +} + +// ValidateSubscriptionID checks that 'input' can be parsed as a Subscription ID +func ValidateSubscriptionID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := ParseSubscriptionID(v); err != nil { + errors = append(errors, err) + } + + return +} + +// ID returns the formatted Subscription ID +func (id SubscriptionId) ID() string { + fmtString := "/subscriptions/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId) +} + +// Segments returns a slice of Resource ID Segments which comprise this Subscription ID +func (id SubscriptionId) Segments() []resourceids.Segment { + return []resourceids.Segment{ + resourceids.StaticSegment("subscriptions", "subscriptions", "subscriptions"), + resourceids.SubscriptionIdSegment("subscriptionId", "12345678-1234-9876-4563-123456789012"), + } +} + +// String returns a human-readable description of this Subscription ID +func (id SubscriptionId) String() string { + components := []string{ + fmt.Sprintf("Subscription: %q", id.SubscriptionId), + } + return fmt.Sprintf("Subscription (%s)", strings.Join(components, "\n")) +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription_test.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription_test.go new file mode 100644 index 0000000000000..d34f2bc59bcad --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/id_subscription_test.go @@ -0,0 +1,159 @@ +package tenants + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.ResourceId = SubscriptionId{} + +func TestNewSubscriptionID(t *testing.T) { + id := NewSubscriptionID("12345678-1234-9876-4563-123456789012") + + if id.SubscriptionId != "12345678-1234-9876-4563-123456789012" { + t.Fatalf("Expected %q but got %q for Segment 'SubscriptionId'", id.SubscriptionId, "12345678-1234-9876-4563-123456789012") + } +} + +func TestFormatSubscriptionID(t *testing.T) { + actual := NewSubscriptionID("12345678-1234-9876-4563-123456789012").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012" + if actual != expected { + t.Fatalf("Expected the Formatted ID to be %q but got %q", expected, actual) + } +} + +func TestParseSubscriptionID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *SubscriptionId + }{ + { + // Incomplete URI + Input: "", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions", + Error: true, + }, + { + // Valid URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012", + Expected: &SubscriptionId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + }, + }, + { + // Invalid (Valid Uri with Extra segment) + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/extra", + Error: true, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ParseSubscriptionID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %+v", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + + } +} + +func TestParseSubscriptionIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *SubscriptionId + }{ + { + // Incomplete URI + Input: "", + Error: true, + }, + { + // Incomplete URI + Input: "/subscriptions", + Error: true, + }, + { + // Incomplete URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs", + Error: true, + }, + { + // Valid URI + Input: "/subscriptions/12345678-1234-9876-4563-123456789012", + Expected: &SubscriptionId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + }, + }, + { + // Invalid (Valid Uri with Extra segment) + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/extra", + Error: true, + }, + { + // Valid URI (mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012", + Expected: &SubscriptionId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + }, + }, + { + // Invalid (Valid Uri with Extra segment - mIxEd CaSe since this is insensitive) + Input: "/sUbScRiPtIoNs/12345678-1234-9876-4563-123456789012/extra", + Error: true, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ParseSubscriptionIDInsensitively(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %+v", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + + } +} + +func TestSegmentsForSubscriptionId(t *testing.T) { + segments := SubscriptionId{}.Segments() + if len(segments) == 0 { + t.Fatalf("SubscriptionId has no segments") + } + + uniqueNames := make(map[string]struct{}, 0) + for _, segment := range segments { + uniqueNames[segment.Name] = struct{}{} + } + if len(uniqueNames) != len(segments) { + t.Fatalf("Expected the Segments to be unique but got %q unique segments and %d total segments", len(uniqueNames), len(segments)) + } +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_checknameavailability_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_checknameavailability_autorest.go new file mode 100644 index 0000000000000..57cb39ff5ea4f --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_checknameavailability_autorest.go @@ -0,0 +1,66 @@ +package tenants + +import ( + "context" + "fmt" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" +) + +type CheckNameAvailabilityResponse struct { + HttpResponse *http.Response + Model *CheckNameAvailabilityResult +} + +// CheckNameAvailability ... +func (c TenantsClient) CheckNameAvailability(ctx context.Context, id SubscriptionId, input CheckNameAvailabilityRequest) (result CheckNameAvailabilityResponse, err error) { + req, err := c.preparerForCheckNameAvailability(ctx, id, input) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "CheckNameAvailability", nil, "Failure preparing request") + return + } + + result.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "CheckNameAvailability", result.HttpResponse, "Failure sending request") + return + } + + result, err = c.responderForCheckNameAvailability(result.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "CheckNameAvailability", result.HttpResponse, "Failure responding to request") + return + } + + return +} + +// preparerForCheckNameAvailability prepares the CheckNameAvailability request. +func (c TenantsClient) preparerForCheckNameAvailability(ctx context.Context, id SubscriptionId, input CheckNameAvailabilityRequest) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPost(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(fmt.Sprintf("%s/providers/Microsoft.AzureActiveDirectory/checkNameAvailability", id.ID())), + autorest.WithJSON(input), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// responderForCheckNameAvailability handles the response to the CheckNameAvailability request. The method always +// closes the http.Response Body. +func (c TenantsClient) responderForCheckNameAvailability(resp *http.Response) (result CheckNameAvailabilityResponse, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result.Model), + autorest.ByClosing()) + result.HttpResponse = resp + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_create_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_create_autorest.go new file mode 100644 index 0000000000000..235d5a68b9458 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_create_autorest.go @@ -0,0 +1,75 @@ +package tenants + +import ( + "context" + "fmt" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/hashicorp/go-azure-helpers/polling" +) + +type CreateResponse struct { + Poller polling.LongRunningPoller + HttpResponse *http.Response +} + +// Create ... +func (c TenantsClient) Create(ctx context.Context, id B2CDirectoryId, input CreateTenant) (result CreateResponse, err error) { + req, err := c.preparerForCreate(ctx, id, input) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Create", nil, "Failure preparing request") + return + } + + result, err = c.senderForCreate(ctx, req) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Create", result.HttpResponse, "Failure sending request") + return + } + + return +} + +// CreateThenPoll performs Create then polls until it's completed +func (c TenantsClient) CreateThenPoll(ctx context.Context, id B2CDirectoryId, input CreateTenant) error { + result, err := c.Create(ctx, id, input) + if err != nil { + return fmt.Errorf("performing Create: %+v", err) + } + + if err := result.Poller.PollUntilDone(); err != nil { + return fmt.Errorf("polling after Create: %+v", err) + } + + return nil +} + +// preparerForCreate prepares the Create request. +func (c TenantsClient) preparerForCreate(ctx context.Context, id B2CDirectoryId, input CreateTenant) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(id.ID()), + autorest.WithJSON(input), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// senderForCreate sends the Create request. The method will close the +// http.Response Body if it receives an error. +func (c TenantsClient) senderForCreate(ctx context.Context, req *http.Request) (future CreateResponse, err error) { + var resp *http.Response + resp, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + return + } + future.Poller, err = polling.NewLongRunningPollerFromResponse(ctx, resp, c.Client) + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_delete_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_delete_autorest.go new file mode 100644 index 0000000000000..550cec4b7859d --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_delete_autorest.go @@ -0,0 +1,73 @@ +package tenants + +import ( + "context" + "fmt" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "github.com/hashicorp/go-azure-helpers/polling" +) + +type DeleteResponse struct { + Poller polling.LongRunningPoller + HttpResponse *http.Response +} + +// Delete ... +func (c TenantsClient) Delete(ctx context.Context, id B2CDirectoryId) (result DeleteResponse, err error) { + req, err := c.preparerForDelete(ctx, id) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Delete", nil, "Failure preparing request") + return + } + + result, err = c.senderForDelete(ctx, req) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Delete", result.HttpResponse, "Failure sending request") + return + } + + return +} + +// DeleteThenPoll performs Delete then polls until it's completed +func (c TenantsClient) DeleteThenPoll(ctx context.Context, id B2CDirectoryId) error { + result, err := c.Delete(ctx, id) + if err != nil { + return fmt.Errorf("performing Delete: %+v", err) + } + + if err := result.Poller.PollUntilDone(); err != nil { + return fmt.Errorf("polling after Delete: %+v", err) + } + + return nil +} + +// preparerForDelete prepares the Delete request. +func (c TenantsClient) preparerForDelete(ctx context.Context, id B2CDirectoryId) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsDelete(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(id.ID()), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// senderForDelete sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (c TenantsClient) senderForDelete(ctx context.Context, req *http.Request) (future DeleteResponse, err error) { + var resp *http.Response + resp, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + return + } + future.Poller, err = polling.NewLongRunningPollerFromResponse(ctx, resp, c.Client) + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_get_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_get_autorest.go new file mode 100644 index 0000000000000..126186719e5de --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_get_autorest.go @@ -0,0 +1,64 @@ +package tenants + +import ( + "context" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" +) + +type GetResponse struct { + HttpResponse *http.Response + Model *Tenant +} + +// Get ... +func (c TenantsClient) Get(ctx context.Context, id B2CDirectoryId) (result GetResponse, err error) { + req, err := c.preparerForGet(ctx, id) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Get", nil, "Failure preparing request") + return + } + + result.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Get", result.HttpResponse, "Failure sending request") + return + } + + result, err = c.responderForGet(result.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Get", result.HttpResponse, "Failure responding to request") + return + } + + return +} + +// preparerForGet prepares the Get request. +func (c TenantsClient) preparerForGet(ctx context.Context, id B2CDirectoryId) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(id.ID()), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// responderForGet handles the response to the Get request. The method always +// closes the http.Response Body. +func (c TenantsClient) responderForGet(resp *http.Response) (result GetResponse, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result.Model), + autorest.ByClosing()) + result.HttpResponse = resp + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbyresourcegroup_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbyresourcegroup_autorest.go new file mode 100644 index 0000000000000..4e1bf15fc569b --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbyresourcegroup_autorest.go @@ -0,0 +1,183 @@ +package tenants + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" +) + +type ListByResourceGroupResponse struct { + HttpResponse *http.Response + Model *[]Tenant + + nextLink *string + nextPageFunc func(ctx context.Context, nextLink string) (ListByResourceGroupResponse, error) +} + +type ListByResourceGroupCompleteResult struct { + Items []Tenant +} + +func (r ListByResourceGroupResponse) HasMore() bool { + return r.nextLink != nil +} + +func (r ListByResourceGroupResponse) LoadMore(ctx context.Context) (resp ListByResourceGroupResponse, err error) { + if !r.HasMore() { + err = fmt.Errorf("no more pages returned") + return + } + return r.nextPageFunc(ctx, *r.nextLink) +} + +// ListByResourceGroup ... +func (c TenantsClient) ListByResourceGroup(ctx context.Context, id ResourceGroupId) (resp ListByResourceGroupResponse, err error) { + req, err := c.preparerForListByResourceGroup(ctx, id) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListByResourceGroup", nil, "Failure preparing request") + return + } + + resp.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListByResourceGroup", resp.HttpResponse, "Failure sending request") + return + } + + resp, err = c.responderForListByResourceGroup(resp.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListByResourceGroup", resp.HttpResponse, "Failure responding to request") + return + } + return +} + +// ListByResourceGroupComplete retrieves all of the results into a single object +func (c TenantsClient) ListByResourceGroupComplete(ctx context.Context, id ResourceGroupId) (ListByResourceGroupCompleteResult, error) { + return c.ListByResourceGroupCompleteMatchingPredicate(ctx, id, TenantPredicate{}) +} + +// ListByResourceGroupCompleteMatchingPredicate retrieves all of the results and then applied the predicate +func (c TenantsClient) ListByResourceGroupCompleteMatchingPredicate(ctx context.Context, id ResourceGroupId, predicate TenantPredicate) (resp ListByResourceGroupCompleteResult, err error) { + items := make([]Tenant, 0) + + page, err := c.ListByResourceGroup(ctx, id) + if err != nil { + err = fmt.Errorf("loading the initial page: %+v", err) + return + } + if page.Model != nil { + for _, v := range *page.Model { + if predicate.Matches(v) { + items = append(items, v) + } + } + } + + for page.HasMore() { + page, err = page.LoadMore(ctx) + if err != nil { + err = fmt.Errorf("loading the next page: %+v", err) + return + } + + if page.Model != nil { + for _, v := range *page.Model { + if predicate.Matches(v) { + items = append(items, v) + } + } + } + } + + out := ListByResourceGroupCompleteResult{ + Items: items, + } + return out, nil +} + +// preparerForListByResourceGroup prepares the ListByResourceGroup request. +func (c TenantsClient) preparerForListByResourceGroup(ctx context.Context, id ResourceGroupId) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(fmt.Sprintf("%s/providers/Microsoft.AzureActiveDirectory/b2cDirectories", id.ID())), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// preparerForListByResourceGroupWithNextLink prepares the ListByResourceGroup request with the given nextLink token. +func (c TenantsClient) preparerForListByResourceGroupWithNextLink(ctx context.Context, nextLink string) (*http.Request, error) { + uri, err := url.Parse(nextLink) + if err != nil { + return nil, fmt.Errorf("parsing nextLink %q: %+v", nextLink, err) + } + queryParameters := map[string]interface{}{} + for k, v := range uri.Query() { + if len(v) == 0 { + continue + } + val := v[0] + val = autorest.Encode("query", val) + queryParameters[k] = val + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(uri.Path), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// responderForListByResourceGroup handles the response to the ListByResourceGroup request. The method always +// closes the http.Response Body. +func (c TenantsClient) responderForListByResourceGroup(resp *http.Response) (result ListByResourceGroupResponse, err error) { + type page struct { + Values []Tenant `json:"value"` + NextLink *string `json:"nextLink"` + } + var respObj page + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&respObj), + autorest.ByClosing()) + result.HttpResponse = resp + result.Model = &respObj.Values + result.nextLink = respObj.NextLink + if respObj.NextLink != nil { + result.nextPageFunc = func(ctx context.Context, nextLink string) (result ListByResourceGroupResponse, err error) { + req, err := c.preparerForListByResourceGroupWithNextLink(ctx, nextLink) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListByResourceGroup", nil, "Failure preparing request") + return + } + + result.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListByResourceGroup", result.HttpResponse, "Failure sending request") + return + } + + result, err = c.responderForListByResourceGroup(result.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListByResourceGroup", result.HttpResponse, "Failure responding to request") + return + } + + return + } + } + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbysubscription_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbysubscription_autorest.go new file mode 100644 index 0000000000000..68605bb461f8d --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_listbysubscription_autorest.go @@ -0,0 +1,183 @@ +package tenants + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" +) + +type ListBySubscriptionResponse struct { + HttpResponse *http.Response + Model *[]Tenant + + nextLink *string + nextPageFunc func(ctx context.Context, nextLink string) (ListBySubscriptionResponse, error) +} + +type ListBySubscriptionCompleteResult struct { + Items []Tenant +} + +func (r ListBySubscriptionResponse) HasMore() bool { + return r.nextLink != nil +} + +func (r ListBySubscriptionResponse) LoadMore(ctx context.Context) (resp ListBySubscriptionResponse, err error) { + if !r.HasMore() { + err = fmt.Errorf("no more pages returned") + return + } + return r.nextPageFunc(ctx, *r.nextLink) +} + +// ListBySubscription ... +func (c TenantsClient) ListBySubscription(ctx context.Context, id SubscriptionId) (resp ListBySubscriptionResponse, err error) { + req, err := c.preparerForListBySubscription(ctx, id) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListBySubscription", nil, "Failure preparing request") + return + } + + resp.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListBySubscription", resp.HttpResponse, "Failure sending request") + return + } + + resp, err = c.responderForListBySubscription(resp.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListBySubscription", resp.HttpResponse, "Failure responding to request") + return + } + return +} + +// ListBySubscriptionComplete retrieves all of the results into a single object +func (c TenantsClient) ListBySubscriptionComplete(ctx context.Context, id SubscriptionId) (ListBySubscriptionCompleteResult, error) { + return c.ListBySubscriptionCompleteMatchingPredicate(ctx, id, TenantPredicate{}) +} + +// ListBySubscriptionCompleteMatchingPredicate retrieves all of the results and then applied the predicate +func (c TenantsClient) ListBySubscriptionCompleteMatchingPredicate(ctx context.Context, id SubscriptionId, predicate TenantPredicate) (resp ListBySubscriptionCompleteResult, err error) { + items := make([]Tenant, 0) + + page, err := c.ListBySubscription(ctx, id) + if err != nil { + err = fmt.Errorf("loading the initial page: %+v", err) + return + } + if page.Model != nil { + for _, v := range *page.Model { + if predicate.Matches(v) { + items = append(items, v) + } + } + } + + for page.HasMore() { + page, err = page.LoadMore(ctx) + if err != nil { + err = fmt.Errorf("loading the next page: %+v", err) + return + } + + if page.Model != nil { + for _, v := range *page.Model { + if predicate.Matches(v) { + items = append(items, v) + } + } + } + } + + out := ListBySubscriptionCompleteResult{ + Items: items, + } + return out, nil +} + +// preparerForListBySubscription prepares the ListBySubscription request. +func (c TenantsClient) preparerForListBySubscription(ctx context.Context, id SubscriptionId) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(fmt.Sprintf("%s/providers/Microsoft.AzureActiveDirectory/b2cDirectories", id.ID())), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// preparerForListBySubscriptionWithNextLink prepares the ListBySubscription request with the given nextLink token. +func (c TenantsClient) preparerForListBySubscriptionWithNextLink(ctx context.Context, nextLink string) (*http.Request, error) { + uri, err := url.Parse(nextLink) + if err != nil { + return nil, fmt.Errorf("parsing nextLink %q: %+v", nextLink, err) + } + queryParameters := map[string]interface{}{} + for k, v := range uri.Query() { + if len(v) == 0 { + continue + } + val := v[0] + val = autorest.Encode("query", val) + queryParameters[k] = val + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsGet(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(uri.Path), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// responderForListBySubscription handles the response to the ListBySubscription request. The method always +// closes the http.Response Body. +func (c TenantsClient) responderForListBySubscription(resp *http.Response) (result ListBySubscriptionResponse, err error) { + type page struct { + Values []Tenant `json:"value"` + NextLink *string `json:"nextLink"` + } + var respObj page + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&respObj), + autorest.ByClosing()) + result.HttpResponse = resp + result.Model = &respObj.Values + result.nextLink = respObj.NextLink + if respObj.NextLink != nil { + result.nextPageFunc = func(ctx context.Context, nextLink string) (result ListBySubscriptionResponse, err error) { + req, err := c.preparerForListBySubscriptionWithNextLink(ctx, nextLink) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListBySubscription", nil, "Failure preparing request") + return + } + + result.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListBySubscription", result.HttpResponse, "Failure sending request") + return + } + + result, err = c.responderForListBySubscription(result.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "ListBySubscription", result.HttpResponse, "Failure responding to request") + return + } + + return + } + } + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_update_autorest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_update_autorest.go new file mode 100644 index 0000000000000..5e5fde9602600 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/method_update_autorest.go @@ -0,0 +1,65 @@ +package tenants + +import ( + "context" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" +) + +type UpdateResponse struct { + HttpResponse *http.Response + Model *Tenant +} + +// Update ... +func (c TenantsClient) Update(ctx context.Context, id B2CDirectoryId, input UpdateTenant) (result UpdateResponse, err error) { + req, err := c.preparerForUpdate(ctx, id, input) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Update", nil, "Failure preparing request") + return + } + + result.HttpResponse, err = c.Client.Send(req, azure.DoRetryWithRegistration(c.Client)) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Update", result.HttpResponse, "Failure sending request") + return + } + + result, err = c.responderForUpdate(result.HttpResponse) + if err != nil { + err = autorest.NewErrorWithError(err, "tenants.TenantsClient", "Update", result.HttpResponse, "Failure responding to request") + return + } + + return +} + +// preparerForUpdate prepares the Update request. +func (c TenantsClient) preparerForUpdate(ctx context.Context, id B2CDirectoryId, input UpdateTenant) (*http.Request, error) { + queryParameters := map[string]interface{}{ + "api-version": defaultApiVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPatch(), + autorest.WithBaseURL(c.baseUri), + autorest.WithPath(id.ID()), + autorest.WithJSON(input), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +// responderForUpdate handles the response to the Update request. The method always +// closes the http.Response Body. +func (c TenantsClient) responderForUpdate(resp *http.Response) (result UpdateResponse, err error) { + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result.Model), + autorest.ByClosing()) + result.HttpResponse = resp + return +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_billingconfig.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_billingconfig.go new file mode 100644 index 0000000000000..11da0bda66f9b --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_billingconfig.go @@ -0,0 +1,6 @@ +package tenants + +type BillingConfig struct { + BillingType *BillingType `json:"billingType,omitempty"` + EffectiveStartDateUtc *string `json:"effectiveStartDateUtc,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityrequest.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityrequest.go new file mode 100644 index 0000000000000..96fc9554609f3 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityrequest.go @@ -0,0 +1,6 @@ +package tenants + +type CheckNameAvailabilityRequest struct { + CountryCode *string `json:"countryCode,omitempty"` + Name *string `json:"name,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityresult.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityresult.go new file mode 100644 index 0000000000000..9b2f8447d7ab7 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_checknameavailabilityresult.go @@ -0,0 +1,7 @@ +package tenants + +type CheckNameAvailabilityResult struct { + Message *string `json:"message,omitempty"` + NameAvailable *bool `json:"nameAvailable,omitempty"` + Reason *string `json:"reason,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenant.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenant.go new file mode 100644 index 0000000000000..204caf6f1616c --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenant.go @@ -0,0 +1,8 @@ +package tenants + +type CreateTenant struct { + Location Location `json:"location"` + Properties TenantPropertiesForCreate `json:"properties"` + Sku Sku `json:"sku"` + Tags *map[string]string `json:"tags,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenantproperties.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenantproperties.go new file mode 100644 index 0000000000000..bb2e6417e9b55 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_createtenantproperties.go @@ -0,0 +1,6 @@ +package tenants + +type CreateTenantProperties struct { + CountryCode string `json:"countryCode"` + DisplayName string `json:"displayName"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_sku.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_sku.go new file mode 100644 index 0000000000000..b6e98f5e5b76e --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_sku.go @@ -0,0 +1,6 @@ +package tenants + +type Sku struct { + Name SkuName `json:"name"` + Tier SkuTier `json:"tier"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenant.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenant.go new file mode 100644 index 0000000000000..93be19daea40f --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenant.go @@ -0,0 +1,11 @@ +package tenants + +type Tenant struct { + Id *string `json:"id,omitempty"` + Location *Location `json:"location,omitempty"` + Name *string `json:"name,omitempty"` + Properties *TenantProperties `json:"properties,omitempty"` + Sku *Sku `json:"sku,omitempty"` + Tags *map[string]string `json:"tags,omitempty"` + Type *string `json:"type,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantproperties.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantproperties.go new file mode 100644 index 0000000000000..735bd05f7117d --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantproperties.go @@ -0,0 +1,8 @@ +package tenants + +type TenantProperties struct { + BillingConfig *BillingConfig `json:"billingConfig,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + TenantId *string `json:"tenantId,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantpropertiesforcreate.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantpropertiesforcreate.go new file mode 100644 index 0000000000000..75700ee51ba25 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_tenantpropertiesforcreate.go @@ -0,0 +1,5 @@ +package tenants + +type TenantPropertiesForCreate struct { + CreateTenantProperties CreateTenantProperties `json:"createTenantProperties"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenant.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenant.go new file mode 100644 index 0000000000000..f9c67c66a9206 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenant.go @@ -0,0 +1,7 @@ +package tenants + +type UpdateTenant struct { + Properties UpdateTenantProperties `json:"properties"` + Sku Sku `json:"sku"` + Tags *map[string]string `json:"tags,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenantproperties.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenantproperties.go new file mode 100644 index 0000000000000..7bebae70f9ec2 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/model_updatetenantproperties.go @@ -0,0 +1,5 @@ +package tenants + +type UpdateTenantProperties struct { + BillingConfig *BillingConfig `json:"billingConfig,omitempty"` +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/predicates.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/predicates.go new file mode 100644 index 0000000000000..e69357135eb4f --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/predicates.go @@ -0,0 +1,24 @@ +package tenants + +type TenantPredicate struct { + Id *string + Name *string + Type *string +} + +func (p TenantPredicate) Matches(input Tenant) bool { + + if p.Id != nil && (input.Id == nil && *p.Id != *input.Id) { + return false + } + + if p.Name != nil && (input.Name == nil && *p.Name != *input.Name) { + return false + } + + if p.Type != nil && (input.Type == nil && *p.Type != *input.Type) { + return false + } + + return true +} diff --git a/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/version.go b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/version.go new file mode 100644 index 0000000000000..915e4df30f7b1 --- /dev/null +++ b/internal/services/aadb2c/sdk/2021-04-01-preview/tenants/version.go @@ -0,0 +1,9 @@ +package tenants + +import "fmt" + +const defaultApiVersion = "2021-04-01-preview" + +func userAgent() string { + return fmt.Sprintf("pandora/tenants/%s", defaultApiVersion) +} diff --git a/internal/services/aadb2c/validate/directory_id.go b/internal/services/aadb2c/validate/directory_id.go new file mode 100644 index 0000000000000..c9494b0cb3329 --- /dev/null +++ b/internal/services/aadb2c/validate/directory_id.go @@ -0,0 +1,21 @@ +package validate + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c/sdk/2021-04-01-preview/tenants" +) + +func B2CDirectoryID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := tenants.ParseB2CDirectoryID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/website/docs/d/aadb2c_directory.html.markdown b/website/docs/d/aadb2c_directory.html.markdown new file mode 100644 index 0000000000000..4be9b508f28f3 --- /dev/null +++ b/website/docs/d/aadb2c_directory.html.markdown @@ -0,0 +1,58 @@ +--- +subcategory: "AAD B2C" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_aadb2c_directory" +description: |- + Gets information about an existing AAD B2C Directory. +--- + +# Data Source: azurerm_aadb2c_directory + +Use this data source to access information about an existing AAD B2C Directory. + +## Example Usage + +```hcl +data "azurerm_aadb2c_directory" "example" { + resource_group_name = "example-rg" + domain_name = "exampleb2ctenant.onmicrosoft.com" +} + +output "tenant_id" { + value = data.azurerm_aadb2c_directory.example.tenant_id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `domain_name` - (Required) Domain name of the B2C tenant, including the `.onmicrosoft.com` suffix. + +* `resource_group_name` - (Required) The name of the Resource Group where the AAD B2C Directory exists. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the AAD B2C Directory. + +* `billing_type` - The type of billing for the AAD B2C tenant. Possible values include: `MAU` or `Auths`. + +* `data_residency_location` - Location in which the B2C tenant is hosted and data resides. See [official docs](https://aka.ms/B2CDataResidenc) for more information. + +* `effective_start_date` - The date from which the billing type took effect. May not be populated until after the first billing cycle. + +* `sku_name` - Billing SKU for the B2C tenant. See [official docs](https://aka.ms/b2cBilling) for more information. + +* `tags` - A mapping of tags assigned to the AAD B2C Directory. + +* `tenant_id` - The Tenant ID for the AAD B2C tenant. + +~> **Note**: The `country_code` and `display_name` are not returned by this data source due to API limitations. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the AAD B2C Directory. diff --git a/website/docs/r/aadb2c_directory.html.markdown b/website/docs/r/aadb2c_directory.html.markdown new file mode 100644 index 0000000000000..ece5f78db3841 --- /dev/null +++ b/website/docs/r/aadb2c_directory.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "AAD B2C" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_aadb2c_directory" +description: |- + Manages an AAD B2C Directory. +--- + +# azurerm_aadb2c_directory + +Manages an AAD B2C Directory. + +## Example Usage + +```hcl +resource "azurerm_aadb2c_directory" "example" { + country_code = "US" + data_residency_location = "United States" + display_name = "example-b2c-tenant" + domain_name = "exampleb2ctenant.onmicrosoft.com" + resource_group_name = "example-rg" + sku_name = "PremiumP1" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `country_code` - (Required) Country code of the B2C tenant. The `country_code` should be valid for the specified `data_residency_location`. See [official docs](https://aka.ms/B2CDataResidency) for valid country codes. Changing this forces a new AAD B2C Directory to be created. + +* `data_residency_location` - (Required) Location in which the B2C tenant is hosted and data resides. The `data_residency_location` should be valid for the specified `country_code`. See [official docs](https://aka.ms/B2CDataResidenc) for more information. Changing this forces a new AAD B2C Directory to be created. + +* `display_name` - (Required) The initial display name of the B2C tenant. Changing this forces a new AAD B2C Directory to be created. + +* `domain_name` - (Required) Domain name of the B2C tenant, including the `.onmicrosoft.com` suffix. Changing this forces a new AAD B2C Directory to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the AAD B2C Directory should exist. Changing this forces a new AAD B2C Directory to be created. + +* `sku_name` - (Required) Billing SKU for the B2C tenant. Must be one of: `PremiumP1` or `PremiumP2` (`Standard` is not supported). See [official docs](https://aka.ms/b2cBilling) for more information. + +* `tags` - (Optional) A mapping of tags which should be assigned to the AAD B2C Directory. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the AAD B2C Directory. + +* `billing_type` - The type of billing for the AAD B2C tenant. Possible values include: `MAU` or `Auths`. + +* `effective_start_date` - The date from which the billing type took effect. May not be populated until after the first billing cycle. + +* `tenant_id` - The Tenant ID for the AAD B2C tenant. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the AAD B2C Directory. +* `read` - (Defaults to 5 minutes) Used when retrieving the AAD B2C Directory. +* `update` - (Defaults to 30 minutes) Used when updating the AAD B2C Directory. +* `delete` - (Defaults to 30 minutes) Used when deleting the AAD B2C Directory. + +## Import + +AAD B2C Directories can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_aadb2c_directory.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.AzureActiveDirectory/b2cDirectories/directory-name +```