Skip to content

Commit

Permalink
663 blueprints match by field and label requirements (#683)
Browse files Browse the repository at this point in the history
* Add MatchExpressions and MatchFields
* added error handling for invalid label selectors

Co-authored-by: Sam Coward <[email protected]>

Co-authored-by: Rasheed Abdul-Aziz <[email protected]>
  • Loading branch information
idoru and squeedee authored Mar 3, 2022
1 parent cfdb279 commit 8941486
Show file tree
Hide file tree
Showing 20 changed files with 2,567 additions and 997 deletions.
63 changes: 62 additions & 1 deletion config/crd/bases/carto.run_clusterdeliveries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,68 @@ spec:
description: 'Specifies the label key-value pairs used to select deliverables
See: https://cartographer.sh/docs/v0.1.0/architecture/#selectors'
type: object
selectorMatchExpressions:
description: 'Specifies the requirements used to select deliverables
based on their labels See: FIXME update docs and provide link'
items:
description: A label selector requirement is a selector that contains
values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to a set
of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator
is In or NotIn, the values array must be non-empty. If the
operator is Exists or DoesNotExist, the values array must
be empty. This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
selectorMatchFields:
description: 'Specifies the requirements used to select deliverables
based on their fields See: FIXME update docs and provide link'
items:
properties:
key:
description: 'Key is the JSON path in the workload to match
against. e.g. for workload: "workload.spec.source.git.url",
e.g. for deliverable: "deliverable.spec.source.git.url"'
minLength: 1
type: string
operator:
description: Operator represents a key's relationship to a set
of values. Valid operators are In, NotIn, Exists and DoesNotExist.
enum:
- In
- NotIn
- Exists
- DoesNotExist
type: string
values:
description: Values is an array of string values. If the operator
is In or NotIn, the values array must be non-empty. If the
operator is Exists or DoesNotExist, the values array must
be empty.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
serviceAccountRef:
description: "ServiceAccountName refers to the Service account with
permissions to create resources submitted by the supply chain. \n
Expand All @@ -270,7 +332,6 @@ spec:
type: object
required:
- resources
- selector
type: object
status:
description: 'Status conforms to the Kubernetes conventions: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties'
Expand Down
63 changes: 62 additions & 1 deletion config/crd/bases/carto.run_clustersupplychains.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,68 @@ spec:
description: 'Specifies the label key-value pairs used to select workloads
See: https://cartographer.sh/docs/v0.1.0/architecture/#selectors'
type: object
selectorMatchExpressions:
description: 'Specifies the requirements used to select workloads
based on their labels See: FIXME update docs and provide link'
items:
description: A label selector requirement is a selector that contains
values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to a set
of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator
is In or NotIn, the values array must be non-empty. If the
operator is Exists or DoesNotExist, the values array must
be empty. This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
selectorMatchFields:
description: 'Specifies the requirements used to select workloads
based on their fields See: FIXME update docs and provide link'
items:
properties:
key:
description: 'Key is the JSON path in the workload to match
against. e.g. for workload: "workload.spec.source.git.url",
e.g. for deliverable: "deliverable.spec.source.git.url"'
minLength: 1
type: string
operator:
description: Operator represents a key's relationship to a set
of values. Valid operators are In, NotIn, Exists and DoesNotExist.
enum:
- In
- NotIn
- Exists
- DoesNotExist
type: string
values:
description: Values is an array of string values. If the operator
is In or NotIn, the values array must be non-empty. If the
operator is Exists or DoesNotExist, the values array must
be empty.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
serviceAccountRef:
description: "ServiceAccountName refers to the Service account with
permissions to create resources submitted by the supply chain. \n
Expand All @@ -276,7 +338,6 @@ spec:
type: object
required:
- resources
- selector
type: object
status:
description: 'Status conforms to the Kubernetes conventions: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties'
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/MakeNowJust/heredoc v1.0.0
github.com/go-logr/logr v1.2.2
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/googleapis/gnostic v0.5.5
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.18.1
github.com/valyala/fasttemplate v1.2.1
Expand Down Expand Up @@ -46,7 +47,6 @@ require (
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
Expand Down
24 changes: 22 additions & 2 deletions pkg/apis/v1alpha1/cluster_delivery.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -70,7 +71,18 @@ type DeliverySpec struct {

// Specifies the label key-value pairs used to select deliverables
// See: https://cartographer.sh/docs/v0.1.0/architecture/#selectors
Selector map[string]string `json:"selector"`
// +optional
Selector map[string]string `json:"selector,omitempty"`

// Specifies the requirements used to select deliverables based on their labels
// See: FIXME update docs and provide link
// +optional
SelectorMatchExpressions []metav1.LabelSelectorRequirement `json:"selectorMatchExpressions,omitempty"`

// Specifies the requirements used to select deliverables based on their fields
// See: FIXME update docs and provide link
// +optional
SelectorMatchFields []FieldSelectorRequirement `json:"selectorMatchFields,omitempty"`

// Additional parameters.
// See: https://cartographer.sh/docs/latest/architecture/#parameter-hierarchy
Expand Down Expand Up @@ -185,10 +197,18 @@ func (c *ClusterDelivery) ValidateDelete() error {
return nil
}

func (c *ClusterDelivery) GetSelector() map[string]string {
func (c *ClusterDelivery) GetMatchLabels() labels.Set {
return c.Spec.Selector
}

func (c *ClusterDelivery) GetMatchExpressions() []metav1.LabelSelectorRequirement {
return c.Spec.SelectorMatchExpressions
}

func (c *ClusterDelivery) GetMatchFields() []FieldSelectorRequirement {
return c.Spec.SelectorMatchFields
}

func init() {
SchemeBuilder.Register(
&ClusterDelivery{},
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/v1alpha1/cluster_delivery_validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ package v1alpha1
import "fmt"

func (c *ClusterDelivery) validateNewState() error {
if len(c.Spec.Selector) == 0 && len(c.Spec.SelectorMatchExpressions) == 0 && len(c.Spec.SelectorMatchFields) == 0 {
return fmt.Errorf("at least one selector, selectorMatchExpression, selectorMatchField must be specified")
}

if err := c.validateParams(); err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var _ = Describe("Delivery Validation", func() {
Namespace: "default",
},
Spec: v1alpha1.DeliverySpec{
Selector: map[string]string { "requires": "at-least-one" },
Resources: []v1alpha1.DeliveryResource{
{
Name: "source-provider",
Expand Down Expand Up @@ -195,6 +196,7 @@ var _ = Describe("Delivery Validation", func() {
Namespace: "default",
},
Spec: v1alpha1.DeliverySpec{
Selector: map[string]string { "one-selector-of-any-kind": "is-needed" },
Resources: []v1alpha1.DeliveryResource{
{
Name: "source-provider",
Expand Down Expand Up @@ -506,6 +508,138 @@ var _ = Describe("Delivery Validation", func() {
})
})
})

Describe("OneOf Selector, SelectorMatchExpressions, or SelectorMatchFields", func() {
var deliveryFactory = func(selector map[string]string, expressions []metav1.LabelSelectorRequirement, fields []v1alpha1.FieldSelectorRequirement) *v1alpha1.ClusterDelivery {
return &v1alpha1.ClusterDelivery{
ObjectMeta: metav1.ObjectMeta{
Name: "delivery-resource",
Namespace: "default",
},
Spec: v1alpha1.DeliverySpec{
Selector: selector,
SelectorMatchExpressions: expressions,
SelectorMatchFields: fields,
Resources: []v1alpha1.DeliveryResource{
{
Name: "source-provider",
TemplateRef: v1alpha1.DeliveryTemplateReference{
Kind: "ClusterSourceTemplate",
Name: "source-template",
},
},
{
Name: "other-source-provider",
TemplateRef: v1alpha1.DeliveryTemplateReference{
Kind: "ClusterSourceTemplate",
Name: "source-template",
},
},
},
},
}

}
Context("No selection", func() {
var delivery *v1alpha1.ClusterDelivery
BeforeEach(func() {
delivery = deliveryFactory(nil, nil, nil)
})

It("on create, returns an error", func() {
Expect(delivery.ValidateCreate()).To(MatchError(
"error validating clusterdelivery [delivery-resource]: at least one selector, selectorMatchExpression, selectorMatchField must be specified",
))
})

It("on update, returns an error", func() {
Expect(delivery.ValidateUpdate(nil)).To(MatchError(
"error validating clusterdelivery [delivery-resource]: at least one selector, selectorMatchExpression, selectorMatchField must be specified",
))
})

It("deletes without error", func() {
Expect(delivery.ValidateDelete()).NotTo(HaveOccurred())
})
})
Context("Empty selection", func() {
var delivery *v1alpha1.ClusterDelivery
BeforeEach(func() {
delivery = deliveryFactory(map[string]string{}, []metav1.LabelSelectorRequirement{}, []v1alpha1.FieldSelectorRequirement{})
})

It("on create, returns an error", func() {
Expect(delivery.ValidateCreate()).To(MatchError(
"error validating clusterdelivery [delivery-resource]: at least one selector, selectorMatchExpression, selectorMatchField must be specified",
))
})

It("on update, returns an error", func() {
Expect(delivery.ValidateUpdate(nil)).To(MatchError(
"error validating clusterdelivery [delivery-resource]: at least one selector, selectorMatchExpression, selectorMatchField must be specified",
))
})

It("deletes without error", func() {
Expect(delivery.ValidateDelete()).NotTo(HaveOccurred())
})
})
Context("A Selector", func() {
var delivery *v1alpha1.ClusterDelivery
BeforeEach(func() {
delivery = deliveryFactory(map[string]string{"foo":"bar"}, nil, nil)
})

It("creates without error", func() {
Expect(delivery.ValidateCreate()).NotTo(HaveOccurred())
})

It("on update, returns an error", func() {
Expect(delivery.ValidateUpdate(nil)).NotTo(HaveOccurred())
})

It("deletes without error", func() {
Expect(delivery.ValidateDelete()).NotTo(HaveOccurred())
})
})
Context("A SelectorMatchExpression", func() {
var delivery *v1alpha1.ClusterDelivery
BeforeEach(func() {
delivery = deliveryFactory(nil, []metav1.LabelSelectorRequirement{{Key: "whatever", Operator: "Exists" }}, nil)
})

It("creates without error", func() {
Expect(delivery.ValidateCreate()).NotTo(HaveOccurred())
})

It("updates without error", func() {
Expect(delivery.ValidateUpdate(nil)).NotTo(HaveOccurred())
})

It("deletes without error", func() {
Expect(delivery.ValidateDelete()).NotTo(HaveOccurred())
})
})
Context("A SelectorMatchFields", func() {
var delivery *v1alpha1.ClusterDelivery
BeforeEach(func() {
delivery = deliveryFactory(nil, nil, []v1alpha1.FieldSelectorRequirement{{Key: "whatever", Operator: "Exists" }})
})

It("creates without error", func() {
Expect(delivery.ValidateCreate()).NotTo(HaveOccurred())
})

It("updates without error", func() {
Expect(delivery.ValidateUpdate(nil)).NotTo(HaveOccurred())
})

It("deletes without error", func() {
Expect(delivery.ValidateDelete()).NotTo(HaveOccurred())
})
})
})

})

var _ = Describe("DeliveryTemplateReference", func() {
Expand Down
Loading

0 comments on commit 8941486

Please sign in to comment.