Skip to content

Commit

Permalink
Add client.ValidateConstraintTemplate interface to replace validation…
Browse files Browse the repository at this point in the history
… logic from client.CeateCRD (#171)

Signed-off-by: Becky Huang <beckyhd@google.com>
  • Loading branch information
becky-hd committed Jan 13, 2022
1 parent 98480b2 commit 09c80a4
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 39 deletions.
68 changes: 42 additions & 26 deletions constraint/pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,23 +197,10 @@ func (c *Client) createBasicTemplateArtifacts(templ *templates.ConstraintTemplat
if err != nil {
return nil, err
}

kind := templ.Spec.CRD.Spec.Names.Kind
if kind == "" {
return nil, fmt.Errorf("%w: ConstraintTemplate %q does not specify CRD Kind",
ErrInvalidConstraintTemplate, templ.GetName())
}

if !strings.EqualFold(templ.ObjectMeta.Name, kind) {
return nil, fmt.Errorf("%w: the ConstraintTemplate's name %q is not equal to the lowercase of CRD's Kind: %q",
ErrInvalidConstraintTemplate, templ.ObjectMeta.Name, strings.ToLower(kind))
}

targetSpec, targetHandler, err := c.validateTargets(templ)
targetSpec, targetHandler, err := c.ValidateConstraintTemplateBasic(templ)
if err != nil {
return nil, fmt.Errorf("failed to validate targets for template %s: %w", templ.Name, err)
return nil, err
}

sch := c.backend.crd.createSchema(templ, targetHandler)

crd, err := c.backend.crd.createCRD(templ, sch)
Expand Down Expand Up @@ -251,6 +238,40 @@ func (c *Client) CreateCRD(templ *templates.ConstraintTemplate) (*apiextensions.
return artifacts.crd, nil
}

func (c *Client) ValidateConstraintTemplateBasic(templ *templates.ConstraintTemplate) (*templates.Target, TargetHandler, error) {
kind := templ.Spec.CRD.Spec.Names.Kind
if kind == "" {
return nil, nil, fmt.Errorf("%w: ConstraintTemplate %q does not specify CRD Kind",
ErrInvalidConstraintTemplate, templ.GetName())
}

if !strings.EqualFold(templ.ObjectMeta.Name, kind) {
return nil, nil, fmt.Errorf("%w: the ConstraintTemplate's name %q is not equal to the lowercase of CRD's Kind: %q",
ErrInvalidConstraintTemplate, templ.ObjectMeta.Name, strings.ToLower(kind))
}

targetSpec, targetHandler, err := c.validateTargets(templ)
if err != nil {
return nil, nil, fmt.Errorf("failed to validate targets for template %s: %w", templ.Name, err)
}
return targetSpec, targetHandler, nil
}

func (c *Client) ValidateConstraintTemplate(templ *templates.ConstraintTemplate) error {
if templ == nil {
return fmt.Errorf(`%w: ConstraintTemplate is nil`,
ErrInvalidConstraintTemplate)
}
if _, _, err := c.ValidateConstraintTemplateBasic(templ); err != nil {
return err
}
if dr, ok := c.backend.driver.(*local.Driver); ok {
_, _, err := dr.ValidateConstraintTemplate(templ)
return err
}
return fmt.Errorf("driver %T is not supported", c.backend.driver)
}

// AddTemplate adds the template source code to OPA and registers the CRD with the client for
// schema validation on calls to AddConstraint. On error, the responses return value
// will still be populated so that partial results can be analyzed.
Expand All @@ -274,22 +295,17 @@ func (c *Client) AddTemplate(templ *templates.ConstraintTemplate) (*types.Respon
if err = c.backend.driver.AddTemplate(templ); err != nil {
return resp, err
}

artifacts, err := c.createBasicTemplateArtifacts(templ)
if err != nil {
return resp, err
}
cpy := templ.DeepCopy()
cpy.Status = templates.ConstraintTemplateStatus{}
c.templates[artifacts.Key()] = &templateEntry{
c.templates[basicArtifacts.Key()] = &templateEntry{
template: cpy,
CRD: artifacts.crd,
Targets: []string{artifacts.targetHandler.GetName()},
CRD: basicArtifacts.crd,
Targets: []string{basicArtifacts.targetHandler.GetName()},
}
if _, ok := c.constraints[artifacts.gk]; !ok {
c.constraints[artifacts.gk] = make(map[string]*unstructured.Unstructured)
if _, ok := c.constraints[basicArtifacts.gk]; !ok {
c.constraints[basicArtifacts.gk] = make(map[string]*unstructured.Unstructured)
}
resp.Handled[artifacts.targetHandler.GetName()] = true
resp.Handled[basicArtifacts.targetHandler.GetName()] = true
return resp, nil
}

Expand Down
244 changes: 244 additions & 0 deletions constraint/pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1359,3 +1359,247 @@ violation[msg] {msg := "always"}`,
})
}
}

func TestClient_ValidateConstraintTemplate(t *testing.T) {
testCases := []struct {
name string
targets []TargetHandler
template *templates.ConstraintTemplate
want *apiextensions.CustomResourceDefinition
wantErr error
}{
{
name: "nil",
template: nil,
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "empty",
template: &templates.ConstraintTemplate{},
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "no CRD kind",
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
},
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "name-kind mismatch",
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Bar",
},
},
},
Targets: []templates.Target{{
Target: "handler",
Rego: `package foo
violation[msg] {msg := "always"}`,
}},
},
},
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "no targets",
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Foo",
},
},
},
},
},
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "wrong target",
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Foo",
},
},
},
Targets: []templates.Target{{
Target: "handler.2",
}},
},
},
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "multiple targets",
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Foo",
},
},
},
Targets: []templates.Target{{
Target: "handler",
Rego: `package foo
violation[msg] {msg := "always"}`,
}, {
Target: "handler.2",
Rego: `package foo
violation[msg] {msg := "always"}`,
}},
},
},
want: nil,
wantErr: ErrInvalidConstraintTemplate,
},
{
name: "no rego",
targets: []TargetHandler{&badHandler{Name: "handler", HasLib: true}},
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Foo",
},
},
},
Targets: []templates.Target{{
Target: "handler",
}},
},
},
want: nil,
wantErr: local.ErrInvalidConstraintTemplate,
},
{
name: "empty rego package",
targets: []TargetHandler{&badHandler{Name: "handler", HasLib: true}},
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Foo",
},
},
},
Targets: []templates.Target{{
Target: "handler",
Rego: `package foo`,
}},
},
},
want: nil,
wantErr: local.ErrInvalidConstraintTemplate,
},
{
name: "minimal working",
targets: []TargetHandler{&badHandler{Name: "handler", HasLib: true}},
template: &templates.ConstraintTemplate{
ObjectMeta: v1.ObjectMeta{Name: "foo"},
Spec: templates.ConstraintTemplateSpec{
CRD: templates.CRD{
Spec: templates.CRDSpec{
Names: templates.Names{
Kind: "Foo",
},
},
},
Targets: []templates.Target{{
Target: "handler",
Rego: `package foo
violation[msg] {msg := "always"}`,
}},
},
},
want: &apiextensions.CustomResourceDefinition{
ObjectMeta: v1.ObjectMeta{
Name: "foo.constraints.gatekeeper.sh",
Labels: map[string]string{"gatekeeper.sh/constraint": "yes"},
},
Spec: apiextensions.CustomResourceDefinitionSpec{
Group: "constraints.gatekeeper.sh",
Version: "v1beta1",
Names: apiextensions.CustomResourceDefinitionNames{
Plural: "foo",
Singular: "foo",
Kind: "Foo",
ListKind: "FooList",
Categories: []string{"constraint", "constraints"},
},
Scope: apiextensions.ClusterScoped,
Subresources: &apiextensions.CustomResourceSubresources{
Status: &apiextensions.CustomResourceSubresourceStatus{},
},
Versions: []apiextensions.CustomResourceDefinitionVersion{{
Name: "v1beta1", Served: true, Storage: true,
}, {
Name: "v1alpha1", Served: true,
}},
Conversion: &apiextensions.CustomResourceConversion{
Strategy: apiextensions.NoneConverter,
},
PreserveUnknownFields: pointer.BoolPtr(false),
},
Status: apiextensions.CustomResourceDefinitionStatus{
StoredVersions: []string{"v1beta1"},
},
},
wantErr: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
d := local.New()

b, err := NewBackend(Driver(d))
if err != nil {
t.Fatal(err)
}
targets := Targets(&handler{})
if tc.targets != nil {
targets = Targets(tc.targets...)
}
c, err := b.NewClient(targets)
if err != nil {
t.Fatal(err)
}

err = c.ValidateConstraintTemplate(tc.template)

if !errors.Is(err, tc.wantErr) {
t.Fatalf("got CreateTemplate() error = %v, want %v",
err, tc.wantErr)
}
})
}
}
4 changes: 2 additions & 2 deletions constraint/pkg/client/drivers/local/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ func Tracing(enabled bool) Arg {
}

func PrintEnabled(enabled bool) Arg {
return func(d *driver) {
return func(d *Driver) {
d.printEnabled = enabled
}
}

func PrintHook(hook print.Hook) Arg {
return func(d *driver) {
return func(d *Driver) {
d.printHook = hook
}
}
Expand Down

0 comments on commit 09c80a4

Please sign in to comment.