Skip to content

Commit

Permalink
Adds support for tags in aws provider spec (#769)
Browse files Browse the repository at this point in the history
* Adds support for tags in aws provider spec

* Remove defaults, small changes to merging logic

* Move tags to the api package, styling changes
  • Loading branch information
suket22 authored Oct 26, 2021
1 parent dacd381 commit 4e0a4b4
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 21 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/prometheus/client_golang v1.11.0
go.uber.org/multierr v1.7.0
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
golang.org/x/tools v0.1.8-0.20211014194737-fc98fb2abd48 // indirect
k8s.io/api v0.20.7
k8s.io/apimachinery v0.20.7
k8s.io/client-go v0.20.7
Expand Down Expand Up @@ -77,7 +78,6 @@ require (
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/api v0.36.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -897,8 +897,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c h1:C0nyHiBU2m0cR6hDiUORWqQIt3h37wsp1255QBSSXqY=
golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
golang.org/x/tools v0.1.8-0.20211014194737-fc98fb2abd48 h1:hk7xRoeg0CG1nRLsd5BZLDUgVpA9bnKylGk1p2/BPH0=
golang.org/x/tools v0.1.8-0.20211014194737-fc98fb2abd48/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
3 changes: 3 additions & 0 deletions pkg/cloudprovider/aws/apis/v1alpha1/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type AWS struct {
// SecurityGroups specify the names of the security groups.
// +optional
SecurityGroupSelector map[string]string `json:"securityGroupSelector,omitempty"`
// Tags to be applied on ec2 resources like instances and launch templates.
// +optional
Tags map[string]string `json:"tags,omitempty"`
}

// Cluster configures the cluster that the provisioner operates against.
Expand Down
16 changes: 16 additions & 0 deletions pkg/cloudprovider/aws/apis/v1alpha1/provider_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (a *AWS) validate(ctx context.Context) (errs *apis.FieldError) {
a.validateLaunchTemplate(),
a.validateSubnets(),
a.validateSecurityGroups(),
a.validateTags(),
a.Cluster.Validate(ctx).ViaField("cluster"),
)
}
Expand Down Expand Up @@ -72,6 +73,21 @@ func (a *AWS) validateSecurityGroups() (errs *apis.FieldError) {
return errs
}

func (a *AWS) validateTags() (errs *apis.FieldError) {
// Avoiding a check on number of tags (hard limit of 50) since that limit is shared by user
// defined and Karpenter tags, and the latter could change over time.
managedTags := ManagedTagsFor(a.Cluster.Name)
for tagKey, tagValue := range a.Tags {
if tagKey == "" {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf(
"the tag with key : '' and value : '%s' is invalid because empty tag keys aren't supported", tagValue), "tags"))
} else if _, exists := managedTags[tagKey]; exists {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("tag key '%s' is reserved", tagKey), "tags"))
}
}
return errs
}

func (c *Cluster) Validate(context.Context) (errs *apis.FieldError) {
if len(c.Name) == 0 {
errs = errs.Also(apis.ErrMissingField("name"))
Expand Down
35 changes: 35 additions & 0 deletions pkg/cloudprovider/aws/apis/v1alpha1/tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License 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 v1alpha1

import (
"fmt"
)

const (
// ClusterTagKeyFormat is set on all Kubernetes owned resources.
ClusterTagKeyFormat = "kubernetes.io/cluster/%s"
// KarpenterTagKeyFormat is set on all Karpenter owned resources.
KarpenterTagKeyFormat = "karpenter.sh/cluster/%s"
)

func ManagedTagsFor(clusterName string) map[string]string {
// tags to be applied on AWS resources created by Karpenter (instances, launchTemplates..)
return map[string]string{
"Name": fmt.Sprintf("karpenter.sh/%s", clusterName),
fmt.Sprintf(ClusterTagKeyFormat, clusterName): "owned",
fmt.Sprintf(KarpenterTagKeyFormat, clusterName): "owned",
}
}
7 changes: 7 additions & 0 deletions pkg/cloudprovider/aws/apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions pkg/cloudprovider/aws/cloudprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ const (
CacheTTL = 60 * time.Second
// CacheCleanupInterval triggers cache cleanup (lazy eviction) at this interval.
CacheCleanupInterval = 10 * time.Minute
// ClusterTagKeyFormat is set on all Kubernetes owned resources.
ClusterTagKeyFormat = "kubernetes.io/cluster/%s"
// KarpenterTagKeyFormat is set on all Karpenter owned resources.
KarpenterTagKeyFormat = "karpenter.sh/cluster/%s"
)

type CloudProvider struct {
Expand Down
36 changes: 22 additions & 14 deletions pkg/cloudprovider/aws/launchtemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type launchTemplateOptions struct {
// Level-triggered fields that may change out of sync.
SecurityGroupsIds []string
AMIID string
Tags map[string]string
}

func (p *LaunchTemplateProvider) Get(ctx context.Context, constraints *v1alpha1.Constraints, instanceTypes []cloudprovider.InstanceType, additionalLabels map[string]string) (map[string][]cloudprovider.InstanceType, error) {
Expand Down Expand Up @@ -109,6 +110,7 @@ func (p *LaunchTemplateProvider) Get(ctx context.Context, constraints *v1alpha1.
InstanceProfile: constraints.InstanceProfile,
AMIID: amiID,
SecurityGroupsIds: securityGroupsIds,
Tags: constraints.Tags,
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -160,6 +162,7 @@ func needsDocker(is []cloudprovider.InstanceType) bool {
}

func (p *LaunchTemplateProvider) createLaunchTemplate(ctx context.Context, options *launchTemplateOptions) (*ec2.LaunchTemplate, error) {
tags := tagsFor(options)
output, err := p.ec2api.CreateLaunchTemplateWithContext(ctx, &ec2.CreateLaunchTemplateInput{
LaunchTemplateName: aws.String(launchTemplateName(options)),
LaunchTemplateData: &ec2.RequestLaunchTemplateData{
Expand All @@ -168,25 +171,16 @@ func (p *LaunchTemplateProvider) createLaunchTemplate(ctx context.Context, optio
},
TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{{
ResourceType: aws.String(ec2.ResourceTypeInstance),
Tags: []*ec2.Tag{
{
Key: aws.String("Name"),
Value: aws.String(fmt.Sprintf("Karpenter/%s", options.ClusterName)),
},
{
Key: aws.String(fmt.Sprintf(ClusterTagKeyFormat, options.ClusterName)),
Value: aws.String("owned"),
},
{
Key: aws.String(fmt.Sprintf(KarpenterTagKeyFormat, options.ClusterName)),
Value: aws.String("owned"),
},
},
Tags: tags,
}},
SecurityGroupIds: aws.StringSlice(options.SecurityGroupsIds),
UserData: aws.String(options.UserData),
ImageId: aws.String(options.AMIID),
},
TagSpecifications: []*ec2.TagSpecification{{
ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
Tags: tags,
}},
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -311,3 +305,17 @@ func (p *LaunchTemplateProvider) GetCABundle(ctx context.Context) (*string, erro
logging.FromContext(ctx).Debugf("Discovered caBundle, length %d", len(transportConfig.TLS.CAData))
return ptr.String(base64.StdEncoding.EncodeToString(transportConfig.TLS.CAData)), nil
}

func tagsFor(options *launchTemplateOptions) []*ec2.Tag {
var tags []*ec2.Tag
for key, value := range functional.UnionStringMaps(
options.Tags,
v1alpha1.ManagedTagsFor(options.ClusterName),
) {
tags = append(tags, &ec2.Tag{
Key: aws.String(key),
Value: aws.String(value),
})
}
return tags
}

0 comments on commit 4e0a4b4

Please sign in to comment.