Skip to content

Commit

Permalink
Support to add ResoureManagerTags to GCP Compute Disk, Image, Snapsho…
Browse files Browse the repository at this point in the history
…t resources

Signed-off-by: arkadeepsen <[email protected]>
  • Loading branch information
arkadeepsen committed Oct 23, 2023
1 parent 55b9d07 commit 4726b09
Show file tree
Hide file tree
Showing 59 changed files with 32,358 additions and 7 deletions.
12 changes: 11 additions & 1 deletion cmd/gce-pd-csi-driver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ var (
maxConcurrentFormatAndMount = flag.Int("max-concurrent-format-and-mount", 1, "If set then format and mount operations are serialized on each node. This is stronger than max-concurrent-format as it includes fsck and other mount operations")
formatAndMountTimeout = flag.Duration("format-and-mount-timeout", 1*time.Minute, "The maximum duration of a format and mount operation before another such operation will be started. Used only if --serialize-format-and-mount")

extraTagsStr = flag.String("extra-tags", "", "Extra tags to attach to each Compute Disk, Image, Snapshot created. It is a comma separated list of parent id, key and value like '<parent_id1>/<tag_key1>/<tag_value1>,...,<parent_idN>/<tag_keyN>/<tag_valueN>'. parent_id is the Organization or the Project ID where the tag key and the tag value resources exist. A maximum of 50 tags bindings is allowed for a resource. See https://cloud.google.com/resource-manager/docs/tags/tags-overview, https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing for details")

version string
)

Expand Down Expand Up @@ -119,6 +121,14 @@ func handle() {
klog.Fatalf("Bad extra volume labels: %v", err.Error())
}

if len(*extraTagsStr) > 0 && !*runControllerService {
klog.Fatalf("Extra tags provided but not running controller")
}
extraTags, err := common.ConvertTagsStringToSlice(*extraTagsStr)
if err != nil {
klog.Fatalf("Bad extra tags: %v", err.Error())
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand All @@ -131,7 +141,7 @@ func handle() {
// Initialize requirements for the controller service
var controllerServer *driver.GCEControllerServer
if *runControllerService {
cloudProvider, err := gce.CreateCloudProvider(ctx, version, *cloudConfigFilePath, *computeEndpoint)
cloudProvider, err := gce.CreateCloudProvider(ctx, version, *cloudConfigFilePath, *computeEndpoint, extraTags)
if err != nil {
klog.Fatalf("Failed to get cloud provider: %v", err.Error())
}
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ go 1.20
require (
cloud.google.com/go/compute/metadata v0.2.3
cloud.google.com/go/kms v1.15.3
cloud.google.com/go/resourcemanager v1.9.1
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.24.0
github.com/container-storage-interface/spec v1.6.0
github.com/google/uuid v1.3.1
github.com/googleapis/gax-go/v2 v2.12.0
github.com/kubernetes-csi/csi-proxy/client v1.1.3
github.com/kubernetes-csi/csi-test/v4 v4.4.0
github.com/onsi/ginkgo/v2 v2.13.0
github.com/onsi/gomega v1.28.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sys v0.13.0
golang.org/x/time v0.3.0
google.golang.org/api v0.148.0
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97
google.golang.org/grpc v1.59.0
Expand All @@ -31,8 +34,10 @@ require (
)

require (
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/iam v1.1.3 // indirect
cloud.google.com/go/longrunning v0.5.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
Expand All @@ -57,7 +62,6 @@ require (
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
Expand Down Expand Up @@ -86,7 +90,6 @@ require (
golang.org/x/sync v0.4.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFO
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
Expand Down Expand Up @@ -361,6 +362,8 @@ cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeN
cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=
cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=
cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=
Expand Down Expand Up @@ -471,6 +474,8 @@ cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7L
cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots=
cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo=
cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=
cloud.google.com/go/resourcemanager v1.9.1 h1:QIAMfndPOHR6yTmMUB0ZN+HSeRmPjR/21Smq5/xwghI=
cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmWGOkPS8gL5gqyjdT8=
cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=
cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=
cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=
Expand Down
95 changes: 95 additions & 0 deletions pkg/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import (
"net/http"
"regexp"
"strings"
"time"

"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
"golang.org/x/time/rate"
"google.golang.org/api/googleapi"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -255,6 +257,86 @@ func ConvertLabelsStringToMap(labels string) (map[string]string, error) {
return labelsMap, nil
}

// ConvertTagsStringToSlice converts the tags from string to Tag slice
// example: "parent_id1/tag_key1/tag_value1,parent_id2/tag_key2/tag_value2" gets
// converted into {"parent_id1/tag_key1/tag_value1", "parent_id2/tag_key2/tag_value2"}
// See https://cloud.google.com/resource-manager/docs/tags/tags-overview,
// https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing for details
func ConvertTagsStringToSlice(tags string) ([]string, error) {
const tagsDelimiter = ","
const tagsParentIDKeyValueDelimiter = "/"

tagsSlice := []string{}
if tags == "" {
return tagsSlice, nil
}

regexParent, _ := regexp.Compile(`(^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$)`)
checkTagParentIDFn := func(parentID string) error {
if !regexParent.MatchString(parentID) {
return fmt.Errorf("tag parent_id %q is invalid. parent_id can have a maximum of 32 characters and cannot be empty. parent_id 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", parentID)
}
return nil
}

regexKey, _ := regexp.Compile(`^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$`)
checkTagKeyFn := func(key string) error {
if !regexKey.MatchString(key) {
return fmt.Errorf("tag key %q is invalid. 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)
}
return nil
}

regexValue, _ := regexp.Compile(`^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\[\]{}\-\s]{0,61}[a-zA-Z0-9])?$`)
checkTagValueFn := func(value string) error {
if !regexValue.MatchString(value) {
return fmt.Errorf("tag value %q is invalid. 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)
}

return nil
}

checkTagParentIDKey := sets.String{}
parentIDkeyValueStrings := strings.Split(tags, tagsDelimiter)
for _, parentIDkeyValueString := range parentIDkeyValueStrings {
parentIDKeyValue := strings.Split(parentIDkeyValueString, tagsParentIDKeyValueDelimiter)

if len(parentIDKeyValue) != 3 {
return nil, fmt.Errorf("tags %q are invalid, correct format: 'parent_id1/key1/value1,parent_id2/key2/value2'", tags)
}

parentIDKeyStr := parentIDkeyValueString[:strings.LastIndex(parentIDkeyValueString, tagsParentIDKeyValueDelimiter)]
if checkTagParentIDKey.Has(parentIDKeyStr) {
return nil, fmt.Errorf("tag parent_id & key combination %q exists more than once", parentIDKeyStr)
}
checkTagParentIDKey.Insert(parentIDKeyStr)

parentID := strings.TrimSpace(parentIDKeyValue[0])
if err := checkTagParentIDFn(parentID); err != nil {
return nil, err
}

key := strings.TrimSpace(parentIDKeyValue[1])
if err := checkTagKeyFn(key); err != nil {
return nil, err
}

value := strings.TrimSpace(parentIDKeyValue[2])
if err := checkTagValueFn(value); err != nil {
return nil, err
}

tagsSlice = append(tagsSlice, strings.TrimSpace(parentIDkeyValueString))
}

const maxNumberOfTags = 50
if len(tagsSlice) > maxNumberOfTags {
return nil, fmt.Errorf("more than %d tags is not allowed, given: %d", maxNumberOfTags, len(tagsSlice))
}

return tagsSlice, nil
}

// ProcessStorageLocations trims and normalizes storage location to lower letters.
func ProcessStorageLocations(storageLocations string) ([]string, error) {
normalizedLoc := strings.ToLower(strings.TrimSpace(storageLocations))
Expand Down Expand Up @@ -392,3 +474,16 @@ func isValidDiskEncryptionKmsKey(DiskEncryptionKmsKey string) bool {
kmsKeyPattern := regexp.MustCompile("projects/[^/]+/locations/([^/]+)/keyRings/[^/]+/cryptoKeys/[^/]+")
return kmsKeyPattern.MatchString(DiskEncryptionKmsKey)
}

// NewLimiter returns a token bucket based request rate limiter after initializing
// the passed values for limit, burst (or token bucket) size. If opted for emptyBucket
// all initial tokens are reserved for the first burst.
func NewLimiter(limit, burst int, emptyBucket bool) *rate.Limiter {
limiter := rate.NewLimiter(rate.Every(time.Second/time.Duration(limit)), burst)

if emptyBucket {
limiter.AllowN(time.Now(), burst)
}

return limiter
}
Loading

0 comments on commit 4726b09

Please sign in to comment.