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  }