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

Add limited support for blueprint assignments #6930

Merged
merged 26 commits into from Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .teamcity/components/generated/services.kt
Expand Up @@ -10,6 +10,7 @@ var services = mapOf(
"authorization" to "Authorization",
"automation" to "Automation",
"batch" to "Batch",
"blueprints" to "Blueprints",
"bot" to "Bot",
"cdn" to "CDN",
"cognitive" to "Cognitive Services",
Expand Down
3 changes: 3 additions & 0 deletions azurerm/internal/clients/client.go
Expand Up @@ -15,6 +15,7 @@ import (
authorization "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization/client"
automation "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation/client"
batch "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch/client"
blueprints "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/blueprints/client"
bot "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/client"
cdn "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cdn/client"
cognitiveServices "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cognitive/client"
Expand Down Expand Up @@ -97,6 +98,7 @@ type Client struct {
Authorization *authorization.Client
Automation *automation.Client
Batch *batch.Client
Blueprints *blueprints.Client
Bot *bot.Client
Cdn *cdn.Client
Cognitive *cognitiveServices.Client
Expand Down Expand Up @@ -180,6 +182,7 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error
client.Authorization = authorization.NewClient(o)
client.Automation = automation.NewClient(o)
client.Batch = batch.NewClient(o)
client.Blueprints = blueprints.NewClient(o)
client.Bot = bot.NewClient(o)
client.Cdn = cdn.NewClient(o)
client.Cognitive = cognitiveServices.NewClient(o)
Expand Down
1 change: 1 addition & 0 deletions azurerm/internal/provider/required_resource_providers.go
Expand Up @@ -20,6 +20,7 @@ func RequiredResourceProviders() map[string]struct{} {
"Microsoft.AppPlatform": {},
"Microsoft.Authorization": {},
"Microsoft.Automation": {},
"Microsoft.Blueprints": {},
"Microsoft.BotService": {},
"Microsoft.Cache": {},
"Microsoft.Cdn": {},
Expand Down
2 changes: 2 additions & 0 deletions azurerm/internal/provider/services.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/blueprints"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cdn"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/cognitive"
Expand Down Expand Up @@ -90,6 +91,7 @@ func SupportedServices() []common.ServiceRegistration {
authorization.Registration{},
automation.Registration{},
batch.Registration{},
blueprints.Registration{},
bot.Registration{},
cdn.Registration{},
cognitive.Registration{},
Expand Down
211 changes: 211 additions & 0 deletions azurerm/internal/services/blueprints/blueprint.go
@@ -0,0 +1,211 @@
package blueprints

import (
"context"
"encoding/json"
"fmt"

"github.com/Azure/azure-sdk-for-go/services/preview/blueprint/mgmt/2018-11-01-preview/blueprint"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/msi/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func ManagedIdentitySchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
// ManagedServiceIdentityTypeNone is not valid; a valid and privileged Identity is required for the service to apply the changes.
// SystemAssigned type not currently supported - The Portal performs significant activity in temporary escalation of permissions to Owner on the target scope
// Such activity in the Provider would be brittle
// string(blueprint.ManagedServiceIdentityTypeSystemAssigned),
string(blueprint.ManagedServiceIdentityTypeUserAssigned),
}, true),
// The first character of value returned by the service is always in lower case - bug?
DiffSuppressFunc: suppress.CaseDifference,
},

"identity_ids": {
// The API only seems to care about the "key" portion of this struct, which is the ResourceID of the Identity
Type: schema.TypeList,
Required: true,
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validate.UserAssignedIdentityId,
},
},

"principal_id": {
Type: schema.TypeString,
Computed: true,
},

"tenant_id": {
Type: schema.TypeString,
Computed: true,
},
},
},
}
}

func blueprintAssignmentCreateStateRefreshFunc(ctx context.Context, client *blueprint.AssignmentsClient, scope, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := client.Get(ctx, scope, name)
if err != nil {
return nil, "", fmt.Errorf("unable to retrieve Blueprint Assignment %q (Scope %q): %+v", name, scope, err)
}
if resp.ProvisioningState == blueprint.Failed {
return resp, string(resp.ProvisioningState), fmt.Errorf("Blueprint Assignment provisioning entered a Failed state.")
}

return resp, string(resp.ProvisioningState), nil
}
}

func blueprintAssignmentDeleteStateRefreshFunc(ctx context.Context, client *blueprint.AssignmentsClient, scope, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := client.Get(ctx, scope, name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return resp, "NotFound", nil
} else {
return nil, "", fmt.Errorf("unable to retrieve Blueprint Assignment %q (Scope %q): %+v", name, scope, err)
}
}

return resp, string(resp.ProvisioningState), nil
}
}

func normalizeAssignmentParameterValuesJSON(jsonString interface{}) string {
if jsonString == nil || jsonString == "" {
return ""
}

var values map[string]*blueprint.ParameterValue
if err := json.Unmarshal([]byte(jsonString.(string)), &values); err != nil {
return fmt.Sprintf("unable to parse JSON: %+v", err)
}

b, _ := json.Marshal(values)
return string(b)
}

func normalizeAssignmentResourceGroupValuesJSON(jsonString interface{}) string {
if jsonString == nil || jsonString == "" {
return ""
}

var values map[string]*blueprint.ResourceGroupValue
if err := json.Unmarshal([]byte(jsonString.(string)), &values); err != nil {
return fmt.Sprintf("unable to parse JSON: %+v", err)
}

b, _ := json.Marshal(values)
return string(b)
}

func expandArmBlueprintAssignmentParameters(input string) map[string]*blueprint.ParameterValue {
var result map[string]*blueprint.ParameterValue
// the string has been validated by the schema, therefore the error is ignored here, since it will never happen.
_ = json.Unmarshal([]byte(input), &result)
return result
}

func expandArmBlueprintAssignmentResourceGroups(input string) map[string]*blueprint.ResourceGroupValue {
var result map[string]*blueprint.ResourceGroupValue
// the string has been validated by the schema, therefore the error is ignored here, since it will never happen.
_ = json.Unmarshal([]byte(input), &result)
return result
}

func expandArmBlueprintAssignmentIdentity(input []interface{}) (*blueprint.ManagedServiceIdentity, error) {
if len(input) == 0 || input[0] == nil {
return nil, fmt.Errorf("Managed Service Identity was empty")
}

raw := input[0].(map[string]interface{})

identity := blueprint.ManagedServiceIdentity{
Type: blueprint.ManagedServiceIdentityType(raw["type"].(string)),
}

identityIdsRaw := raw["identity_ids"].([]interface{})
identityIds := make(map[string]*blueprint.UserAssignedIdentity)
for _, v := range identityIdsRaw {
identityIds[v.(string)] = &blueprint.UserAssignedIdentity{}
}
identity.UserAssignedIdentities = identityIds

return &identity, nil
}

func flattenArmBlueprintAssignmentIdentity(input *blueprint.ManagedServiceIdentity) []interface{} {
if input == nil {
return []interface{}{}
}

identityIds := make([]string, 0)
if input.UserAssignedIdentities != nil {
for k := range input.UserAssignedIdentities {
identityIds = append(identityIds, k)
}
}

principalId := ""
if input.PrincipalID != nil {
principalId = *input.PrincipalID
}

tenantId := ""
if input.TenantID != nil {
tenantId = *input.TenantID
}

return []interface{}{
map[string]interface{}{
"type": string(input.Type),
"identity_ids": identityIds,
"principal_id": principalId,
"tenant_id": tenantId,
},
}
}

func flattenArmBlueprintAssignmentParameters(input map[string]*blueprint.ParameterValue) (string, error) {
if len(input) == 0 {
return "", nil
}

b, err := json.Marshal(input)
if err != nil {
return "", err
}

return string(b), nil
}

func flattenArmBlueprintAssignmentResourceGroups(input map[string]*blueprint.ResourceGroupValue) (string, error) {
if len(input) == 0 {
return "", nil
}

b, err := json.Marshal(input)
if err != nil {
return "", err
}

return string(b), nil
}