Skip to content

Commit 5fe0523

Browse files
committed
Support to add ResoureManagerTags to GCP Compute Disk, Image, Snapshot resources
Signed-off-by: arkadeepsen <[email protected]>
1 parent d1f263d commit 5fe0523

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+32292
-7
lines changed

cmd/gce-pd-csi-driver/main.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ var (
6666
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")
6767
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")
6868

69+
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")
70+
6971
version string
7072
)
7173

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

124+
if len(*extraTagsStr) > 0 && !*runControllerService {
125+
klog.Fatalf("Extra tags provided but not running controller")
126+
}
127+
extraTags, err := common.ConvertTagsStringToSlice(*extraTagsStr)
128+
if err != nil {
129+
klog.Fatalf("Bad extra tags: %v", err.Error())
130+
}
131+
122132
ctx, cancel := context.WithCancel(context.Background())
123133
defer cancel()
124134

@@ -131,7 +141,7 @@ func handle() {
131141
// Initialize requirements for the controller service
132142
var controllerServer *driver.GCEControllerServer
133143
if *runControllerService {
134-
cloudProvider, err := gce.CreateCloudProvider(ctx, version, *cloudConfigFilePath, *computeEndpoint)
144+
cloudProvider, err := gce.CreateCloudProvider(ctx, version, *cloudConfigFilePath, *computeEndpoint, extraTags)
135145
if err != nil {
136146
klog.Fatalf("Failed to get cloud provider: %v", err.Error())
137147
}

go.mod

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ go 1.20
55
require (
66
cloud.google.com/go/compute/metadata v0.2.3
77
cloud.google.com/go/kms v1.12.1
8+
cloud.google.com/go/resourcemanager v1.9.1
89
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.24.0
910
github.com/container-storage-interface/spec v1.6.0
1011
github.com/google/uuid v1.3.0
12+
github.com/googleapis/gax-go/v2 v2.12.0
1113
github.com/kubernetes-csi/csi-proxy/client v1.1.1
1214
github.com/kubernetes-csi/csi-test/v4 v4.4.0
1315
github.com/onsi/ginkgo/v2 v2.7.1
1416
github.com/onsi/gomega v1.25.0
1517
golang.org/x/oauth2 v0.10.0
1618
golang.org/x/sys v0.10.0
19+
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
1720
google.golang.org/api v0.134.0
1821
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130
1922
google.golang.org/grpc v1.56.2
@@ -31,8 +34,10 @@ require (
3134
)
3235

3336
require (
37+
cloud.google.com/go v0.110.4 // indirect
3438
cloud.google.com/go/compute v1.20.1 // indirect
3539
cloud.google.com/go/iam v1.1.0 // indirect
40+
cloud.google.com/go/longrunning v0.5.1 // indirect
3641
github.com/Microsoft/go-winio v0.4.17 // indirect
3742
github.com/PuerkitoBio/purell v1.1.1 // indirect
3843
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -54,7 +59,6 @@ require (
5459
github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea // indirect
5560
github.com/google/s2a-go v0.1.4 // indirect
5661
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
57-
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
5862
github.com/hashicorp/errwrap v1.0.0 // indirect
5963
github.com/hashicorp/go-multierror v1.1.0 // indirect
6064
github.com/imdario/mergo v0.3.12 // indirect
@@ -81,7 +85,6 @@ require (
8185
golang.org/x/net v0.12.0 // indirect
8286
golang.org/x/term v0.10.0 // indirect
8387
golang.org/x/text v0.11.0 // indirect
84-
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
8588
google.golang.org/appengine v1.6.7 // indirect
8689
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect
8790
google.golang.org/genproto/googleapis/rpc v0.0.0-20230720185612-659f7aaaa771 // indirect

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECH
3131
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
3232
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
3333
cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk=
34+
cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
3435
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
3536
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
3637
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -49,11 +50,15 @@ cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5
4950
cloud.google.com/go/kms v1.12.1 h1:xZmZuwy2cwzsocmKDOPu4BL7umg8QXagQx6fKVmf45U=
5051
cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM=
5152
cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls=
53+
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
54+
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
5255
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
5356
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
5457
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
5558
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
5659
cloud.google.com/go/pubsub v1.4.0/go.mod h1:LFrqilwgdw4X2cJS9ALgzYmMu+ULyrUN6IHV3CPK4TM=
60+
cloud.google.com/go/resourcemanager v1.9.1 h1:QIAMfndPOHR6yTmMUB0ZN+HSeRmPjR/21Smq5/xwghI=
61+
cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmWGOkPS8gL5gqyjdT8=
5762
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
5863
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
5964
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=

pkg/common/utils.go

+95
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import (
2323
"net/http"
2424
"regexp"
2525
"strings"
26+
"time"
2627

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

260+
// ConvertTagsStringToSlice converts the tags from string to Tag slice
261+
// example: "parent_id1/tag_key1/tag_value1,parent_id2/tag_key2/tag_value2" gets
262+
// converted into {"parent_id1/tag_key1/tag_value1", "parent_id2/tag_key2/tag_value2"}
263+
// See https://cloud.google.com/resource-manager/docs/tags/tags-overview,
264+
// https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing for details
265+
func ConvertTagsStringToSlice(tags string) ([]string, error) {
266+
const tagsDelimiter = ","
267+
const tagsParentIDKeyValueDelimiter = "/"
268+
269+
tagsSlice := []string{}
270+
if tags == "" {
271+
return tagsSlice, nil
272+
}
273+
274+
regexParent, _ := regexp.Compile(`(^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$)`)
275+
checkTagParentIDFn := func(parentID string) error {
276+
if !regexParent.MatchString(parentID) {
277+
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)
278+
}
279+
return nil
280+
}
281+
282+
regexKey, _ := regexp.Compile(`^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$`)
283+
checkTagKeyFn := func(key string) error {
284+
if !regexKey.MatchString(key) {
285+
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)
286+
}
287+
return nil
288+
}
289+
290+
regexValue, _ := regexp.Compile(`^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\[\]{}\-\s]{0,61}[a-zA-Z0-9])?$`)
291+
checkTagValueFn := func(value string) error {
292+
if !regexValue.MatchString(value) {
293+
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)
294+
}
295+
296+
return nil
297+
}
298+
299+
checkTagParentIDKey := sets.String{}
300+
parentIDkeyValueStrings := strings.Split(tags, tagsDelimiter)
301+
for _, parentIDkeyValueString := range parentIDkeyValueStrings {
302+
parentIDKeyValue := strings.Split(parentIDkeyValueString, tagsParentIDKeyValueDelimiter)
303+
304+
if len(parentIDKeyValue) != 3 {
305+
return nil, fmt.Errorf("tags %q are invalid, correct format: 'parent_id1/key1/value1,parent_id2/key2/value2'", tags)
306+
}
307+
308+
parentIDKeyStr := parentIDkeyValueString[:strings.LastIndex(parentIDkeyValueString, tagsParentIDKeyValueDelimiter)]
309+
if checkTagParentIDKey.Has(parentIDKeyStr) {
310+
return nil, fmt.Errorf("tag parent_id & key combination %q exists more than once", parentIDKeyStr)
311+
}
312+
checkTagParentIDKey.Insert(parentIDKeyStr)
313+
314+
parentID := strings.TrimSpace(parentIDKeyValue[0])
315+
if err := checkTagParentIDFn(parentID); err != nil {
316+
return nil, err
317+
}
318+
319+
key := strings.TrimSpace(parentIDKeyValue[1])
320+
if err := checkTagKeyFn(key); err != nil {
321+
return nil, err
322+
}
323+
324+
value := strings.TrimSpace(parentIDKeyValue[2])
325+
if err := checkTagValueFn(value); err != nil {
326+
return nil, err
327+
}
328+
329+
tagsSlice = append(tagsSlice, strings.TrimSpace(parentIDkeyValueString))
330+
}
331+
332+
const maxNumberOfTags = 50
333+
if len(tagsSlice) > maxNumberOfTags {
334+
return nil, fmt.Errorf("more than %d tags is not allowed, given: %d", maxNumberOfTags, len(tagsSlice))
335+
}
336+
337+
return tagsSlice, nil
338+
}
339+
258340
// ProcessStorageLocations trims and normalizes storage location to lower letters.
259341
func ProcessStorageLocations(storageLocations string) ([]string, error) {
260342
normalizedLoc := strings.ToLower(strings.TrimSpace(storageLocations))
@@ -392,3 +474,16 @@ func isValidDiskEncryptionKmsKey(DiskEncryptionKmsKey string) bool {
392474
kmsKeyPattern := regexp.MustCompile("projects/[^/]+/locations/([^/]+)/keyRings/[^/]+/cryptoKeys/[^/]+")
393475
return kmsKeyPattern.MatchString(DiskEncryptionKmsKey)
394476
}
477+
478+
// NewLimiter returns a token bucket based request rate limiter after initializing
479+
// the passed values for limit, burst (or token bucket) size. If opted for emptyBucket
480+
// all initial tokens are reserved for the first burst.
481+
func NewLimiter(limit, burst int, emptyBucket bool) *rate.Limiter {
482+
limiter := rate.NewLimiter(rate.Every(time.Second/time.Duration(limit)), burst)
483+
484+
if emptyBucket {
485+
limiter.AllowN(time.Now(), burst)
486+
}
487+
488+
return limiter
489+
}

0 commit comments

Comments
 (0)