Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blueprints: swap to go-azure-sdk #21569

Merged
merged 12 commits into from
Aug 4, 2023
4 changes: 3 additions & 1 deletion internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error
if client.Batch, err = batch.NewClient(o); err != nil {
return fmt.Errorf("building clients for Batch: %+v", err)
}
client.Blueprints = blueprints.NewClient(o)
if client.Blueprints, err = blueprints.NewClient(o); err != nil {
return fmt.Errorf("building clients for BluePrints: %+v", err)
}
client.Bot = bot.NewClient(o)
client.Cdn = cdn.NewClient(o)
if client.Cognitive, err = cognitiveServices.NewClient(o); err != nil {
Expand Down
194 changes: 89 additions & 105 deletions internal/services/blueprints/blueprint_assignment_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import (
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/blueprint/mgmt/2018-11-01-preview/blueprint" // nolint: staticcheck
"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-sdk/resource-manager/blueprints/2018-11-01-preview/assignment"
"github.com/hashicorp/go-azure-sdk/resource-manager/blueprints/2018-11-01-preview/publishedblueprint"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/blueprints/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/blueprints/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand All @@ -30,7 +33,7 @@ func resourceBlueprintAssignment() *pluginsdk.Resource {
Delete: resourceBlueprintAssignmentDelete,

Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
_, err := parse.AssignmentID(id)
_, err := assignment.ParseScopedBlueprintAssignmentID(id)
return err
}),

Expand Down Expand Up @@ -58,12 +61,12 @@ func resourceBlueprintAssignment() *pluginsdk.Resource {

"location": commonschema.Location(),

"identity": commonschema.UserAssignedIdentityRequired(),
"identity": commonschema.SystemOrUserAssignedIdentityRequired(),

"version_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validate.VersionID,
ValidateFunc: publishedblueprint.ValidateScopedVersionID,
},

"parameter_values": {
Expand All @@ -85,11 +88,11 @@ func resourceBlueprintAssignment() *pluginsdk.Resource {
"lock_mode": {
Type: pluginsdk.TypeString,
Optional: true,
Default: string(blueprint.None),
Default: string(assignment.AssignmentLockModeNone),
ValidateFunc: validation.StringInSlice([]string{
string(blueprint.AssignmentLockModeNone),
string(blueprint.AssignmentLockModeAllResourcesReadOnly),
string(blueprint.AssignmentLockModeAllResourcesDoNotDelete),
string(assignment.AssignmentLockModeNone),
string(assignment.AssignmentLockModeAllResourcesReadOnly),
string(assignment.AssignmentLockModeAllResourcesDoNotDelete),
}, false),
// The first character of value returned by the service is always in lower case.
DiffSuppressFunc: suppress.CaseDifference,
Expand Down Expand Up @@ -142,34 +145,33 @@ func resourceBlueprintAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
id := assignment.NewScopedBlueprintAssignmentID(d.Get("target_subscription_id").(string), d.Get("name").(string))
blueprintId := d.Get("version_id").(string)
targetScope := d.Get("target_subscription_id").(string)

if d.IsNewResource() {
resp, err := client.Get(ctx, targetScope, name)
resp, err := client.Get(ctx, id)
if err != nil {
if !utils.ResponseWasNotFound(resp.Response) {
return fmt.Errorf("failure checking for existing Blueprint Assignment %q in scope %q", name, targetScope)
if !response.WasNotFound(resp.HttpResponse) {
return fmt.Errorf("checking for an existing %s: %+v", id, err)
}
}
if !utils.ResponseWasNotFound(resp.Response) {
return tf.ImportAsExistsError("azurerm_blueprint_assignment", *resp.ID)
if !response.WasNotFound(resp.HttpResponse) {
return tf.ImportAsExistsError("azurerm_blueprint_assignment", id.ID())
}
}

assignment := blueprint.Assignment{
AssignmentProperties: &blueprint.AssignmentProperties{
BlueprintID: utils.String(blueprintId), // This is mislabeled - The ID is that of the Published Version, not just the Blueprint
Scope: utils.String(targetScope),
payload := assignment.Assignment{
Properties: assignment.AssignmentProperties{
BlueprintId: pointer.To(blueprintId), // This is mislabeled - The ID is that of the Published Version, not just the Blueprint
Scope: pointer.To(id.ResourceScope),
},
Location: utils.String(azure.NormalizeLocation(d.Get("location"))),
Location: location.Normalize(d.Get("location").(string)),
}

if lockModeRaw, ok := d.GetOk("lock_mode"); ok {
assignmentLockSettings := &blueprint.AssignmentLockSettings{}
assignmentLockSettings := &assignment.AssignmentLockSettings{}
lockMode := lockModeRaw.(string)
assignmentLockSettings.Mode = blueprint.AssignmentLockMode(lockMode)
assignmentLockSettings.Mode = pointer.To(assignment.AssignmentLockMode(lockMode))
if lockMode != "None" {
excludedPrincipalsRaw := d.Get("lock_exclude_principals").([]interface{})
if len(excludedPrincipalsRaw) != 0 {
Expand All @@ -181,54 +183,52 @@ func resourceBlueprintAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int
assignmentLockSettings.ExcludedActions = utils.ExpandStringSlice(excludedActionsRaw)
}
}
assignment.AssignmentProperties.Locks = assignmentLockSettings
payload.Properties.Locks = assignmentLockSettings
}

identity, err := expandArmBlueprintAssignmentIdentity(d.Get("identity").([]interface{}))
i, err := identity.ExpandSystemOrUserAssignedMap(d.Get("identity").([]interface{}))
if err != nil {
return fmt.Errorf("expanding `identity`: %+v", err)
}
assignment.Identity = identity
payload.Identity = *i

if paramValuesRaw := d.Get("parameter_values"); paramValuesRaw != "" {
assignment.Parameters = expandArmBlueprintAssignmentParameters(paramValuesRaw.(string))
payload.Properties.Parameters = expandArmBlueprintAssignmentParameters(paramValuesRaw.(string))
} else {
assignment.Parameters = expandArmBlueprintAssignmentParameters("{}")
payload.Properties.Parameters = expandArmBlueprintAssignmentParameters("{}")
}

if resourceGroupsRaw := d.Get("resource_groups"); resourceGroupsRaw != "" {
assignment.ResourceGroups = expandArmBlueprintAssignmentResourceGroups(resourceGroupsRaw.(string))
payload.Properties.ResourceGroups = expandArmBlueprintAssignmentResourceGroups(resourceGroupsRaw.(string))
} else {
assignment.ResourceGroups = expandArmBlueprintAssignmentResourceGroups("{}")
payload.Properties.ResourceGroups = expandArmBlueprintAssignmentResourceGroups("{}")
}

resp, err := client.CreateOrUpdate(ctx, targetScope, name, assignment)
if err != nil {
if _, err = client.CreateOrUpdate(ctx, id, payload); err != nil {
return err
}

deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("internal-error: context had no deadline")
}
stateConf := &pluginsdk.StateChangeConf{
Pending: []string{
string(blueprint.Waiting),
string(blueprint.Validating),
string(blueprint.Creating),
string(blueprint.Deploying),
string(blueprint.Locking),
string(assignment.AssignmentProvisioningStateWaiting),
string(assignment.AssignmentProvisioningStateValidating),
string(assignment.AssignmentProvisioningStateCreating),
string(assignment.AssignmentProvisioningStateDeploying),
string(assignment.AssignmentProvisioningStateLocking),
},
Target: []string{string(blueprint.Succeeded)},
Refresh: blueprintAssignmentCreateStateRefreshFunc(ctx, client, targetScope, name),
Timeout: d.Timeout(pluginsdk.TimeoutCreate),
Target: []string{string(assignment.AssignmentProvisioningStateSucceeded)},
Refresh: blueprintAssignmentCreateStateRefreshFunc(ctx, client, id),
Timeout: time.Until(deadline),
}

if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("failed waiting for Blueprint Assignment %q (Scope %q): %+v", name, targetScope, err)
return fmt.Errorf("failed waiting for Blueprint Assignment %s: %+v", id.String(), err)
}

if resp.ID == nil || *resp.ID == "" {
return fmt.Errorf("could not read ID from Blueprint Assignment %q on scope %q", name, targetScope)
}

d.SetId(*resp.ID)
d.SetId(id.ID())

return resourceBlueprintAssignmentRead(d, meta)
}
Expand All @@ -238,81 +238,66 @@ func resourceBlueprintAssignmentRead(d *pluginsdk.ResourceData, meta interface{}
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.AssignmentID(d.Id())
id, err := assignment.ParseScopedBlueprintAssignmentID(d.Id())
if err != nil {
return err
}

resp, err := client.Get(ctx, id.Scope, id.Name)
resp, err := client.Get(ctx, *id)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[INFO] the Blueprint Assignment %q does not exist - removing from state", id.Name)
if response.WasNotFound(resp.HttpResponse) {
log.Printf("[INFO] the Blueprint Assignment %q does not exist - removing from state", id.String())
d.SetId("")
return nil
}

return fmt.Errorf("Read failed for Blueprint Assignment (%q): %+v", id.Name, err)
}

if resp.Name != nil {
d.Set("name", resp.Name)
return fmt.Errorf("Read failed for Blueprint Assignment (%q): %+v", id.String(), err)
}

if resp.Scope != nil {
d.Set("target_subscription_id", resp.Scope)
}
d.Set("name", id.BlueprintAssignmentName)
if model := resp.Model; model != nil {
p := model.Properties

if resp.Location != nil {
d.Set("location", azure.NormalizeLocation(*resp.Location))
}
d.Set("location", azure.NormalizeLocation(model.Location))
d.Set("target_subscription_id", pointer.From(p.Scope))
d.Set("version_id", pointer.From(p.BlueprintId))
d.Set("display_name", pointer.From(p.DisplayName))
d.Set("description", pointer.From(p.Description))

identity, err := flattenArmBlueprintAssignmentIdentity(resp.Identity)
if err != nil {
return fmt.Errorf("flattening `identity`: %+v", err)
}
if err := d.Set("identity", identity); err != nil {
return fmt.Errorf("setting `identity`: %+v", err)
}

if resp.AssignmentProperties != nil {
if resp.AssignmentProperties.BlueprintID != nil {
d.Set("version_id", resp.AssignmentProperties.BlueprintID)
}

if resp.AssignmentProperties.Parameters != nil {
params, err := flattenArmBlueprintAssignmentParameters(resp.Parameters)
if p.Parameters != nil {
params, err := flattenArmBlueprintAssignmentParameters(p.Parameters)
if err != nil {
return err
}
d.Set("parameter_values", params)
}

if resp.AssignmentProperties.ResourceGroups != nil {
resourceGroups, err := flattenArmBlueprintAssignmentResourceGroups(resp.ResourceGroups)
if p.ResourceGroups != nil {
resourceGroups, err := flattenArmBlueprintAssignmentResourceGroups(p.ResourceGroups)
if err != nil {
return err
}
d.Set("resource_groups", resourceGroups)
}

// Locks
if locks := resp.Locks; locks != nil {
d.Set("lock_mode", locks.Mode)
if locks := p.Locks; locks != nil {
d.Set("lock_mode", string(pointer.From(locks.Mode)))
if locks.ExcludedPrincipals != nil {
d.Set("lock_exclude_principals", locks.ExcludedPrincipals)
}
if locks.ExcludedActions != nil {
d.Set("lock_exclude_actions", locks.ExcludedActions)
}
}
}

if resp.DisplayName != nil {
d.Set("display_name", resp.DisplayName)
}

if resp.Description != nil {
d.Set("description", resp.Description)
i, err := identity.FlattenSystemOrUserAssignedMap(&model.Identity)
if err != nil {
return err
}
if err := d.Set("identity", i); err != nil {
return fmt.Errorf("setting `identity`: %+v", err)
}
}

return nil
Expand All @@ -323,36 +308,35 @@ func resourceBlueprintAssignmentDelete(d *pluginsdk.ResourceData, meta interface
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.AssignmentID(d.Id())
id, err := assignment.ParseScopedBlueprintAssignmentID(d.Id())
if err != nil {
return err
}

// We use none here to align the previous behaviour of the blueprint resource
// TODO: we could add a features flag for the blueprint to empower terraform when deleting the blueprint to delete all the generated resources as well
resp, err := client.Delete(ctx, id.Scope, id.Name, blueprint.None)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return nil
}
return fmt.Errorf("failed to delete Blueprint Assignment %q from scope %q: %+v", id.Name, id.Scope, err)
if _, err := client.Delete(ctx, *id, assignment.DeleteOperationOptions{}); err != nil {
return fmt.Errorf("failed to delete Blueprint Assignment %q from scope %q: %+v", id.BlueprintAssignmentName, id.ResourceScope, err)
}

deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("internal-error: context had no deadline")
}
stateConf := &pluginsdk.StateChangeConf{
Pending: []string{
string(blueprint.Waiting),
string(blueprint.Validating),
string(blueprint.Locking),
string(blueprint.Deleting),
string(blueprint.Failed),
string(assignment.AssignmentProvisioningStateWaiting),
string(assignment.AssignmentProvisioningStateValidating),
string(assignment.AssignmentProvisioningStateLocking),
string(assignment.AssignmentProvisioningStateDeleting),
string(assignment.AssignmentProvisioningStateFailed),
},
Target: []string{"NotFound"},
Refresh: blueprintAssignmentDeleteStateRefreshFunc(ctx, client, id.Scope, id.Name),
Timeout: d.Timeout(pluginsdk.TimeoutDelete),
Refresh: blueprintAssignmentDeleteStateRefreshFunc(ctx, client, *id),
Timeout: time.Until(deadline),
}

if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("Failed waiting for Blueprint Assignment %q (Scope %q): %+v", id.Name, id.Scope, err)
return fmt.Errorf("waiting for Blueprint Assignment %q: %+v", id.String(), err)
}

return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"fmt"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-sdk/resource-manager/blueprints/2018-11-01-preview/assignment"
"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/services/blueprints/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

type BlueprintAssignmentResource struct{}
Expand Down Expand Up @@ -107,17 +107,17 @@ func TestAccBlueprintAssignment_managementGroup(t *testing.T) {
}

func (t BlueprintAssignmentResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.AssignmentID(state.ID)
id, err := assignment.ParseScopedBlueprintAssignmentID(state.ID)
if err != nil {
return nil, err
}

resp, err := clients.Blueprints.AssignmentsClient.Get(ctx, id.Scope, id.Name)
resp, err := clients.Blueprints.AssignmentsClient.Get(ctx, *id)
if err != nil {
return nil, fmt.Errorf("retrieving Blueprint Assignment %q (scope %q) was not found", id.Name, id.Scope)
return nil, fmt.Errorf("retrieving Blueprint Assignment %s was not found", id.String())
}

return utils.Bool(resp.AssignmentProperties != nil), nil
return pointer.To(resp.Model != nil), nil
}

func (BlueprintAssignmentResource) basic(data acceptance.TestData, bpName string, version string) string {
Expand Down
Loading