-
Notifications
You must be signed in to change notification settings - Fork 731
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Anlan Du <adu47249@gmail.com>
- Loading branch information
Showing
3 changed files
with
282 additions
and
66 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package syncutil | ||
|
||
import ( | ||
"encoding/json" | ||
"strings" | ||
|
||
"github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
) | ||
|
||
// syncAnnotationName is the name of the annotation that stores | ||
// GVKS that are required to be synced. | ||
const SyncAnnotationName = "metadata.gatekeeper.sh/requiresSyncData" | ||
|
||
// SyncRequirements contains a list of ANDed requirements, each of which | ||
// contains an expanded set of equivalent (ORed) GVKs. | ||
type SyncRequirements []GVKEquivalenceSet | ||
|
||
// GVKEquivalenceSet is a set of GVKs that a template can use | ||
// interchangeably in its referential policy implementation. | ||
type GVKEquivalenceSet map[schema.GroupVersionKind]struct{} | ||
|
||
// CompactSyncRequirements contains a list of ANDed requirements, each of | ||
type CompactSyncRequirements [][]CompactGVKEquivalenceSet | ||
|
||
// compactGVKEquivalenceSet contains a set of equivalent GVKs, expressed | ||
// in the compact form [groups, versions, kinds] where any combination of | ||
// items from these three fields can be considered a valid equivalent. | ||
// Used solely for unmarshalling. | ||
type CompactGVKEquivalenceSet struct { | ||
Groups []string `json:"groups"` | ||
Versions []string `json:"versions"` | ||
Kinds []string `json:"kinds"` | ||
} | ||
|
||
// ReadSyncRequirements parses the sync requirements from a | ||
// constraint template. | ||
func ReadSyncRequirements(t *templates.ConstraintTemplate) (SyncRequirements, error) { | ||
if t.ObjectMeta.Annotations != nil { | ||
if annotation, exists := t.ObjectMeta.Annotations[SyncAnnotationName]; exists { | ||
annotation = strings.Trim(annotation, "\n\"") | ||
compactSyncRequirements := CompactSyncRequirements{} | ||
decoder := json.NewDecoder(strings.NewReader(annotation)) | ||
decoder.DisallowUnknownFields() | ||
err := decoder.Decode(&compactSyncRequirements) | ||
if err != nil { | ||
return nil, err | ||
} | ||
requirements, err := ExpandCompactRequirements(compactSyncRequirements) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return requirements, nil | ||
} | ||
} | ||
return SyncRequirements{}, nil | ||
} | ||
|
||
// Takes a compactGVKSet and expands and unions it with the set of | ||
// GVKs pointed to by the 'expandedEquivalentSet' argument. | ||
func ExpandCompactEquivalenceSet(compactEquivalenceSet CompactGVKEquivalenceSet) GVKEquivalenceSet { | ||
equivalenceSet := GVKEquivalenceSet{} | ||
for _, group := range compactEquivalenceSet.Groups { | ||
for _, version := range compactEquivalenceSet.Versions { | ||
for _, kind := range compactEquivalenceSet.Kinds { | ||
equivalenceSet[schema.GroupVersionKind{Group: group, Version: version, Kind: kind}] = struct{}{} | ||
} | ||
} | ||
} | ||
return equivalenceSet | ||
} | ||
|
||
// Convert | ||
func ExpandCompactRequirements(compactSyncRequirements CompactSyncRequirements) (SyncRequirements, error) { | ||
syncRequirements := SyncRequirements{} | ||
for _, compactRequirement := range compactSyncRequirements { | ||
requirement := GVKEquivalenceSet{} | ||
for _, compactEquivalenceSet := range compactRequirement { | ||
for equivalentGVK := range ExpandCompactEquivalenceSet(compactEquivalenceSet) { | ||
requirement[equivalentGVK] = struct{}{} | ||
} | ||
} | ||
syncRequirements = append(syncRequirements, requirement) | ||
} | ||
return syncRequirements, 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,196 @@ | ||
package syncutil | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
func TestReadSyncRequirements(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
template *templates.ConstraintTemplate | ||
want SyncRequirements | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "test with basic valid annotation", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "\n\"[[{\"groups\": [\"group1\"], \"versions\": [\"version1\"], \"kinds\": [\"kind1\"]}]]\"", | ||
}, | ||
}, | ||
}, | ||
want: SyncRequirements{ | ||
{ | ||
{ | ||
Group: "group1", | ||
Version: "version1", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "test with valid annotation with multiple groups, versions, and kinds", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "\n\"[[{\"groups\": [\"group1\", \"group2\"], \"versions\": [\"version1\", \"version2\"], \"kinds\": [\"kind1\", \"kind2\"]}]]\"", | ||
}, | ||
}, | ||
}, | ||
want: SyncRequirements{ | ||
{ | ||
{ | ||
Group: "group1", | ||
Version: "version1", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
{ | ||
Group: "group1", | ||
Version: "version1", | ||
Kind: "kind2", | ||
}: struct{}{}, | ||
{ | ||
Group: "group1", | ||
Version: "version2", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
{ | ||
Group: "group1", | ||
Version: "version2", | ||
Kind: "kind2", | ||
}: struct{}{}, | ||
{ | ||
Group: "group2", | ||
Version: "version1", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
{ | ||
Group: "group2", | ||
Version: "version1", | ||
Kind: "kind2", | ||
}: struct{}{}, | ||
{ | ||
Group: "group2", | ||
Version: "version2", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
{ | ||
Group: "group2", | ||
Version: "version2", | ||
Kind: "kind2", | ||
}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "test with valid annotation with multiple equivalence sets", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "\n\"[[{\"groups\": [\"group1\"], \"versions\": [\"version1\"], \"kinds\": [\"kind1\"]}, {\"groups\": [\"group2\"], \"versions\": [\"version2\"], \"kinds\": [\"kind2\"]}]]\"", | ||
}, | ||
}, | ||
}, | ||
want: SyncRequirements{ | ||
{ | ||
{ | ||
Group: "group1", | ||
Version: "version1", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
{ | ||
Group: "group2", | ||
Version: "version2", | ||
Kind: "kind2", | ||
}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "test with valid annotation with multiple requirements", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "\n\"[[{\"groups\": [\"group1\"], \"versions\": [\"version1\"], \"kinds\": [\"kind1\"]}], [{\"groups\": [\"group2\"], \"versions\": [\"version2\"], \"kinds\": [\"kind2\"]}]]\"", | ||
}, | ||
}, | ||
}, | ||
want: SyncRequirements{ | ||
{ | ||
{ | ||
Group: "group1", | ||
Version: "version1", | ||
Kind: "kind1", | ||
}: struct{}{}, | ||
}, | ||
{ | ||
{ | ||
Group: "group2", | ||
Version: "version2", | ||
Kind: "kind2", | ||
}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "test with no requiressyncdata annotation", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{}, | ||
}, | ||
}, | ||
want: SyncRequirements{}, | ||
}, | ||
{ | ||
name: "test with empty requiressyncdata annotation", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "", | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "test with invalid requiressyncdata annotation", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "invalid", | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "test with requiressyncdata annotation with invalid keys", | ||
template: &templates.ConstraintTemplate{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
"metadata.gatekeeper.sh/requiresSyncData": "\n\"[[{\"group\": [\"group1\"], \"version\": [\"version1\"], \"kind\": [\"kind1\"]}]]\"", | ||
}, | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := ReadSyncRequirements(tt.template) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("ReadSyncRequirements() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("ReadSyncRequirements() got = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |