Skip to content

Commit 160b839

Browse files
authored
update for default resource tags (#335)
Issue #, if available: aws-controllers-k8s/community#1261 Description of changes: * Removes `service.k8s.aws/managed=true` and `services.k8s.aws/created=%UTC_NOW%` tags and adds the new `services.k8s.aws/controller-version=%CONTROLLER_VERSION%` tag as resource-tags input for ACK controller. * Updates ACK runtime to `v0.19.0` * Adds new `TagConfig` field inside `ResourceConfig` * Adds the code-generation for implementation of `AWSResourceManager.EnsureTags()` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 6acf40f commit 160b839

File tree

18 files changed

+556
-9
lines changed

18 files changed

+556
-9
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/aws-controllers-k8s/code-generator
33
go 1.17
44

55
require (
6-
github.com/aws-controllers-k8s/runtime v0.18.4
6+
github.com/aws-controllers-k8s/runtime v0.19.0
77
github.com/aws/aws-sdk-go v1.42.0
88
github.com/dlclark/regexp2 v1.4.0
99
// pin to v0.1.1 due to release problem with v0.1.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
9090
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
9191
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
9292
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
93-
github.com/aws-controllers-k8s/runtime v0.18.4 h1:iwLYNwhbuiWZrHPoulGj75oT+alE91wCNkF1FUELiAw=
94-
github.com/aws-controllers-k8s/runtime v0.18.4/go.mod h1:oA8ML1/LL3chPn26P6SzBNu1CUI2nekB+PTqykNs0qU=
93+
github.com/aws-controllers-k8s/runtime v0.19.0 h1:+O5a6jBSBAd8XTNMrVCIYu4G+ZUPZe/G5eopVFO18Dc=
94+
github.com/aws-controllers-k8s/runtime v0.19.0/go.mod h1:oA8ML1/LL3chPn26P6SzBNu1CUI2nekB+PTqykNs0qU=
9595
github.com/aws/aws-sdk-go v1.42.0 h1:BMZws0t8NAhHFsfnT3B40IwD13jVDG5KerlRksctVIw=
9696
github.com/aws/aws-sdk-go v1.42.0/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
9797
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=

pkg/config/resource.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,28 @@ type ResourceConfig struct {
9696
// IsARNPrimaryKey determines whether the CRD uses the ARN as the primary
9797
// identifier in the ReadOne operations.
9898
IsARNPrimaryKey bool `json:"is_arn_primary_key"`
99+
// TagConfig contains instructions for the code generator to generate
100+
// custom code for ensuring tags
101+
TagConfig *TagConfig `json:"tags,omitempty"`
102+
}
103+
104+
// TagConfig instructs the code generator on how to generate functions that
105+
// ensure that controller tags are added to the AWS Resource
106+
type TagConfig struct {
107+
// Ignore is a boolean that indicates whether ensuring controller tags
108+
// should be ignored for a resource. For AWS resources which do not
109+
// support tagging, this should be set to True
110+
Ignore bool `json:"ignore,omitempty"`
111+
// Path represents the field path for the member which contains the tags
112+
Path *string `json:"path,omitempty"`
113+
// KeyMemberName is the name of field which represents AWS tag key inside tag
114+
// struct. This is only used for tag fields with shape as list of struct,
115+
// where the struct represents a single tag.
116+
KeyMemberName *string `json:"key_name,omitempty"`
117+
// ValueMemberName is the name of field which represents AWS tag value inside
118+
// tag struct. This is only used for tag fields with shape as list of struct,
119+
// where the struct represents a single tag.
120+
ValueMemberName *string `json:"value_name,omitempty"`
99121
}
100122

101123
// SyncedConfig instructs the code generator on how to generate functions that checks
@@ -625,3 +647,14 @@ func (c *Config) GetListOpMatchFieldNames(
625647
}
626648
return rConfig.ListOperation.MatchFields
627649
}
650+
651+
// TagsAreIgnored returns whether ensuring controller tags should be ignored
652+
// for a resource or not.
653+
func (c *Config) TagsAreIgnored(resName string) bool {
654+
if rConfig, found := c.Resources[resName]; found {
655+
if tagConfig := rConfig.TagConfig; tagConfig != nil {
656+
return tagConfig.Ignore
657+
}
658+
}
659+
return false
660+
}

pkg/generate/ack/controller.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ var (
169169
"CheckNilReferencesPath": func(f *ackmodel.Field, sourceVarName string) string {
170170
return code.CheckNilReferencesPath(f, sourceVarName)
171171
},
172+
"GoCodeInitializeNestedStructField": func(r *ackmodel.CRD,
173+
sourceVarName string, f *ackmodel.Field, apiPkgImportName string,
174+
indentLevel int) string {
175+
return code.InitializeNestedStructField(r, sourceVarName, f,
176+
apiPkgImportName, indentLevel)
177+
},
172178
}
173179
)
174180

@@ -220,9 +226,14 @@ func Controller(
220226
"references.go.tpl",
221227
"resource.go.tpl",
222228
"sdk.go.tpl",
229+
"tags.go.tpl",
223230
}
224231
for _, crd := range crds {
225232
for _, target := range targets {
233+
// skip adding "tags.go.tpl" file if tagging is ignored for a crd
234+
if target == "tags.go.tpl" && crd.Config().TagsAreIgnored(crd.Names.Original) {
235+
continue
236+
}
226237
outPath := filepath.Join("pkg/resource", crd.Names.Snake, strings.TrimSuffix(target, ".tpl"))
227238
tplPath := filepath.Join("pkg/resource", target)
228239
crdVars := &templateCRDVars{

pkg/generate/ack/hook.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ code paths:
6363
* late_initialize_post_read_one
6464
* references_pre_resolve
6565
* references_post_resolve
66+
* ensure_tags
67+
* convert_tags
68+
* convert_tags_pre_to_ack_tags
69+
* convert_tags_post_to_ack_tags
70+
* convert_tags_pre_from_ack_tags
71+
* convert_tags_post_from_ack_tags
6672
6773
The "pre_build_request" hooks are called BEFORE the call to construct
6874
the Input shape that is used in the API operation and therefore BEFORE
@@ -119,6 +125,24 @@ method
119125
The "references_post_resolve" hooks are called AFTER resolving the
120126
references for all Reference fields inside AWSResourceManager.ResolveReferences()
121127
method
128+
129+
The "ensure_tags" hooks provide the complete custom implementation for
130+
AWSResourceManager.EnsureTags() method
131+
132+
The "convert_tags" hooks provide the complete custom implementation for
133+
"ToACKTags" and "FromACKTags" methods.
134+
135+
The "convert_tags_pre_to_ack_tags" are called before converting the K8s
136+
resource tags into ACK tags
137+
138+
The "convert_tags_post_to_ack_tags" are called after converting the K8s
139+
resource tags into ACK tags
140+
141+
The "convert_tags_pre_from_ack_tags" are called before converting the ACK
142+
tags into K8s resource tags
143+
144+
The "convert_tags_post_from_ack_tags" are called after converting the ACK
145+
tags into K8s resource tags
122146
*/
123147

124148
// ResourceHookCode returns a string with custom callback code for a resource

pkg/generate/ack/runtime_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ func (frm *fakeRM) IsSynced(context.Context, acktypes.AWSResource) (bool, error)
134134
return true, nil
135135
}
136136

137+
func (frm *fakeRM) EnsureTags(
138+
context.Context,
139+
acktypes.AWSResource,
140+
acktypes.ServiceControllerMetadata,
141+
) error {
142+
return nil
143+
}
144+
137145
// This test is mostly just a hack to introduce a Go module dependency between
138146
// the ACK runtime library and the code generator. The code generator doesn't
139147
// actually depend on Go code in the ACK runtime, but it *produces* templated

pkg/generate/code/initialize_field.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package code
15+
16+
import (
17+
"fmt"
18+
"strings"
19+
20+
"github.com/aws-controllers-k8s/code-generator/pkg/fieldpath"
21+
22+
"github.com/aws-controllers-k8s/code-generator/pkg/model"
23+
)
24+
25+
// InitializeNestedStructField returns the go code for initializing a nested
26+
// struct field. Currently this method only supports the struct shape for
27+
// nested elements.
28+
//
29+
// TODO(vijtrip2): Refactor the code out of set_resource.go for generating
30+
// constructors and reuse here. This method is currently being used for handling
31+
// nested Tagging fields.
32+
//
33+
// Example: generated code for "Logging.LoggingEnabled.TargetBucket" field
34+
// inside "s3" "bucket" crd looks like:
35+
//
36+
// ```
37+
// r.ko.Spec.Logging = &svcapitypes.BucketLoggingStatus{}
38+
// r.ko.Spec.Logging.LoggingEnabled = &svcapitypes.LoggingEnabled{}
39+
// ```
40+
func InitializeNestedStructField(
41+
r *model.CRD,
42+
sourceVarName string,
43+
field *model.Field,
44+
// apiPkgAlias contains the imported package alias where the type definition
45+
// for nested structs is present.
46+
// ex: svcapitypes "github.com/aws-controllers-k8s/s3-controller/apis/v1alpha1"
47+
apiPkgAlias string,
48+
// Number of levels of indentation to use
49+
indentLevel int,
50+
) string {
51+
out := ""
52+
indent := strings.Repeat("\t", indentLevel)
53+
fieldPath := field.Path
54+
if fieldPath != "" {
55+
fp := fieldpath.FromString(fieldPath)
56+
if fp.Size() > 1 {
57+
// replace the front field name with front field shape name inside
58+
// the field path to construct the fieldShapePath
59+
front := fp.Front()
60+
frontField := r.Fields[front]
61+
if frontField == nil {
62+
panic(fmt.Sprintf("unable to find the field with name %s"+
63+
" for fieldpath %s", front, fieldPath))
64+
}
65+
if frontField.ShapeRef == nil {
66+
panic(fmt.Sprintf("nil ShapeRef for field %s", front))
67+
}
68+
fieldShapePath := strings.Replace(fieldPath, front,
69+
frontField.ShapeRef.ShapeName, 1)
70+
fsp := fieldpath.FromString(fieldShapePath)
71+
var index int
72+
// Build the prefix to access elements in field path.
73+
// Use the front of fieldpath to determine whether the field is
74+
// a spec field or status field.
75+
elemAccessPrefix := sourceVarName
76+
if _, found := r.SpecFields[front]; found {
77+
elemAccessPrefix = fmt.Sprintf("%s%s", elemAccessPrefix,
78+
r.Config().PrefixConfig.SpecField)
79+
} else {
80+
elemAccessPrefix = fmt.Sprintf("%s%s", elemAccessPrefix,
81+
r.Config().PrefixConfig.StatusField)
82+
}
83+
var importPath string
84+
if apiPkgAlias != "" {
85+
importPath = fmt.Sprintf("%s.", apiPkgAlias)
86+
}
87+
// traverse over the fieldShapePath and initialize every element
88+
// except the last.
89+
for index < fsp.Size()-1 {
90+
elemName := fp.At(index)
91+
elemShapeRef := fsp.ShapeRefAt(frontField.ShapeRef, index)
92+
if elemShapeRef.Shape.Type != "structure" {
93+
panic(fmt.Sprintf("only nested structures are supported."+
94+
" Shape type for %s is %s inside fieldpath %s", elemName,
95+
elemShapeRef.Shape.Type, fieldPath))
96+
}
97+
out += fmt.Sprintf("%s%s.%s = &%s%s{}\n",
98+
indent, elemAccessPrefix, elemName, importPath,
99+
elemShapeRef.GoTypeElem())
100+
elemAccessPrefix = fmt.Sprintf("%s.%s", elemAccessPrefix,
101+
elemName)
102+
index++
103+
}
104+
}
105+
}
106+
return out
107+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package code_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/aws-controllers-k8s/code-generator/pkg/generate/code"
7+
8+
"github.com/aws-controllers-k8s/code-generator/pkg/testutil"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestInitializeNestedStructField(t *testing.T) {
13+
assert := assert.New(t)
14+
15+
g := testutil.NewModelForServiceWithOptions(t, "s3",
16+
&testutil.TestingModelOptions{GeneratorConfigFile: "generator-with-tags.yaml"})
17+
18+
crd := testutil.GetCRDByName(t, g, "Bucket")
19+
assert.NotNil(crd)
20+
21+
f := crd.Fields["Logging.LoggingEnabled.TargetBucket"]
22+
23+
s := code.InitializeNestedStructField(crd, "r.ko", f,
24+
"svcapitypes", 1)
25+
expected :=
26+
` r.ko.Spec.Logging = &svcapitypes.BucketLoggingStatus{}
27+
r.ko.Spec.Logging.LoggingEnabled = &svcapitypes.LoggingEnabled{}
28+
`
29+
assert.Equal(expected, s)
30+
}

pkg/model/model_apigwv2_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ func TestAPIGatewayV2_Api(t *testing.T) {
5454
crd := getCRDByName("Api", crds)
5555
require.NotNil(crd)
5656

57+
assert.False(crd.Config().TagsAreIgnored(crd.Names.Original))
58+
tfName, err := crd.GetTagFieldName()
59+
assert.Nil(err)
60+
assert.Equal("Tags", tfName)
61+
62+
tf, err := crd.GetTagField()
63+
assert.NotNil(tf)
64+
assert.Nil(err)
65+
assert.Equal("Tags", tf.Names.Original)
66+
5767
assert.Equal("API", crd.Names.Camel)
5868
assert.Equal("api", crd.Names.CamelLower)
5969
assert.Equal("api", crd.Names.Snake)
@@ -84,6 +94,17 @@ func TestAPIGatewayV2_Route(t *testing.T) {
8494
crd := getCRDByName("Route", crds)
8595
require.NotNil(crd)
8696

97+
assert.True(crd.Config().TagsAreIgnored(crd.Names.Original))
98+
tfName, err := crd.GetTagFieldName()
99+
assert.NotNil(err)
100+
assert.Empty(tfName)
101+
102+
tf, err := crd.GetTagField()
103+
assert.Nil(tf)
104+
105+
assert.Empty(crd.GetTagKeyMemberName())
106+
assert.Empty(crd.GetTagValueMemberName())
107+
87108
assert.Equal("Route", crd.Names.Camel)
88109
assert.Equal("route", crd.Names.CamelLower)
89110
assert.Equal("route", crd.Names.Snake)

pkg/model/model_ecr_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ func TestECRRepository(t *testing.T) {
3434
crd := getCRDByName("Repository", crds)
3535
require.NotNil(crd)
3636

37+
assert.False(crd.Config().TagsAreIgnored(crd.Names.Original))
38+
tfName, err := crd.GetTagFieldName()
39+
assert.Nil(err)
40+
assert.Equal("Tags", tfName)
41+
42+
tf, err := crd.GetTagField()
43+
assert.NotNil(tf)
44+
assert.Nil(err)
45+
assert.Equal("Tags", tf.Names.Original)
46+
47+
tagKeyMemberName := crd.GetTagKeyMemberName()
48+
tagValueMemberName := crd.GetTagValueMemberName()
49+
assert.Equal("Key", tagKeyMemberName)
50+
assert.Equal("Value", tagValueMemberName)
3751
// The ECR Repository API has just the C and R of the normal CRUD
3852
// operations:
3953
//

0 commit comments

Comments
 (0)