Introduce document plugin subcommand
airship document plugin is intended to be executed as an exec plugin for kustomize document model. Environment variable is used to gather plugin configuration. Plugin to execute is determined based on group-version-kind specified in plugin configuration. Each airship plugin must implement plugin interface. Relates-To: #173 Change-Id: I4f6c3b5be140c0d8fd7519f1cedd33de1cef662c
This commit is contained in:
parent
a0cb765d73
commit
39ee048451
@ -29,6 +29,7 @@ func NewDocumentCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Com
|
||||
|
||||
documentRootCmd.AddCommand(NewDocumentPullCommand(rootSettings))
|
||||
documentRootCmd.AddCommand(NewRenderCommand(rootSettings))
|
||||
documentRootCmd.AddCommand(NewDocumentPluginCommand(rootSettings))
|
||||
|
||||
return documentRootCmd
|
||||
}
|
||||
|
66
cmd/document/plugin.go
Normal file
66
cmd/document/plugin.go
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 document
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/plugin"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
var longDescription = `Subcommand reads configuration file CONFIG passed as
|
||||
a first argument and determines a particular plugin to execute. Additional
|
||||
arguments may be passed to this sub-command abd can be used by the
|
||||
particular plugin. CONFIG file must be structured as kubernetes
|
||||
manifest (i.e. resource) and must have 'apiVersion' and 'kind' keys.
|
||||
|
||||
Example:
|
||||
$ cat /tmp/generator.yaml
|
||||
---
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: BareMetalHostGenerator
|
||||
spec:
|
||||
hostList:
|
||||
- mac: 00:aa:bb:cc:dd
|
||||
powerAddress: redfish+http://1.2.3.4/
|
||||
|
||||
$ airshipctl document plugin /tmp/generator.yaml
|
||||
|
||||
subcommand will try to identify appropriate plugin using apiVersion and
|
||||
kind keys (a.k.a group, version, kind) as an identifier. If appropriate
|
||||
plugin was not found command returns an error.
|
||||
`
|
||||
|
||||
// NewDocumentPluginCommand creates a new command which can act as kustomize
|
||||
// exec plugin.
|
||||
func NewDocumentPluginCommand(rootSetting *environment.AirshipCTLSettings) *cobra.Command {
|
||||
pluginCmd := &cobra.Command{
|
||||
Use: "plugin CONFIG [ARGS]",
|
||||
Short: "used as kustomize exec plugin",
|
||||
Long: longDescription,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cfg, err := ioutil.ReadFile(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return plugin.ConfigureAndRun(rootSetting, cfg, cmd.InOrStdin(), cmd.OutOrStdout())
|
||||
},
|
||||
}
|
||||
return pluginCmd
|
||||
}
|
48
cmd/document/plugin_test.go
Normal file
48
cmd/document/plugin_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 document
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"opendev.org/airship/airshipctl/testutil"
|
||||
)
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
cmdTests := []*testutil.CmdTest{
|
||||
{
|
||||
Name: "document-plugin-cmd-with-help",
|
||||
CmdLine: "--help",
|
||||
Cmd: NewDocumentPluginCommand(nil),
|
||||
},
|
||||
{
|
||||
Name: "document-plugin-cmd-with-empty-args",
|
||||
CmdLine: "",
|
||||
Error: fmt.Errorf("requires at least 1 arg(s), only received 0"),
|
||||
Cmd: NewDocumentPluginCommand(nil),
|
||||
},
|
||||
{
|
||||
Name: "document-plugin-cmd-with-nonexistent-config",
|
||||
CmdLine: "/some/random/path.yaml",
|
||||
Error: fmt.Errorf("open /some/random/path.yaml: no such file or directory"),
|
||||
Cmd: NewDocumentPluginCommand(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cmdTests {
|
||||
testutil.RunTest(t, tt)
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ Usage:
|
||||
|
||||
Available Commands:
|
||||
help Help about any command
|
||||
plugin used as kustomize exec plugin
|
||||
pull pulls documents from remote git repository
|
||||
render Render documents from model
|
||||
|
||||
|
7
cmd/document/testdata/TestPluginGoldenOutput/document-plugin-cmd-with-empty-args.golden
vendored
Normal file
7
cmd/document/testdata/TestPluginGoldenOutput/document-plugin-cmd-with-empty-args.golden
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
Error: requires at least 1 arg(s), only received 0
|
||||
Usage:
|
||||
plugin CONFIG [ARGS] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for plugin
|
||||
|
27
cmd/document/testdata/TestPluginGoldenOutput/document-plugin-cmd-with-help.golden
vendored
Normal file
27
cmd/document/testdata/TestPluginGoldenOutput/document-plugin-cmd-with-help.golden
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Subcommand reads configuration file CONFIG passed as
|
||||
a first argument and determines a particular plugin to execute. Additional
|
||||
arguments may be passed to this sub-command abd can be used by the
|
||||
particular plugin. CONFIG file must be structured as kubernetes
|
||||
manifest (i.e. resource) and must have 'apiVersion' and 'kind' keys.
|
||||
|
||||
Example:
|
||||
$ cat /tmp/generator.yaml
|
||||
---
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: BareMetalHostGenerator
|
||||
spec:
|
||||
hostList:
|
||||
- mac: 00:aa:bb:cc:dd
|
||||
powerAddress: redfish+http://1.2.3.4/
|
||||
|
||||
$ airshipctl document plugin /tmp/generator.yaml
|
||||
|
||||
subcommand will try to identify appropriate plugin using apiVersion and
|
||||
kind keys (a.k.a group, version, kind) as an identifier. If appropriate
|
||||
plugin was not found command returns an error.
|
||||
|
||||
Usage:
|
||||
plugin CONFIG [ARGS] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for plugin
|
@ -0,0 +1,7 @@
|
||||
Error: open /some/random/path.yaml: no such file or directory
|
||||
Usage:
|
||||
plugin CONFIG [ARGS] [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for plugin
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/krusty"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
@ -84,8 +83,11 @@ func NewBundle(fSys FileSystem, kustomizePath string) (Bundle, error) {
|
||||
var o = krusty.Options{
|
||||
DoLegacyResourceSort: true, // Default and what we want
|
||||
LoadRestrictions: options.LoadRestrictions,
|
||||
DoPrune: false, // Default
|
||||
PluginConfig: konfig.DisabledPluginConfig(), // Default
|
||||
DoPrune: false, // Default
|
||||
PluginConfig: &types.PluginConfig{
|
||||
AbsPluginHome: kustomizePath,
|
||||
PluginRestrictions: types.PluginRestrictionsNone,
|
||||
},
|
||||
}
|
||||
|
||||
kustomizer := krusty.MakeKustomizer(fSys, &o)
|
||||
|
32
pkg/document/plugin/errors.go
Normal file
32
pkg/document/plugin/errors.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// ErrPluginNotFound is returned if a plugin was not found in the plugin
|
||||
// registry
|
||||
type ErrPluginNotFound struct {
|
||||
//PluginID group, version and kind plugin identifier
|
||||
PluginID schema.GroupVersionKind
|
||||
}
|
||||
|
||||
func (e ErrPluginNotFound) Error() string {
|
||||
return fmt.Sprintf("plugin identified by %s was not found", e.PluginID.String())
|
||||
}
|
50
pkg/document/plugin/run.go
Normal file
50
pkg/document/plugin/run.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/plugin/types"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
// Registry contains factory functions for the available plugins
|
||||
var Registry = make(map[schema.GroupVersionKind]types.Factory)
|
||||
|
||||
// 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(settings *environment.AirshipCTLSettings, pluginCfg []byte, in io.Reader, out io.Writer) error {
|
||||
var cfg unstructured.Unstructured
|
||||
if err := yaml.Unmarshal(pluginCfg, &cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
pluginFactory, ok := Registry[cfg.GroupVersionKind()]
|
||||
if !ok {
|
||||
return ErrPluginNotFound{PluginID: cfg.GroupVersionKind()}
|
||||
}
|
||||
|
||||
plugin, err := pluginFactory(settings, pluginCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return plugin.Run(in, out)
|
||||
}
|
61
pkg/document/plugin/run_test.go
Normal file
61
pkg/document/plugin/run_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
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 plugin_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/plugin"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
func TestConfigureAndRun(t *testing.T) {
|
||||
testCases := []struct {
|
||||
pluginCfg []byte
|
||||
settings *environment.AirshipCTLSettings
|
||||
expectedError string
|
||||
in io.Reader
|
||||
out io.Writer
|
||||
}{
|
||||
{
|
||||
pluginCfg: []byte(""),
|
||||
expectedError: "plugin identified by /, Kind= was not found",
|
||||
},
|
||||
{
|
||||
pluginCfg: []byte(`---
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: UnknownPlugin
|
||||
spec:
|
||||
someField: someValue`),
|
||||
expectedError: "plugin identified by airshipit.org/v1alpha1, Kind=UnknownPlugin was not found",
|
||||
},
|
||||
{
|
||||
pluginCfg: []byte(`---
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: BareMetalGenereator
|
||||
spec: -
|
||||
someField: someValu`),
|
||||
expectedError: "error converting YAML to JSON: yaml: line 4: block sequence entries are not allowed in this context",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := plugin.ConfigureAndRun(tc.settings, tc.pluginCfg, tc.in, tc.out)
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
}
|
||||
}
|
30
pkg/document/plugin/types/plugin.go
Normal file
30
pkg/document/plugin/types/plugin.go
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 types
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
// Plugin interface for airship document plugins
|
||||
type Plugin interface {
|
||||
Run(io.Reader, io.Writer) error
|
||||
}
|
||||
|
||||
// Factory function for plugins. Functions of such type are used in the plugin
|
||||
// registry to instantiate a plugin object
|
||||
type Factory func(*environment.AirshipCTLSettings, []byte) (Plugin, error)
|
Loading…
Reference in New Issue
Block a user