diff --git a/pkg/config/resource.go b/pkg/config/resource.go index 4e63fdbf..7ad73f4e 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -40,6 +40,16 @@ type ResourceConfig struct { // Synced contains instructions for the code generator to generate Go code // that verifies whether a resource is synced or not. Synced *SyncedConfig `json:"synced"` + // Updateable contains instructions for the code generator to generate + // guard code that checks whether a resource can be updated based on its + // current status. If the resource is not in an allowed state, the update + // operation is requeued. + Updateable *UpdateableConfig `json:"updateable,omitempty"` + // Deletable contains instructions for the code generator to generate + // guard code that checks whether a resource can be deleted based on its + // current status. If the resource is not in an allowed state, the delete + // operation is requeued. + Deletable *DeletableConfig `json:"deletable,omitempty"` // Renames identifies fields in Operations that should be renamed. Renames *RenamesConfig `json:"renames,omitempty"` // ListOperation contains instructions for the code generator to generate @@ -160,6 +170,42 @@ type SyncedCondition struct { In []string `json:"in"` } +// StatusCondition represents a single field condition for updateable/deletable +// guards. It uses the same path+in pattern as SyncedCondition but is a +// separate type to allow future divergence (e.g. requeue_after_seconds). +type StatusCondition struct { + // Path of the field. e.g. Status.Status + Path *string `json:"path"` + // In contains the list of values the field must be IN for the operation + // to proceed. If the field value is NOT in this list, the operation is + // requeued. + In []string `json:"in"` +} + +// UpdateableConfig instructs the code generator on how to generate guard code +// that checks whether a resource can be updated based on its current status. +type UpdateableConfig struct { + // When is a list of conditions. ALL conditions must be satisfied for the + // resource to be considered updateable. If any condition is not met, the + // update is requeued. + When []StatusCondition `json:"when"` + // RequeueAfterSeconds is the delay in seconds before the requeue. + // Defaults to 30. + RequeueAfterSeconds *int `json:"requeue_after_seconds,omitempty"` +} + +// DeletableConfig instructs the code generator on how to generate guard code +// that checks whether a resource can be deleted based on its current status. +type DeletableConfig struct { + // When is a list of conditions. ALL conditions must be satisfied for the + // resource to be considered deletable. If any condition is not met, the + // delete is requeued. + When []StatusCondition `json:"when"` + // RequeueAfterSeconds is the delay in seconds before the requeue. + // Defaults to 30. + RequeueAfterSeconds *int `json:"requeue_after_seconds,omitempty"` +} + // HooksConfig instructs the code generator how to inject custom callback hooks // at various places in the resource manager and SDK linkage code. // diff --git a/pkg/generate/ack/controller.go b/pkg/generate/ack/controller.go index cd4e18e9..fffdee19 100644 --- a/pkg/generate/ack/controller.go +++ b/pkg/generate/ack/controller.go @@ -148,6 +148,12 @@ var ( "GoCodeIsSynced": func(r *ackmodel.CRD, resVarName string, indentLevel int) (string, error) { return code.ResourceIsSynced(r.Config(), r, resVarName, indentLevel) }, + "GoCodeResourceIsUpdateable": func(r *ackmodel.CRD, resVarName string, indentLevel int) (string, error) { + return code.ResourceIsUpdateable(r.Config(), r, resVarName, indentLevel) + }, + "GoCodeResourceIsDeletable": func(r *ackmodel.CRD, resVarName string, indentLevel int) (string, error) { + return code.ResourceIsDeletable(r.Config(), r, resVarName, indentLevel) + }, "GoCodeCompareStruct": func(r *ackmodel.CRD, shape *awssdkmodel.Shape, deltaVarName string, sourceVarName string, targetVarName string, fieldPath string, indentLevel int) (string, error) { return code.CompareStruct(r.Config(), r, nil, shape, deltaVarName, sourceVarName, targetVarName, fieldPath, indentLevel) }, diff --git a/pkg/generate/code/updateable.go b/pkg/generate/code/updateable.go new file mode 100644 index 00000000..9e419340 --- /dev/null +++ b/pkg/generate/code/updateable.go @@ -0,0 +1,188 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package code + +import ( + "fmt" + "strings" + + ackgenconfig "github.com/aws-controllers-k8s/code-generator/pkg/config" + "github.com/aws-controllers-k8s/code-generator/pkg/model" +) + +// defaultRequeueAfterSeconds is the default delay in seconds before a requeue +// when a resource is not in an allowed state for update or delete. +const defaultRequeueAfterSeconds = 30 + +// ResourceIsUpdateable returns Go code that checks whether a resource can be +// updated based on its current status. If the resource is NOT updateable, the +// generated code returns ackrequeue.NeededAfter. +// +// This follows the same pattern as ResourceIsSynced in synced.go. +// +// Sample output: +// +// if latest.ko.Status.Status != nil { +// if !ackutil.InStrings(*latest.ko.Status.Status, []string{"ACTIVE", "AVAILABLE"}) { +// return nil, ackrequeue.NeededAfter( +// fmt.Errorf("resource is in %s state, cannot be updated", +// *latest.ko.Status.Status), +// time.Duration(30)*time.Second, +// ) +// } +// } +func ResourceIsUpdateable( + cfg *ackgenconfig.Config, + r *model.CRD, + // resource variable name — "latest" for sdkUpdate + resVarName string, + // Number of levels of indentation to use + indentLevel int, +) (string, error) { + return resourceIsGuarded(cfg, r, resVarName, indentLevel, "updateable", "updated") +} + +// ResourceIsDeletable returns Go code that checks whether a resource can be +// deleted based on its current status. If the resource is NOT deletable, the +// generated code returns ackrequeue.NeededAfter. +// +// Sample output: +// +// if r.ko.Status.Status != nil { +// if !ackutil.InStrings(*r.ko.Status.Status, []string{"ACTIVE", "AVAILABLE", "FAILED"}) { +// return nil, ackrequeue.NeededAfter( +// fmt.Errorf("resource is in %s state, cannot be deleted", +// *r.ko.Status.Status), +// time.Duration(30)*time.Second, +// ) +// } +// } +func ResourceIsDeletable( + cfg *ackgenconfig.Config, + r *model.CRD, + // resource variable name — "r" for sdkDelete + resVarName string, + // Number of levels of indentation to use + indentLevel int, +) (string, error) { + return resourceIsGuarded(cfg, r, resVarName, indentLevel, "deletable", "deleted") +} + +// resourceIsGuarded is the shared implementation for ResourceIsUpdateable and +// ResourceIsDeletable. It reads the appropriate config block and generates +// guard code for each condition. +func resourceIsGuarded( + cfg *ackgenconfig.Config, + r *model.CRD, + resVarName string, + indentLevel int, + // configKey is "updateable" or "deletable" + configKey string, + // opVerb is "updated" or "deleted" — used in the error message + opVerb string, +) (string, error) { + out := "\n" + resConfig := cfg.GetResourceConfig(r.Names.Original) + if resConfig == nil { + return out, nil + } + + var conditions []ackgenconfig.StatusCondition + var requeueSeconds int + + switch configKey { + case "updateable": + if resConfig.Updateable == nil || len(resConfig.Updateable.When) == 0 { + return out, nil + } + conditions = resConfig.Updateable.When + requeueSeconds = defaultRequeueAfterSeconds + if resConfig.Updateable.RequeueAfterSeconds != nil && + *resConfig.Updateable.RequeueAfterSeconds > 0 { + requeueSeconds = *resConfig.Updateable.RequeueAfterSeconds + } + case "deletable": + if resConfig.Deletable == nil || len(resConfig.Deletable.When) == 0 { + return out, nil + } + conditions = resConfig.Deletable.When + requeueSeconds = defaultRequeueAfterSeconds + if resConfig.Deletable.RequeueAfterSeconds != nil && + *resConfig.Deletable.RequeueAfterSeconds > 0 { + requeueSeconds = *resConfig.Deletable.RequeueAfterSeconds + } + default: + return "", fmt.Errorf("unknown config key %q", configKey) + } + + for _, condCfg := range conditions { + if condCfg.Path == nil || *condCfg.Path == "" { + return "", fmt.Errorf( + "resource %q: %s.when condition has empty path", + r.Names.Original, configKey, + ) + } + if len(condCfg.In) == 0 { + return "", fmt.Errorf( + "resource %q, path %q: %s.when condition 'in' must not be empty", + r.Names.Original, *condCfg.Path, configKey, + ) + } + + _, err := getTopLevelField(r, *condCfg.Path) + if err != nil { + return "", fmt.Errorf( + "resource %q: cannot find field for path %q: %w", + r.Names.Original, *condCfg.Path, err, + ) + } + + out += renderGuardBlock( + resVarName, *condCfg.Path, condCfg.In, + requeueSeconds, opVerb, indentLevel, + ) + } + + return out, nil +} + +// renderGuardBlock produces the Go source code for a single condition check. +// It generates a nil check on the field pointer, then an InStrings check +// against the allowed values, returning ackrequeue.NeededAfter if the value +// is not in the allowed set. +func renderGuardBlock( + resVarName string, + fieldPath string, + allowedValues []string, + requeueSeconds int, + opVerb string, + indentLevel int, +) string { + indent := strings.Repeat("\t", indentLevel) + fullPath := fmt.Sprintf("%s.ko.%s", resVarName, fieldPath) + + valuesSlice := fmt.Sprintf(`[]string{"%s"}`, strings.Join(allowedValues, `", "`)) + + out := "" + out += fmt.Sprintf("%sif %s != nil {\n", indent, fullPath) + out += fmt.Sprintf("%s\tif !ackutil.InStrings(*%s, %s) {\n", indent, fullPath, valuesSlice) + out += fmt.Sprintf("%s\t\treturn nil, ackrequeue.NeededAfter(\n", indent) + out += fmt.Sprintf("%s\t\t\tfmt.Errorf(\"resource is in %%s state, cannot be %s\",\n", indent, opVerb) + out += fmt.Sprintf("%s\t\t\t\t*%s),\n", indent, fullPath) + out += fmt.Sprintf("%s\t\t\ttime.Duration(%d)*time.Second,\n", indent, requeueSeconds) + out += fmt.Sprintf("%s\t\t)\n", indent) + out += fmt.Sprintf("%s\t}\n", indent) + out += fmt.Sprintf("%s}\n", indent) + return out +} diff --git a/pkg/generate/code/updateable_test.go b/pkg/generate/code/updateable_test.go new file mode 100644 index 00000000..6a07e37f --- /dev/null +++ b/pkg/generate/code/updateable_test.go @@ -0,0 +1,227 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package code_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aws-controllers-k8s/code-generator/pkg/generate/code" + "github.com/aws-controllers-k8s/code-generator/pkg/testutil" +) + +func TestResourceIsUpdateable_SingleCondition(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "lambda") + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + expected := ` + if latest.ko.Status.State != nil { + if !ackutil.InStrings(*latest.ko.Status.State, []string{"Active"}) { + return nil, ackrequeue.NeededAfter( + fmt.Errorf("resource is in %s state, cannot be updated", + *latest.ko.Status.State), + time.Duration(30)*time.Second, + ) + } + } +` + got, err := code.ResourceIsUpdateable( + crd.Config(), crd, "latest", 1, + ) + require.NoError(err) + assert.Equal(expected, got) +} +func TestResourceIsDeletable_SingleCondition(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "lambda") + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + expected := ` + if r.ko.Status.State != nil { + if !ackutil.InStrings(*r.ko.Status.State, []string{"Active", "Failed"}) { + return nil, ackrequeue.NeededAfter( + fmt.Errorf("resource is in %s state, cannot be deleted", + *r.ko.Status.State), + time.Duration(30)*time.Second, + ) + } + } +` + got, err := code.ResourceIsDeletable( + crd.Config(), crd, "r", 1, + ) + require.NoError(err) + assert.Equal(expected, got) +} + +func TestResourceIsUpdateable_MultipleConditions_CustomRequeue(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "lambda", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-updateable-multi-condition.yaml", + }) + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + expected := ` + if latest.ko.Status.State != nil { + if !ackutil.InStrings(*latest.ko.Status.State, []string{"Active"}) { + return nil, ackrequeue.NeededAfter( + fmt.Errorf("resource is in %s state, cannot be updated", + *latest.ko.Status.State), + time.Duration(15)*time.Second, + ) + } + } + if latest.ko.Status.LastUpdateStatus != nil { + if !ackutil.InStrings(*latest.ko.Status.LastUpdateStatus, []string{"Successful"}) { + return nil, ackrequeue.NeededAfter( + fmt.Errorf("resource is in %s state, cannot be updated", + *latest.ko.Status.LastUpdateStatus), + time.Duration(15)*time.Second, + ) + } + } +` + got, err := code.ResourceIsUpdateable( + crd.Config(), crd, "latest", 1, + ) + require.NoError(err) + assert.Equal(expected, got) +} + +func TestResourceIsUpdateable_NoConfig(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "lambda") + crd := testutil.GetCRDByName(t, g, "CodeSigningConfig") + require.NotNil(crd) + + got, err := code.ResourceIsUpdateable( + crd.Config(), crd, "latest", 1, + ) + require.NoError(err) + assert.Equal("\n", got) +} + +func TestResourceIsDeletable_NoConfig(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForService(t, "lambda") + crd := testutil.GetCRDByName(t, g, "CodeSigningConfig") + require.NotNil(crd) + + got, err := code.ResourceIsDeletable( + crd.Config(), crd, "r", 1, + ) + require.NoError(err) + assert.Equal("\n", got) +} + +func TestResourceIsUpdateable_EmptyPath(t *testing.T) { + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "lambda", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-updateable-empty-path.yaml", + }) + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + _, err := code.ResourceIsUpdateable( + crd.Config(), crd, "latest", 1, + ) + require.Error(err) + require.Contains(err.Error(), "empty path") +} + +func TestResourceIsUpdateable_EmptyIn(t *testing.T) { + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "lambda", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-updateable-empty-in.yaml", + }) + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + _, err := code.ResourceIsUpdateable( + crd.Config(), crd, "latest", 1, + ) + require.Error(err) + require.Contains(err.Error(), "must not be empty") +} + +func TestResourceIsDeletable_MultipleConditions_CustomRequeue(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "lambda", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-deletable-multi-condition.yaml", + }) + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + expected := ` + if r.ko.Status.State != nil { + if !ackutil.InStrings(*r.ko.Status.State, []string{"Active"}) { + return nil, ackrequeue.NeededAfter( + fmt.Errorf("resource is in %s state, cannot be deleted", + *r.ko.Status.State), + time.Duration(20)*time.Second, + ) + } + } + if r.ko.Status.LastUpdateStatus != nil { + if !ackutil.InStrings(*r.ko.Status.LastUpdateStatus, []string{"Successful"}) { + return nil, ackrequeue.NeededAfter( + fmt.Errorf("resource is in %s state, cannot be deleted", + *r.ko.Status.LastUpdateStatus), + time.Duration(20)*time.Second, + ) + } + } +` + got, err := code.ResourceIsDeletable( + crd.Config(), crd, "r", 1, + ) + require.NoError(err) + assert.Equal(expected, got) +} + +func TestResourceIsUpdateable_InvalidFieldPath(t *testing.T) { + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "lambda", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-updateable-invalid-path.yaml", + }) + crd := testutil.GetCRDByName(t, g, "Function") + require.NotNil(crd) + + _, err := code.ResourceIsUpdateable( + crd.Config(), crd, "latest", 1, + ) + require.Error(err) + require.Contains(err.Error(), "cannot find field") +} diff --git a/pkg/testdata/models/apis/lambda/0000-00-00/generator-deletable-multi-condition.yaml b/pkg/testdata/models/apis/lambda/0000-00-00/generator-deletable-multi-condition.yaml new file mode 100644 index 00000000..aaec24f6 --- /dev/null +++ b/pkg/testdata/models/apis/lambda/0000-00-00/generator-deletable-multi-condition.yaml @@ -0,0 +1,30 @@ +resources: + Function: + fields: + CodeLocation: + is_read_only: true + from: + operation: GetFunction + path: Code.Location + CodeRepositoryType: + is_read_only: true + from: + operation: GetFunction + path: Code.RepositoryType + deletable: + when: + - path: Status.State + in: + - Active + - path: Status.LastUpdateStatus + in: + - Successful + requeue_after_seconds: 20 +ignore: + field_paths: + - CreateFunctionInput.Architectures + - CreateFunctionInput.LoggingConfig + - CreateFunctionInput.EphemeralStorage + - FunctionCode.SourceKMSKeyArn + - CreateFunctionInput.SnapStart + - CreateFunctionInput.VpcConfig.Ipv6AllowedForDualStack diff --git a/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-empty-in.yaml b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-empty-in.yaml new file mode 100644 index 00000000..347f713f --- /dev/null +++ b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-empty-in.yaml @@ -0,0 +1,25 @@ +resources: + Function: + fields: + CodeLocation: + is_read_only: true + from: + operation: GetFunction + path: Code.Location + CodeRepositoryType: + is_read_only: true + from: + operation: GetFunction + path: Code.RepositoryType + updateable: + when: + - path: Status.State + in: [] +ignore: + field_paths: + - CreateFunctionInput.Architectures + - CreateFunctionInput.LoggingConfig + - CreateFunctionInput.EphemeralStorage + - FunctionCode.SourceKMSKeyArn + - CreateFunctionInput.SnapStart + - CreateFunctionInput.VpcConfig.Ipv6AllowedForDualStack diff --git a/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-empty-path.yaml b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-empty-path.yaml new file mode 100644 index 00000000..f0bea216 --- /dev/null +++ b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-empty-path.yaml @@ -0,0 +1,26 @@ +resources: + Function: + fields: + CodeLocation: + is_read_only: true + from: + operation: GetFunction + path: Code.Location + CodeRepositoryType: + is_read_only: true + from: + operation: GetFunction + path: Code.RepositoryType + updateable: + when: + - path: "" + in: + - Active +ignore: + field_paths: + - CreateFunctionInput.Architectures + - CreateFunctionInput.LoggingConfig + - CreateFunctionInput.EphemeralStorage + - FunctionCode.SourceKMSKeyArn + - CreateFunctionInput.SnapStart + - CreateFunctionInput.VpcConfig.Ipv6AllowedForDualStack diff --git a/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-invalid-path.yaml b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-invalid-path.yaml new file mode 100644 index 00000000..39c3551a --- /dev/null +++ b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-invalid-path.yaml @@ -0,0 +1,26 @@ +resources: + Function: + fields: + CodeLocation: + is_read_only: true + from: + operation: GetFunction + path: Code.Location + CodeRepositoryType: + is_read_only: true + from: + operation: GetFunction + path: Code.RepositoryType + updateable: + when: + - path: Status.NonExistentField + in: + - Active +ignore: + field_paths: + - CreateFunctionInput.Architectures + - CreateFunctionInput.LoggingConfig + - CreateFunctionInput.EphemeralStorage + - FunctionCode.SourceKMSKeyArn + - CreateFunctionInput.SnapStart + - CreateFunctionInput.VpcConfig.Ipv6AllowedForDualStack diff --git a/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-multi-condition.yaml b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-multi-condition.yaml new file mode 100644 index 00000000..aaaf34b7 --- /dev/null +++ b/pkg/testdata/models/apis/lambda/0000-00-00/generator-updateable-multi-condition.yaml @@ -0,0 +1,30 @@ +resources: + Function: + fields: + CodeLocation: + is_read_only: true + from: + operation: GetFunction + path: Code.Location + CodeRepositoryType: + is_read_only: true + from: + operation: GetFunction + path: Code.RepositoryType + updateable: + when: + - path: Status.State + in: + - Active + - path: Status.LastUpdateStatus + in: + - Successful + requeue_after_seconds: 15 +ignore: + field_paths: + - CreateFunctionInput.Architectures + - CreateFunctionInput.LoggingConfig + - CreateFunctionInput.EphemeralStorage + - FunctionCode.SourceKMSKeyArn + - CreateFunctionInput.SnapStart + - CreateFunctionInput.VpcConfig.Ipv6AllowedForDualStack diff --git a/pkg/testdata/models/apis/lambda/0000-00-00/generator.yaml b/pkg/testdata/models/apis/lambda/0000-00-00/generator.yaml index 20b3aaf7..53b0789a 100644 --- a/pkg/testdata/models/apis/lambda/0000-00-00/generator.yaml +++ b/pkg/testdata/models/apis/lambda/0000-00-00/generator.yaml @@ -25,6 +25,17 @@ resources: in: - 1 - 2 + updateable: + when: + - path: Status.State + in: + - Active + deletable: + when: + - path: Status.State + in: + - Active + - Failed CodeSigningConfig: fields: Tags: diff --git a/templates/pkg/resource/sdk.go.tpl b/templates/pkg/resource/sdk.go.tpl index 25bf7e2e..119dfa4a 100644 --- a/templates/pkg/resource/sdk.go.tpl +++ b/templates/pkg/resource/sdk.go.tpl @@ -151,6 +151,7 @@ func (rm *resourceManager) sdkDelete( {{- if .CRD.CustomDeleteMethodName }} {{- template "sdk_delete_custom" . }} {{- else if .CRD.Ops.Delete }} +{{ GoCodeResourceIsDeletable .CRD "r" 1 }} {{- if $hookCode := Hook .CRD "sdk_delete_pre_build_request" }} {{ $hookCode }} {{- end }} diff --git a/templates/pkg/resource/sdk_update.go.tpl b/templates/pkg/resource/sdk_update.go.tpl index 46e5ef79..fadb2995 100644 --- a/templates/pkg/resource/sdk_update.go.tpl +++ b/templates/pkg/resource/sdk_update.go.tpl @@ -10,6 +10,7 @@ func (rm *resourceManager) sdkUpdate( defer func() { exit(err) }() +{{ GoCodeResourceIsUpdateable .CRD "latest" 1 }} {{- if $hookCode := Hook .CRD "sdk_update_pre_build_request" }} {{ $hookCode }} {{- end }} diff --git a/templates/pkg/resource/sdk_update_custom.go.tpl b/templates/pkg/resource/sdk_update_custom.go.tpl index 3a3aa46e..88daf1e2 100644 --- a/templates/pkg/resource/sdk_update_custom.go.tpl +++ b/templates/pkg/resource/sdk_update_custom.go.tpl @@ -5,6 +5,7 @@ func (rm *resourceManager) sdkUpdate( latest *resource, delta *ackcompare.Delta, ) (*resource, error) { +{{ GoCodeResourceIsUpdateable .CRD "latest" 1 }} return rm.{{ .CRD.CustomUpdateMethodName }}(ctx, desired, latest, delta) } {{- end -}} diff --git a/templates/pkg/resource/sdk_update_set_attributes.go.tpl b/templates/pkg/resource/sdk_update_set_attributes.go.tpl index 7660beec..72ed8083 100644 --- a/templates/pkg/resource/sdk_update_set_attributes.go.tpl +++ b/templates/pkg/resource/sdk_update_set_attributes.go.tpl @@ -12,6 +12,7 @@ func (rm *resourceManager) sdkUpdate( exit(err) }() +{{ GoCodeResourceIsUpdateable .CRD "latest" 1 }} {{- if $hookCode := Hook .CRD "sdk_update_pre_build_request" }} {{ $hookCode }} {{- end }}