From 6b049439e5059514bd84470db4df2180edb4318f Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Tue, 26 Jan 2021 12:28:47 +0100 Subject: [PATCH] cleanup --- .drone.star | 35 ++++- main.go | 243 ++++++++++++++++++++++++++++++++ plugin.go | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 664 insertions(+), 3 deletions(-) create mode 100644 main.go create mode 100644 plugin.go diff --git a/.drone.star b/.drone.star index 11cbe01..0ee6916 100644 --- a/.drone.star +++ b/.drone.star @@ -36,6 +36,12 @@ def testing(ctx): "commands": [ "go run honnef.co/go/tools/cmd/staticcheck ./...", ], + "volumes": [ + { + "name": "gopath", + "path": "/go", + }, + ], }, { "name": "lint", @@ -43,7 +49,12 @@ def testing(ctx): "commands": [ "go run golang.org/x/lint/golint -set_exit_status ./...", ], - + "volumes": [ + { + "name": "gopath", + "path": "/go", + }, + ], }, { "name": "vet", @@ -51,6 +62,12 @@ def testing(ctx): "commands": [ "go vet ./...", ], + "volumes": [ + { + "name": "gopath", + "path": "/go", + }, + ], }, { "name": "test", @@ -58,6 +75,18 @@ def testing(ctx): "commands": [ "go test -cover ./...", ], + "volumes": [ + { + "name": "gopath", + "path": "/go", + }, + ], + }, + ], + "volumes": [ + { + "name": "gopath", + "temp": {}, }, ], "trigger": { @@ -72,11 +101,11 @@ def testing(ctx): def linux(ctx, arch): if ctx.build.event == "tag": build = [ - 'go build -v -ldflags "-X main.version=%s" -a -tags netgo -o release/linux/%s/drone-ansible ./cmd/drone-ansible' % (ctx.build.ref.replace("refs/tags/v", ""), arch), + 'go build -v -ldflags "-X main.version=%s" -a -tags netgo -o release/linux/%s/drone-ansible .' % (ctx.build.ref.replace("refs/tags/v", ""), arch), ] else: build = [ - 'go build -v -ldflags "-X main.version=%s" -a -tags netgo -o release/linux/%s/drone-ansible ./cmd/drone-ansible' % (ctx.build.commit[0:8], arch), + 'go build -v -ldflags "-X main.version=%s" -a -tags netgo -o release/linux/%s/drone-ansible .' % (ctx.build.commit[0:8], arch), ] steps = [ diff --git a/main.go b/main.go new file mode 100644 index 0000000..520b9a7 --- /dev/null +++ b/main.go @@ -0,0 +1,243 @@ +package main + +import ( + "log" + "os" + + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + version = "unknown" +) + +func main() { + app := cli.NewApp() + app.Name = "ansible plugin" + app.Usage = "ansible plugin" + app.Action = run + app.Version = version + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "requirements", + Usage: "path to python requirements", + EnvVar: "PLUGIN_REQUIREMENTS", + }, + cli.StringFlag{ + Name: "galaxy", + Usage: "path to galaxy requirements", + EnvVar: "PLUGIN_GALAXY", + }, + cli.StringSliceFlag{ + Name: "inventory", + Usage: "specify inventory host path", + EnvVar: "PLUGIN_INVENTORY,PLUGIN_INVENTORIES", + }, + cli.StringSliceFlag{ + Name: "playbook", + Usage: "list of playbooks to apply", + EnvVar: "PLUGIN_PLAYBOOK,PLUGIN_PLAYBOOKS", + }, + cli.StringFlag{ + Name: "limit", + Usage: "further limit selected hosts to an additional pattern", + EnvVar: "PLUGIN_LIMIT", + }, + cli.StringFlag{ + Name: "skip-tags", + Usage: "only run plays and tasks whose tags do not match", + EnvVar: "PLUGIN_SKIP_TAGS", + }, + cli.StringFlag{ + Name: "start-at-task", + Usage: "start the playbook at the task matching this name", + EnvVar: "PLUGIN_START_AT_TASK", + }, + cli.StringFlag{ + Name: "tags", + Usage: "only run plays and tasks tagged with these values", + EnvVar: "PLUGIN_TAGS", + }, + cli.StringSliceFlag{ + Name: "extra-vars", + Usage: "set additional variables as key=value", + EnvVar: "PLUGIN_EXTRA_VARS,ANSIBLE_EXTRA_VARS", + }, + cli.StringSliceFlag{ + Name: "module-path", + Usage: "prepend paths to module library", + EnvVar: "PLUGIN_MODULE_PATH", + }, + cli.BoolFlag{ + Name: "check", + Usage: "run a check, do not apply any changes", + EnvVar: "PLUGIN_CHECK", + }, + cli.BoolFlag{ + Name: "diff", + Usage: "show the differences, may print secrets", + EnvVar: "PLUGIN_DIFF", + }, + cli.BoolFlag{ + Name: "flush-cache", + Usage: "clear the fact cache for every host in inventory", + EnvVar: "PLUGIN_FLUSH_CACHE", + }, + cli.BoolFlag{ + Name: "force-handlers", + Usage: "run handlers even if a task fails", + EnvVar: "PLUGIN_FORCE_HANDLERS", + }, + cli.BoolFlag{ + Name: "list-hosts", + Usage: "outputs a list of matching hosts", + EnvVar: "PLUGIN_LIST_HOSTS", + }, + cli.BoolFlag{ + Name: "list-tags", + Usage: "list all available tags", + EnvVar: "PLUGIN_LIST_TAGS", + }, + cli.BoolFlag{ + Name: "list-tasks", + Usage: "list all tasks that would be executed", + EnvVar: "PLUGIN_LIST_TASKS", + }, + cli.BoolFlag{ + Name: "syntax-check", + Usage: "perform a syntax check on the playbook", + EnvVar: "PLUGIN_SYNTAX_CHECK", + }, + cli.IntFlag{ + Name: "forks", + Usage: "specify number of parallel processes to use", + EnvVar: "PLUGIN_FORKS", + Value: 5, + }, + cli.StringFlag{ + Name: "vault-id", + Usage: "the vault identity to use", + EnvVar: "PLUGIN_VAULT_ID,ANSIBLE_VAULT_ID", + }, + cli.StringFlag{ + Name: "vault-password", + Usage: "the vault password to use", + EnvVar: "PLUGIN_VAULT_PASSWORD,ANSIBLE_VAULT_PASSWORD", + }, + cli.IntFlag{ + Name: "verbose", + Usage: "level of verbosity, 0 up to 4", + EnvVar: "PLUGIN_VERBOSE", + }, + cli.StringFlag{ + Name: "private-key", + Usage: "use this key to authenticate the connection", + EnvVar: "PLUGIN_PRIVATE_KEY,ANSIBLE_PRIVATE_KEY", + }, + cli.StringFlag{ + Name: "user", + Usage: "connect as this user", + EnvVar: "PLUGIN_USER,ANSIBLE_USER", + }, + cli.StringFlag{ + Name: "connection", + Usage: "connection type to use", + EnvVar: "PLUGIN_CONNECTION", + }, + cli.IntFlag{ + Name: "timeout", + Usage: "override the connection timeout in seconds", + EnvVar: "PLUGIN_TIMEOUT", + }, + cli.StringFlag{ + Name: "ssh-common-args", + Usage: "specify common arguments to pass to sftp/scp/ssh", + EnvVar: "PLUGIN_SSH_COMMON_ARGS", + }, + cli.StringFlag{ + Name: "sftp-extra-args", + Usage: "specify extra arguments to pass to sftp only", + EnvVar: "PLUGIN_SFTP_EXTRA_ARGS", + }, + cli.StringFlag{ + Name: "scp-extra-args", + Usage: "specify extra arguments to pass to scp only", + EnvVar: "PLUGIN_SCP_EXTRA_ARGS", + }, + cli.StringFlag{ + Name: "ssh-extra-args", + Usage: "specify extra arguments to pass to ssh only", + EnvVar: "PLUGIN_SSH_EXTRA_ARGS", + }, + cli.BoolFlag{ + Name: "become", + Usage: "run operations with become", + EnvVar: "PLUGIN_BECOME", + }, + cli.StringFlag{ + Name: "become-method", + Usage: "privilege escalation method to use", + EnvVar: "PLUGIN_BECOME_METHOD,ANSIBLE_BECOME_METHOD", + }, + cli.StringFlag{ + Name: "become-user", + Usage: "run operations as this user", + EnvVar: "PLUGIN_BECOME_USER,ANSIBLE_BECOME_USER", + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} + +func run(c *cli.Context) error { + plugin := Plugin{ + Config: Config{ + Requirements: c.String("requirements"), + Galaxy: c.String("galaxy"), + Inventories: c.StringSlice("inventory"), + Playbooks: c.StringSlice("playbook"), + Limit: c.String("limit"), + SkipTags: c.String("skip-tags"), + StartAtTask: c.String("start-at-task"), + Tags: c.String("tags"), + ExtraVars: c.StringSlice("extra-vars"), + ModulePath: c.StringSlice("module-path"), + Check: c.Bool("check"), + Diff: c.Bool("diff"), + FlushCache: c.Bool("flush-cache"), + ForceHandlers: c.Bool("force-handlers"), + ListHosts: c.Bool("list-hosts"), + ListTags: c.Bool("list-tags"), + ListTasks: c.Bool("list-tasks"), + SyntaxCheck: c.Bool("syntax-check"), + Forks: c.Int("forks"), + VaultID: c.String("vailt-id"), + VaultPassword: c.String("vault-password"), + Verbose: c.Int("verbose"), + PrivateKey: c.String("private-key"), + User: c.String("user"), + Connection: c.String("connection"), + Timeout: c.Int("timeout"), + SSHCommonArgs: c.String("ssh-common-args"), + SFTPExtraArgs: c.String("sftp-extra-args"), + SCPExtraArgs: c.String("scp-extra-args"), + SSHExtraArgs: c.String("ssh-extra-args"), + Become: c.Bool("become"), + BecomeMethod: c.String("become-method"), + BecomeUser: c.String("become-user"), + }, + } + + if len(plugin.Config.Playbooks) == 0 { + return errors.New("you must provide a playbook") + } + + if len(plugin.Config.Inventories) == 0 { + return errors.New("you must provide an inventory") + } + + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..eb6e2b3 --- /dev/null +++ b/plugin.go @@ -0,0 +1,389 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +var ansibleFolder = "/etc/ansible" +var ansibleConfig = "/etc/ansible/ansible.cfg" + +var ansibleContent = ` +[defaults] +host_key_checking = False +` + +type ( + // Config for the plugin. + Config struct { + Requirements string + Galaxy string + Inventories []string + Playbooks []string + Limit string + SkipTags string + StartAtTask string + Tags string + ExtraVars []string + ModulePath []string + Check bool + Diff bool + FlushCache bool + ForceHandlers bool + ListHosts bool + ListTags bool + ListTasks bool + SyntaxCheck bool + Forks int + VaultID string + VaultPassword string + VaultPasswordFile string + Verbose int + PrivateKey string + PrivateKeyFile string + User string + Connection string + Timeout int + SSHCommonArgs string + SFTPExtraArgs string + SCPExtraArgs string + SSHExtraArgs string + Become bool + BecomeMethod string + BecomeUser string + } + + // Plugin definition. + Plugin struct { + Config Config + } +) + +// Exec provides the implementation of the plugin. +func (p *Plugin) Exec() error { + if err := p.playbooks(); err != nil { + return err + } + + if err := p.ansibleConfig(); err != nil { + return err + } + + if p.Config.PrivateKey != "" { + if err := p.privateKey(); err != nil { + return err + } + + defer os.Remove(p.Config.PrivateKeyFile) + } + + if p.Config.VaultPassword != "" { + if err := p.vaultPass(); err != nil { + return err + } + + defer os.Remove(p.Config.VaultPasswordFile) + } + + commands := []*exec.Cmd{ + p.versionCommand(), + } + + if p.Config.Requirements != "" { + commands = append(commands, p.requirementsCommand()) + } + + if p.Config.Galaxy != "" { + commands = append(commands, p.galaxyCommand()) + } + + for _, inventory := range p.Config.Inventories { + commands = append(commands, p.ansibleCommand(inventory)) + } + + for _, cmd := range commands { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "ANSIBLE_FORCE_COLOR=1") + + trace(cmd) + + if err := cmd.Run(); err != nil { + return err + } + } + + return nil +} + +func (p *Plugin) ansibleConfig() error { + if err := os.MkdirAll(ansibleFolder, os.ModePerm); err != nil { + return errors.Wrap(err, "failed to create ansible directory") + } + + if err := ioutil.WriteFile(ansibleConfig, []byte(ansibleContent), 0600); err != nil { + return errors.Wrap(err, "failed to create ansible config") + } + + return nil +} + +func (p *Plugin) privateKey() error { + tmpfile, err := ioutil.TempFile("", "privateKey") + + if err != nil { + return errors.Wrap(err, "failed to create private key file") + } + + if _, err := tmpfile.Write([]byte(p.Config.PrivateKey)); err != nil { + return errors.Wrap(err, "failed to write private key file") + } + + if err := tmpfile.Close(); err != nil { + return errors.Wrap(err, "failed to close private key file") + } + + p.Config.PrivateKeyFile = tmpfile.Name() + return nil +} + +func (p *Plugin) vaultPass() error { + tmpfile, err := ioutil.TempFile("", "vaultPass") + + if err != nil { + return errors.Wrap(err, "failed to create vault password file") + } + + if _, err := tmpfile.Write([]byte(p.Config.VaultPassword)); err != nil { + return errors.Wrap(err, "failed to write vault password file") + } + + if err := tmpfile.Close(); err != nil { + return errors.Wrap(err, "failed to close vault password file") + } + + p.Config.VaultPasswordFile = tmpfile.Name() + return nil +} + +func (p *Plugin) playbooks() error { + var ( + playbooks []string + ) + + for _, p := range p.Config.Playbooks { + files, err := filepath.Glob(p) + + if err != nil { + playbooks = append(playbooks, p) + continue + } + + playbooks = append(playbooks, files...) + } + + if len(playbooks) == 0 { + return errors.New("failed to find playbook files") + } + + p.Config.Playbooks = playbooks + return nil +} + +func (p *Plugin) versionCommand() *exec.Cmd { + args := []string{ + "--version", + } + + return exec.Command( + "ansible", + args..., + ) +} + +func (p *Plugin) requirementsCommand() *exec.Cmd { + args := []string{ + "install", + "--upgrade", + "--requirement", + p.Config.Requirements, + } + + return exec.Command( + "pip", + args..., + ) +} + +func (p *Plugin) galaxyCommand() *exec.Cmd { + args := []string{ + "install", + "--force", + "--role-file", + p.Config.Galaxy, + } + + if p.Config.Verbose > 0 { + args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.Config.Verbose))) + } + + return exec.Command( + "ansible-galaxy", + args..., + ) +} + +func (p *Plugin) ansibleCommand(inventory string) *exec.Cmd { + args := []string{ + "--inventory", + inventory, + } + + if len(p.Config.ModulePath) > 0 { + args = append(args, "--module-path", strings.Join(p.Config.ModulePath, ":")) + } + + if p.Config.VaultID != "" { + args = append(args, "--vault-id", p.Config.VaultID) + } + + if p.Config.VaultPasswordFile != "" { + args = append(args, "--vault-password-file", p.Config.VaultPasswordFile) + } + + for _, v := range p.Config.ExtraVars { + args = append(args, "--extra-vars", v) + } + + if p.Config.ListHosts { + args = append(args, "--list-hosts") + args = append(args, p.Config.Playbooks...) + + return exec.Command( + "ansible-playbook", + args..., + ) + } + + if p.Config.SyntaxCheck { + args = append(args, "--syntax-check") + args = append(args, p.Config.Playbooks...) + + return exec.Command( + "ansible-playbook", + args..., + ) + } + + if p.Config.Check { + args = append(args, "--check") + } + + if p.Config.Diff { + args = append(args, "--diff") + } + + if p.Config.FlushCache { + args = append(args, "--flush-cache") + } + + if p.Config.ForceHandlers { + args = append(args, "--force-handlers") + } + + if p.Config.Forks != 5 { + args = append(args, "--forks", strconv.Itoa(p.Config.Forks)) + } + + if p.Config.Limit != "" { + args = append(args, "--limit", p.Config.Limit) + } + + if p.Config.ListTags { + args = append(args, "--list-tags") + } + + if p.Config.ListTasks { + args = append(args, "--list-tasks") + } + + if p.Config.SkipTags != "" { + args = append(args, "--skip-tags", p.Config.SkipTags) + } + + if p.Config.StartAtTask != "" { + args = append(args, "--start-at-task", p.Config.StartAtTask) + } + + if p.Config.Tags != "" { + args = append(args, "--tags", p.Config.Tags) + } + + if p.Config.PrivateKeyFile != "" { + args = append(args, "--private-key", p.Config.PrivateKeyFile) + } + + if p.Config.User != "" { + args = append(args, "--user", p.Config.User) + } + + if p.Config.Connection != "" { + args = append(args, "--connection", p.Config.Connection) + } + + if p.Config.Timeout != 0 { + args = append(args, "--timeout", strconv.Itoa(p.Config.Timeout)) + } + + if p.Config.SSHCommonArgs != "" { + args = append(args, "--ssh-common-args", p.Config.SSHCommonArgs) + } + + if p.Config.SFTPExtraArgs != "" { + args = append(args, "--sftp-extra-args", p.Config.SFTPExtraArgs) + } + + if p.Config.SCPExtraArgs != "" { + args = append(args, "--scp-extra-args", p.Config.SCPExtraArgs) + } + + if p.Config.SSHExtraArgs != "" { + args = append(args, "--ssh-extra-args", p.Config.SSHExtraArgs) + } + + if p.Config.Become { + args = append(args, "--become") + } + + if p.Config.BecomeMethod != "" { + args = append(args, "--become-method", p.Config.BecomeMethod) + } + + if p.Config.BecomeUser != "" { + args = append(args, "--become-user", p.Config.BecomeUser) + } + + if p.Config.Verbose > 0 { + args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.Config.Verbose))) + } + + args = append(args, p.Config.Playbooks...) + + return exec.Command( + "ansible-playbook", + args..., + ) +} + +func trace(cmd *exec.Cmd) { + fmt.Println("$", strings.Join(cmd.Args, " ")) +}