diff --git a/Makefile b/Makefile index 8c4b02e..40d38ed 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,6 @@ GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(G XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GOTESTSUM_PACKAGE ?= gotest.tools/gotestsum@latest -GENERATE ?= XGO_VERSION := go-1.22.x XGO_TARGETS ?= linux/amd64,linux/arm64 @@ -65,11 +64,7 @@ lint: golangci-lint .PHONY: generate generate: - $(GO) generate $(GENERATE) - -.PHONY: generate-docs -generate-docs: - $(GO) generate ./cmd/$(EXECUTABLE)/flags.go + $(GO) generate $(PACKAGES) .PHONY: test test: diff --git a/cmd/wp-ansible/docs.go b/cmd/wp-ansible/docs.go deleted file mode 100644 index 5c33a2d..0000000 --- a/cmd/wp-ansible/docs.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build generate -// +build generate - -package main - -import ( - "bytes" - "embed" - "fmt" - "os" - "text/template" - - "github.com/thegeeklab/wp-ansible/plugin" - "github.com/thegeeklab/wp-plugin-go/docs" - wp "github.com/thegeeklab/wp-plugin-go/plugin" - wp_template "github.com/thegeeklab/wp-plugin-go/template" - "github.com/urfave/cli/v2" -) - -//go:embed templates/docs-data.yaml.tmpl -var yamlTemplate embed.FS - -func main() { - settings := &plugin.Settings{} - app := &cli.App{ - Flags: settingsFlags(settings, wp.FlagsPluginCategory), - } - - out, err := toYAML(app) - if err != nil { - panic(err) - } - - fi, err := os.Create("../../docs/data/data-raw.yaml") - if err != nil { - panic(err) - } - defer fi.Close() - if _, err := fi.WriteString(out); err != nil { - panic(err) - } -} - -func toYAML(app *cli.App) (string, error) { - var w bytes.Buffer - - yamlTmpl, err := template.New("docs").Funcs(wp_template.LoadFuncMap()).ParseFS(yamlTemplate, "templates/docs-data.yaml.tmpl") - if err != nil { - fmt.Println(yamlTmpl) - return "", err - } - - if err := yamlTmpl.ExecuteTemplate(&w, "docs-data.yaml.tmpl", docs.GetTemplateData(app)); err != nil { - return "", err - } - - return w.String(), nil -} diff --git a/cmd/wp-ansible/flags.go b/cmd/wp-ansible/flags.go deleted file mode 100644 index 44b7eb1..0000000 --- a/cmd/wp-ansible/flags.go +++ /dev/null @@ -1,248 +0,0 @@ -package main - -import ( - "github.com/thegeeklab/wp-ansible/plugin" - "github.com/urfave/cli/v2" -) - -// SettingsFlags has the cli.Flags for the plugin.Settings. -// -//go:generate go run docs.go flags.go -func settingsFlags(settings *plugin.Settings, category string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: "python-requirements", - Usage: "path to python requirements file", - EnvVars: []string{"PLUGIN_PYTHON_REQUIREMENTS"}, - Destination: &settings.PythonRequirements, - Category: category, - }, - &cli.StringFlag{ - Name: "galaxy-requirements", - Usage: "path to galaxy requirements file", - EnvVars: []string{"PLUGIN_GALAXY_REQUIREMENTS"}, - Destination: &settings.GalaxyRequirements, - Category: category, - }, - &cli.StringSliceFlag{ - Name: "inventory", - Usage: "path to inventory file", - EnvVars: []string{"PLUGIN_INVENTORY", "PLUGIN_INVENTORIES"}, - Required: true, - Destination: &settings.Inventories, - Category: category, - }, - &cli.StringSliceFlag{ - Name: "playbook", - Usage: "list of playbooks to apply", - EnvVars: []string{"PLUGIN_PLAYBOOK", "PLUGIN_PLAYBOOKS"}, - Required: true, - Destination: &settings.Playbooks, - Category: category, - }, - &cli.StringFlag{ - Name: "limit", - Usage: "limit selected hosts to an additional pattern", - EnvVars: []string{"PLUGIN_LIMIT"}, - Destination: &settings.Limit, - Category: category, - }, - &cli.StringFlag{ - Name: "skip-tags", - Usage: "only run plays and tasks whose tags do not match", - EnvVars: []string{"PLUGIN_SKIP_TAGS"}, - Destination: &settings.SkipTags, - Category: category, - }, - &cli.StringFlag{ - Name: "start-at-task", - Usage: "start the playbook at the task matching this name", - EnvVars: []string{"PLUGIN_START_AT_TASK"}, - Destination: &settings.StartAtTask, - Category: category, - }, - &cli.StringFlag{ - Name: "tags", - Usage: "only run plays and tasks tagged with these values", - EnvVars: []string{"PLUGIN_TAGS"}, - Destination: &settings.Tags, - Category: category, - }, - &cli.StringSliceFlag{ - Name: "extra-vars", - Usage: "set additional variables as `key=value`", - EnvVars: []string{"PLUGIN_EXTRA_VARS", "ANSIBLE_EXTRA_VARS"}, - Destination: &settings.ExtraVars, - Category: category, - }, - &cli.StringSliceFlag{ - Name: "module-path", - Usage: "prepend paths to module library", - EnvVars: []string{"PLUGIN_MODULE_PATH"}, - Destination: &settings.ModulePath, - Category: category, - }, - &cli.BoolFlag{ - Name: "check", - Usage: "run a check, do not apply any changes", - EnvVars: []string{"PLUGIN_CHECK"}, - Destination: &settings.Check, - Category: category, - }, - &cli.BoolFlag{ - Name: "diff", - Usage: "show the differences, may print secrets", - EnvVars: []string{"PLUGIN_DIFF"}, - Destination: &settings.Diff, - Category: category, - }, - &cli.BoolFlag{ - Name: "flush-cache", - Usage: "clear the fact cache for every host in inventory", - EnvVars: []string{"PLUGIN_FLUSH_CACHE"}, - Destination: &settings.FlushCache, - Category: category, - }, - &cli.BoolFlag{ - Name: "force-handlers", - Usage: "run handlers even if a task fails", - EnvVars: []string{"PLUGIN_FORCE_HANDLERS"}, - Destination: &settings.ForceHandlers, - Category: category, - }, - &cli.BoolFlag{ - Name: "list-hosts", - Usage: "outputs a list of matching hosts", - EnvVars: []string{"PLUGIN_LIST_HOSTS"}, - Destination: &settings.ListHosts, - Category: category, - }, - &cli.BoolFlag{ - Name: "list-tags", - Usage: "list all available tags", - EnvVars: []string{"PLUGIN_LIST_TAGS"}, - Destination: &settings.ListTags, - Category: category, - }, - &cli.BoolFlag{ - Name: "list-tasks", - Usage: "list all tasks that would be executed", - EnvVars: []string{"PLUGIN_LIST_TASKS"}, - Destination: &settings.ListTasks, - Category: category, - }, - &cli.BoolFlag{ - Name: "syntax-check", - Usage: "perform a syntax check on the playbook", - EnvVars: []string{"PLUGIN_SYNTAX_CHECK"}, - Destination: &settings.SyntaxCheck, - Category: category, - }, - &cli.IntFlag{ - Name: "forks", - Usage: "specify number of parallel processes to use", - EnvVars: []string{"PLUGIN_FORKS"}, - Value: plugin.AnsibleForksDefault, - Destination: &settings.Forks, - Category: category, - }, - &cli.StringFlag{ - Name: "vault-id", - Usage: "the vault identity to use", - EnvVars: []string{"PLUGIN_VAULT_ID", "ANSIBLE_VAULT_ID"}, - Destination: &settings.VaultID, - Category: category, - }, - &cli.StringFlag{ - Name: "vault-password", - Usage: "the vault password to use", - EnvVars: []string{"PLUGIN_VAULT_PASSWORD", "ANSIBLE_VAULT_PASSWORD"}, - Destination: &settings.VaultPassword, - Category: category, - }, - &cli.IntFlag{ - Name: "verbose", - Usage: "level of verbosity, 0 up to 4", - EnvVars: []string{"PLUGIN_VERBOSE"}, - Destination: &settings.Verbose, - Category: category, - }, - &cli.StringFlag{ - Name: "private-key", - Usage: "SSH private key used to authenticate the connection", - EnvVars: []string{"PLUGIN_PRIVATE_KEY", "ANSIBLE_PRIVATE_KEY"}, - Destination: &settings.PrivateKey, - Category: category, - }, - &cli.StringFlag{ - Name: "user", - Usage: "connect as this user", - EnvVars: []string{"PLUGIN_USER", "ANSIBLE_USER"}, - Destination: &settings.User, - Category: category, - }, - &cli.StringFlag{ - Name: "connection", - Usage: "connection type to use", - EnvVars: []string{"PLUGIN_CONNECTION"}, - Destination: &settings.Connection, - Category: category, - }, - &cli.IntFlag{ - Name: "timeout", - Usage: "override the connection timeout in seconds", - EnvVars: []string{"PLUGIN_TIMEOUT"}, - Destination: &settings.Timeout, - Category: category, - }, - &cli.StringFlag{ - Name: "ssh-common-args", - Usage: "specify common arguments to pass to SFTP, SCP and SSH connections", - EnvVars: []string{"PLUGIN_SSH_COMMON_ARGS"}, - Destination: &settings.SSHCommonArgs, - Category: category, - }, - &cli.StringFlag{ - Name: "sftp-extra-args", - Usage: "specify extra arguments to pass to SFTP connections only", - EnvVars: []string{"PLUGIN_SFTP_EXTRA_ARGS"}, - Destination: &settings.SFTPExtraArgs, - Category: category, - }, - &cli.StringFlag{ - Name: "scp-extra-args", - Usage: "specify extra arguments to pass to SCP connections only", - EnvVars: []string{"PLUGIN_SCP_EXTRA_ARGS"}, - Destination: &settings.SCPExtraArgs, - Category: category, - }, - &cli.StringFlag{ - Name: "ssh-extra-args", - Usage: "specify extra arguments to pass to SSH connections only", - EnvVars: []string{"PLUGIN_SSH_EXTRA_ARGS"}, - Destination: &settings.SSHExtraArgs, - Category: category, - }, - &cli.BoolFlag{ - Name: "become", - Usage: "enable privilege escalation", - EnvVars: []string{"PLUGIN_BECOME"}, - Destination: &settings.Become, - Category: category, - }, - &cli.StringFlag{ - Name: "become-method", - Usage: "privilege escalation method to use", - EnvVars: []string{"PLUGIN_BECOME_METHOD", "ANSIBLE_BECOME_METHOD"}, - Destination: &settings.BecomeMethod, - Category: category, - }, - &cli.StringFlag{ - Name: "become-user", - Usage: "privilege escalation user to use", - EnvVars: []string{"PLUGIN_BECOME_USER", "ANSIBLE_BECOME_USER"}, - Destination: &settings.BecomeUser, - Category: category, - }, - } -} diff --git a/cmd/wp-ansible/main.go b/cmd/wp-ansible/main.go index 4b0877a..fd368aa 100644 --- a/cmd/wp-ansible/main.go +++ b/cmd/wp-ansible/main.go @@ -1,11 +1,7 @@ package main import ( - "fmt" - "github.com/thegeeklab/wp-ansible/plugin" - - wp "github.com/thegeeklab/wp-plugin-go/plugin" ) //nolint:gochecknoglobals @@ -15,14 +11,5 @@ var ( ) func main() { - settings := &plugin.Settings{} - options := wp.Options{ - Name: "wp-ansible", - Description: "Manage infrastructure with Ansible", - Version: BuildVersion, - VersionMetadata: fmt.Sprintf("date=%s", BuildDate), - Flags: settingsFlags(settings, wp.FlagsPluginCategory), - } - - plugin.New(options, settings).Run() + plugin.New(nil, BuildVersion, BuildDate).Run() } diff --git a/cmd/wp-ansible/templates/docs-data.yaml.tmpl b/cmd/wp-ansible/templates/docs-data.yaml.tmpl deleted file mode 100644 index e453a95..0000000 --- a/cmd/wp-ansible/templates/docs-data.yaml.tmpl +++ /dev/null @@ -1,18 +0,0 @@ ---- -{{- if .GlobalArgs }} -properties: -{{- range $v := .GlobalArgs }} - - name: {{ $v.Name }} - {{- with $v.Description }} - description: | - {{ . | ToSentence }} - {{- end }} - {{- with $v.Type }} - type: {{ . }} - {{- end }} - {{- with $v.Default }} - defaultValue: {{ . }} - {{- end }} - required: {{ default false $v.Required }} -{{ end -}} -{{ end -}} diff --git a/docs/content/_index.md b/docs/content/_index.md index d6170f9..a43aa69 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -29,6 +29,7 @@ steps: private_key: from_secret: ansible_private_key inventory: deployment/hosts.yml + syntax_check: true ``` ### Parameters @@ -57,9 +58,10 @@ docker build --file Containerfile.multiarch --tag thegeeklab/wp-ansible . ```Shell docker run --rm \ - -e PLUGIN_PLAYBOOK=deployment/playbook.yml \ + -e PLUGIN_PLAYBOOK=playbook.yml \ + -e PLUGIN_INVENTORY=inventory.yml \ -e PLUGIN_SYNTAX_CHECK=true \ - -v $(pwd):/build:z \ + -v $(pwd)/testdata:/build:z \ -w /build \ thegeeklab/wp-ansible ``` diff --git a/docs/data/data.yaml b/docs/data/data.yaml index 71a4668..bcea49a 100644 --- a/docs/data/data.yaml +++ b/docs/data/data.yaml @@ -72,6 +72,13 @@ properties: type: string required: false + - name: insecure_skip_verify + description: | + Skip SSL verification. + type: bool + defaultValue: false + required: false + - name: inventory description: | Path to inventory file. @@ -105,6 +112,13 @@ properties: defaultValue: false required: false + - name: log_level + description: | + Plugin log level. + type: string + defaultValue: "info" + required: false + - name: module_path description: | Prepend paths to module library. diff --git a/go.mod b/go.mod index cbb5345..ed400ff 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/thegeeklab/wp-ansible go 1.22 require ( - github.com/thegeeklab/wp-plugin-go v1.7.1 + github.com/rs/zerolog v1.32.0 + github.com/thegeeklab/wp-plugin-go v1.8.0 + github.com/thegeeklab/wp-plugin-go/v2 v2.0.0 github.com/urfave/cli/v2 v2.27.2 golang.org/x/sys v0.19.0 ) @@ -21,7 +23,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect - github.com/rs/zerolog v1.32.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect diff --git a/go.sum b/go.sum index a4b3a56..ce55f13 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,10 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/thegeeklab/wp-plugin-go v1.7.1 h1:zfR/rfNPuyVhXJu1fsLfp4+Mz2pTf6WwW/mIqw9750I= -github.com/thegeeklab/wp-plugin-go v1.7.1/go.mod h1:Ixi5plt9tpFGTu6yc/Inm5DcDpp3xPTeohfr86gf2EU= +github.com/thegeeklab/wp-plugin-go v1.8.0 h1:hmXXMRYUauSKN7QaWsQBzufzGVfrViJaDJisKh7JeKU= +github.com/thegeeklab/wp-plugin-go v1.8.0/go.mod h1:OvXWizBaHdZ77KTQKmJI6ssg33dy3eoG0t81rt5AgfY= +github.com/thegeeklab/wp-plugin-go/v2 v2.0.0 h1:7dChME92qqpdtvDM4O2FZOoRv3jfkjlHCw/ijbE9SxQ= +github.com/thegeeklab/wp-plugin-go/v2 v2.0.0/go.mod h1:KRfDolkPSpO7Zx54Y0ofTFA7Cvd+7bHTHzYnYAo9WYg= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= diff --git a/internal/docs/main.go b/internal/docs/main.go new file mode 100644 index 0000000..f085746 --- /dev/null +++ b/internal/docs/main.go @@ -0,0 +1,42 @@ +//go:build generate +// +build generate + +package main + +import ( + "context" + "flag" + "net/http" + "os" + "time" + + "github.com/thegeeklab/wp-ansible/plugin" + "github.com/thegeeklab/wp-plugin-go/docs" + wp_template "github.com/thegeeklab/wp-plugin-go/template" +) + +func main() { + tmpl := "https://raw.githubusercontent.com/thegeeklab/woodpecker-plugins/main/templates/docs-data.yaml.tmpl" + client := http.Client{ + Timeout: 30 * time.Second, + } + + p := plugin.New(nil) + + out, err := wp_template.Render(context.Background(), client, tmpl, docs.GetTemplateData(p.App)) + if err != nil { + panic(err) + } + + outputFile := flag.String("output", "", "Output file path") + flag.Parse() + + if *outputFile == "" { + panic("no output file specified") + } + + err = os.WriteFile(*outputFile, []byte(out), 0o644) + if err != nil { + panic(err) + } +} diff --git a/plugin/ansible.go b/plugin/ansible.go index a5b56e7..8699862 100644 --- a/plugin/ansible.go +++ b/plugin/ansible.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" "golang.org/x/sys/execabs" ) @@ -31,8 +32,11 @@ const ansibleContent = ` host_key_checking = False ` -var ErrAnsiblePlaybookNotFound = errors.New("playbook not found") +var ErrAnsiblePlaybookNotFound = errors.New("no playbook found") +// ansibleConfig creates the Ansible configuration directory and file. +// It ensures the directory exists and writes the Ansible configuration +// content to the config file with strict file permissions. func (p *Plugin) ansibleConfig() error { if err := os.MkdirAll(ansibleFolder, os.ModePerm); err != nil { return fmt.Errorf("failed to create ansible directory: %w", err) @@ -45,6 +49,9 @@ func (p *Plugin) ansibleConfig() error { return nil } +// privateKey creates a temporary file containing the private key specified in the plugin settings, +// and sets the PrivateKeyFile field in the plugin settings to the name of the temporary file. +// This is used to pass the private key to the Ansible command. func (p *Plugin) privateKey() error { tmpfile, err := os.CreateTemp("", "privateKey") if err != nil { @@ -64,6 +71,9 @@ func (p *Plugin) privateKey() error { return nil } +// vaultPass creates a temporary file containing the vault password and sets the VaultPasswordFile +// field in the Plugin's Settings. This allows the vault password to be used when running +// Ansible commands that require it. func (p *Plugin) vaultPass() error { tmpfile, err := os.CreateTemp("", "vaultPass") if err != nil { @@ -83,13 +93,16 @@ func (p *Plugin) vaultPass() error { return nil } -func (p *Plugin) playbooks() error { +// getPlaybooks retrieves a list of playbook files based on the configured playbook patterns. +// If any of the patterns fail to match any files, the original pattern is included in the list. +// If no playbooks are found, ErrAnsiblePlaybookNotFound is returned. +func (p *Plugin) getPlaybooks() error { var playbooks []string - for _, p := range p.Settings.Playbooks.Value() { - files, err := filepath.Glob(p) + for _, pb := range p.Settings.Playbooks.Value() { + files, err := filepath.Glob(pb) if err != nil { - playbooks = append(playbooks, p) + playbooks = append(playbooks, pb) continue } @@ -98,6 +111,8 @@ func (p *Plugin) playbooks() error { } if len(playbooks) == 0 { + log.Debug().Strs("patterns", p.Settings.Playbooks.Value()).Msg("no playbooks found") + return ErrAnsiblePlaybookNotFound } @@ -106,18 +121,20 @@ func (p *Plugin) playbooks() error { return nil } -func (p *Plugin) versionCommand() *execabs.Cmd { +func (p *Plugin) versionCommand() *Cmd { args := []string{ "--version", } - return execabs.Command( - ansibleBin, - args..., - ) + return &Cmd{ + Cmd: execabs.Command(ansibleBin, args...), + } } -func (p *Plugin) pythonRequirementsCommand() *execabs.Cmd { +// pythonRequirementsCommand returns an execabs.Cmd that runs the pip install +// command with the specified Python requirements file and upgrades any existing +// packages. +func (p *Plugin) pythonRequirementsCommand() *Cmd { args := []string{ "install", "--upgrade", @@ -125,13 +142,14 @@ func (p *Plugin) pythonRequirementsCommand() *execabs.Cmd { p.Settings.PythonRequirements, } - return execabs.Command( - pipBin, - args..., - ) + return &Cmd{ + Cmd: execabs.Command(pipBin, args...), + } } -func (p *Plugin) galaxyRequirementsCommand() *execabs.Cmd { +// galaxyRequirementsCommand returns an execabs.Cmd that runs the Ansible Galaxy +// install command with the specified role file and verbosity level. +func (p *Plugin) galaxyRequirementsCommand() *Cmd { args := []string{ "install", "--force", @@ -143,13 +161,14 @@ func (p *Plugin) galaxyRequirementsCommand() *execabs.Cmd { args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.Settings.Verbose))) } - return execabs.Command( - ansibleGalaxyBin, - args..., - ) + return &Cmd{ + Cmd: execabs.Command(ansibleGalaxyBin, args...), + } } -func (p *Plugin) ansibleCommand(inventory string) *execabs.Cmd { +// ansibleCommand returns an execabs.Cmd that runs the Ansible playbook with the +// specified inventory file and various configuration options set on the Plugin struct. +func (p *Plugin) ansibleCommand(inventory string) *Cmd { args := []string{ "--inventory", inventory, @@ -175,20 +194,18 @@ func (p *Plugin) ansibleCommand(inventory string) *execabs.Cmd { args = append(args, "--list-hosts") args = append(args, p.Settings.Playbooks.Value()...) - return execabs.Command( - ansiblePlaybookBin, - args..., - ) + return &Cmd{ + Cmd: execabs.Command(ansiblePlaybookBin, args...), + } } if p.Settings.SyntaxCheck { args = append(args, "--syntax-check") args = append(args, p.Settings.Playbooks.Value()...) - return execabs.Command( - ansiblePlaybookBin, - args..., - ) + return &Cmd{ + Cmd: execabs.Command(ansiblePlaybookBin, args...), + } } if p.Settings.Check { @@ -285,8 +302,8 @@ func (p *Plugin) ansibleCommand(inventory string) *execabs.Cmd { args = append(args, p.Settings.Playbooks.Value()...) - return execabs.Command( - ansiblePlaybookBin, - args..., - ) + return &Cmd{ + Cmd: execabs.Command(ansiblePlaybookBin, args...), + Private: false, + } } diff --git a/plugin/impl.go b/plugin/impl.go index b593583..508e44c 100644 --- a/plugin/impl.go +++ b/plugin/impl.go @@ -5,8 +5,7 @@ import ( "fmt" "os" - "github.com/thegeeklab/wp-plugin-go/trace" - "golang.org/x/sys/execabs" + "github.com/thegeeklab/wp-plugin-go/v2/trace" ) func (p *Plugin) run(_ context.Context) error { @@ -28,7 +27,10 @@ func (p *Plugin) Validate() error { // Execute provides the implementation of the plugin. func (p *Plugin) Execute() error { - if err := p.playbooks(); err != nil { + batchCmd := make([]*Cmd, 0) + batchCmd = append(batchCmd, p.versionCommand()) + + if err := p.getPlaybooks(); err != nil { return err } @@ -52,32 +54,27 @@ func (p *Plugin) Execute() error { defer os.Remove(p.Settings.VaultPasswordFile) } - commands := []*execabs.Cmd{ - p.versionCommand(), - } - if p.Settings.PythonRequirements != "" { - commands = append(commands, p.pythonRequirementsCommand()) + batchCmd = append(batchCmd, p.pythonRequirementsCommand()) } if p.Settings.GalaxyRequirements != "" { - commands = append(commands, p.galaxyRequirementsCommand()) + batchCmd = append(batchCmd, p.galaxyRequirementsCommand()) } for _, inventory := range p.Settings.Inventories.Value() { - commands = append(commands, p.ansibleCommand(inventory)) + batchCmd = append(batchCmd, p.ansibleCommand(inventory)) } - for _, cmd := range commands { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + for _, bc := range batchCmd { + bc.Stdout = os.Stdout + bc.Stderr = os.Stderr + trace.Cmd(bc.Cmd) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "ANSIBLE_FORCE_COLOR=1") + bc.Env = os.Environ() + bc.Env = append(bc.Env, "ANSIBLE_FORCE_COLOR=1") - trace.Cmd(cmd) - - if err := cmd.Run(); err != nil { + if err := bc.Run(); err != nil { return err } } diff --git a/plugin/plugin.go b/plugin/plugin.go index bf35fe4..79d6cac 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -1,10 +1,15 @@ package plugin import ( - wp "github.com/thegeeklab/wp-plugin-go/plugin" + "fmt" + + wp "github.com/thegeeklab/wp-plugin-go/v2/plugin" "github.com/urfave/cli/v2" + "golang.org/x/sys/execabs" ) +//go:generate go run ../internal/docs/main.go -output=../docs/data/data-raw.yaml + // Plugin implements provide the plugin. type Plugin struct { *wp.Plugin @@ -50,15 +55,277 @@ type Settings struct { BecomeUser string } -func New(options wp.Options, settings *Settings) *Plugin { - p := &Plugin{} +type Cmd struct { + *execabs.Cmd + Private bool +} - if options.Execute == nil { - options.Execute = p.run +func New(e wp.ExecuteFunc, build ...string) *Plugin { + p := &Plugin{ + Settings: &Settings{}, + } + + options := wp.Options{ + Name: "wp-ansible", + Description: "Manage infrastructure with Ansible", + Flags: Flags(p.Settings, wp.FlagsPluginCategory), + Execute: p.run, + HideWoodpeckerFlags: true, + } + + if len(build) > 0 { + options.Version = build[0] + } + + if len(build) > 1 { + options.VersionMetadata = fmt.Sprintf("date=%s", build[1]) + } + + if e != nil { + options.Execute = e } p.Plugin = wp.New(options) - p.Settings = settings return p } + +// Flags returns a slice of CLI flags for the plugin. +func Flags(settings *Settings, category string) []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "python-requirements", + Usage: "path to python requirements file", + EnvVars: []string{"PLUGIN_PYTHON_REQUIREMENTS"}, + Destination: &settings.PythonRequirements, + Category: category, + }, + &cli.StringFlag{ + Name: "galaxy-requirements", + Usage: "path to galaxy requirements file", + EnvVars: []string{"PLUGIN_GALAXY_REQUIREMENTS"}, + Destination: &settings.GalaxyRequirements, + Category: category, + }, + &cli.StringSliceFlag{ + Name: "inventory", + Usage: "path to inventory file", + EnvVars: []string{"PLUGIN_INVENTORY", "PLUGIN_INVENTORIES"}, + Required: true, + Destination: &settings.Inventories, + Category: category, + }, + &cli.StringSliceFlag{ + Name: "playbook", + Usage: "list of playbooks to apply", + EnvVars: []string{"PLUGIN_PLAYBOOK", "PLUGIN_PLAYBOOKS"}, + Required: true, + Destination: &settings.Playbooks, + Category: category, + }, + &cli.StringFlag{ + Name: "limit", + Usage: "limit selected hosts to an additional pattern", + EnvVars: []string{"PLUGIN_LIMIT"}, + Destination: &settings.Limit, + Category: category, + }, + &cli.StringFlag{ + Name: "skip-tags", + Usage: "only run plays and tasks whose tags do not match", + EnvVars: []string{"PLUGIN_SKIP_TAGS"}, + Destination: &settings.SkipTags, + Category: category, + }, + &cli.StringFlag{ + Name: "start-at-task", + Usage: "start the playbook at the task matching this name", + EnvVars: []string{"PLUGIN_START_AT_TASK"}, + Destination: &settings.StartAtTask, + Category: category, + }, + &cli.StringFlag{ + Name: "tags", + Usage: "only run plays and tasks tagged with these values", + EnvVars: []string{"PLUGIN_TAGS"}, + Destination: &settings.Tags, + Category: category, + }, + &cli.StringSliceFlag{ + Name: "extra-vars", + Usage: "set additional variables as `key=value`", + EnvVars: []string{"PLUGIN_EXTRA_VARS", "ANSIBLE_EXTRA_VARS"}, + Destination: &settings.ExtraVars, + Category: category, + }, + &cli.StringSliceFlag{ + Name: "module-path", + Usage: "prepend paths to module library", + EnvVars: []string{"PLUGIN_MODULE_PATH"}, + Destination: &settings.ModulePath, + Category: category, + }, + &cli.BoolFlag{ + Name: "check", + Usage: "run a check, do not apply any changes", + EnvVars: []string{"PLUGIN_CHECK"}, + Destination: &settings.Check, + Category: category, + }, + &cli.BoolFlag{ + Name: "diff", + Usage: "show the differences, may print secrets", + EnvVars: []string{"PLUGIN_DIFF"}, + Destination: &settings.Diff, + Category: category, + }, + &cli.BoolFlag{ + Name: "flush-cache", + Usage: "clear the fact cache for every host in inventory", + EnvVars: []string{"PLUGIN_FLUSH_CACHE"}, + Destination: &settings.FlushCache, + Category: category, + }, + &cli.BoolFlag{ + Name: "force-handlers", + Usage: "run handlers even if a task fails", + EnvVars: []string{"PLUGIN_FORCE_HANDLERS"}, + Destination: &settings.ForceHandlers, + Category: category, + }, + &cli.BoolFlag{ + Name: "list-hosts", + Usage: "outputs a list of matching hosts", + EnvVars: []string{"PLUGIN_LIST_HOSTS"}, + Destination: &settings.ListHosts, + Category: category, + }, + &cli.BoolFlag{ + Name: "list-tags", + Usage: "list all available tags", + EnvVars: []string{"PLUGIN_LIST_TAGS"}, + Destination: &settings.ListTags, + Category: category, + }, + &cli.BoolFlag{ + Name: "list-tasks", + Usage: "list all tasks that would be executed", + EnvVars: []string{"PLUGIN_LIST_TASKS"}, + Destination: &settings.ListTasks, + Category: category, + }, + &cli.BoolFlag{ + Name: "syntax-check", + Usage: "perform a syntax check on the playbook", + EnvVars: []string{"PLUGIN_SYNTAX_CHECK"}, + Destination: &settings.SyntaxCheck, + Category: category, + }, + &cli.IntFlag{ + Name: "forks", + Usage: "specify number of parallel processes to use", + EnvVars: []string{"PLUGIN_FORKS"}, + Value: AnsibleForksDefault, + Destination: &settings.Forks, + Category: category, + }, + &cli.StringFlag{ + Name: "vault-id", + Usage: "the vault identity to use", + EnvVars: []string{"PLUGIN_VAULT_ID", "ANSIBLE_VAULT_ID"}, + Destination: &settings.VaultID, + Category: category, + }, + &cli.StringFlag{ + Name: "vault-password", + Usage: "the vault password to use", + EnvVars: []string{"PLUGIN_VAULT_PASSWORD", "ANSIBLE_VAULT_PASSWORD"}, + Destination: &settings.VaultPassword, + Category: category, + }, + &cli.IntFlag{ + Name: "verbose", + Usage: "level of verbosity, 0 up to 4", + EnvVars: []string{"PLUGIN_VERBOSE"}, + Destination: &settings.Verbose, + Category: category, + }, + &cli.StringFlag{ + Name: "private-key", + Usage: "SSH private key used to authenticate the connection", + EnvVars: []string{"PLUGIN_PRIVATE_KEY", "ANSIBLE_PRIVATE_KEY"}, + Destination: &settings.PrivateKey, + Category: category, + }, + &cli.StringFlag{ + Name: "user", + Usage: "connect as this user", + EnvVars: []string{"PLUGIN_USER", "ANSIBLE_USER"}, + Destination: &settings.User, + Category: category, + }, + &cli.StringFlag{ + Name: "connection", + Usage: "connection type to use", + EnvVars: []string{"PLUGIN_CONNECTION"}, + Destination: &settings.Connection, + Category: category, + }, + &cli.IntFlag{ + Name: "timeout", + Usage: "override the connection timeout in seconds", + EnvVars: []string{"PLUGIN_TIMEOUT"}, + Destination: &settings.Timeout, + Category: category, + }, + &cli.StringFlag{ + Name: "ssh-common-args", + Usage: "specify common arguments to pass to SFTP, SCP and SSH connections", + EnvVars: []string{"PLUGIN_SSH_COMMON_ARGS"}, + Destination: &settings.SSHCommonArgs, + Category: category, + }, + &cli.StringFlag{ + Name: "sftp-extra-args", + Usage: "specify extra arguments to pass to SFTP connections only", + EnvVars: []string{"PLUGIN_SFTP_EXTRA_ARGS"}, + Destination: &settings.SFTPExtraArgs, + Category: category, + }, + &cli.StringFlag{ + Name: "scp-extra-args", + Usage: "specify extra arguments to pass to SCP connections only", + EnvVars: []string{"PLUGIN_SCP_EXTRA_ARGS"}, + Destination: &settings.SCPExtraArgs, + Category: category, + }, + &cli.StringFlag{ + Name: "ssh-extra-args", + Usage: "specify extra arguments to pass to SSH connections only", + EnvVars: []string{"PLUGIN_SSH_EXTRA_ARGS"}, + Destination: &settings.SSHExtraArgs, + Category: category, + }, + &cli.BoolFlag{ + Name: "become", + Usage: "enable privilege escalation", + EnvVars: []string{"PLUGIN_BECOME"}, + Destination: &settings.Become, + Category: category, + }, + &cli.StringFlag{ + Name: "become-method", + Usage: "privilege escalation method to use", + EnvVars: []string{"PLUGIN_BECOME_METHOD", "ANSIBLE_BECOME_METHOD"}, + Destination: &settings.BecomeMethod, + Category: category, + }, + &cli.StringFlag{ + Name: "become-user", + Usage: "privilege escalation user to use", + EnvVars: []string{"PLUGIN_BECOME_USER", "ANSIBLE_BECOME_USER"}, + Destination: &settings.BecomeUser, + Category: category, + }, + } +} diff --git a/testdata/inventory.yml b/testdata/inventory.yml new file mode 100644 index 0000000..655914c --- /dev/null +++ b/testdata/inventory.yml @@ -0,0 +1,6 @@ +--- +ungrouped: + hosts: + localhost: + ansible_connection: local + ansible_python_interpreter: auto_silent diff --git a/testdata/playbook.yml b/testdata/playbook.yml new file mode 100644 index 0000000..6014ba4 --- /dev/null +++ b/testdata/playbook.yml @@ -0,0 +1,6 @@ +--- +- hosts: all + tasks: + - name: Hello World + debug: + msg: "Say hello"