Skip to content

Commit 65223df

Browse files
jchunkinsankitathomas
authored andcommitted
Expand validation to annotations
- Add validation function for well known case sensitive annotation names - Use new validation function in CSV and OperatorGroups - Add minimal OperatorGroup validator calling new function - Add / update unit tests and test data - update usage readme Signed-off-by: John Hunkins <[email protected]> (upstream api commit: 1ce77c819f6b2672adf424884af076b4de282632)
1 parent cb2c6b4 commit 65223df

File tree

11 files changed

+482
-1
lines changed

11 files changed

+482
-1
lines changed

staging/api/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ You can install the `operator-verify` tool from source using:
1717

1818
To verify your ClusterServiceVersion yaml,
1919

20-
`$ operator-verify verify /path/to/filename.yaml`
20+
`$ operator-verify manifests /path/to/filename.yaml`

staging/api/pkg/operators/v1alpha1/clusterserviceversion_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
ClusterServiceVersionKind = "ClusterServiceVersion"
2323
OperatorGroupNamespaceAnnotationKey = "olm.operatorNamespace"
2424
InstallStrategyNameDeployment = "deployment"
25+
SkipRangeAnnotationKey = "olm.skipRange"
2526
)
2627

2728
// InstallModeType is a supported type of install mode for CSV installation
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
v1 "github.com/operator-framework/api/pkg/operators/v1"
8+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
9+
"github.com/operator-framework/api/pkg/validation/errors"
10+
)
11+
12+
// CaseSensitiveAnnotationKeySet is a set of annotation keys that are case sensitive
13+
// and can be used for validation purposes. The key is always lowercase and the value
14+
// contains the expected case sensitive string. This may not be an exhaustive list.
15+
var CaseSensitiveAnnotationKeySet = map[string]string{
16+
17+
strings.ToLower(v1.OperatorGroupAnnotationKey): v1.OperatorGroupAnnotationKey,
18+
strings.ToLower(v1.OperatorGroupNamespaceAnnotationKey): v1.OperatorGroupNamespaceAnnotationKey,
19+
strings.ToLower(v1.OperatorGroupTargetsAnnotationKey): v1.OperatorGroupTargetsAnnotationKey,
20+
strings.ToLower(v1.OperatorGroupProvidedAPIsAnnotationKey): v1.OperatorGroupProvidedAPIsAnnotationKey,
21+
strings.ToLower(v1alpha1.SkipRangeAnnotationKey): v1alpha1.SkipRangeAnnotationKey,
22+
}
23+
24+
/*
25+
ValidateAnnotationNames will check annotation keys to ensure they are using
26+
proper case. Uses CaseSensitiveAnnotationKeySet as a source for keys
27+
which are known to be case sensitive. This function can be used anywhere
28+
annotations need to be checked for case sensitivity.
29+
30+
Arguments
31+
32+
• annotations: annotations map usually obtained from ObjectMeta.GetAnnotations()
33+
34+
• value: is the field or file that caused an error or warning
35+
36+
Returns
37+
38+
• errs: Any errors that may have been detected with the annotation keys provided
39+
*/
40+
func ValidateAnnotationNames(annotations map[string]string, value interface{}) (errs []errors.Error) {
41+
// for every annotation provided
42+
for annotationKey := range annotations {
43+
// check the case sensitive key set for a matching lowercase annotation
44+
if knownCaseSensitiveKey, ok := CaseSensitiveAnnotationKeySet[strings.ToLower(annotationKey)]; ok {
45+
// we have a case-insensitive match... now check to see if the case is really correct
46+
if annotationKey != knownCaseSensitiveKey {
47+
// annotation key supplied is invalid due to bad case.
48+
errs = append(errs, errors.ErrFailedValidation(fmt.Sprintf("provided annotation %s uses wrong case and should be %s instead", annotationKey, knownCaseSensitiveKey), value))
49+
}
50+
}
51+
}
52+
return errs
53+
}

staging/api/pkg/validation/internal/csv.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ func validateCSV(csv *v1alpha1.ClusterServiceVersion) errors.ManifestResult {
4646
result.Add(validateInstallModes(csv)...)
4747
// check missing optional/mandatory fields.
4848
result.Add(checkFields(*csv)...)
49+
// validate case sensitive annotation names
50+
result.Add(ValidateAnnotationNames(csv.GetAnnotations(), csv.GetName())...)
4951
return result
5052
}
5153

staging/api/pkg/validation/internal/csv_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ func TestValidateCSV(t *testing.T) {
4949
},
5050
filepath.Join("testdata", "incorrect.csv.with.conversion.webhook.yaml"),
5151
},
52+
{
53+
validatorFuncTest{
54+
description: "invalid annotation name for csv",
55+
wantErr: true,
56+
errors: []errors.Error{
57+
errors.ErrFailedValidation("provided annotation olm.skiprange uses wrong case and should be olm.skipRange instead", "etcdoperator.v0.9.0"),
58+
errors.ErrFailedValidation("provided annotation olm.operatorgroup uses wrong case and should be olm.operatorGroup instead", "etcdoperator.v0.9.0"),
59+
errors.ErrFailedValidation("provided annotation olm.operatornamespace uses wrong case and should be olm.operatorNamespace instead", "etcdoperator.v0.9.0"),
60+
},
61+
},
62+
filepath.Join("testdata", "badAnnotationNames.csv.yaml"),
63+
},
5264
}
5365
for _, c := range cases {
5466
b, err := ioutil.ReadFile(c.csvPath)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package internal
2+
3+
import (
4+
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
5+
operatorsv1alpha2 "github.com/operator-framework/api/pkg/operators/v1alpha2"
6+
"github.com/operator-framework/api/pkg/validation/errors"
7+
interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
8+
)
9+
10+
// OperatorGroupValidator is a validator for OperatorGroup
11+
var OperatorGroupValidator interfaces.Validator = interfaces.ValidatorFunc(validateOperatorGroups)
12+
13+
func validateOperatorGroups(objs ...interface{}) (results []errors.ManifestResult) {
14+
for _, obj := range objs {
15+
switch v := obj.(type) {
16+
case *operatorsv1.OperatorGroup:
17+
results = append(results, validateOperatorGroupV1(v))
18+
case *operatorsv1alpha2.OperatorGroup:
19+
results = append(results, validateOperatorGroupV1Alpha2(v))
20+
}
21+
}
22+
return results
23+
}
24+
25+
func validateOperatorGroupV1Alpha2(operatorGroup *operatorsv1alpha2.OperatorGroup) (result errors.ManifestResult) {
26+
// validate case sensitive annotation names
27+
result.Add(ValidateAnnotationNames(operatorGroup.GetAnnotations(), operatorGroup.GetName())...)
28+
return result
29+
}
30+
31+
func validateOperatorGroupV1(operatorGroup *operatorsv1.OperatorGroup) (result errors.ManifestResult) {
32+
// validate case sensitive annotation names
33+
result.Add(ValidateAnnotationNames(operatorGroup.GetAnnotations(), operatorGroup.GetName())...)
34+
return result
35+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package internal
2+
3+
import (
4+
"io/ioutil"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/ghodss/yaml"
9+
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
10+
"github.com/operator-framework/api/pkg/validation/errors"
11+
)
12+
13+
func TestValidateOperatorGroup(t *testing.T) {
14+
cases := []struct {
15+
validatorFuncTest
16+
operatorGroupPath string
17+
}{
18+
{
19+
validatorFuncTest{
20+
description: "successfully validated",
21+
},
22+
filepath.Join("testdata", "correct.og.yaml"),
23+
},
24+
{
25+
validatorFuncTest{
26+
description: "invalid annotation name for operator group",
27+
wantErr: true,
28+
errors: []errors.Error{
29+
errors.ErrFailedValidation("provided annotation olm.providedapis uses wrong case and should be olm.providedAPIs instead", "nginx-hbvsw"),
30+
},
31+
},
32+
filepath.Join("testdata", "badAnnotationNames.og.yaml"),
33+
},
34+
}
35+
for _, c := range cases {
36+
b, err := ioutil.ReadFile(c.operatorGroupPath)
37+
if err != nil {
38+
t.Fatalf("Error reading OperatorGroup path %s: %v", c.operatorGroupPath, err)
39+
}
40+
og := operatorsv1.OperatorGroup{}
41+
if err = yaml.Unmarshal(b, &og); err != nil {
42+
t.Fatalf("Error unmarshalling OperatorGroup at path %s: %v", c.operatorGroupPath, err)
43+
}
44+
result := validateOperatorGroupV1(&og)
45+
c.check(t, result)
46+
}
47+
}

0 commit comments

Comments
 (0)