The `scheduler-k3s` metadata commands were all per-key, so matching an app's stored annotations, labels or node sysctls to a declared set meant reading the report, computing the difference in both directions and issuing one command per added, changed and removed key, with a failure partway through leaving a mixture of the two sets. A `--replace` flag on `annotations:set`, `labels:set`, `node-sysctls:set` and `autoscaling-auth:set` now writes the declared map in a single call, taking trailing `key=value` pairs everywhere except `autoscaling-auth:set`, which keeps its repeatable `--metadata` flag. Every pair is parsed before anything is written, so a rejected pair leaves the stored map untouched, and a pair ending in `=` stores an empty value, which the per-key form cannot express. An empty pair list is rejected rather than treated as a request to remove everything, so a generated list that expands to nothing cannot silently drop an app's metadata; `annotations:clear`, `labels:clear` and `node-sysctls:clear` handle that case, taking the same optional filters as their report commands. The `--resource-type` flag and the app name are now validated on the set commands as well, where a typo previously exited zero and wrote a property that no report and no deploy ever read.
189 lines
5.2 KiB
Go
189 lines
5.2 KiB
Go
package scheduler_k3s
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseMetadataPairs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pairs []string
|
|
want map[string]string
|
|
}{
|
|
{
|
|
name: "empty list",
|
|
pairs: []string{},
|
|
want: map[string]string{},
|
|
},
|
|
{
|
|
name: "multiple pairs",
|
|
pairs: []string{"foo=bar", "baz=qux"},
|
|
want: map[string]string{"foo": "bar", "baz": "qux"},
|
|
},
|
|
{
|
|
name: "trailing equals stores an empty value",
|
|
pairs: []string{"foo="},
|
|
want: map[string]string{"foo": ""},
|
|
},
|
|
{
|
|
name: "value containing equals is kept whole",
|
|
pairs: []string{"foo=a=b"},
|
|
want: map[string]string{"foo": "a=b"},
|
|
},
|
|
{
|
|
name: "kubernetes style key",
|
|
pairs: []string{"prometheus.io/scrape=true"},
|
|
want: map[string]string{"prometheus.io/scrape": "true"},
|
|
},
|
|
{
|
|
name: "multi-line value",
|
|
pairs: []string{"foo=line one\nline two"},
|
|
want: map[string]string{"foo": "line one\nline two"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
got, err := parseMetadataPairs(test.pairs)
|
|
if err != nil {
|
|
t.Fatalf("parseMetadataPairs(%q) returned error: %s", test.pairs, err)
|
|
}
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("parseMetadataPairs(%q) = %v, want %v", test.pairs, got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseMetadataPairsErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pairs []string
|
|
want string
|
|
}{
|
|
{
|
|
name: "missing equals",
|
|
pairs: []string{"foo"},
|
|
want: "Invalid key=value pair: foo",
|
|
},
|
|
{
|
|
name: "empty key",
|
|
pairs: []string{"=bar"},
|
|
want: "Invalid key=value pair: =bar",
|
|
},
|
|
{
|
|
name: "empty pair",
|
|
pairs: []string{""},
|
|
want: "Invalid key=value pair: ",
|
|
},
|
|
{
|
|
name: "duplicate key",
|
|
pairs: []string{"foo=bar", "foo=qux"},
|
|
want: "Duplicate key specified: foo",
|
|
},
|
|
{
|
|
name: "duplicate key with identical value",
|
|
pairs: []string{"foo=bar", "foo=bar"},
|
|
want: "Duplicate key specified: foo",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
got, err := parseMetadataPairs(test.pairs)
|
|
if err == nil {
|
|
t.Fatalf("parseMetadataPairs(%q) = %v, want error", test.pairs, got)
|
|
}
|
|
if err.Error() != test.want {
|
|
t.Errorf("parseMetadataPairs(%q) error = %q, want %q", test.pairs, err.Error(), test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateResourceType(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
field metadataField
|
|
resourceType string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid annotation resource type",
|
|
field: annotationsField,
|
|
resourceType: "deployment",
|
|
},
|
|
{
|
|
name: "valid label resource type",
|
|
field: labelsField,
|
|
resourceType: "deployment",
|
|
},
|
|
{
|
|
// An empty resource type is an absent filter for the clear commands.
|
|
// The set commands reject it separately.
|
|
name: "empty resource type passes",
|
|
field: annotationsField,
|
|
resourceType: "",
|
|
},
|
|
{
|
|
name: "unknown resource type",
|
|
field: annotationsField,
|
|
resourceType: "deploymnet",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
// Keda resources do not flow user labels through, so they are
|
|
// annotation-only resource types.
|
|
name: "keda resource type is valid for annotations",
|
|
field: annotationsField,
|
|
resourceType: "keda_scaled_object",
|
|
},
|
|
{
|
|
name: "keda resource type is invalid for labels",
|
|
field: labelsField,
|
|
resourceType: "keda_scaled_object",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := validateResourceType(test.field, test.resourceType)
|
|
if test.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("validateResourceType(%s, %q) = nil, want error", test.field.Name, test.resourceType)
|
|
}
|
|
if !strings.Contains(err.Error(), "Invalid resource-type specified, valid resource types include: ") {
|
|
t.Errorf("validateResourceType(%s, %q) error = %q, want the valid type list", test.field.Name, test.resourceType, err.Error())
|
|
}
|
|
for _, valid := range test.field.ResourceTypes {
|
|
if !strings.Contains(err.Error(), valid) {
|
|
t.Errorf("validateResourceType(%s, %q) error = %q, missing valid type %s", test.field.Name, test.resourceType, err.Error(), valid)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("validateResourceType(%s, %q) returned error: %s", test.field.Name, test.resourceType, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMetadataPropertyName(t *testing.T) {
|
|
// Annotations are stored unprefixed and labels under a labels. prefix, which is
|
|
// what getAnnotation and getLabel already build.
|
|
if got := metadataPropertyName(annotationsField, "web", "deployment"); got != "web.deployment" {
|
|
t.Errorf("metadataPropertyName(annotations, web, deployment) = %q, want web.deployment", got)
|
|
}
|
|
if got := metadataPropertyName(labelsField, "web", "deployment"); got != "labels.web.deployment" {
|
|
t.Errorf("metadataPropertyName(labels, web, deployment) = %q, want labels.web.deployment", got)
|
|
}
|
|
if got := metadataPropertyName(annotationsField, GlobalProcessType, "pod"); got != "--global.pod" {
|
|
t.Errorf("metadataPropertyName(annotations, --global, pod) = %q, want --global.pod", got)
|
|
}
|
|
}
|