Skip to content

Commit 03d8b24

Browse files
authored
Merge pull request #718 from mattcary/labels
Add gce disk labels support via create volume parameters
2 parents b24f1a7 + 42b0d4b commit 03d8b24

File tree

16 files changed

+413
-27
lines changed

16 files changed

+413
-27
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ See Github [Issues](https://github.com/kubernetes-sigs/gcp-compute-persistent-di
6868
| type | `pd-ssd` OR `pd-standard` | `pd-standard` | Type allows you to choose between standard Persistent Disks or Solid State Drive Persistent Disks |
6969
| replication-type | `none` OR `regional-pd` | `none` | Replication type allows you to choose between Zonal Persistent Disks or Regional Persistent Disks |
7070
| disk-encryption-kms-key | Fully qualified resource identifier for the key to use to encrypt new disks. | Empty string. | Encrypt disk using Customer Managed Encryption Key (CMEK). See [GKE Docs](https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek#create_a_cmek_protected_attached_disk) for details. |
71+
| labels | `key1=value1,key2=value2` | | Labels allow you to assign custom [GCE Disk labels](https://cloud.google.com/compute/docs/labeling-resources). |
7172

7273
### Topology
7374

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
"k8s.io/klog"
2626

27-
cliflag "k8s.io/component-base/cli/flag"
27+
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
2828
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute"
2929
metadataservice "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/metadata"
3030
driver "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-pd-csi-driver"
@@ -39,7 +39,7 @@ var (
3939
runNodeService = flag.Bool("run-node-service", true, "If set to false then the CSI driver does not activate its node service (default: true)")
4040
httpEndpoint = flag.String("http-endpoint", "", "The TCP network address where the prometheus metrics endpoint will listen (example: `:8080`). The default is empty string, which means metrics endpoint is disabled.")
4141
metricsPath = flag.String("metrics-path", "/metrics", "The HTTP path where prometheus metrics will be exposed. Default is `/metrics`.")
42-
extraVolumeLabels map[string]string
42+
extraVolumeLabelsStr = flag.String("extra-labels", "", "Extra labels to attach to each PD created. It is a comma separated list of key value pairs like '<key1>=<value1>,<key2>=<value2>'. See https://cloud.google.com/compute/docs/labeling-resources for details")
4343
version string
4444
)
4545

@@ -58,7 +58,6 @@ func init() {
5858
}
5959

6060
func main() {
61-
flag.Var(cliflag.NewMapStringString(&extraVolumeLabels), "extra-labels", "Extra labels to attach to each PD created. It is a comma separated list of key value pairs like '<key1>=<value1>,<key2>=<value2>'")
6261
flag.Parse()
6362
rand.Seed(time.Now().UnixNano())
6463
handle()
@@ -79,6 +78,14 @@ func handle() {
7978
mm.EmitGKEComponentVersion()
8079
}
8180

81+
if len(*extraVolumeLabelsStr) > 0 && !*runControllerService {
82+
klog.Fatalf("Extra volume labels provided but not running controller")
83+
}
84+
extraVolumeLabels, err := common.ConvertLabelsStringToMap(*extraVolumeLabelsStr)
85+
if err != nil {
86+
klog.Fatalf("Bad extra volume labels: %v", err)
87+
}
88+
8289
gceDriver := driver.GetGCEDriver()
8390

8491
//Initialize GCE Driver
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# for gce-pd-driver
2+
- op: add
3+
path: /spec/template/spec/containers/4/args/2
4+
value: "--extra-labels=csi=gce-pd-driver # Used for testing, not to be merged to stable"

deploy/kubernetes/overlays/prow-gke-release-staging-rc-master/kustomization.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,13 @@ namespace:
44
gce-pd-csi-driver
55
resources:
66
- ../stable-master
7+
patchesJson6902:
8+
# disk_labels.yaml is used for testing and should not be merged to stable.
9+
- target:
10+
group: apps
11+
version: v1
12+
kind: Deployment
13+
name: csi-gce-pd-controller
14+
path: disk_labels.yaml
715
transformers:
816
- ../../images/prow-gke-release-staging-rc-master
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Example StorageClass that adds labels to the GCP PD.
2+
# This requires v1.2.1 or higher.
3+
apiVersion: storage.k8s.io/v1
4+
kind: StorageClass
5+
metadata:
6+
name: csi-gce-pd
7+
provisioner: pd.csi.storage.gke.io
8+
parameters:
9+
labels: key1=value1,key2=value2
10+
volumeBindingMode: WaitForFirstConsumer

pkg/common/parameters.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
ParameterKeyType = "type"
2626
ParameterKeyReplicationType = "replication-type"
2727
ParameterKeyDiskEncryptionKmsKey = "disk-encryption-kms-key"
28+
ParameterKeyLabels = "labels"
2829

2930
replicationTypeNone = "none"
3031

@@ -33,7 +34,7 @@ const (
3334
ParameterKeyPVCNamespace = "csi.storage.k8s.io/pvc/namespace"
3435
ParameterKeyPVName = "csi.storage.k8s.io/pv/name"
3536

36-
// Keys for tags to attach to the provisioned disk.
37+
// Keys for tags to put in the provisioned disk description.
3738
tagKeyCreatedForClaimNamespace = "kubernetes.io/created-for/pvc/namespace"
3839
tagKeyCreatedForClaimName = "kubernetes.io/created-for/pvc/name"
3940
tagKeyCreatedForVolumeName = "kubernetes.io/created-for/pv/name"
@@ -60,7 +61,9 @@ type DiskParameters struct {
6061
}
6162

6263
// ExtractAndDefaultParameters will take the relevant parameters from a map and
63-
// put them into a well defined struct making sure to default unspecified fields
64+
// put them into a well defined struct making sure to default unspecified fields.
65+
// extraVolumeLabels are added as labels; if there are also labels specified in
66+
// parameters, any matching extraVolumeLabels will be overridden.
6467
func ExtractAndDefaultParameters(parameters map[string]string, driverName string, extraVolumeLabels map[string]string) (DiskParameters, error) {
6568
p := DiskParameters{
6669
DiskType: "pd-standard", // Default
@@ -70,6 +73,10 @@ func ExtractAndDefaultParameters(parameters map[string]string, driverName string
7073
Labels: make(map[string]string), // Default
7174
}
7275

76+
for k, v := range extraVolumeLabels {
77+
p.Labels[k] = v
78+
}
79+
7380
for k, v := range parameters {
7481
if k == "csiProvisionerSecretName" || k == "csiProvisionerSecretNamespace" {
7582
// These are hardcoded secrets keys required to function but not needed by GCE PD
@@ -93,15 +100,21 @@ func ExtractAndDefaultParameters(parameters map[string]string, driverName string
93100
p.Tags[tagKeyCreatedForClaimNamespace] = v
94101
case ParameterKeyPVName:
95102
p.Tags[tagKeyCreatedForVolumeName] = v
103+
case ParameterKeyLabels:
104+
paramLabels, err := ConvertLabelsStringToMap(v)
105+
if err != nil {
106+
return p, fmt.Errorf("parameters contain invalid labels parameter: %w", err)
107+
}
108+
// Override any existing labels with those from this parameter.
109+
for labelKey, labelValue := range paramLabels {
110+
p.Labels[labelKey] = labelValue
111+
}
96112
default:
97113
return p, fmt.Errorf("parameters contains invalid option %q", k)
98114
}
99115
}
100116
if len(p.Tags) > 0 {
101117
p.Tags[tagKeyCreatedBy] = driverName
102118
}
103-
for k, v := range extraVolumeLabels {
104-
p.Labels[k] = v
105-
}
106119
return p, nil
107120
}

pkg/common/parameters_test.go

+44-17
Original file line numberDiff line numberDiff line change
@@ -37,50 +37,53 @@ func TestExtractAndDefaultParameters(t *testing.T) {
3737
DiskType: "pd-standard",
3838
ReplicationType: "none",
3939
DiskEncryptionKMSKey: "",
40-
Tags: make(map[string]string),
41-
Labels: make(map[string]string),
40+
Tags: map[string]string{},
41+
Labels: map[string]string{},
4242
},
4343
},
4444
{
4545
name: "specified empties",
46-
parameters: map[string]string{ParameterKeyType: "", ParameterKeyReplicationType: "", ParameterKeyDiskEncryptionKmsKey: ""},
46+
parameters: map[string]string{ParameterKeyType: "", ParameterKeyReplicationType: "", ParameterKeyDiskEncryptionKmsKey: "", ParameterKeyLabels: ""},
4747
labels: map[string]string{},
4848
expectParams: DiskParameters{
4949
DiskType: "pd-standard",
5050
ReplicationType: "none",
5151
DiskEncryptionKMSKey: "",
52-
Tags: make(map[string]string),
53-
Labels: make(map[string]string),
52+
Tags: map[string]string{},
53+
Labels: map[string]string{},
5454
},
5555
},
5656
{
5757
name: "random keys",
58-
parameters: map[string]string{ParameterKeyType: "", "foo": "", ParameterKeyDiskEncryptionKmsKey: ""},
58+
parameters: map[string]string{ParameterKeyType: "", "foo": "", ParameterKeyDiskEncryptionKmsKey: "", ParameterKeyLabels: ""},
5959
labels: map[string]string{},
6060
expectErr: true,
6161
},
6262
{
63-
name: "real values",
64-
parameters: map[string]string{ParameterKeyType: "pd-ssd", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key"},
63+
name: "values from parameters",
64+
parameters: map[string]string{ParameterKeyType: "pd-ssd", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key", ParameterKeyLabels: "key1=value1,key2=value2"},
6565
labels: map[string]string{},
6666
expectParams: DiskParameters{
6767
DiskType: "pd-ssd",
6868
ReplicationType: "regional-pd",
6969
DiskEncryptionKMSKey: "foo/key",
70-
Tags: make(map[string]string),
71-
Labels: make(map[string]string),
70+
Tags: map[string]string{},
71+
Labels: map[string]string{
72+
"key1": "value1",
73+
"key2": "value2",
74+
},
7275
},
7376
},
7477
{
75-
name: "real values, checking balanced pd",
78+
name: "values from parameters, checking balanced pd",
7679
parameters: map[string]string{ParameterKeyType: "pd-balanced", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key"},
7780
labels: map[string]string{},
7881
expectParams: DiskParameters{
7982
DiskType: "pd-balanced",
8083
ReplicationType: "regional-pd",
8184
DiskEncryptionKMSKey: "foo/key",
82-
Tags: make(map[string]string),
83-
Labels: make(map[string]string),
85+
Tags: map[string]string{},
86+
Labels: map[string]string{},
8487
},
8588
},
8689
{
@@ -91,8 +94,8 @@ func TestExtractAndDefaultParameters(t *testing.T) {
9194
DiskType: "pd-standard",
9295
ReplicationType: "none",
9396
DiskEncryptionKMSKey: "foo/key",
94-
Tags: make(map[string]string),
95-
Labels: make(map[string]string),
97+
Tags: map[string]string{},
98+
Labels: map[string]string{},
9699
},
97100
},
98101
{
@@ -104,11 +107,11 @@ func TestExtractAndDefaultParameters(t *testing.T) {
104107
ReplicationType: "none",
105108
DiskEncryptionKMSKey: "",
106109
Tags: map[string]string{tagKeyCreatedForClaimName: "testPVCName", tagKeyCreatedForClaimNamespace: "testPVCNamespace", tagKeyCreatedForVolumeName: "testPVName", tagKeyCreatedBy: "testDriver"},
107-
Labels: make(map[string]string),
110+
Labels: map[string]string{},
108111
},
109112
},
110113
{
111-
name: "labels",
114+
name: "extra labels",
112115
parameters: map[string]string{},
113116
labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
114117
expectParams: DiskParameters{
@@ -119,6 +122,30 @@ func TestExtractAndDefaultParameters(t *testing.T) {
119122
Labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
120123
},
121124
},
125+
{
126+
name: "parameter and extra labels",
127+
parameters: map[string]string{ParameterKeyLabels: "key1=value1,key2=value2"},
128+
labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
129+
expectParams: DiskParameters{
130+
DiskType: "pd-standard",
131+
ReplicationType: "none",
132+
DiskEncryptionKMSKey: "",
133+
Tags: map[string]string{},
134+
Labels: map[string]string{"key1": "value1", "key2": "value2", "label-1": "label-value-1", "label-2": "label-value-2"},
135+
},
136+
},
137+
{
138+
name: "parameter and extra labels, overlapping",
139+
parameters: map[string]string{ParameterKeyLabels: "label-1=value-a,key1=value1"},
140+
labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
141+
expectParams: DiskParameters{
142+
DiskType: "pd-standard",
143+
ReplicationType: "none",
144+
DiskEncryptionKMSKey: "",
145+
Tags: map[string]string{},
146+
Labels: map[string]string{"key1": "value1", "label-1": "value-a", "label-2": "label-value-2"},
147+
},
148+
},
122149
}
123150

124151
for _, tc := range tests {

pkg/common/utils.go

+59
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package common
1818

1919
import (
2020
"fmt"
21+
"regexp"
2122
"strings"
2223

2324
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
@@ -156,3 +157,61 @@ func CreateNodeID(project, zone, name string) string {
156157
func CreateZonalVolumeID(project, zone, name string) string {
157158
return fmt.Sprintf(volIDZonalFmt, project, zone, name)
158159
}
160+
161+
// ConvertLabelsStringToMap converts the labels from string to map
162+
// example: "key1=value1,key2=value2" gets converted into {"key1": "value1", "key2": "value2"}
163+
// See https://cloud.google.com/compute/docs/labeling-resources#label_format for details.
164+
func ConvertLabelsStringToMap(labels string) (map[string]string, error) {
165+
const labelsDelimiter = ","
166+
const labelsKeyValueDelimiter = "="
167+
168+
labelsMap := make(map[string]string)
169+
if labels == "" {
170+
return labelsMap, nil
171+
}
172+
173+
regexKey, _ := regexp.Compile(`^\p{Ll}[\p{Ll}0-9_-]{0,62}$`)
174+
checkLabelKeyFn := func(key string) error {
175+
if !regexKey.MatchString(key) {
176+
return fmt.Errorf("label value %q is invalid (should start with lowercase letter / lowercase letter, digit, _ and - chars are allowed / 1-63 characters", key)
177+
}
178+
return nil
179+
}
180+
181+
regexValue, _ := regexp.Compile(`^[\p{Ll}0-9_-]{0,63}$`)
182+
checkLabelValueFn := func(value string) error {
183+
if !regexValue.MatchString(value) {
184+
return fmt.Errorf("label value %q is invalid (lowercase letter, digit, _ and - chars are allowed / 0-63 characters", value)
185+
}
186+
187+
return nil
188+
}
189+
190+
keyValueStrings := strings.Split(labels, labelsDelimiter)
191+
for _, keyValue := range keyValueStrings {
192+
keyValue := strings.Split(keyValue, labelsKeyValueDelimiter)
193+
194+
if len(keyValue) != 2 {
195+
return nil, fmt.Errorf("labels %q are invalid, correct format: 'key1=value1,key2=value2'", labels)
196+
}
197+
198+
key := strings.TrimSpace(keyValue[0])
199+
if err := checkLabelKeyFn(key); err != nil {
200+
return nil, err
201+
}
202+
203+
value := strings.TrimSpace(keyValue[1])
204+
if err := checkLabelValueFn(value); err != nil {
205+
return nil, err
206+
}
207+
208+
labelsMap[key] = value
209+
}
210+
211+
const maxNumberOfLabels = 64
212+
if len(labelsMap) > maxNumberOfLabels {
213+
return nil, fmt.Errorf("more than %d labels is not allowed, given: %d", maxNumberOfLabels, len(labelsMap))
214+
}
215+
216+
return labelsMap, nil
217+
}

0 commit comments

Comments
 (0)