Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions data/data/install.openshift.io_installconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3199,6 +3199,81 @@ spec:
description: Region specifies the GCP region where the cluster
will be created.
type: string
userLabels:
description: userLabels has additional keys and values that the
installer will add as labels to all resources that it creates
on GCP. Resources created by the cluster itself may not include
these labels. This is a TechPreview feature and requires setting
CustomNoUpgrade featureSet with GCPLabelsTags featureGate enabled
or TechPreviewNoUpgrade featureSet to configure labels.
items:
description: UserLabel is a label to apply to GCP resources
created for the cluster.
properties:
key:
description: key is the key part of the label. A label key
can have a maximum of 63 characters and cannot be empty.
Label must begin with a lowercase letter, and must contain
only lowercase letters, numeric characters, and the following
special characters `_-`.
type: string
value:
description: value is the value part of the label. A label
value can have a maximum of 63 characters and cannot be
empty. Value must contain only lowercase letters, numeric
characters, and the following special characters `_-`.
type: string
required:
- key
- value
type: object
type: array
userTags:
description: userTags has additional keys and values that the
installer will add as tags to all resources that it creates
on GCP. Resources created by the cluster itself may not include
these tags. Tag key and tag value should be the shortnames of
the tag key and tag value resource. This is a TechPreview feature
and requires setting CustomNoUpgrade featureSet with GCPLabelsTags
featureGate enabled or TechPreviewNoUpgrade featureSet to configure
tags.
items:
description: UserTag is a tag to apply to GCP resources created
for the cluster.
properties:
key:
description: 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 `._-`.
type: string
parentID:
description: '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 ID or Project
ID refer to the following pages: 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.'
type: string
value:
description: 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.
type: string
required:
- key
- parentID
- value
type: object
type: array
required:
- projectID
- region
Expand Down
52 changes: 52 additions & 0 deletions pkg/types/gcp/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,56 @@ type Platform struct {
// such as the current env OPENSHIFT_INSTALL_OS_IMAGE_OVERRIDE
// +optional
Licenses []string `json:"licenses,omitempty"`

// userLabels has additional keys and values that the installer will add as
// labels to all resources that it creates on GCP. Resources created by the
// cluster itself may not include these labels. This is a TechPreview feature
// and requires setting CustomNoUpgrade featureSet with GCPLabelsTags featureGate
// enabled or TechPreviewNoUpgrade featureSet to configure labels.
UserLabels []UserLabel `json:"userLabels,omitempty"`

// userTags has additional keys and values that the installer will add as
// tags to all resources that it creates on GCP. Resources created by the
// cluster itself may not include these tags. Tag key and tag value should
// be the shortnames of the tag key and tag value resource. This is a TechPreview
// feature and requires setting CustomNoUpgrade featureSet with GCPLabelsTags
// featureGate enabled or TechPreviewNoUpgrade featureSet to configure tags.
UserTags []UserTag `json:"userTags,omitempty"`
}

// UserLabel is a label to apply to GCP resources created for the cluster.
type UserLabel struct {
// key is the key part of the label. A label key can have a maximum of 63 characters
// and cannot be empty. Label must begin with a lowercase letter, and must contain
// only lowercase letters, numeric characters, and the following special characters `_-`.
Key string `json:"key"`

// value is the value part of the label. A label value can have a maximum of 63 characters
// and cannot be empty. Value must contain only lowercase letters, numeric characters, and
// the following special characters `_-`.
Value string `json:"value"`
}

// UserTag is a tag to apply to GCP resources created for the cluster.
type UserTag 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 ID or Project ID refer to the following pages:
// 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.
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 `._-`.
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.
Value string `json:"value"`
}
117 changes: 117 additions & 0 deletions pkg/types/gcp/validation/platform.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package validation

import (
"fmt"
"os"
"regexp"
"sort"

"k8s.io/apimachinery/pkg/util/validation/field"
Expand Down Expand Up @@ -65,8 +67,34 @@ var (
sort.Strings(validValues)
return validValues
}()

// userLabelKeyRegex is for verifying that the label key contains only allowed characters.
userLabelKeyRegex = regexp.MustCompile(`^[a-z][0-9a-z_-]{1,62}$`)

// userLabelValueRegex is for verifying that the label value contains only allowed characters.
userLabelValueRegex = regexp.MustCompile(`^[0-9a-z_-]{1,63}$`)

// userLabelKeyPrefixRegex is for verifying that the label key does not contain restricted prefixes.
userLabelKeyPrefixRegex = regexp.MustCompile(`^(?i)(kubernetes\-io|openshift\-io)`)

// userTagKeyRegex is for verifying that the tag key contains only allowed characters.
userTagKeyRegex = regexp.MustCompile(`^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$`)

// userTagValueRegex is for verifying that the tag value contains only allowed characters.
userTagValueRegex = regexp.MustCompile(`^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\[\]{}\-\s]{0,61}[a-zA-Z0-9])?$`)

// userTagParentIDRegex is for verifying that the tag parentID contains only allowed characters.
userTagParentIDRegex = regexp.MustCompile(`(^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$)`)
)

// maxUserLabelLimit is the maximum userLabels that can be configured as defined in openshift/api.
// https://github.com/openshift/api/commit/ae73a19d05c35068af16c9aeff375d0b7c936a8a#diff-07b264a49084976b670fb699badaca1795027d6ea732a99226a5388104f6174fR592-R602
const maxUserLabelLimit = 32

// maxUserTagLimit is the maximum userTags that can be configured as defined in openshift/api.
// https://github.com/openshift/api/commit/ae73a19d05c35068af16c9aeff375d0b7c936a8a#diff-07b264a49084976b670fb699badaca1795027d6ea732a99226a5388104f6174fR604-R613
const maxUserTagLimit = 50

// ValidatePlatform checks that the specified platform is valid.
func ValidatePlatform(p *gcp.Platform, fldPath *field.Path, ic *types.InstallConfig) field.ErrorList {
allErrs := field.ErrorList{}
Expand Down Expand Up @@ -109,5 +137,94 @@ func ValidatePlatform(p *gcp.Platform, fldPath *field.Path, ic *types.InstallCon
}
}

// check if configured userLabels are valid.
allErrs = append(allErrs, validateUserLabels(p.UserLabels, fldPath.Child("userLabels"))...)

// check if configured userTags are valid.
allErrs = append(allErrs, validateUserTags(p.UserTags, fldPath.Child("userTags"))...)

return allErrs
}

// validateUserLabels verifies if configured number of UserLabels is not more than
// allowed limit and the label keys and values are valid.
func validateUserLabels(labels []gcp.UserLabel, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(labels) == 0 {
return allErrs
}

if len(labels) > maxUserLabelLimit {
allErrs = append(allErrs, field.TooMany(fldPath, len(labels), maxUserLabelLimit))
}

for _, label := range labels {
if err := validateLabel(label.Key, label.Value); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Key(label.Key), label.Value, err.Error()))
}
}
return allErrs
}

// validateLabel checks the following to ensure that the label configured is acceptable.
// - The key and value contain only allowed characters.
// - The key is not empty and at most 63 characters and starts with a lowercase letter.
// - The value is not empty and at most 63 characters.
// - The key and value must contain only lowercase letters, numeric characters,
// underscores, and dashes.
// - The key cannot be Name or have kubernetes.io, openshift.io prefixes.
func validateLabel(key, value string) error {
if !userLabelKeyRegex.MatchString(key) {
return fmt.Errorf("label key is invalid or contains invalid characters. Label key can have a maximum of 63 characters and cannot be empty. Label key must begin with a lowercase letter, and must contain only lowercase letters, numeric characters, and the following special characters `_-`")
}
if !userLabelValueRegex.MatchString(value) {
return fmt.Errorf("label value is invalid or contains invalid characters. Label value can have a maximum of 63 characters and cannot be empty. Value must contain only lowercase letters, numeric characters, and the following special characters `_-`")
}
if userLabelKeyPrefixRegex.MatchString(key) {
return fmt.Errorf("label key contains restricted prefix. Label key cannot have `kubernetes-io`, `openshift-io` prefixes")
}
return nil
}

// validateUserTags verifies if configured number of UserTags is not more than
// allowed limit and the tag keys and values are valid.
func validateUserTags(tags []gcp.UserTag, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(tags) == 0 {
return allErrs
}

if len(tags) > maxUserTagLimit {
allErrs = append(allErrs, field.TooMany(fldPath, len(tags), maxUserTagLimit))
}

for _, tag := range tags {
if err := validateTag(tag.ParentID, tag.Key, tag.Value); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Key(tag.Key), tag.Value, err.Error()))
}
}

return allErrs
}

// validateTag checks the following to ensure that the tag configured is acceptable. Though
// the criteria is for tag resources to pre-exist, tags will be validated to catch the
// error much earlier.
// - The key and value contain only allowed characters.
// - The key and value is not empty and can have at most 63 characters.
// - The ParentID can be either OrganizationID or ProjectID.
// - OrganizationID must consist of decimal numbers, and cannot have leading zeroes.
// - 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.
func validateTag(parentID, key, value string) error {
if !userTagParentIDRegex.MatchString(parentID) {
return fmt.Errorf("tag parentID is invalid or contains invalid characters. ParentID can have a maximum of 32 characters and cannot be empty. ParentID can be either OrganizationID or ProjectID. OrganizationID must consist of decimal numbers, and cannot have leading zeroes and 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")
}
if !userTagKeyRegex.MatchString(key) {
return fmt.Errorf("tag key is invalid or contains invalid characters. 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 `._-`")
}
if !userTagValueRegex.MatchString(value) {
return fmt.Errorf("tag value is invalid or contains invalid characters. 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")
}
return nil
}
Loading