Skip to content
This repository was archived by the owner on Jun 23, 2020. It is now read-only.

Commit bc792a2

Browse files
committed
Fixes #47 - support default and custom defined/freeform tags
1 parent 80e7828 commit bc792a2

File tree

2 files changed

+177
-2
lines changed

2 files changed

+177
-2
lines changed

pkg/provisioner/block/block.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ import (
3838
)
3939

4040
const (
41-
ociVolumeID = "ociVolumeID"
42-
ociVolumeBackupID = "volume.beta.kubernetes.io/oci-volume-source"
41+
ociVolumeID = "ociVolumeID"
42+
ociVolumeBackupID = "volume.beta.kubernetes.io/oci-volume-source"
43+
ociTagAnnotation = "oraclecloud.com/additional-tags"
44+
45+
defaultTagsEnvVar = "OCI_DEFAULT_TAGS"
4346
volumePrefixEnvVarName = "OCI_VOLUME_NAME_PREFIX"
4447
fsType = "fsType"
4548
)
@@ -113,6 +116,14 @@ func (block *blockProvisioner) Provision(options controller.VolumeOptions, ad *i
113116
SizeInMBs: common.Int(volSizeMB),
114117
}
115118

119+
definedTags, freeformTags, err := getTags(options.PVC.Annotations)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
volumeDetails.DefinedTags = definedTags
125+
volumeDetails.FreeformTags = freeformTags
126+
116127
if value, ok := options.PVC.Annotations[ociVolumeBackupID]; ok {
117128
glog.Infof("Creating volume from backup ID %s", value)
118129
volumeDetails.SourceDetails = &core.VolumeSourceFromVolumeBackupDetails{Id: &value}
@@ -172,6 +183,72 @@ func (block *blockProvisioner) Provision(options controller.VolumeOptions, ad *i
172183
return pv, nil
173184
}
174185

186+
func getTags(annotations map[string]string) (defined map[string]map[string]interface{}, freeform map[string]string, err error) {
187+
defaultDefinedTags, defaultFreeformTags, err := parseTags(os.Getenv(defaultTagsEnvVar))
188+
if err != nil {
189+
return nil, nil, err
190+
}
191+
192+
definedTags, freeformTags, err := parseTags(annotations[ociTagAnnotation])
193+
if err != nil {
194+
return nil, nil, err
195+
}
196+
197+
// merge annotation tags with default tags
198+
for namespace, tags := range definedTags {
199+
if _, ok := defaultDefinedTags[namespace]; !ok {
200+
defaultDefinedTags[namespace] = map[string]interface{}{}
201+
}
202+
203+
for tag, value := range tags {
204+
defaultDefinedTags[namespace][tag] = value
205+
}
206+
}
207+
208+
for tag, value := range freeformTags {
209+
defaultFreeformTags[tag] = value
210+
}
211+
212+
return defaultDefinedTags, defaultFreeformTags, nil
213+
}
214+
215+
func parseTags(tagStr string) (defined map[string]map[string]interface{}, freeform map[string]string, err error) {
216+
217+
defined = map[string]map[string]interface{}{}
218+
freeform = map[string]string{}
219+
220+
if tagStr == "" {
221+
return
222+
}
223+
224+
for _, tag := range strings.Split(tagStr, ",") {
225+
parts := strings.Split(tag, "=")
226+
if len(parts) != 2 {
227+
return nil, nil, fmt.Errorf("tag format must follow (<namespace>.)<tagkey>=<value>: %q", tag)
228+
}
229+
230+
key, value := parts[0], parts[1]
231+
232+
keyParts := strings.Split(key, ".")
233+
if len(keyParts) == 1 {
234+
freeform[key] = value
235+
} else if len(keyParts) == 2 {
236+
namespace, key := keyParts[0], keyParts[1]
237+
namespaceTags, ok := defined[namespace]
238+
if !ok {
239+
namespaceTags = map[string]interface{}{}
240+
defined[namespace] = namespaceTags
241+
}
242+
243+
namespaceTags[key] = value
244+
} else {
245+
return nil, nil, fmt.Errorf("tag format must follow (<namespace>.)<tagkey>=<value>: %q", tag)
246+
}
247+
}
248+
249+
return
250+
}
251+
175252
// Delete destroys a OCI volume created by Provision
176253
func (block *blockProvisioner) Delete(volume *v1.PersistentVolume) error {
177254
volID, ok := volume.Annotations[ociVolumeID]

pkg/provisioner/block/block_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package block
1717
import (
1818
"context"
1919
"fmt"
20+
"os"
21+
"reflect"
2022
"testing"
2123
"time"
2224

@@ -181,6 +183,102 @@ func TestVolumeRoundingLogic(t *testing.T) {
181183
}
182184
}
183185

186+
func TestGetTags(t *testing.T) {
187+
testCases := map[string]struct {
188+
setup func()
189+
annotations map[string]string
190+
expectedDefinedTags map[string]map[string]interface{}
191+
expectedFreeformTags map[string]string
192+
}{
193+
"no default or annotation tags": {
194+
setup: func() {
195+
os.Setenv(defaultTagsEnvVar, "")
196+
},
197+
annotations: map[string]string{},
198+
expectedDefinedTags: map[string]map[string]interface{}{},
199+
expectedFreeformTags: map[string]string{},
200+
},
201+
"valid tags only default": {
202+
setup: func() {
203+
os.Setenv(defaultTagsEnvVar, "defaultnamespace.default=test,default=test")
204+
},
205+
annotations: map[string]string{},
206+
expectedDefinedTags: map[string]map[string]interface{}{
207+
"defaultnamespace": map[string]interface{}{
208+
"default": "test",
209+
},
210+
},
211+
expectedFreeformTags: map[string]string{
212+
"default": "test",
213+
},
214+
},
215+
"valid tags with default": {
216+
setup: func() {
217+
os.Setenv(defaultTagsEnvVar, "namespace1.default=test,default=test")
218+
},
219+
annotations: map[string]string{
220+
ociTagAnnotation: "namespace1.test=foo,namespace2.test=bar,bar=baz,namespace1.test2=bar,foo=bar",
221+
},
222+
expectedDefinedTags: map[string]map[string]interface{}{
223+
"namespace1": map[string]interface{}{
224+
"test": "foo",
225+
"test2": "bar",
226+
"default": "test",
227+
},
228+
"namespace2": map[string]interface{}{
229+
"test": "bar",
230+
},
231+
},
232+
expectedFreeformTags: map[string]string{
233+
"bar": "baz",
234+
"foo": "bar",
235+
"default": "test",
236+
},
237+
},
238+
"override defaults with annotation": {
239+
setup: func() {
240+
os.Setenv(defaultTagsEnvVar, "namespace1.default=test,default=test")
241+
},
242+
annotations: map[string]string{
243+
ociTagAnnotation: "namespace1.default=foo,default=bar",
244+
},
245+
expectedDefinedTags: map[string]map[string]interface{}{
246+
"namespace1": map[string]interface{}{
247+
"default": "foo",
248+
},
249+
},
250+
expectedFreeformTags: map[string]string{
251+
"default": "bar",
252+
},
253+
},
254+
}
255+
256+
for name, tc := range testCases {
257+
t.Run(name, func(t *testing.T) {
258+
defer func() {
259+
os.Setenv(defaultTagsEnvVar, "")
260+
}()
261+
262+
if tc.setup != nil {
263+
tc.setup()
264+
}
265+
266+
defined, freeform, err := getTags(tc.annotations)
267+
if err != nil {
268+
t.Fatal(err)
269+
}
270+
271+
if !reflect.DeepEqual(defined, tc.expectedDefinedTags) {
272+
t.Errorf("defined tags not equal: got %v ; wanted %v", defined, tc.expectedDefinedTags)
273+
}
274+
if !reflect.DeepEqual(freeform, tc.expectedFreeformTags) {
275+
t.Errorf("freeform tags not equal: got %v ; wanted %v", freeform, tc.expectedFreeformTags)
276+
}
277+
})
278+
279+
}
280+
}
281+
184282
func createPVC(size string) *v1.PersistentVolumeClaim {
185283
return &v1.PersistentVolumeClaim{
186284
ObjectMeta: metav1.ObjectMeta{},

0 commit comments

Comments
 (0)