Separate plugins from airshipctl

This commit is contained in:
Ian Howell 2019-05-14 11:24:40 -05:00
parent df6b11515b
commit 6f879da587
11 changed files with 135 additions and 30 deletions

@ -5,9 +5,10 @@ EXECUTABLE_CLI := airshipctl
SCRIPTS_DIR := scripts
PLUGIN_DIR := plugins
PLUGIN_SOURCES := $(wildcard $(PLUGIN_DIR)/*/*.go)
PLUGIN_OBJECTS := $(PLUGIN_SOURCES:%.go=%.so)
PLUGIN_DIR := _plugins
PLUGIN_BIN := $(PLUGIN_DIR)/bin
PLUGIN_INT := $(patsubst $(PLUGIN_DIR)/internal/%,$(PLUGIN_BIN)/%.so,$(wildcard $(PLUGIN_DIR)/internal/*))
PLUGIN_EXT := $(wildcard $(PLUGIN_DIR)/external/*)
# linting
LINTER_CMD := "github.com/golangci/golangci-lint/cmd/golangci-lint" run
@ -56,6 +57,10 @@ clean:
@rm -fr $(BINDIR)
@rm -fr $(COVER_FILE)
.PHONY: plugin-clean
plugin-clean:
@rm -fr $(PLUGIN_BIN)
.PHONY: docs
docs:
@echo "TODO"
@ -67,7 +72,8 @@ update-golden:
@go test $(PKG) $(TESTFLAGS)
.PHONY: plugin
plugin: $(PLUGIN_OBJECTS)
plugin: $(PLUGIN_INT)
@for plugin in $(PLUGIN_EXT); do $(MAKE) -C $${plugin}; done
%.so: %.go
@go build -buildmode=plugin -o $@ $<
$(PLUGIN_BIN)/%.so: $(PLUGIN_DIR)/*/%/*.go
@go build -buildmode=plugin -o $@ $^

22
_plugins/external/example/example.go vendored Normal file

@ -0,0 +1,22 @@
package main
import (
"fmt"
"io"
"github.com/spf13/cobra"
)
//nolint:deadcode,unused,unparam
func NewCommand(out io.Writer, args []string) *cobra.Command {
exampleCommand := &cobra.Command{
Use: "example",
Short: "an example command",
// Hidden is set to true because this is an example
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintln(out, "Hello world!")
},
}
return exampleCommand
}

2
_plugins/external/example/makefile vendored Normal file

@ -0,0 +1,2 @@
all:
@go build -buildmode=plugin -o ../../bin/example.so example.go

@ -13,23 +13,6 @@ import (
"k8s.io/client-go/tools/clientcmd"
)
//nolint:unused
var kubeConfigFilePath string
//nolint:deadcode,unused
func NewCommand(out io.Writer, args []string) *cobra.Command {
workflowRootCmd := &cobra.Command{
Use: "workflow",
Short: "access to workflows",
Aliases: []string{"workflows", "wf"},
}
workflowRootCmd.PersistentFlags().StringVar(&kubeConfigFilePath, "kubeconfig", "", "path to kubeconfig")
workflowRootCmd.AddCommand(NewWorkflowListCommand(out, args))
return workflowRootCmd
}
//nolint:unused
func NewWorkflowListCommand(out io.Writer, args []string) *cobra.Command {

@ -0,0 +1,24 @@
package main
import (
"io"
"github.com/spf13/cobra"
)
//nolint:unused
var kubeConfigFilePath string
//nolint:deadcode,unused
func NewCommand(out io.Writer, args []string) *cobra.Command {
workflowRootCmd := &cobra.Command{
Use: "workflow",
Short: "access to workflows",
Aliases: []string{"workflows", "wf"},
}
workflowRootCmd.PersistentFlags().StringVar(&kubeConfigFilePath, "kubeconfig", "", "path to kubeconfig")
workflowRootCmd.AddCommand(NewWorkflowListCommand(out, args))
return workflowRootCmd
}

@ -5,16 +5,20 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/ian-howell/airshipctl/pkg/environment"
"github.com/ian-howell/airshipctl/pkg/log"
"github.com/ian-howell/airshipctl/pkg/plugin"
"github.com/ian-howell/airshipctl/pkg/util"
"github.com/spf13/cobra"
)
var settings environment.AirshipCTLSettings
const defaultPluginDir = "_plugins/bin"
// NewRootCmd creates the root `airshipctl` command. All other commands are
// subcommands branching from this one
func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) {
@ -29,13 +33,15 @@ func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) {
rootCmd.AddCommand(NewVersionCommand(out))
workflowPlugin := "plugins/internal/workflow.so"
if _, err := os.Stat(workflowPlugin); err == nil {
rootCmd.AddCommand(plugin.CreateCommandFromPlugin(workflowPlugin, out, args))
}
if err := rootCmd.PersistentFlags().Parse(args); err != nil {
return nil, errors.New("could not parse flags: " + err.Error())
if _, err := os.Stat(defaultPluginDir); err == nil {
pluginFiles, err := util.ReadDir(defaultPluginDir)
if err != nil {
return nil, errors.New("could not read plugins: " + err.Error())
}
for _, pluginFile := range pluginFiles {
pathToPlugin := filepath.Join(defaultPluginDir, pluginFile.Name())
rootCmd.AddCommand(plugin.CreateCommandFromPlugin(pathToPlugin, out, args))
}
}
log.Init(&settings, out)

1
pkg/util/test1.txt Normal file

@ -0,0 +1 @@
testdata

1
pkg/util/test2.txt Normal file

@ -0,0 +1 @@
testdata

1
pkg/util/test3.txt Normal file

@ -0,0 +1 @@
testdata

@ -12,3 +12,13 @@ func IsReadable(path string) error {
}
return f.Close()
}
// ReadDir does the same thing as ioutil.ReadDir, but it doesn't sort the files.
func ReadDir(dirname string) ([]os.FileInfo, error) {
f, err := os.Open(dirname)
if err != nil {
return []os.FileInfo{}, err
}
defer f.Close()
return f.Readdir(-1)
}

@ -3,6 +3,7 @@ package util_test
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/ian-howell/airshipctl/pkg/util"
@ -38,3 +39,51 @@ func TestIsReadable(t *testing.T) {
t.Errorf("Expected '%s' error, got '%s'", expected, err.Error())
}
}
func TestReadDir(t *testing.T) {
dir, err := ioutil.TempDir("", "airshipctl-tests")
if err != nil {
t.Fatalf("Could not create a temporary directory: %s", err.Error())
}
defer os.RemoveAll(dir)
testFiles := []string{
"test1.txt",
"test2.txt",
"test3.txt",
}
for _, testFile := range testFiles {
if err := ioutil.WriteFile(filepath.Join(dir, testFile), []byte("testdata"), 0666); err != nil {
t.Fatalf("Could not create test file '%s': %s", testFile, err.Error())
}
}
files, err := util.ReadDir(dir)
if err != nil {
t.Fatalf("Unexpected error while reading directory: %s", err.Error())
}
if len(files) != len(testFiles) {
t.Errorf("Expected %d files, got %d", len(testFiles), len(files))
}
for _, testFile := range testFiles {
found := false
for _, actualFile := range files {
if testFile == actualFile.Name() {
found = true
break
}
}
if !found {
t.Errorf("Could not find test file '%s'", testFile)
}
}
os.RemoveAll(dir)
if _, err := util.ReadDir(dir); err == nil {
t.Error("Expected an error when reading non-existant directory")
}
}