diff --git a/pkg/api/v1alpha1/groupversion_info.go b/pkg/api/v1alpha1/groupversion_info.go index fe14e81ef..d796560ed 100644 --- a/pkg/api/v1alpha1/groupversion_info.go +++ b/pkg/api/v1alpha1/groupversion_info.go @@ -49,6 +49,8 @@ func init() { &ImageConfiguration{}, &RemoteDirectConfiguration{}, &ClusterMap{}, + &ReplacementTransformer{}, + &Templater{}, ) _ = AddToScheme(Scheme) //nolint:errcheck } diff --git a/pkg/api/v1alpha1/replacement_plugin_types.go b/pkg/api/v1alpha1/replacement_plugin_types.go new file mode 100644 index 000000000..6ac48f1ee --- /dev/null +++ b/pkg/api/v1alpha1/replacement_plugin_types.go @@ -0,0 +1,57 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/kustomize/api/types" +) + +// +kubebuilder:object:root=true + +// ReplacementTransformer plugin configuration for airship document model +type ReplacementTransformer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Replacements list of source and target field to do a replacement + Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"` + Tst []string +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplacementTransformer) DeepCopyInto(out *ReplacementTransformer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Replacements != nil { + out.Replacements = make([]types.Replacement, len(in.Replacements)) + for i, repl := range in.Replacements { + out.Replacements[i] = types.Replacement{ + Source: &types.ReplSource{ + ObjRef: &types.Target{}, + FieldRef: repl.Source.FieldRef, + Value: repl.Source.Value, + }, + Target: &types.ReplTarget{ + ObjRef: &types.Selector{}, + FieldRefs: repl.Target.FieldRefs, + }, + } + *(out.Replacements[i].Source.ObjRef) = *(in.Replacements[i].Source.ObjRef) + *(out.Replacements[i].Target.ObjRef) = *(in.Replacements[i].Target.ObjRef) + } + } +} diff --git a/pkg/document/plugin/templater/v1alpha1/types.go b/pkg/api/v1alpha1/templater_plugin_types.go similarity index 68% rename from pkg/document/plugin/templater/v1alpha1/types.go rename to pkg/api/v1alpha1/templater_plugin_types.go index f2ebf72c5..fd0d6975e 100644 --- a/pkg/document/plugin/templater/v1alpha1/types.go +++ b/pkg/api/v1alpha1/templater_plugin_types.go @@ -16,9 +16,12 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) -// Templater plugin for airship document model +// +kubebuilder:object:root=true + +// Templater plugin configuration for airship document model type Templater struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -29,3 +32,13 @@ type Templater struct { // to be used to render the object defined in Spec field Template string `json:"template,omitempty"` } + +// NOTE map[string]interface is not supported by controller gen + +// DeepCopyInto is copying the receiver, writing into out. in must be non-nil. +func (in *Templater) DeepCopyInto(out *Templater) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Values = runtime.DeepCopyJSON(in.Values) +} diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index b7a8fa6e4..ce2250d4f 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -490,3 +490,39 @@ func (in *RemoteDirectConfiguration) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplacementTransformer. +func (in *ReplacementTransformer) DeepCopy() *ReplacementTransformer { + if in == nil { + return nil + } + out := new(ReplacementTransformer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplacementTransformer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Templater. +func (in *Templater) DeepCopy() *Templater { + if in == nil { + return nil + } + out := new(Templater) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Templater) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/pkg/document/plugin/replacement/v1alpha1/errors.go b/pkg/document/plugin/replacement/errors.go similarity index 99% rename from pkg/document/plugin/replacement/v1alpha1/errors.go rename to pkg/document/plugin/replacement/errors.go index eda3cf641..a73ada0e1 100644 --- a/pkg/document/plugin/replacement/v1alpha1/errors.go +++ b/pkg/document/plugin/replacement/errors.go @@ -12,7 +12,7 @@ limitations under the License. */ -package v1alpha1 +package replacement import ( "fmt" diff --git a/pkg/document/plugin/replacement/init.go b/pkg/document/plugin/replacement/init.go index 2a5ab8ee9..24aa9f44c 100644 --- a/pkg/document/plugin/replacement/init.go +++ b/pkg/document/plugin/replacement/init.go @@ -17,11 +17,17 @@ package replacement import ( "k8s.io/apimachinery/pkg/runtime/schema" - replv1alpha1 "opendev.org/airship/airshipctl/pkg/document/plugin/replacement/v1alpha1" + airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/document/plugin/types" ) // RegisterPlugin registers BareMetalHost generator plugin -func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) { - registry[replv1alpha1.GetGVK()] = replv1alpha1.New +func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) error { + obj := &airshipv1.ReplacementTransformer{} + gvks, _, err := airshipv1.Scheme.ObjectKinds(obj) + if err != nil { + return err + } + registry[gvks[0]] = New + return nil } diff --git a/pkg/document/plugin/replacement/v1alpha1/transformer.go b/pkg/document/plugin/replacement/transformer.go similarity index 89% rename from pkg/document/plugin/replacement/v1alpha1/transformer.go rename to pkg/document/plugin/replacement/transformer.go index 712e40a10..3dfb33022 100644 --- a/pkg/document/plugin/replacement/v1alpha1/transformer.go +++ b/pkg/document/plugin/replacement/transformer.go @@ -1,7 +1,7 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package replacement import ( "fmt" @@ -11,13 +11,13 @@ import ( "strconv" "strings" - "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" - "sigs.k8s.io/yaml" + airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types" ) @@ -31,22 +31,29 @@ const ( dotReplacer = "$$$$" ) -// GetGVK returns group, version, kind object used to register version -// of the plugin -func GetGVK() schema.GroupVersionKind { - return schema.GroupVersionKind{ - Group: "airshipit.org", - Version: "v1alpha1", - Kind: "ReplacementTransformer", - } +type plugin struct { + *airshipv1.ReplacementTransformer } // New creates new instance of the plugin -func New(cfg []byte) (plugtypes.Plugin, error) { - p := &plugin{} - if err := p.Config(nil, cfg); err != nil { +func New(obj map[string]interface{}) (plugtypes.Plugin, error) { + cfg := &airshipv1.ReplacementTransformer{} + err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj, cfg) + if err != nil { return nil, err } + p := &plugin{ReplacementTransformer: cfg} + for _, r := range p.Replacements { + if r.Source == nil { + return nil, ErrBadConfiguration{Msg: "`from` must be specified in one replacement"} + } + if r.Target == nil { + return nil, ErrBadConfiguration{Msg: "`to` must be specified in one replacement"} + } + if r.Source.ObjRef != nil && r.Source.Value != "" { + return nil, ErrBadConfiguration{Msg: "only one of fieldref and value is allowed in one replacement"} + } + } return p, nil } @@ -82,28 +89,6 @@ func (p *plugin) Run(in io.Reader, out io.Writer) error { return nil } -// Config function reads replacements configuration -func (p *plugin) Config( - _ *resmap.PluginHelpers, c []byte) error { - p.Replacements = []types.Replacement{} - err := yaml.Unmarshal(c, p) - if err != nil { - return err - } - for _, r := range p.Replacements { - if r.Source == nil { - return ErrBadConfiguration{Msg: "`from` must be specified in one replacement"} - } - if r.Target == nil { - return ErrBadConfiguration{Msg: "`to` must be specified in one replacement"} - } - if r.Source.ObjRef != nil && r.Source.Value != "" { - return ErrBadConfiguration{Msg: "only one of fieldref and value is allowed in one replacement"} - } - } - return nil -} - // Transform resources using configured replacements func (p *plugin) Transform(m resmap.ResMap) error { var err error diff --git a/pkg/document/plugin/replacement/v1alpha1/transformer_test.go b/pkg/document/plugin/replacement/transformer_test.go similarity index 97% rename from pkg/document/plugin/replacement/v1alpha1/transformer_test.go rename to pkg/document/plugin/replacement/transformer_test.go index 3fd98d40d..7047fb8f8 100644 --- a/pkg/document/plugin/replacement/v1alpha1/transformer_test.go +++ b/pkg/document/plugin/replacement/transformer_test.go @@ -1,7 +1,7 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package v1alpha1_test +package replacement_test import ( "bytes" @@ -11,12 +11,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - replv1alpha1 "opendev.org/airship/airshipctl/pkg/document/plugin/replacement/v1alpha1" + "sigs.k8s.io/yaml" + + "opendev.org/airship/airshipctl/pkg/document/plugin/replacement" plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types" ) func samplePlugin(t *testing.T) plugtypes.Plugin { - plugin, err := replv1alpha1.New([]byte(` + cfg := make(map[string]interface{}) + conf := ` apiVersion: airshipit.org/v1alpha1 kind: ReplacementTransformer metadata: @@ -28,17 +31,15 @@ replacements: objref: kind: Deployment fieldrefs: - - spec.template.spec.containers[name=nginx-latest].image`)) + - spec.template.spec.containers[name=nginx-latest].image` + err := yaml.Unmarshal([]byte(conf), &cfg) + require.NoError(t, err) + plugin, err := replacement.New(cfg) require.NoError(t, err) return plugin } -func TestMalformedConfig(t *testing.T) { - _, err := replv1alpha1.New([]byte("--")) - assert.Error(t, err) -} - func TestMalformedInput(t *testing.T) { plugin := samplePlugin(t) err := plugin.Run(strings.NewReader("--"), &bytes.Buffer{}) @@ -978,7 +979,7 @@ metadata: name: notImportantHere replacements: - source: - value: 12345678 + value: "12345678" target: objref: kind: KubeadmControlPlane @@ -1011,7 +1012,10 @@ spec: } for _, tc := range testCases { - plugin, err := replv1alpha1.New([]byte(tc.cfg)) + cfg := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(tc.cfg), &cfg) + require.NoError(t, err) + plugin, err := replacement.New(cfg) require.NoError(t, err) buf := &bytes.Buffer{} diff --git a/pkg/document/plugin/replacement/v1alpha1/types.go b/pkg/document/plugin/replacement/v1alpha1/types.go deleted file mode 100644 index f0fb7c7d5..000000000 --- a/pkg/document/plugin/replacement/v1alpha1/types.go +++ /dev/null @@ -1,25 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package v1alpha1 - -import ( - "sigs.k8s.io/kustomize/api/types" -) - -// Find matching image declarations and replace -// the name, tag and/or digest. -type plugin struct { - Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"` -} diff --git a/pkg/document/plugin/run.go b/pkg/document/plugin/run.go index 3c548c6ed..bee266643 100644 --- a/pkg/document/plugin/run.go +++ b/pkg/document/plugin/run.go @@ -27,28 +27,38 @@ import ( "opendev.org/airship/airshipctl/pkg/document/plugin/types" ) -// Registry contains factory functions for the available plugins -var Registry = make(map[schema.GroupVersionKind]types.Factory) - -func init() { - replacement.RegisterPlugin(Registry) - templater.RegisterPlugin(Registry) +// DefaultPlugins returns map with plugin factories +func DefaultPlugins() (map[schema.GroupVersionKind]types.Factory, error) { + registry := make(map[schema.GroupVersionKind]types.Factory) + if err := replacement.RegisterPlugin(registry); err != nil { + return nil, err + } + if err := templater.RegisterPlugin(registry); err != nil { + return nil, err + } + return registry, nil } // ConfigureAndRun executes particular plugin based on group, version, kind // which have been specified in configuration file. Config file should be // supplied as a first element of args slice func ConfigureAndRun(pluginCfg []byte, in io.Reader, out io.Writer) error { - var cfg unstructured.Unstructured - if err := yaml.Unmarshal(pluginCfg, &cfg); err != nil { + rawCfg := make(map[string]interface{}) + if err := yaml.Unmarshal(pluginCfg, &rawCfg); err != nil { return err } - pluginFactory, ok := Registry[cfg.GroupVersionKind()] + uCfg := &unstructured.Unstructured{} + uCfg.SetUnstructuredContent(rawCfg) + registry, err := DefaultPlugins() + if err != nil { + return err + } + pluginFactory, ok := registry[uCfg.GroupVersionKind()] if !ok { - return ErrPluginNotFound{PluginID: cfg.GroupVersionKind()} + return ErrPluginNotFound{PluginID: uCfg.GroupVersionKind()} } - plugin, err := pluginFactory(pluginCfg) + plugin, err := pluginFactory(rawCfg) if err != nil { return err } diff --git a/pkg/document/plugin/templater/init.go b/pkg/document/plugin/templater/init.go index 734e34404..6a7895355 100644 --- a/pkg/document/plugin/templater/init.go +++ b/pkg/document/plugin/templater/init.go @@ -17,11 +17,17 @@ package templater import ( "k8s.io/apimachinery/pkg/runtime/schema" - tmplv1alpha1 "opendev.org/airship/airshipctl/pkg/document/plugin/templater/v1alpha1" + airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/document/plugin/types" ) // RegisterPlugin registers BareMetalHost generator plugin -func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) { - registry[tmplv1alpha1.GetGVK()] = tmplv1alpha1.New +func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) error { + obj := &airshipv1.Templater{} + gvks, _, err := airshipv1.Scheme.ObjectKinds(obj) + if err != nil { + return err + } + registry[gvks[0]] = New + return nil } diff --git a/pkg/document/plugin/templater/v1alpha1/templater.go b/pkg/document/plugin/templater/templater.go similarity index 72% rename from pkg/document/plugin/templater/v1alpha1/templater.go rename to pkg/document/plugin/templater/templater.go index c65c26dd4..a28fc9a8a 100644 --- a/pkg/document/plugin/templater/v1alpha1/templater.go +++ b/pkg/document/plugin/templater/templater.go @@ -12,7 +12,7 @@ limitations under the License. */ -package v1alpha1 +package templater import ( "io" @@ -20,33 +20,31 @@ import ( "github.com/Masterminds/sprig" - "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" + airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types" ) -// GetGVK returns group, version, kind object used to register version -// of the plugin -func GetGVK() schema.GroupVersionKind { - return schema.GroupVersionKind{ - Group: "airshipit.org", - Version: "v1alpha1", - Kind: "Templater", - } +type plugin struct { + *airshipv1.Templater } // New creates new instance of the plugin -func New(cfg []byte) (plugtypes.Plugin, error) { - t := &Templater{} - if err := yaml.Unmarshal(cfg, t); err != nil { +func New(obj map[string]interface{}) (plugtypes.Plugin, error) { + cfg := &airshipv1.Templater{} + err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj, cfg) + if err != nil { return nil, err } - return t, nil + return &plugin{ + Templater: cfg, + }, nil } // Run templater plugin -func (t *Templater) Run(_ io.Reader, out io.Writer) error { +func (t *plugin) Run(_ io.Reader, out io.Writer) error { funcMap := sprig.TxtFuncMap() funcMap["toYaml"] = toYaml tmpl, err := template.New("tmpl").Funcs(funcMap).Parse(t.Template) diff --git a/pkg/document/plugin/templater/v1alpha1/templater_test.go b/pkg/document/plugin/templater/templater_test.go similarity index 89% rename from pkg/document/plugin/templater/v1alpha1/templater_test.go rename to pkg/document/plugin/templater/templater_test.go index d82d12df7..0159bef87 100644 --- a/pkg/document/plugin/templater/v1alpha1/templater_test.go +++ b/pkg/document/plugin/templater/templater_test.go @@ -12,7 +12,7 @@ limitations under the License. */ -package v1alpha1_test +package templater_test import ( "bytes" @@ -20,15 +20,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" - tmplv1alpha1 "opendev.org/airship/airshipctl/pkg/document/plugin/templater/v1alpha1" + "opendev.org/airship/airshipctl/pkg/document/plugin/templater" ) -func TestMalformedConfig(t *testing.T) { - _, err := tmplv1alpha1.New([]byte("--")) - assert.Error(t, err) -} - func TestTemplater(t *testing.T) { testCases := []struct { cfg string @@ -119,7 +115,10 @@ template: | } for _, tc := range testCases { - plugin, err := tmplv1alpha1.New([]byte(tc.cfg)) + cfg := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(tc.cfg), &cfg) + require.NoError(t, err) + plugin, err := templater.New(cfg) require.NoError(t, err) buf := &bytes.Buffer{} err = plugin.Run(nil, buf) diff --git a/pkg/document/plugin/types/plugin.go b/pkg/document/plugin/types/plugin.go index e9a092eae..51f0e7649 100644 --- a/pkg/document/plugin/types/plugin.go +++ b/pkg/document/plugin/types/plugin.go @@ -25,4 +25,4 @@ type Plugin interface { // Factory function for plugins. Functions of such type are used in the plugin // registry to instantiate a plugin object -type Factory func([]byte) (Plugin, error) +type Factory func(map[string]interface{}) (Plugin, error)