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{},
&RemoteDirectConfiguration{},
&ClusterMap{},
&ReplacementTransformer{},
&Templater{},
)
_ = 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 (
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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

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"
)
// 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
}

View File

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

View File

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

View File

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

View File

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