kubernetes-sigs/cluster-api-provider-gcp

Support to add ResoureManagerTags to Compute Instances

bharath-b-rh opened this issue · 2 comments

/kind feature

GCP Tags are key-value pairs that are bind to the GCP resources. Unlike currently supported labels, tags are not part of the resource metadata but resource in itself. Tag Keys, Values, Bindings are all discreet resources. Tags are used for defining IAM policy conditions, Organization conditionals policies and integrating with Cloud billing for cost management, which are not supported by labels.

Describe the solution you'd like

  1. Able to define the list of tags to add to the Compute Instances during cluster creation.
  2. New Compute resources created for the cluster must have the tags defined.

Anything else you would like to add:
Currently tag resources tag keys and tag values can only be created at Organization and Project level with the required permissions. Tag keys and Tag Values will be created by the user and only the Tag bindings to the Compute Instance to be created and which would require below changes

  1. Update GCPClusterSpec with new field for defining tags.
type GCPClusterSpec struct {
	// resourceManagerTags is an optional set of tags to apply to GCP resources managed
        // by the GCP provider. GCP supports a maximum of 50 tags per resource.
	// +kubebuilder:validation:MaxItems=50
	// +listType=map
	// +listMapKey=key
	// +optional
	ResourceManagerTags ResourceManagerTag `json:"resourceManagerTags,omitempty"`
}

// ResourceManagerTag is a tag to apply to GCP resources managed by the GCP provider.
type ResourceManagerTag struct {
	// parentID is the ID of the hierarchical resource where the tags are defined
	// e.g. at the Organization or the Project level. To find the Organization or Project ID ref
	// https://cloud.google.com/resource-manager/docs/creating-managing-organization#retrieving_your_organization_id
	// https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects
	// An OrganizationID must consist of decimal numbers, and cannot have leading zeroes.
	// A ProjectID must be 6 to 30 characters in length, can only contain lowercase letters,
	// numbers, and hyphens, and must start with a letter, and cannot end with a hyphen.
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=32
	// +kubebuilder:validation:Pattern=`(^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$)`
	ParentID string `json:"parentID"`

	// key is the key part of the tag. A tag key can have a maximum of 63 characters and cannot
	// be empty. Tag key must begin and end with an alphanumeric character, and must contain
	// only uppercase, lowercase alphanumeric characters, and the following special
        // characters `._-`.
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=63
	// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$`
	Key string `json:"key"`

	// value is the value part of the tag. A tag value can have a maximum of 63 characters and
	// cannot be empty. Tag value must begin and end with an alphanumeric character, and must
	// contain only uppercase, lowercase alphanumeric characters, and the following special
        // characters `_-.@%=+:,*#&(){}[]` and spaces.
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=63
	// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\[\]{}\-\s]{0,61}[a-zA-Z0-9])?$`
	Value string `json:"value"`
}
  1. Update GCPMachineSpec with new field for defining tags.
type GCPMachineSpec struct {
	// resourceManagerTags is an optional set of tags to apply to GCP resources managed
        // by the GCP provider. GCP supports a maximum of 50 tags per resource.
	// +kubebuilder:validation:MaxItems=50
	// +listType=map
	// +listMapKey=key
	// +optional
	ResourceManagerTags ResourceManagerTag `json:"resourceManagerTags,omitempty"`
}

ResourceManagerTags can be included during InstanceSpec generation like below

// InstanceSpec returns instance spec.
func (m *MachineScope) InstanceSpec(log logr.Logger) *compute.Instance {
	instance := &compute.Instance{
		Params: &compute.InstanceParams{
			ResourceManagerTags: m.ClusterGetter.ResourceManagerTags().AddResourceManagerTags(m.GCPMachine.Spec.ResourceManagerTags),
		},
	}

Users provide names of the Tag Keys(e.g. foo) and Tag Values(e.g. bar), but the InstanceParams expects the unique IDs generated by GCP service to be provided as below.

// InstanceParams: Additional instance params.
type InstanceParams struct {
	// ResourceManagerTags: Resource manager tags to be bound to the
	// instance. Tag keys and values have the same definition as resource
	// manager tags. Keys must be in the format `tagKeys/{tag_key_id}`, and
	// values are in the format `tagValues/456`. The field is ignored (both
	// PUT & PATCH) when empty.
	ResourceManagerTags map[string]string `json:"resourceManagerTags,omitempty"`
}

And this requires additional logic to transform the user provided input to one as expected by the API.

func getTagValuesNames(ctx context.Context, cred string, tagList []machinev1.ResourceManagerTag) (map[string]string, error) {
	client, err := getTagValuesClient(ctx, cred)
	if err != nil {
		return nil, err
	}
	defer client.Close()

	tagValueList := make(map[string]string, len(tagList))
	getTagValuesReq := &rscmgrpb.GetNamespacedTagValueRequest{}
	for _, tag := range tagList {
		getTagValuesReq.Name = fmt.Sprintf("%s/%s/%s", tag.ParentID, tag.Key, tag.Value)
		value, err := client.GetNamespacedTagValue(ctx, getTagValuesReq)
		if err != nil {
			return nil, err
		}
		tagValueList[value.Parent] = value.Name
	}

	return tagValueList, nil
}

Reference Links

/assign

/lifecycle active