sigs.k8s.io/cluster-api-provider-aws@v1.5.5/api/v1beta1/tags.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1beta1 18 19 import ( 20 "fmt" 21 "regexp" 22 23 "github.com/google/go-cmp/cmp" 24 "k8s.io/apimachinery/pkg/types" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 27 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 28 ) 29 30 // Tags defines a map of tags. 31 type Tags map[string]string 32 33 // Equals returns true if the tags are equal. 34 // This func is deprecated and should not be used. 35 func (t Tags) Equals(other Tags) bool { 36 return cmp.Equal(t, other) 37 } 38 39 // HasOwned returns true if the tags contains a tag that marks the resource as owned by the cluster from the perspective of this management tooling. 40 func (t Tags) HasOwned(cluster string) bool { 41 value, ok := t[ClusterTagKey(cluster)] 42 return ok && ResourceLifecycle(value) == ResourceLifecycleOwned 43 } 44 45 // HasAWSCloudProviderOwned returns true if the tags contains a tag that marks the resource as owned by the cluster from the perspective of the in-tree cloud provider. 46 func (t Tags) HasAWSCloudProviderOwned(cluster string) bool { 47 value, ok := t[ClusterAWSCloudProviderTagKey(cluster)] 48 return ok && ResourceLifecycle(value) == ResourceLifecycleOwned 49 } 50 51 // GetRole returns the Cluster API role for the tagged resource. 52 func (t Tags) GetRole() string { 53 return t[NameAWSClusterAPIRole] 54 } 55 56 // Difference returns the difference between this map of tags and the other map of tags. 57 // Items are considered equals if key and value are equals. 58 func (t Tags) Difference(other Tags) Tags { 59 res := make(Tags, len(t)) 60 61 for key, value := range t { 62 if otherValue, ok := other[key]; ok && value == otherValue { 63 continue 64 } 65 res[key] = value 66 } 67 68 return res 69 } 70 71 // Merge merges in tags from other. If a tag already exists, it is replaced by the tag in other. 72 func (t Tags) Merge(other Tags) { 73 for k, v := range other { 74 t[k] = v 75 } 76 } 77 78 // Validate checks if tags are valid for the AWS API/Resources. 79 // Keys must have at least 1 and max 128 characters. 80 // Values must be max 256 characters long. 81 // Keys and Values can only have alphabets, numbers, spaces and _ . : / = + - @ as characters. 82 // Tag's key cannot have prefix "aws:". 83 // Max count of User tags for a specific resource can be 50. 84 func (t Tags) Validate() []*field.Error { 85 // Defines the maximum number of user tags which can be created for a specific resource 86 const maxUserTagsAllowed = 50 87 var errs field.ErrorList 88 var userTagCount = len(t) 89 re := regexp.MustCompile(`^[a-zA-Z0-9\\s\_\.\:\=\+\-\@\/]*$`) 90 91 for k, v := range t { 92 if len(k) < 1 { 93 errs = append(errs, 94 field.Invalid(field.NewPath("spec", "additionalTags"), k, "key cannot be empty"), 95 ) 96 } 97 if len(k) > 128 { 98 errs = append(errs, 99 field.Invalid(field.NewPath("spec", "additionalTags"), k, "key cannot be longer than 128 characters"), 100 ) 101 } 102 if len(v) > 256 { 103 errs = append(errs, 104 field.Invalid(field.NewPath("spec", "additionalTags"), v, "value cannot be longer than 256 characters"), 105 ) 106 } 107 if wrongUserTagNomenclature(k) { 108 errs = append(errs, 109 field.Invalid(field.NewPath("spec", "additionalTags"), k, "user created tag's key cannot have prefix aws:"), 110 ) 111 } 112 val := re.MatchString(k) 113 if !val { 114 errs = append(errs, 115 field.Invalid(field.NewPath("spec", "additionalTags"), k, "key cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ ."), 116 ) 117 } 118 val = re.MatchString(v) 119 if !val { 120 errs = append(errs, 121 field.Invalid(field.NewPath("spec", "additionalTags"), v, "value cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ ."), 122 ) 123 } 124 } 125 126 if userTagCount > maxUserTagsAllowed { 127 errs = append(errs, 128 field.Invalid(field.NewPath("spec", "additionalTags"), t, "user created tags cannot be more than 50"), 129 ) 130 } 131 132 return errs 133 } 134 135 // Checks whether the tag created is user tag or not. 136 func wrongUserTagNomenclature(k string) bool { 137 return len(k) > 3 && k[0:4] == "aws:" 138 } 139 140 // ResourceLifecycle configures the lifecycle of a resource. 141 type ResourceLifecycle string 142 143 const ( 144 // ResourceLifecycleOwned is the value we use when tagging resources to indicate 145 // that the resource is considered owned and managed by the cluster, 146 // and in particular that the lifecycle is tied to the lifecycle of the cluster. 147 ResourceLifecycleOwned = ResourceLifecycle("owned") 148 149 // ResourceLifecycleShared is the value we use when tagging resources to indicate 150 // that the resource is shared between multiple clusters, and should not be destroyed 151 // if the cluster is destroyed. 152 ResourceLifecycleShared = ResourceLifecycle("shared") 153 154 // NameKubernetesAWSCloudProviderPrefix is the tag name used by the cloud provider to logically 155 // separate independent cluster resources. We use it to identify which resources we expect 156 // to be permissive about state changes. 157 // logically independent clusters running in the same AZ. 158 // The tag key = NameKubernetesAWSCloudProviderPrefix + clusterID 159 // The tag value is an ownership value. 160 NameKubernetesAWSCloudProviderPrefix = "kubernetes.io/cluster/" 161 162 // NameAWSProviderPrefix is the tag prefix we use to differentiate 163 // cluster-api-provider-aws owned components from other tooling that 164 // uses NameKubernetesClusterPrefix. 165 NameAWSProviderPrefix = "sigs.k8s.io/cluster-api-provider-aws/" 166 167 // NameAWSProviderOwned is the tag name we use to differentiate 168 // cluster-api-provider-aws owned components from other tooling that 169 // uses NameKubernetesClusterPrefix. 170 NameAWSProviderOwned = NameAWSProviderPrefix + "cluster/" 171 172 // NameAWSClusterAPIRole is the tag name we use to mark roles for resources 173 // dedicated to this cluster api provider implementation. 174 NameAWSClusterAPIRole = NameAWSProviderPrefix + "role" 175 176 // NameAWSSubnetAssociation is the tag name we use to mark association for resources 177 // dedicated to this cluster api provider implementation. 178 NameAWSSubnetAssociation = NameAWSProviderPrefix + "association" 179 180 // SecondarySubnetTagValue is the secondary subnet tag constant value. 181 SecondarySubnetTagValue = "secondary" 182 183 // APIServerRoleTagValue describes the value for the apiserver role. 184 APIServerRoleTagValue = "apiserver" 185 186 // BastionRoleTagValue describes the value for the bastion role. 187 BastionRoleTagValue = "bastion" 188 189 // CommonRoleTagValue describes the value for the common role. 190 CommonRoleTagValue = "common" 191 192 // PublicRoleTagValue describes the value for the public role. 193 PublicRoleTagValue = "public" 194 195 // PrivateRoleTagValue describes the value for the private role. 196 PrivateRoleTagValue = "private" 197 198 // MachineNameTagKey is the key for machine name. 199 MachineNameTagKey = "MachineName" 200 ) 201 202 // ClusterTagKey generates the key for resources associated with a cluster. 203 func ClusterTagKey(name string) string { 204 return fmt.Sprintf("%s%s", NameAWSProviderOwned, name) 205 } 206 207 // ClusterAWSCloudProviderTagKey generates the key for resources associated a cluster's AWS cloud provider. 208 func ClusterAWSCloudProviderTagKey(name string) string { 209 return fmt.Sprintf("%s%s", NameKubernetesAWSCloudProviderPrefix, name) 210 } 211 212 // BuildParams is used to build tags around an aws resource. 213 type BuildParams struct { 214 // Lifecycle determines the resource lifecycle. 215 Lifecycle ResourceLifecycle 216 217 // ClusterName is the cluster associated with the resource. 218 ClusterName string 219 220 // ResourceID is the unique identifier of the resource to be tagged. 221 ResourceID string 222 223 // Name is the name of the resource, it's applied as the tag "Name" on AWS. 224 // +optional 225 Name *string 226 227 // Role is the role associated to the resource. 228 // +optional 229 Role *string 230 231 // Any additional tags to be added to the resource. 232 // +optional 233 Additional Tags 234 } 235 236 // WithMachineName tags the namespaced machine name 237 // The machine name will be tagged with key "MachineName". 238 func (b BuildParams) WithMachineName(m *clusterv1.Machine) BuildParams { 239 machineNamespacedName := types.NamespacedName{Namespace: m.Namespace, Name: m.Name} 240 b.Additional[MachineNameTagKey] = machineNamespacedName.String() 241 return b 242 } 243 244 // WithCloudProvider tags the cluster ownership for a resource. 245 func (b BuildParams) WithCloudProvider(name string) BuildParams { 246 b.Additional[ClusterAWSCloudProviderTagKey(name)] = string(ResourceLifecycleOwned) 247 return b 248 } 249 250 // Build builds tags including the cluster tag and returns them in map form. 251 func Build(params BuildParams) Tags { 252 tags := make(Tags) 253 for k, v := range params.Additional { 254 tags[k] = v 255 } 256 257 if params.ClusterName != "" { 258 tags[ClusterTagKey(params.ClusterName)] = string(params.Lifecycle) 259 } 260 if params.Role != nil { 261 tags[NameAWSClusterAPIRole] = *params.Role 262 } 263 264 if params.Name != nil { 265 tags["Name"] = *params.Name 266 } 267 268 return tags 269 }