Implement document filtering using api objects
Change-Id: I9ac70ed85c949224d091e9e3afead70084b38170 Co-Authored-By: Vladimir Kozhukalov <kozhukalov@gmail.com>
This commit is contained in:
parent
cad8177fee
commit
c05286d3f6
@ -16,15 +16,9 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersionKind is group version used to register these objects
|
||||
GroupVersionKind = schema.GroupVersionKind{Group: "airshipit.org", Version: "v1alpha1", Kind: "Clusterctl"}
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// Clusterctl provides information about clusterctl components
|
||||
|
@ -15,8 +15,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/clusterctl/client"
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
@ -71,21 +69,22 @@ func (c *Command) Init() error {
|
||||
}
|
||||
|
||||
func clusterctlOptions(bundle document.Bundle) (*airshipv1.Clusterctl, error) {
|
||||
doc, err := bundle.SelectOne(document.NewClusterctlSelector())
|
||||
cctl := &airshipv1.Clusterctl{}
|
||||
selector, err := document.NewSelector().ByObject(cctl, airshipv1.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := &airshipv1.Clusterctl{}
|
||||
b, err := doc.AsYAML()
|
||||
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO (kkalynovskyi) instead of this, use kubernetes serializer
|
||||
err = yaml.Unmarshal(b, options)
|
||||
if err != nil {
|
||||
|
||||
if err := doc.ToAPIObject(cctl, airshipv1.Scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return options, nil
|
||||
|
||||
return cctl, nil
|
||||
}
|
||||
|
||||
func getBundle(conf *config.Config) (document.Bundle, error) {
|
||||
|
@ -15,6 +15,9 @@
|
||||
package document
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/yaml"
|
||||
@ -47,6 +50,7 @@ type Document interface {
|
||||
Label(map[string]string)
|
||||
MarshalJSON() ([]byte, error)
|
||||
ToObject(interface{}) error
|
||||
ToAPIObject(runtime.Object, *runtime.Scheme) error
|
||||
}
|
||||
|
||||
// Factory implements Document
|
||||
@ -188,6 +192,23 @@ func (d *Factory) ToObject(obj interface{}) error {
|
||||
return yaml.Unmarshal(docYAML, obj)
|
||||
}
|
||||
|
||||
// ToAPIObject de-serializes a document into a runtime.Object
|
||||
func (d *Factory) ToAPIObject(obj runtime.Object, scheme *runtime.Scheme) error {
|
||||
y, err := d.AsYAML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
yamlSerializer := json.NewSerializerWithOptions(
|
||||
json.DefaultMetaFactory,
|
||||
scheme,
|
||||
scheme,
|
||||
json.SerializerOptions{Yaml: true, Pretty: false, Strict: false})
|
||||
|
||||
_, _, err = yamlSerializer.Decode(y, nil, obj)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewDocument is a convenience method to construct a new Document. Although
|
||||
// an error is unlikely at this time, this provides some future proofing for
|
||||
// when we want more strict airship specific validation of documents getting
|
||||
|
@ -21,6 +21,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
airapiv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/testutil"
|
||||
)
|
||||
@ -102,6 +105,33 @@ func TestDocument(t *testing.T) {
|
||||
assert.Equal(expectedObj, actualObj)
|
||||
})
|
||||
|
||||
t.Run("ToAPIObject", func(t *testing.T) {
|
||||
expectedObj := &airapiv1.Clusterctl{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Clusterctl",
|
||||
APIVersion: "airshipit.org/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "clusterctl-v1",
|
||||
},
|
||||
Providers: []*airapiv1.Provider{
|
||||
{
|
||||
Name: "aws",
|
||||
Type: "InfrastructureProvider",
|
||||
URL: "/manifests/capi/infra/aws/v0.3.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
sel, err := document.NewSelector().ByObject(expectedObj, airapiv1.Scheme)
|
||||
require.NoError(err)
|
||||
doc, err := bundle.SelectOne(sel)
|
||||
require.NoError(err)
|
||||
actualObj := &airapiv1.Clusterctl{}
|
||||
err = doc.ToAPIObject(actualObj, airapiv1.Scheme)
|
||||
assert.NoError(err)
|
||||
assert.Equal(expectedObj, actualObj)
|
||||
})
|
||||
|
||||
t.Run("GetString", func(t *testing.T) {
|
||||
doc, err := bundle.GetByName("some-random-deployment-we-will-filter")
|
||||
require.NoError(err, "Unexpected error trying to GetByName")
|
||||
|
@ -16,6 +16,8 @@ package document
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// ErrDocNotFound returned if desired document not found by selector
|
||||
@ -41,6 +43,12 @@ type ErrDocumentMalformed struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// ErrRuntimeObjectKind returned if runtime object contains either none or
|
||||
// more than one Kinds defined in schema
|
||||
type ErrRuntimeObjectKind struct {
|
||||
Obj runtime.Object
|
||||
}
|
||||
|
||||
func (e ErrDocNotFound) Error() string {
|
||||
return fmt.Sprintf("document filtered by selector %v found no documents", e.Selector)
|
||||
}
|
||||
@ -56,3 +64,7 @@ func (e ErrDocumentDataKeyNotFound) Error() string {
|
||||
func (e ErrDocumentMalformed) Error() string {
|
||||
return fmt.Sprintf("document %q is malformed: %q", e.DocName, e.Message)
|
||||
}
|
||||
|
||||
func (e ErrRuntimeObjectKind) Error() string {
|
||||
return fmt.Sprintf("object %#v has either none or multiple kinds in scheme (expected one)", e.Obj)
|
||||
}
|
||||
|
@ -18,10 +18,11 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
)
|
||||
|
||||
// Selector provides abstraction layer in front of kustomize selector
|
||||
@ -81,6 +82,28 @@ func (s Selector) ByAnnotation(annotationSelector string) Selector {
|
||||
return s
|
||||
}
|
||||
|
||||
// ByObject select by runtime object defined in API schema
|
||||
func (s Selector) ByObject(obj runtime.Object, scheme *runtime.Scheme) (Selector, error) {
|
||||
gvks, _, err := scheme.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return Selector{}, err
|
||||
}
|
||||
|
||||
if len(gvks) != 1 {
|
||||
return Selector{}, ErrRuntimeObjectKind{Obj: obj}
|
||||
}
|
||||
result := NewSelector().ByGvk(gvks[0].Group, gvks[0].Version, gvks[0].Kind)
|
||||
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return Selector{}, err
|
||||
}
|
||||
if name := accessor.GetName(); name != "" {
|
||||
result = result.ByName(name)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// String is a convenience function which dumps all relevant information about a Selector in the following format:
|
||||
// [Key1=Value1, Key2=Value2, ...]
|
||||
func (s Selector) String() string {
|
||||
@ -166,12 +189,3 @@ func NewClusterctlMetadataSelector() Selector {
|
||||
ClusterctlMetadataVersion,
|
||||
ClusterctlMetadataKind)
|
||||
}
|
||||
|
||||
// NewClusterctlSelector returns a selector to get document that controls how clusterctl
|
||||
// components will be applied
|
||||
func NewClusterctlSelector() Selector {
|
||||
return NewSelector().ByGvk(
|
||||
airshipv1.GroupVersionKind.Group,
|
||||
airshipv1.GroupVersionKind.Version,
|
||||
airshipv1.GroupVersionKind.Kind)
|
||||
}
|
||||
|
@ -19,7 +19,13 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
k8sv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
airapiv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/testutil"
|
||||
)
|
||||
@ -54,12 +60,6 @@ func TestSelectorsPositive(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, doc, 1)
|
||||
})
|
||||
|
||||
t.Run("TestNewClusterctlSelector", func(t *testing.T) {
|
||||
docs, err := bundle.Select(document.NewClusterctlSelector())
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelectorsNegative(t *testing.T) {
|
||||
@ -142,3 +142,65 @@ func TestSelectorString(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectorToObject(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Object
|
||||
expectedSel document.Selector
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "Selector with GVK",
|
||||
obj: &airapiv1.Clusterctl{},
|
||||
expectedSel: document.Selector{
|
||||
Selector: types.Selector{
|
||||
Gvk: resid.Gvk{
|
||||
Group: "airshipit.org",
|
||||
Version: "v1alpha1",
|
||||
Kind: "Clusterctl",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "Unregistered object",
|
||||
obj: &k8sv1.Pod{},
|
||||
expectedSel: document.Selector{},
|
||||
expectedErr: "no kind is registered for the type v1.Pod in scheme",
|
||||
},
|
||||
{
|
||||
name: "Selector with GVK and Name",
|
||||
obj: &airapiv1.Clusterctl{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "clusterctl-v1",
|
||||
},
|
||||
},
|
||||
expectedSel: document.Selector{
|
||||
Selector: types.Selector{
|
||||
Gvk: resid.Gvk{
|
||||
Group: "airshipit.org",
|
||||
Version: "v1alpha1",
|
||||
Kind: "Clusterctl",
|
||||
},
|
||||
Name: "clusterctl-v1",
|
||||
},
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actualSel, err := document.NewSelector().
|
||||
ByObject(tt.obj, airapiv1.Scheme)
|
||||
if test.expectedErr != "" {
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedSel, actualSel)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
9
pkg/document/testdata/common/clusterctl.yaml
vendored
Normal file
9
pkg/document/testdata/common/clusterctl.yaml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: Clusterctl
|
||||
metadata:
|
||||
name: clusterctl-v1
|
||||
providers:
|
||||
- name: "aws"
|
||||
type: "InfrastructureProvider"
|
||||
url: "/manifests/capi/infra/aws/v0.3.0"
|
@ -3,4 +3,5 @@ resources:
|
||||
- tiller.yaml
|
||||
- argo.yaml
|
||||
- initially_ignored.yaml
|
||||
- custom_resource.yaml
|
||||
- custom_resource.yaml
|
||||
- clusterctl.yaml
|
Loading…
Reference in New Issue
Block a user