From 746141f6acaaeacb82573ca8bb96812ec5288058 Mon Sep 17 00:00:00 2001 From: Kostiantyn Kalynovskyi Date: Fri, 21 Feb 2020 23:14:27 +0000 Subject: [PATCH] [#11] Implement selector kustomize primitive Introduce Selector object that would serve as adaptor to kustomize, this provides a layer between kustomize in a way that there is no more need to import types package from kustomize by other modules. Change-Id: Ib9e892e8b71d1808e427dd5240e4f313cc366225 --- pkg/cluster/initinfra/infra.go | 13 ++++---- pkg/document/bundle.go | 32 ++++++++------------ pkg/document/bundle_test.go | 46 ++++++++++++++++++++++++---- pkg/document/selectors.go | 53 +++++++++++++++++++++++++++++++++ pkg/document/testdata/argo.yaml | 2 ++ pkg/remote/remote_direct.go | 15 ++++------ 6 files changed, 122 insertions(+), 39 deletions(-) create mode 100644 pkg/document/selectors.go diff --git a/pkg/cluster/initinfra/infra.go b/pkg/cluster/initinfra/infra.go index 1a5f35ae6..e2de5a880 100644 --- a/pkg/cluster/initinfra/infra.go +++ b/pkg/cluster/initinfra/infra.go @@ -2,7 +2,6 @@ package initinfra import ( "sigs.k8s.io/kustomize/v3/pkg/fs" - "sigs.k8s.io/kustomize/v3/pkg/types" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" @@ -71,15 +70,19 @@ func (infra *Infra) Deploy() error { return err } - filterSelector := types.Selector{ - LabelSelector: document.EphemeralClusterSelector, - } + ls := document.EphemeralClusterSelector + selector := document.NewSelector().ByLabel(ls) // Get documents that are annotated to belong to initinfra - docs, err := b.Select(filterSelector) + docs, err := b.Select(selector) if err != nil { return err } + if len(docs) == 0 { + return document.ErrDocNotFound{ + Selector: ls, + } + } // Label every document indicating that it was deployed by initinfra module for further reference // This may be used later to get all resources that are part of initinfra module, for monitoring, alerting diff --git a/pkg/document/bundle.go b/pkg/document/bundle.go index 011c15d3a..7844b4191 100644 --- a/pkg/document/bundle.go +++ b/pkg/document/bundle.go @@ -8,13 +8,11 @@ import ( "sigs.k8s.io/kustomize/v3/k8sdeps/transformer" "sigs.k8s.io/kustomize/v3/k8sdeps/validator" "sigs.k8s.io/kustomize/v3/pkg/fs" - "sigs.k8s.io/kustomize/v3/pkg/gvk" "sigs.k8s.io/kustomize/v3/pkg/loader" "sigs.k8s.io/kustomize/v3/pkg/plugins" "sigs.k8s.io/kustomize/v3/pkg/resmap" "sigs.k8s.io/kustomize/v3/pkg/resource" "sigs.k8s.io/kustomize/v3/pkg/target" - "sigs.k8s.io/kustomize/v3/pkg/types" docplugins "opendev.org/airship/airshipctl/pkg/document/plugins" "opendev.org/airship/airshipctl/pkg/log" @@ -51,11 +49,11 @@ type Bundle interface { SetKustomizeBuildOptions(KustomizeBuildOptions) error SetFileSystem(fs.FileSystem) error GetFileSystem() fs.FileSystem - Select(selector types.Selector) ([]Document, error) + Select(selector Selector) ([]Document, error) GetByGvk(string, string, string) ([]Document, error) GetByName(string) (Document, error) - GetByAnnotation(string) ([]Document, error) - GetByLabel(string) ([]Document, error) + GetByAnnotation(annotationSelector string) ([]Document, error) + GetByLabel(labelSelector string) ([]Document, error) GetAllDocuments() ([]Document, error) } @@ -188,11 +186,11 @@ func (b *BundleFactory) GetByName(name string) (Document, error) { } } -// Select offers a direct interface to pass a Kustomize Selector to the bundle -// returning Documents that match the criteria -func (b *BundleFactory) Select(selector types.Selector) ([]Document, error) { +// Select offers an interface to pass a Selector, built on top of kustomize Selector +// to the bundle returning Documents that match the criteria +func (b *BundleFactory) Select(selector Selector) ([]Document, error) { // use the kustomize select method - resources, err := b.ResMap.Select(selector) + resources, err := b.ResMap.Select(selector.Selector) if err != nil { return []Document{}, err } @@ -211,19 +209,17 @@ func (b *BundleFactory) Select(selector types.Selector) ([]Document, error) { } // GetByAnnotation is a convenience method to get documents for a particular annotation -func (b *BundleFactory) GetByAnnotation(annotation string) ([]Document, error) { +func (b *BundleFactory) GetByAnnotation(annotationSelector string) ([]Document, error) { // Construct kustomize annotation selector - selector := types.Selector{AnnotationSelector: annotation} - + selector := NewSelector().ByAnnotation(annotationSelector) // pass it to the selector return b.Select(selector) } // GetByLabel is a convenience method to get documents for a particular label -func (b *BundleFactory) GetByLabel(label string) ([]Document, error) { - // Construct kustomize annotation selector - selector := types.Selector{LabelSelector: label} - +func (b *BundleFactory) GetByLabel(labelSelector string) ([]Document, error) { + // Construct kustomize label selector + selector := NewSelector().ByLabel(labelSelector) // pass it to the selector return b.Select(selector) } @@ -231,10 +227,8 @@ func (b *BundleFactory) GetByLabel(label string) ([]Document, error) { // GetByGvk is a convenience method to get documents for a particular Gvk tuple func (b *BundleFactory) GetByGvk(group, version, kind string) ([]Document, error) { // Construct kustomize gvk object - g := gvk.Gvk{Group: group, Version: version, Kind: kind} - + selector := NewSelector().ByGvk(group, version, kind) // pass it to the selector - selector := types.Selector{Gvk: g} return b.Select(selector) } diff --git a/pkg/document/bundle_test.go b/pkg/document/bundle_test.go index bd4c2cce9..ea2faded3 100644 --- a/pkg/document/bundle_test.go +++ b/pkg/document/bundle_test.go @@ -6,9 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "sigs.k8s.io/kustomize/v3/pkg/gvk" - "sigs.k8s.io/kustomize/v3/pkg/types" + "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/testutil" ) @@ -57,8 +56,7 @@ func TestBundleDocumentFiltering(t *testing.T) { // Select* tests test the Kustomize selector, which requires we build Kustomize // selector objects which is useful for advanced searches that // need to stack filters - g := gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"} - selector := types.Selector{Gvk: g} + selector := document.NewSelector().ByGvk("apps", "v1", "Deployment") docs, err := bundle.Select(selector) require.NoError(err, "Error trying to select resources") @@ -67,7 +65,7 @@ func TestBundleDocumentFiltering(t *testing.T) { t.Run("SelectAnnotations", func(t *testing.T) { // Find documents with a particular annotation, namely airshipit.org/clustertype=ephemeral - selector := types.Selector{AnnotationSelector: "airshipit.org/clustertype=ephemeral"} + selector := document.NewSelector().ByAnnotation("airshipit.org/clustertype=ephemeral") docs, err := bundle.Select(selector) require.NoError(err, "Error trying to select annotated resources") @@ -78,12 +76,48 @@ func TestBundleDocumentFiltering(t *testing.T) { // Find documents with a particular label, namely app=workflow-controller // note how this will only find resources labeled at the top most level (metadata.labels) // and not spec templates with this label (spec.template.metadata.labels) - selector := types.Selector{LabelSelector: "app=workflow-controller"} + selector := document.NewSelector().ByLabel("app=workflow-controller") docs, err := bundle.Select(selector) require.NoError(err, "Error trying to select labeled resources") + assert.Len(docs, 1, "SelectLabels returned wrong number of resources") }) + t.Run("SelectByLabelAndName", func(t *testing.T) { + // Find documents with a particular label and name, + // namely app=workflow-controller and name workflow-controller + selector := document.NewSelector().ByName("workflow-controller").ByLabel("app=workflow-controller") + docs, err := bundle.Select(selector) + require.NoError(err, "Error trying to select labeled with name resources") + + assert.Len(docs, 1, "SelectByLabelAndName returned wrong number of resources") + }) + + t.Run("SelectByTwoLabels", func(t *testing.T) { + // Find documents with two particular label, namely app=workflow-controller arbitrary-label=some-label + selector := document.NewSelector(). + ByLabel("app=workflow-controller"). + ByLabel("arbitrary-label=some-label") + docs, err := bundle.Select(selector) + require.NoError(err, "Error trying to select by two labels") + + assert.Len(docs, 1, "SelectByTwoLabels returned wrong number of resources") + assert.Equal("workflow-controller", docs[0].GetName()) + }) + + t.Run("SelectByTwoAnnotations", func(t *testing.T) { + // Find documents with two particular annotations, + // namely app=workflow-controller and name workflow-controller + selector := document.NewSelector(). + ByAnnotation("airshipit.org/clustertype=target"). + ByAnnotation("airshipit.org/random-payload=random") + docs, err := bundle.Select(selector) + require.NoError(err, "Error trying to select by two annotations") + + assert.Len(docs, 1, "SelectByTwoAnnotations returned wrong number of resources") + assert.Equal("argo-ui", docs[0].GetName()) + }) + t.Run("Write", func(t *testing.T) { // Ensure we can write out a bundle // diff --git a/pkg/document/selectors.go b/pkg/document/selectors.go new file mode 100644 index 000000000..6f7a5b971 --- /dev/null +++ b/pkg/document/selectors.go @@ -0,0 +1,53 @@ +package document + +import ( + "strings" + + "sigs.k8s.io/kustomize/v3/pkg/gvk" + "sigs.k8s.io/kustomize/v3/pkg/types" +) + +// Selector provides abstraction layer in front of kustomize selector +type Selector struct { + types.Selector +} + +// NewSelector returns instance of Selector container +func NewSelector() Selector { + return Selector{} +} + +// Following set of functions allows to build selector object +// by name, gvk, label selector and annotation selector + +// ByName select by name +func (s Selector) ByName(name string) Selector { + s.Name = name + return s +} + +// ByGvk select by gvk +func (s Selector) ByGvk(group, version, kind string) Selector { + s.Gvk = gvk.Gvk{Group: group, Version: version, Kind: kind} + return s +} + +// ByLabel select by label selector +func (s Selector) ByLabel(labelSelector string) Selector { + if s.LabelSelector != "" { + s.LabelSelector = strings.Join([]string{s.LabelSelector, labelSelector}, ",") + } else { + s.LabelSelector = labelSelector + } + return s +} + +// ByAnnotation select by annotation selector. +func (s Selector) ByAnnotation(annotationSelector string) Selector { + if s.AnnotationSelector != "" { + s.AnnotationSelector = strings.Join([]string{s.AnnotationSelector, annotationSelector}, ",") + } else { + s.AnnotationSelector = annotationSelector + } + return s +} diff --git a/pkg/document/testdata/argo.yaml b/pkg/document/testdata/argo.yaml index ff9384cce..ac20a1b57 100644 --- a/pkg/document/testdata/argo.yaml +++ b/pkg/document/testdata/argo.yaml @@ -232,6 +232,7 @@ kind: Deployment metadata: annotations: airshipit.org/clustertype: target + airshipit.org/random-payload: random name: argo-ui spec: selector: @@ -266,6 +267,7 @@ metadata: airshipit.org/clustertype: target labels: app: workflow-controller + arbitrary-label: some-label name: workflow-controller spec: selector: diff --git a/pkg/remote/remote_direct.go b/pkg/remote/remote_direct.go index ee6b126dc..960958a31 100644 --- a/pkg/remote/remote_direct.go +++ b/pkg/remote/remote_direct.go @@ -13,8 +13,6 @@ import ( "opendev.org/airship/airshipctl/pkg/remote/redfish" "sigs.k8s.io/kustomize/v3/pkg/fs" - "sigs.k8s.io/kustomize/v3/pkg/gvk" - "sigs.k8s.io/kustomize/v3/pkg/types" ) const ( @@ -89,18 +87,17 @@ func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (*config.Re return nil, "", err } - label := document.EphemeralClusterSelector - filter := types.Selector{ - Gvk: gvk.FromKind(AirshipHostKind), - LabelSelector: label, - } - docs, err := docBundle.Select(filter) + ls := document.EphemeralClusterSelector + selector := document.NewSelector(). + ByGvk("", "", AirshipHostKind). + ByLabel(ls) + docs, err := docBundle.Select(selector) if err != nil { return nil, "", err } if len(docs) == 0 { return nil, "", document.ErrDocNotFound{ - Selector: label, + Selector: ls, Kind: AirshipHostKind, } }