-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: Add support for overriding environment label This change adds support for overriding the environment label using fields on the environment as per #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. * Fix misspellings --------- Co-authored-by: Julien Duchesne <[email protected]>
- Loading branch information
1 parent
5e0eb01
commit b44bdc9
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: "Overridden 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: "Overridden 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 | ||
} |