-
Notifications
You must be signed in to change notification settings - Fork 203
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add namespace, label, and annotation support to asoctl
Fixes #3865
- Loading branch information
Showing
6 changed files
with
325 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
*/ | ||
|
||
package annotations | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
type Annotation struct { | ||
Key string | ||
Value string | ||
} | ||
|
||
// Parse parses an annotation. Amazingly there doesn't seem to be a function in client-go or similar that does this | ||
func Parse(s string) (Annotation, error) { | ||
split := strings.Split(s, "=") | ||
if len(split) != 2 { | ||
return Annotation{}, errors.Errorf("%s must have two parts separated by '='", s) | ||
} | ||
|
||
// see https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set | ||
key := split[0] | ||
value := split[1] | ||
|
||
if len(key) == 0 { | ||
return Annotation{}, errors.New("key can't be length 0") | ||
} | ||
|
||
keySplit := strings.Split(key, "/") | ||
if len(keySplit) > 2 { | ||
return Annotation{}, errors.Errorf("key %s must contain only a single '/'", key) | ||
} | ||
|
||
var name string | ||
var prefix string | ||
if len(keySplit) == 1 { | ||
name = key | ||
} else { | ||
// Len == 2 | ||
prefix = keySplit[0] | ||
name = keySplit[1] | ||
|
||
} | ||
|
||
if len(key) > 63 { | ||
return Annotation{}, errors.Errorf("name %s must be 63 characters or less", name) | ||
} | ||
|
||
if len(prefix) > 253 { | ||
return Annotation{}, errors.Errorf("prefix %s must be 253 characters or less", prefix) | ||
} | ||
|
||
// TODO: Could enforce character restrictions too but not bothering for now | ||
|
||
return Annotation{ | ||
Key: key, | ||
Value: value, | ||
}, nil | ||
} | ||
|
||
// ParseAll parses all the given annotations and returns a collection of parsed annotations | ||
func ParseAll(annotations []string) ([]Annotation, error) { | ||
result := make([]Annotation, 0, len(annotations)) | ||
|
||
for _, annotation := range annotations { | ||
parsed, err := Parse(annotation) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "failed parsing %s", annotation) | ||
} | ||
result = append(result, parsed) | ||
} | ||
|
||
return result, nil | ||
} |
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,52 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
*/ | ||
|
||
package annotations_test | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/gomega" | ||
|
||
"github.com/Azure/azure-service-operator/v2/internal/util/annotations" | ||
) | ||
|
||
func TestParse(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
annotation string | ||
wantKey string | ||
wantValue string | ||
wantErr bool | ||
}{ | ||
{"example.com/annotation=value", "example.com/annotation", "value", false}, | ||
{"example.com/annotation=", "example.com/annotation", "", false}, | ||
{"=value", "", "", true}, | ||
{"example.com/annotation", "", "", true}, | ||
{"example.com/test/annotation", "", "", true}, | ||
{"thisisaverylongannotationname_solonginfactthatitisgoingtocauseanerror", "", "", true}, | ||
{"", "", "", true}, | ||
} | ||
|
||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.annotation, func(t *testing.T) { | ||
t.Parallel() | ||
g := NewGomegaWithT(t) | ||
|
||
actual, err := annotations.Parse(tt.annotation) | ||
|
||
if tt.wantErr { | ||
g.Expect(err).To(HaveOccurred()) | ||
} else { | ||
g.Expect(err).ToNot(HaveOccurred()) | ||
} | ||
|
||
g.Expect(actual.Key).To(Equal(tt.wantKey)) | ||
g.Expect(actual.Value).To(Equal(tt.wantValue)) | ||
}) | ||
} | ||
} |
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,48 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
*/ | ||
|
||
package labels | ||
|
||
import ( | ||
"github.com/pkg/errors" | ||
|
||
"github.com/Azure/azure-service-operator/v2/internal/annotations" | ||
) | ||
|
||
type Label struct { | ||
Key string | ||
Value string | ||
} | ||
|
||
// Parse parses a label. Amazingly there doesn't seem to be a function in client-go or similar that does this. | ||
// There does exist an apimachinery labels.Parse but it parses label selectors not labels themselves. | ||
func Parse(s string) (Label, error) { | ||
// Currently the label restrictions are exactly the same as annotations, | ||
// so we can just re-use annotation parse here | ||
annotation, err := annotations.Parse(s) | ||
if err != nil { | ||
return Label{}, err | ||
} | ||
|
||
return Label{ | ||
Key: annotation.Key, | ||
Value: annotation.Value, | ||
}, nil | ||
} | ||
|
||
// ParseAll parses all the given labels and returns a collection of parsed labels | ||
func ParseAll(labels []string) ([]Label, error) { | ||
result := make([]Label, 0, len(labels)) | ||
|
||
for _, label := range labels { | ||
parsed, err := Parse(label) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "failed parsing %s", label) | ||
} | ||
result = append(result, parsed) | ||
} | ||
|
||
return result, nil | ||
} |
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,52 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
*/ | ||
|
||
package labels_test | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/gomega" | ||
|
||
"github.com/Azure/azure-service-operator/v2/internal/util/labels" | ||
) | ||
|
||
func TestParse(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
label string | ||
wantKey string | ||
wantValue string | ||
wantErr bool | ||
}{ | ||
{"example.com/label=value", "example.com/label", "value", false}, | ||
{"example.com/label=", "example.com/label", "", false}, | ||
{"=value", "", "", true}, | ||
{"example.com/label", "", "", true}, | ||
{"example.com/test/label", "", "", true}, | ||
{"thisisaverylonglabelname_solonginfactthatitisgoingtocauseanerror", "", "", true}, | ||
{"", "", "", true}, | ||
} | ||
|
||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.label, func(t *testing.T) { | ||
t.Parallel() | ||
g := NewGomegaWithT(t) | ||
|
||
actual, err := labels.Parse(tt.label) | ||
|
||
if tt.wantErr { | ||
g.Expect(err).To(HaveOccurred()) | ||
} else { | ||
g.Expect(err).ToNot(HaveOccurred()) | ||
} | ||
|
||
g.Expect(actual.Key).To(Equal(tt.wantKey)) | ||
g.Expect(actual.Value).To(Equal(tt.wantValue)) | ||
}) | ||
} | ||
} |