Skip to content

Commit

Permalink
[minor_change] Introduction of a provider level flag to prevent creat…
Browse files Browse the repository at this point in the history
…ion of objects that are already existing in APIC configuration
  • Loading branch information
akinross committed Jul 3, 2024
1 parent bd77190 commit 0e6e6b5
Show file tree
Hide file tree
Showing 67 changed files with 3,962 additions and 118 deletions.
5 changes: 5 additions & 0 deletions aci/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ func Provider() *schema.Provider {
Optional: true,
Description: "Global annotation for the provider. This can also be set as the ACI_ANNOTATION environment variable.",
},
"allow_existing_on_create": &schema.Schema{
Type: schema.TypeBool,
Description: "Allow existing objects to be managed. This can also be set as the ACI_ALLOW_EXISTING_ON_CREATE environment variable.",
Optional: true,
},
},

ResourcesMap: map[string]*schema.Resource{
Expand Down
10 changes: 9 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,13 @@ NOTE: either 'password' OR 'private_key' and 'cert_name' must be provided for th
- `validate_relation_dn` (Boolean) Flag to validate if a object with entered relation Dn exists in the APIC.
- Default: `true`
- Environment variable: `ACI_VAL_REL_DN`
- `annotation` (String) Global annotation for the provider.
- `annotation` (String) Global annotation for the provider.
- Default: `orchestrator:terraform`
- Environment variable: `ACI_ANNOTATION`
- `allow_existing_on_create` (Boolean) Global flag to validate existing objects in APIC are allowed to be managed.
- Default: `true`
- Environment variable: `ACI_ALLOW_EXISTING_ON_CREATE`

~> The existence of an object in APIC can only be verified when the distinguished name (DN) can be constructed during plan. The verification <b>cannot</b> take place when a `parent_dn` attribute or any of the `naming` attributes ( attributes that are required to construct the DN ) of a resource are unknown ( known after apply ) during plan. An example of a unknown attribute input would be to use a DN reference ( `aci_tenant.example.id` ) to a resource that is being configured in the same plan. The DN of the object <b>cannot</b> be determined prior to apply and thus the existence of the object in APIC <b>cannot</b> be verified during plan. In these cases the verification will be performed during the apply operation.

-> Disabling this flag will suppress an additional API call during the plan phase for newly defined resources in the configuration.
10 changes: 9 additions & 1 deletion gen/templates/index.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,13 @@ NOTE: either 'password' OR 'private_key' and 'cert_name' must be provided for th
- `validate_relation_dn` (Boolean) Flag to validate if a object with entered relation Dn exists in the APIC.
- Default: `true`
- Environment variable: `ACI_VAL_REL_DN`
- `annotation` (String) Global annotation for the provider.
- `annotation` (String) Global annotation for the provider.
- Default: `orchestrator:terraform`
- Environment variable: `ACI_ANNOTATION`
- `allow_existing_on_create` (Boolean) Global flag to validate existing objects in APIC are allowed to be managed.
- Default: `true`
- Environment variable: `ACI_ALLOW_EXISTING_ON_CREATE`

~> The existence of an object in APIC can only be verified when the distinguished name (DN) can be constructed during plan. The verification <b>cannot</b> take place when a `parent_dn` attribute or any of the `naming` attributes ( attributes that are required to construct the DN ) of a resource are unknown ( known after apply ) during plan. An example of a unknown attribute input would be to use a DN reference ( `aci_tenant.example.id` ) to a resource that is being configured in the same plan. The DN of the object <b>cannot</b> be determined prior to apply and thus the existence of the object in APIC <b>cannot</b> be verified during plan. In these cases the verification will be performed during the apply operation.

-> Disabling this flag will suppress an additional API call during the plan phase for newly defined resources in the configuration.
28 changes: 28 additions & 0 deletions gen/templates/provider.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
)

var globalAnnotation string
var globalAllowExistingOnCreate bool

// Ensure AciProvider satisfies various provider interfaces.
var _ provider.Provider = &AciProvider{}
Expand All @@ -52,6 +53,7 @@ type AciProviderModel struct {
ValidateRelationDn types.String `tfsdk:"validate_relation_dn"`
MaxRetries types.String `tfsdk:"retries"`
Annotation types.String `tfsdk:"annotation"`
AllowExistingOnCreate types.Bool `tfsdk:"allow_existing_on_create"`
}

func (p *AciProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
Expand Down Expand Up @@ -123,6 +125,10 @@ func (p *AciProvider) Schema(ctx context.Context, req provider.SchemaRequest, re
Description: "Global annotation for the provider. This can also be set as the ACI_ANNOTATION environment variable.",
Optional: true,
},
"allow_existing_on_create": schema.BoolAttribute{
Description: "Allow existing objects to be managed. This can also be set as the ACI_ALLOW_EXISTING_ON_CREATE environment variable.",
Optional: true,
},
},
}
}
Expand All @@ -148,6 +154,7 @@ func (p *AciProvider) Configure(ctx context.Context, req provider.ConfigureReque
validateRelationDn := stringToBool(resp, "insecure", getStringAttribute(data.ValidateRelationDn, "ACI_VAL_REL_DN"), true)
maxRetries := stringToInt(resp, "retries", getStringAttribute(data.MaxRetries, "ACI_RETRIES"), 2)
setGlobalAnnotation(data.Annotation, "ACI_ANNOTATION")
setGlobalAllowExistingOnCreate(resp, data.AllowExistingOnCreate, "ACI_ALLOW_EXISTING_ON_CREATE")

if username == "" {
resp.Diagnostics.AddError(
Expand Down Expand Up @@ -224,6 +231,27 @@ func New(version string) func() provider.Provider {
}
}

func setGlobalAllowExistingOnCreate(resp *provider.ConfigureResponse, attribute basetypes.BoolValue, envKey string) {

if attribute.IsNull() {
envValue, found := os.LookupEnv(envKey)
if found {
boolValue, err := strconv.ParseBool(envValue)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Invalid input '%s'", envValue),
fmt.Sprintf("A boolean value must be provided for %s", envKey),
)
}
globalAllowExistingOnCreate = boolValue
} else {
globalAllowExistingOnCreate = true
}
} else {
globalAllowExistingOnCreate = attribute.ValueBool()
}
}

func setGlobalAnnotation(attribute basetypes.StringValue, envKey string) {

if attribute.IsNull() {
Expand Down
41 changes: 32 additions & 9 deletions gen/templates/resource.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -448,18 +448,30 @@ func set{{ .ResourceClassName }}LegacyAttributes(ctx context.Context, diags *dia
}
{{- end }}
}
{{- end }}

func (r *{{.ResourceClassName}}Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
if !req.Plan.Raw.IsNull() {
var planData, stateData, configData *{{.ResourceClassName}}ResourceModel
var planData, stateData{{ if .LegacyAttributes}}, configData{{end}} *{{.ResourceClassName}}ResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planData)...)
resp.Diagnostics.Append(req.State.Get(ctx, &stateData)...)
resp.Diagnostics.Append(req.Config.Get(ctx, &configData)...)
{{ if .LegacyAttributes}}resp.Diagnostics.Append(req.Config.Get(ctx, &configData)...){{end}}

if resp.Diagnostics.HasError() {
return
}

if stateData == nil && !globalAllowExistingOnCreate {{if .HasParent }}&& !planData.ParentDn.IsUnknown() {{end}}{{range .Properties}}{{if .IsNaming}}&& !planData.{{ .Name }}.IsUnknown() {{end}}{{end}}{
var createCheckData *{{.ResourceClassName}}ResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &createCheckData)...)
set{{.ResourceClassName}}Id(ctx, createCheckData)
CheckDn(ctx, &resp.Diagnostics, r.client, "{{.PkgName}}", createCheckData.Id.ValueString())
if resp.Diagnostics.HasError() {
return
}
}

{{ if .LegacyAttributes}}
{{ range .LegacyAttributes}}{{$SetName := .Name}}
{{- if and (ne .ReplacedBy.AttributeName "") (eq (getMigrationType .ValueType) "String") (isNewAttributeStringType .ReplacedBy.AttributeName) }}
if !configData.{{ .Name }}.IsNull() {
Expand Down Expand Up @@ -706,11 +718,10 @@ func (r *{{.ResourceClassName}}Resource) ModifyPlan(ctx context.Context, req res
{{- end }}
}
{{ end }}

resp.Diagnostics.Append(resp.Plan.Set(ctx, &planData)...)
{{ end }}
}
}
{{- end}}

func (r *{{.ResourceClassName}}Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
tflog.Debug(ctx, "Start metadata of resource: aci_{{.ResourceName}}")
Expand Down Expand Up @@ -1011,6 +1022,13 @@ func (r *{{.ResourceClassName}}Resource) Create(ctx context.Context, req resourc
resp.Diagnostics.Append(req.Plan.Get(ctx, &stateData)...)
set{{.ResourceClassName}}Id(ctx, stateData)
getAndSet{{.ResourceClassName}}Attributes(ctx, &resp.Diagnostics, r.client, stateData)
if !globalAllowExistingOnCreate && !stateData.Id.IsNull() {
resp.Diagnostics.AddError(
"Object Already Exists",
fmt.Sprintf("The {{.PkgName}} object with DN '%s' already exists.", stateData.Id.ValueString()),
)
return
}
{{- end}}

var data *{{.ResourceClassName}}ResourceModel
Expand All @@ -1032,9 +1050,9 @@ func (r *{{.ResourceClassName}}Resource) Create(ctx context.Context, req resourc
data.{{ .ResourceClassName }}.ElementsAs(ctx, &{{.PkgName}}Plan, false)
stateData.{{ .ResourceClassName }}.ElementsAs(ctx, &{{.PkgName}}State, false)
{{- end}}
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, data{{- range .Children}}, {{.PkgName}}Plan, {{.PkgName}}State{{- end}})
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, true, data{{- range .Children}}, {{.PkgName}}Plan, {{.PkgName}}State{{- end}})
{{- else}}
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, data)
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, true, data)
{{- end}}

if resp.Diagnostics.HasError() {
Expand Down Expand Up @@ -1104,9 +1122,9 @@ func (r *{{.ResourceClassName}}Resource) Update(ctx context.Context, req resourc
data.{{ .ResourceClassName }}.ElementsAs(ctx, &{{.PkgName}}Plan, false)
stateData.{{ .ResourceClassName }}.ElementsAs(ctx, &{{.PkgName}}State, false)
{{- end}}
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, data{{- range .Children}}, {{.PkgName}}Plan, {{.PkgName}}State{{- end}})
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, false, data{{- range .Children}}, {{.PkgName}}Plan, {{.PkgName}}State{{- end}})
{{- else}}
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, data)
jsonPayload := get{{.ResourceClassName}}CreateJsonPayload(ctx, &resp.Diagnostics, false, data)
{{- end}}

if resp.Diagnostics.HasError() {
Expand Down Expand Up @@ -1432,9 +1450,14 @@ func get{{$.ResourceClassName}}{{ .ResourceClassName }}ChildPayloads(ctx context
}
{{- end}}

func get{{.ResourceClassName}}CreateJsonPayload(ctx context.Context, diags *diag.Diagnostics, data *{{.ResourceClassName}}ResourceModel{{- range .Children}}, {{.PkgName}}Plan, {{.PkgName}}State []{{.ResourceClassName}}{{$.ResourceClassName}}ResourceModel{{- end}}) *container.Container {
func get{{.ResourceClassName}}CreateJsonPayload(ctx context.Context, diags *diag.Diagnostics, createType bool, data *{{.ResourceClassName}}ResourceModel{{- range .Children}}, {{.PkgName}}Plan, {{.PkgName}}State []{{.ResourceClassName}}{{$.ResourceClassName}}ResourceModel{{- end}}) *container.Container {
payloadMap := map[string]interface{}{}
payloadMap["attributes"] = map[string]string{}

if createType && !globalAllowExistingOnCreate {
payloadMap["attributes"].(map[string]string)["status"] = "created"
}

{{- if .HasChild}}
childPayloads := []map[string]interface{}{}
{{ range .Children}}
Expand Down
Loading

0 comments on commit 0e6e6b5

Please sign in to comment.