forked from grafana/tanka
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for overriding environment label
This change adds support for overriding the environment label using fields on the environment as per grafana#824. To achieve this, the NameLabel() function that generates this needed to be lifted to the Environment struct (from Metadata) to ensure it can access the fields it needs. Additionally, its signature now returns an error as there are ways errors could occur during this generation now that should be surfaced neatly to the user.
- Loading branch information
1 parent
a0ac957
commit a9bc0cd
Showing
6 changed files
with
285 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package v1alpha1 | ||
|
||
import ( | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestEnvironmentNameLabel(t *testing.T) { | ||
type testCase struct { | ||
name string | ||
inputEnvironment *Environment | ||
expectedLabelPreHash string | ||
expectError bool | ||
} | ||
|
||
testCases := []testCase{ | ||
{ | ||
name: "Default environment label hash", | ||
inputEnvironment: &Environment{ | ||
Spec: Spec{ | ||
Namespace: "default", | ||
}, | ||
Metadata: Metadata{ | ||
Name: "environments/a-nice-go-test", | ||
Namespace: "main.jsonnet", | ||
}, | ||
}, | ||
expectedLabelPreHash: "environments/a-nice-go-test:main.jsonnet", | ||
}, | ||
{ | ||
name: "Overriden single nested field", | ||
inputEnvironment: &Environment{ | ||
Spec: Spec{ | ||
Namespace: "default", | ||
TankaEnvLabelFromFields: []string{ | ||
".metadata.name", | ||
}, | ||
}, | ||
Metadata: Metadata{ | ||
Name: "environments/another-nice-go-test", | ||
}, | ||
}, | ||
expectedLabelPreHash: "environments/another-nice-go-test", | ||
}, | ||
{ | ||
name: "Overriden multiple nested field", | ||
inputEnvironment: &Environment{ | ||
Spec: Spec{ | ||
Namespace: "default", | ||
TankaEnvLabelFromFields: []string{ | ||
".metadata.name", | ||
".spec.namespace", | ||
}, | ||
}, | ||
Metadata: Metadata{ | ||
Name: "environments/another-nice-go-test", | ||
}, | ||
}, | ||
expectedLabelPreHash: "environments/another-nice-go-test:default", | ||
}, | ||
{ | ||
name: "Override field of map type", | ||
inputEnvironment: &Environment{ | ||
Spec: Spec{ | ||
TankaEnvLabelFromFields: []string{ | ||
".metadata.labels.project", | ||
}, | ||
}, | ||
Metadata: Metadata{ | ||
Name: "environments/another-nice-go-test", | ||
Labels: map[string]string{ | ||
"project": "an-equally-nice-project", | ||
}, | ||
}, | ||
}, | ||
expectedLabelPreHash: "an-equally-nice-project", | ||
}, | ||
{ | ||
name: "Label value not primitive type", | ||
inputEnvironment: &Environment{ | ||
Spec: Spec{ | ||
TankaEnvLabelFromFields: []string{ | ||
".metadata", | ||
}, | ||
}, | ||
Metadata: Metadata{ | ||
Name: "environments/another-nice-go-test", | ||
}, | ||
}, | ||
expectError: true, | ||
}, | ||
{ | ||
name: "Attempted descent past non-object like type", | ||
inputEnvironment: &Environment{ | ||
Spec: Spec{ | ||
TankaEnvLabelFromFields: []string{ | ||
".metadata.name.nonExistent", | ||
}, | ||
}, | ||
Metadata: Metadata{ | ||
Name: "environments/not-an-object", | ||
}, | ||
}, | ||
expectError: true, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
expectedLabelHashParts := sha256.Sum256([]byte(tc.expectedLabelPreHash)) | ||
expectedLabelHashChars := []rune(hex.EncodeToString(expectedLabelHashParts[:])) | ||
expectedLabelHash := string(expectedLabelHashChars[:48]) | ||
actualLabelHash, err := tc.inputEnvironment.NameLabel() | ||
|
||
if tc.expectedLabelPreHash != "" { | ||
assert.Equal(t, expectedLabelHash, actualLabelHash) | ||
} else { | ||
assert.Equal(t, "", actualLabelHash) | ||
} | ||
|
||
if tc.expectError { | ||
assert.Error(t, err) | ||
} else { | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package v1alpha1 | ||
|
||
import ( | ||
"errors" | ||
"reflect" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
func getDeepFieldAsString(obj interface{}, keyPath []string) (string, error) { | ||
if !isSupportedType(obj, []reflect.Kind{reflect.Struct, reflect.Pointer, reflect.Map}) { | ||
return "", errors.New("intermediary objects must be object types") | ||
} | ||
|
||
objValue := reflectValue(obj) | ||
objType := objValue.Type() | ||
|
||
var nextFieldValue reflect.Value | ||
|
||
switch objType.Kind() { | ||
case reflect.Struct, reflect.Pointer: | ||
fieldsCount := objType.NumField() | ||
|
||
for i := 0; i < fieldsCount; i++ { | ||
candidateType := objType.Field(i) | ||
candidateValue := objValue.Field(i) | ||
jsonTag := candidateType.Tag.Get("json") | ||
|
||
if strings.Split(jsonTag, ",")[0] == keyPath[0] { | ||
nextFieldValue = candidateValue | ||
break | ||
} | ||
} | ||
|
||
case reflect.Map: | ||
for _, key := range objValue.MapKeys() { | ||
nextFieldValue = objValue.MapIndex(key) | ||
} | ||
} | ||
|
||
if len(keyPath) == 1 { | ||
return getReflectValueAsString(nextFieldValue) | ||
} | ||
|
||
if nextFieldValue.Type().Kind() == reflect.Pointer { | ||
nextFieldValue = nextFieldValue.Elem() | ||
} | ||
|
||
return getDeepFieldAsString(nextFieldValue.Interface(), keyPath[1:]) | ||
} | ||
|
||
func getReflectValueAsString(val reflect.Value) (string, error) { | ||
switch val.Type().Kind() { | ||
case reflect.String: | ||
return val.String(), nil | ||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
return strconv.FormatInt(val.Int(), 10), nil | ||
case reflect.Float32: | ||
return strconv.FormatFloat(val.Float(), 'f', -1, 32), nil | ||
case reflect.Float64: | ||
return strconv.FormatFloat(val.Float(), 'f', -1, 64), nil | ||
case reflect.Bool: | ||
return strconv.FormatBool(val.Bool()), nil | ||
default: | ||
return "", errors.New("unsupported value type") | ||
} | ||
} | ||
|
||
func reflectValue(obj interface{}) reflect.Value { | ||
var val reflect.Value | ||
|
||
if reflect.TypeOf(obj).Kind() == reflect.Pointer { | ||
val = reflect.ValueOf(obj).Elem() | ||
} else { | ||
val = reflect.ValueOf(obj) | ||
} | ||
|
||
return val | ||
} | ||
|
||
func isSupportedType(obj interface{}, types []reflect.Kind) bool { | ||
for _, t := range types { | ||
if reflect.TypeOf(obj).Kind() == t { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} |