Move plugins configurations to API module

Data structure representing plugin configurations should be a part of
airshipctl API module. Plugin implementations will reside in document
package.

Change-Id: Id2e359b747a16a5573052cfb05c1148d346db508
Relates-To: #322
This commit is contained in:
Dmitry Ukov 2020-08-26 18:37:29 +04:00
parent 1660efc231
commit 034efc3682
14 changed files with 206 additions and 115 deletions

View File

@ -49,6 +49,8 @@ func init() {
&ImageConfiguration{}, &ImageConfiguration{},
&RemoteDirectConfiguration{}, &RemoteDirectConfiguration{},
&ClusterMap{}, &ClusterMap{},
&ReplacementTransformer{},
&Templater{},
) )
_ = AddToScheme(Scheme) //nolint:errcheck _ = AddToScheme(Scheme) //nolint:errcheck
} }

View File

@ -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)
}
}
}

View File

@ -16,9 +16,12 @@ package v1alpha1
import ( import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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 { type Templater struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"` metav1.ObjectMeta `json:"metadata,omitempty"`
@ -29,3 +32,13 @@ type Templater struct {
// to be used to render the object defined in Spec field // to be used to render the object defined in Spec field
Template string `json:"template,omitempty"` 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)
}

View File

@ -490,3 +490,39 @@ func (in *RemoteDirectConfiguration) DeepCopyObject() runtime.Object {
} }
return nil 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
}

View File

@ -12,7 +12,7 @@
limitations under the License. limitations under the License.
*/ */
package v1alpha1 package replacement
import ( import (
"fmt" "fmt"

View File

@ -17,11 +17,17 @@ package replacement
import ( import (
"k8s.io/apimachinery/pkg/runtime/schema" "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" "opendev.org/airship/airshipctl/pkg/document/plugin/types"
) )
// RegisterPlugin registers BareMetalHost generator plugin // RegisterPlugin registers BareMetalHost generator plugin
func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) { func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) error {
registry[replv1alpha1.GetGVK()] = replv1alpha1.New obj := &airshipv1.ReplacementTransformer{}
gvks, _, err := airshipv1.Scheme.ObjectKinds(obj)
if err != nil {
return err
}
registry[gvks[0]] = New
return nil
} }

View File

@ -1,7 +1,7 @@
// Copyright 2019 The Kubernetes Authors. // Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package replacement
import ( import (
"fmt" "fmt"
@ -11,13 +11,13 @@ import (
"strconv" "strconv"
"strings" "strings"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types" "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" plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types"
) )
@ -31,22 +31,29 @@ const (
dotReplacer = "$$$$" dotReplacer = "$$$$"
) )
// GetGVK returns group, version, kind object used to register version type plugin struct {
// of the plugin *airshipv1.ReplacementTransformer
func GetGVK() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: "airshipit.org",
Version: "v1alpha1",
Kind: "ReplacementTransformer",
}
} }
// New creates new instance of the plugin // New creates new instance of the plugin
func New(cfg []byte) (plugtypes.Plugin, error) { func New(obj map[string]interface{}) (plugtypes.Plugin, error) {
p := &plugin{} cfg := &airshipv1.ReplacementTransformer{}
if err := p.Config(nil, cfg); err != nil { err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj, cfg)
if err != nil {
return nil, err 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 return p, nil
} }
@ -82,28 +89,6 @@ func (p *plugin) Run(in io.Reader, out io.Writer) error {
return nil 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 // Transform resources using configured replacements
func (p *plugin) Transform(m resmap.ResMap) error { func (p *plugin) Transform(m resmap.ResMap) error {
var err error var err error

View File

@ -1,7 +1,7 @@
// Copyright 2019 The Kubernetes Authors. // Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1_test package replacement_test
import ( import (
"bytes" "bytes"
@ -11,12 +11,15 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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" plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types"
) )
func samplePlugin(t *testing.T) plugtypes.Plugin { func samplePlugin(t *testing.T) plugtypes.Plugin {
plugin, err := replv1alpha1.New([]byte(` cfg := make(map[string]interface{})
conf := `
apiVersion: airshipit.org/v1alpha1 apiVersion: airshipit.org/v1alpha1
kind: ReplacementTransformer kind: ReplacementTransformer
metadata: metadata:
@ -28,17 +31,15 @@ replacements:
objref: objref:
kind: Deployment kind: Deployment
fieldrefs: 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) require.NoError(t, err)
return plugin return plugin
} }
func TestMalformedConfig(t *testing.T) {
_, err := replv1alpha1.New([]byte("--"))
assert.Error(t, err)
}
func TestMalformedInput(t *testing.T) { func TestMalformedInput(t *testing.T) {
plugin := samplePlugin(t) plugin := samplePlugin(t)
err := plugin.Run(strings.NewReader("--"), &bytes.Buffer{}) err := plugin.Run(strings.NewReader("--"), &bytes.Buffer{})
@ -978,7 +979,7 @@ metadata:
name: notImportantHere name: notImportantHere
replacements: replacements:
- source: - source:
value: 12345678 value: "12345678"
target: target:
objref: objref:
kind: KubeadmControlPlane kind: KubeadmControlPlane
@ -1011,7 +1012,10 @@ spec:
} }
for _, tc := range testCases { 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) require.NoError(t, err)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}

View File

@ -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"`
}

View File

@ -27,28 +27,38 @@ import (
"opendev.org/airship/airshipctl/pkg/document/plugin/types" "opendev.org/airship/airshipctl/pkg/document/plugin/types"
) )
// Registry contains factory functions for the available plugins // DefaultPlugins returns map with plugin factories
var Registry = make(map[schema.GroupVersionKind]types.Factory) func DefaultPlugins() (map[schema.GroupVersionKind]types.Factory, error) {
registry := make(map[schema.GroupVersionKind]types.Factory)
func init() { if err := replacement.RegisterPlugin(registry); err != nil {
replacement.RegisterPlugin(Registry) return nil, err
templater.RegisterPlugin(Registry) }
if err := templater.RegisterPlugin(registry); err != nil {
return nil, err
}
return registry, nil
} }
// ConfigureAndRun executes particular plugin based on group, version, kind // ConfigureAndRun executes particular plugin based on group, version, kind
// which have been specified in configuration file. Config file should be // which have been specified in configuration file. Config file should be
// supplied as a first element of args slice // supplied as a first element of args slice
func ConfigureAndRun(pluginCfg []byte, in io.Reader, out io.Writer) error { func ConfigureAndRun(pluginCfg []byte, in io.Reader, out io.Writer) error {
var cfg unstructured.Unstructured rawCfg := make(map[string]interface{})
if err := yaml.Unmarshal(pluginCfg, &cfg); err != nil { if err := yaml.Unmarshal(pluginCfg, &rawCfg); err != nil {
return err 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 { if !ok {
return ErrPluginNotFound{PluginID: cfg.GroupVersionKind()} return ErrPluginNotFound{PluginID: uCfg.GroupVersionKind()}
} }
plugin, err := pluginFactory(pluginCfg) plugin, err := pluginFactory(rawCfg)
if err != nil { if err != nil {
return err return err
} }

View File

@ -17,11 +17,17 @@ package templater
import ( import (
"k8s.io/apimachinery/pkg/runtime/schema" "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" "opendev.org/airship/airshipctl/pkg/document/plugin/types"
) )
// RegisterPlugin registers BareMetalHost generator plugin // RegisterPlugin registers BareMetalHost generator plugin
func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) { func RegisterPlugin(registry map[schema.GroupVersionKind]types.Factory) error {
registry[tmplv1alpha1.GetGVK()] = tmplv1alpha1.New obj := &airshipv1.Templater{}
gvks, _, err := airshipv1.Scheme.ObjectKinds(obj)
if err != nil {
return err
}
registry[gvks[0]] = New
return nil
} }

View File

@ -12,7 +12,7 @@
limitations under the License. limitations under the License.
*/ */
package v1alpha1 package templater
import ( import (
"io" "io"
@ -20,33 +20,31 @@ import (
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types" plugtypes "opendev.org/airship/airshipctl/pkg/document/plugin/types"
) )
// GetGVK returns group, version, kind object used to register version type plugin struct {
// of the plugin *airshipv1.Templater
func GetGVK() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: "airshipit.org",
Version: "v1alpha1",
Kind: "Templater",
}
} }
// New creates new instance of the plugin // New creates new instance of the plugin
func New(cfg []byte) (plugtypes.Plugin, error) { func New(obj map[string]interface{}) (plugtypes.Plugin, error) {
t := &Templater{} cfg := &airshipv1.Templater{}
if err := yaml.Unmarshal(cfg, t); err != nil { err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj, cfg)
if err != nil {
return nil, err return nil, err
} }
return t, nil return &plugin{
Templater: cfg,
}, nil
} }
// Run templater plugin // 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 := sprig.TxtFuncMap()
funcMap["toYaml"] = toYaml funcMap["toYaml"] = toYaml
tmpl, err := template.New("tmpl").Funcs(funcMap).Parse(t.Template) tmpl, err := template.New("tmpl").Funcs(funcMap).Parse(t.Template)

View File

@ -12,7 +12,7 @@
limitations under the License. limitations under the License.
*/ */
package v1alpha1_test package templater_test
import ( import (
"bytes" "bytes"
@ -20,15 +20,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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) { func TestTemplater(t *testing.T) {
testCases := []struct { testCases := []struct {
cfg string cfg string
@ -119,7 +115,10 @@ template: |
} }
for _, tc := range testCases { 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) require.NoError(t, err)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err = plugin.Run(nil, buf) err = plugin.Run(nil, buf)

View File

@ -25,4 +25,4 @@ type Plugin interface {
// Factory function for plugins. Functions of such type are used in the plugin // Factory function for plugins. Functions of such type are used in the plugin
// registry to instantiate a plugin object // registry to instantiate a plugin object
type Factory func([]byte) (Plugin, error) type Factory func(map[string]interface{}) (Plugin, error)