diff --git a/.drone.star b/.drone.star index 4eb7660..82ff2aa 100644 --- a/.drone.star +++ b/.drone.star @@ -101,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 .' % (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 ./cmd/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 .' % (ctx.build.commit[0:8], arch), + '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), ] steps = [ diff --git a/.gitignore b/.gitignore index d9a2815..32d7142 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -release/ +/release/ +/drone-ansible* coverage.out -drone-ansible .drone.yml diff --git a/cmd/drone-ansible/config.go b/cmd/drone-ansible/config.go new file mode 100644 index 0000000..3bd7a0d --- /dev/null +++ b/cmd/drone-ansible/config.go @@ -0,0 +1,211 @@ +package main + +import ( + "github.com/owncloud-ci/drone-ansible/plugin" + "github.com/urfave/cli/v2" +) + +// settingsFlags has the cli.Flags for the plugin.Settings. +func settingsFlags(settings *plugin.Settings) []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "requirements", + Usage: "path to python requirements", + EnvVars: []string{"PLUGIN_REQUIREMENTS"}, + Destination: &settings.Requirements, + }, + &cli.StringFlag{ + Name: "galaxy", + Usage: "path to galaxy requirements", + EnvVars: []string{"PLUGIN_GALAXY"}, + Destination: &settings.Galaxy, + }, + &cli.StringSliceFlag{ + Name: "inventory", + Usage: "specify inventory host path", + EnvVars: []string{"PLUGIN_INVENTORY", "PLUGIN_INVENTORIES"}, + Destination: &settings.Inventories, + }, + &cli.StringSliceFlag{ + Name: "playbook", + Usage: "list of playbooks to apply", + EnvVars: []string{"PLUGIN_PLAYBOOK", "PLUGIN_PLAYBOOKS"}, + Destination: &settings.Playbooks, + }, + &cli.StringFlag{ + Name: "limit", + Usage: "further limit selected hosts to an additional pattern", + EnvVars: []string{"PLUGIN_LIMIT"}, + Destination: &settings.Limit, + }, + &cli.StringFlag{ + Name: "skip-tags", + Usage: "only run plays and tasks whose tags do not match", + EnvVars: []string{"PLUGIN_SKIP_TAGS"}, + Destination: &settings.SkipTags, + }, + &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, + }, + &cli.StringFlag{ + Name: "tags", + Usage: "only run plays and tasks tagged with these values", + EnvVars: []string{"PLUGIN_TAGS"}, + Destination: &settings.Tags, + }, + &cli.StringSliceFlag{ + Name: "extra-vars", + Usage: "set additional variables as key=value", + EnvVars: []string{"PLUGIN_EXTRA_VARS", "ANSIBLE_EXTRA_VARS"}, + Destination: &settings.ExtraVars, + }, + &cli.StringSliceFlag{ + Name: "module-path", + Usage: "prepend paths to module library", + EnvVars: []string{"PLUGIN_MODULE_PATH"}, + Destination: &settings.ModulePath, + }, + &cli.BoolFlag{ + Name: "check", + Usage: "run a check, do not apply any changes", + EnvVars: []string{"PLUGIN_CHECK"}, + Destination: &settings.Check, + }, + &cli.BoolFlag{ + Name: "diff", + Usage: "show the differences, may print secrets", + EnvVars: []string{"PLUGIN_DIFF"}, + Destination: &settings.Diff, + }, + &cli.BoolFlag{ + Name: "flush-cache", + Usage: "clear the fact cache for every host in inventory", + EnvVars: []string{"PLUGIN_FLUSH_CACHE"}, + Destination: &settings.FlushCache, + }, + &cli.BoolFlag{ + Name: "force-handlers", + Usage: "run handlers even if a task fails", + EnvVars: []string{"PLUGIN_FORCE_HANDLERS"}, + Destination: &settings.ForceHandlers, + }, + &cli.BoolFlag{ + Name: "list-hosts", + Usage: "outputs a list of matching hosts", + EnvVars: []string{"PLUGIN_LIST_HOSTS"}, + Destination: &settings.ListHosts, + }, + &cli.BoolFlag{ + Name: "list-tags", + Usage: "list all available tags", + EnvVars: []string{"PLUGIN_LIST_TAGS"}, + Destination: &settings.ListTags, + }, + &cli.BoolFlag{ + Name: "list-tasks", + Usage: "list all tasks that would be executed", + EnvVars: []string{"PLUGIN_LIST_TASKS"}, + Destination: &settings.ListTasks, + }, + &cli.BoolFlag{ + Name: "syntax-check", + Usage: "perform a syntax check on the playbook", + EnvVars: []string{"PLUGIN_SYNTAX_CHECK"}, + Destination: &settings.SyntaxCheck, + }, + &cli.IntFlag{ + Name: "forks", + Usage: "specify number of parallel processes to use", + EnvVars: []string{"PLUGIN_FORKS"}, + Value: 5, + Destination: &settings.Forks, + }, + &cli.StringFlag{ + Name: "vault-id", + Usage: "the vault identity to use", + EnvVars: []string{"PLUGIN_VAULT_ID", "ANSIBLE_VAULT_ID"}, + Destination: &settings.VaultID, + }, + &cli.StringFlag{ + Name: "vault-password", + Usage: "the vault password to use", + EnvVars: []string{"PLUGIN_VAULT_PASSWORD", "ANSIBLE_VAULT_PASSWORD"}, + Destination: &settings.VaultPassword, + }, + &cli.IntFlag{ + Name: "verbose", + Usage: "level of verbosity, 0 up to 4", + EnvVars: []string{"PLUGIN_VERBOSE"}, + Destination: &settings.Verbose, + }, + &cli.StringFlag{ + Name: "private-key", + Usage: "use this key to authenticate the connection", + EnvVars: []string{"PLUGIN_PRIVATE_KEY", "ANSIBLE_PRIVATE_KEY"}, + Destination: &settings.PrivateKey, + }, + &cli.StringFlag{ + Name: "user", + Usage: "connect as this user", + EnvVars: []string{"PLUGIN_USER", "ANSIBLE_USER"}, + Destination: &settings.User, + }, + &cli.StringFlag{ + Name: "connection", + Usage: "connection type to use", + EnvVars: []string{"PLUGIN_CONNECTION"}, + Destination: &settings.Connection, + }, + &cli.IntFlag{ + Name: "timeout", + Usage: "override the connection timeout in seconds", + EnvVars: []string{"PLUGIN_TIMEOUT"}, + Destination: &settings.Timeout, + }, + &cli.StringFlag{ + Name: "ssh-common-args", + Usage: "specify common arguments to pass to sftp/scp/ssh", + EnvVars: []string{"PLUGIN_SSH_COMMON_ARGS"}, + Destination: &settings.SSHCommonArgs, + }, + &cli.StringFlag{ + Name: "sftp-extra-args", + Usage: "specify extra arguments to pass to sftp only", + EnvVars: []string{"PLUGIN_SFTP_EXTRA_ARGS"}, + Destination: &settings.SFTPExtraArgs, + }, + &cli.StringFlag{ + Name: "scp-extra-args", + Usage: "specify extra arguments to pass to scp only", + EnvVars: []string{"PLUGIN_SCP_EXTRA_ARGS"}, + Destination: &settings.SCPExtraArgs, + }, + &cli.StringFlag{ + Name: "ssh-extra-args", + Usage: "specify extra arguments to pass to ssh only", + EnvVars: []string{"PLUGIN_SSH_EXTRA_ARGS"}, + Destination: &settings.SSHExtraArgs, + }, + &cli.BoolFlag{ + Name: "become", + Usage: "run operations with become", + EnvVars: []string{"PLUGIN_BECOME"}, + Destination: &settings.Become, + }, + &cli.StringFlag{ + Name: "become-method", + Usage: "privilege escalation method to use", + EnvVars: []string{"PLUGIN_BECOME_METHOD", "ANSIBLE_BECOME_METHOD"}, + Destination: &settings.BecomeMethod, + }, + &cli.StringFlag{ + Name: "become-user", + Usage: "run operations as this user", + EnvVars: []string{"PLUGIN_BECOME_USER", "ANSIBLE_BECOME_USER"}, + Destination: &settings.BecomeUser, + }, + } +} diff --git a/cmd/drone-ansible/main.go b/cmd/drone-ansible/main.go new file mode 100644 index 0000000..8167f00 --- /dev/null +++ b/cmd/drone-ansible/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "os" + + "github.com/joho/godotenv" + "github.com/owncloud-ci/drone-ansible/plugin" + "github.com/urfave/cli/v2" + + "github.com/drone-plugins/drone-plugin-lib/errors" + "github.com/drone-plugins/drone-plugin-lib/urfave" +) + +var version = "unknown" + +func main() { + settings := &plugin.Settings{} + + if _, err := os.Stat("/run/drone/env"); err == nil { + godotenv.Overload("/run/drone/env") + } + + app := &cli.App{ + Name: "drone-ansible", + Usage: "provision infrastructure with Ansible", + Version: version, + Flags: append(settingsFlags(settings), urfave.Flags()...), + Action: run(settings), + } + + if err := app.Run(os.Args); err != nil { + errors.HandleExit(err) + } +} + +func run(settings *plugin.Settings) cli.ActionFunc { + return func(ctx *cli.Context) error { + urfave.LoggingFromContext(ctx) + + plugin := plugin.New( + *settings, + urfave.PipelineFromContext(ctx), + urfave.NetworkFromContext(ctx), + ) + + if err := plugin.Validate(); err != nil { + if e, ok := err.(errors.ExitCoder); ok { + return e + } + + return errors.ExitMessagef("validation failed: %w", err) + } + + if err := plugin.Execute(); err != nil { + if e, ok := err.(errors.ExitCoder); ok { + return e + } + + return errors.ExitMessagef("execution failed: %w", err) + } + + return nil + } +} diff --git a/go.mod b/go.mod index a868c3d..f5fe0a0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,11 @@ go 1.15 require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/drone-plugins/drone-plugin-lib v0.4.0 + github.com/joho/godotenv v1.3.0 github.com/pkg/errors v0.9.1 github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/urfave/cli v1.22.5 + github.com/sirupsen/logrus v1.7.0 // indirect + github.com/urfave/cli/v2 v2.3.0 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect ) diff --git a/go.sum b/go.sum index fd40308..d791e4b 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,19 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/drone-plugins/drone-plugin-lib v0.4.0 h1:qywEYGhquUuid6zNLmKia8CWY1TUa8jPQQ/G9ozfAmc= +github.com/drone-plugins/drone-plugin-lib v0.4.0/go.mod h1:EgqogX38GoJFtckeSQyhBJYX8P+KWBPhdprAVvyRxF8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -13,7 +24,28 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= -github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/main.go b/main.go deleted file mode 100644 index 520b9a7..0000000 --- a/main.go +++ /dev/null @@ -1,243 +0,0 @@ -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 deleted file mode 100644 index eb6e2b3..0000000 --- a/plugin.go +++ /dev/null @@ -1,389 +0,0 @@ -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, " ")) -} diff --git a/plugin/ansible.go b/plugin/ansible.go new file mode 100644 index 0000000..1228573 --- /dev/null +++ b/plugin/ansible.go @@ -0,0 +1,285 @@ +package plugin + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +var ansibleFolder = "/etc/ansible" +var ansibleConfig = "/etc/ansible/ansible.cfg" + +var ansibleContent = ` +[defaults] +host_key_checking = False +` + +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.settings.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.settings.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.settings.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.settings.VaultPasswordFile = tmpfile.Name() + return nil +} + +func (p *Plugin) playbooks() error { + var ( + playbooks []string + ) + + for _, p := range p.settings.Playbooks.Value() { + 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.settings.Playbooks = *cli.NewStringSlice(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.settings.Requirements, + } + + return exec.Command( + "pip", + args..., + ) +} + +func (p *Plugin) galaxyCommand() *exec.Cmd { + args := []string{ + "install", + "--force", + "--role-file", + p.settings.Galaxy, + } + + if p.settings.Verbose > 0 { + args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.settings.Verbose))) + } + + return exec.Command( + "ansible-galaxy", + args..., + ) +} + +func (p *Plugin) ansibleCommand(inventory string) *exec.Cmd { + args := []string{ + "--inventory", + inventory, + } + + if len(p.settings.ModulePath.Value()) > 0 { + args = append(args, "--module-path", strings.Join(p.settings.ModulePath.Value(), ":")) + } + + if p.settings.VaultID != "" { + args = append(args, "--vault-id", p.settings.VaultID) + } + + if p.settings.VaultPasswordFile != "" { + args = append(args, "--vault-password-file", p.settings.VaultPasswordFile) + } + + for _, v := range p.settings.ExtraVars.Value() { + args = append(args, "--extra-vars", v) + } + + if p.settings.ListHosts { + args = append(args, "--list-hosts") + args = append(args, p.settings.Playbooks.Value()...) + + return exec.Command( + "ansible-playbook", + args..., + ) + } + + if p.settings.SyntaxCheck { + args = append(args, "--syntax-check") + args = append(args, p.settings.Playbooks.Value()...) + + return exec.Command( + "ansible-playbook", + args..., + ) + } + + if p.settings.Check { + args = append(args, "--check") + } + + if p.settings.Diff { + args = append(args, "--diff") + } + + if p.settings.FlushCache { + args = append(args, "--flush-cache") + } + + if p.settings.ForceHandlers { + args = append(args, "--force-handlers") + } + + if p.settings.Forks != 5 { + args = append(args, "--forks", strconv.Itoa(p.settings.Forks)) + } + + if p.settings.Limit != "" { + args = append(args, "--limit", p.settings.Limit) + } + + if p.settings.ListTags { + args = append(args, "--list-tags") + } + + if p.settings.ListTasks { + args = append(args, "--list-tasks") + } + + if p.settings.SkipTags != "" { + args = append(args, "--skip-tags", p.settings.SkipTags) + } + + if p.settings.StartAtTask != "" { + args = append(args, "--start-at-task", p.settings.StartAtTask) + } + + if p.settings.Tags != "" { + args = append(args, "--tags", p.settings.Tags) + } + + if p.settings.PrivateKeyFile != "" { + args = append(args, "--private-key", p.settings.PrivateKeyFile) + } + + if p.settings.User != "" { + args = append(args, "--user", p.settings.User) + } + + if p.settings.Connection != "" { + args = append(args, "--connection", p.settings.Connection) + } + + if p.settings.Timeout != 0 { + args = append(args, "--timeout", strconv.Itoa(p.settings.Timeout)) + } + + if p.settings.SSHCommonArgs != "" { + args = append(args, "--ssh-common-args", p.settings.SSHCommonArgs) + } + + if p.settings.SFTPExtraArgs != "" { + args = append(args, "--sftp-extra-args", p.settings.SFTPExtraArgs) + } + + if p.settings.SCPExtraArgs != "" { + args = append(args, "--scp-extra-args", p.settings.SCPExtraArgs) + } + + if p.settings.SSHExtraArgs != "" { + args = append(args, "--ssh-extra-args", p.settings.SSHExtraArgs) + } + + if p.settings.Become { + args = append(args, "--become") + } + + if p.settings.BecomeMethod != "" { + args = append(args, "--become-method", p.settings.BecomeMethod) + } + + if p.settings.BecomeUser != "" { + args = append(args, "--become-user", p.settings.BecomeUser) + } + + if p.settings.Verbose > 0 { + args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.settings.Verbose))) + } + + args = append(args, p.settings.Playbooks.Value()...) + + return exec.Command( + "ansible-playbook", + args..., + ) +} + +func trace(cmd *exec.Cmd) { + fmt.Println("$", strings.Join(cmd.Args, " ")) +} diff --git a/plugin/impl.go b/plugin/impl.go new file mode 100644 index 0000000..82378de --- /dev/null +++ b/plugin/impl.go @@ -0,0 +1,120 @@ +package plugin + +import ( + "os" + "os/exec" + + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +// Settings for the Plugin. +type Settings struct { + Requirements string + Galaxy string + Inventories cli.StringSlice + Playbooks cli.StringSlice + Limit string + SkipTags string + StartAtTask string + Tags string + ExtraVars cli.StringSlice + ModulePath cli.StringSlice + 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 +} + +// Validate handles the settings validation of the plugin. +func (p *Plugin) Validate() error { + if len(p.settings.Playbooks.Value()) == 0 { + return errors.New("you must provide a playbook") + } + + if len(p.settings.Inventories.Value()) == 0 { + return errors.New("you must provide an inventory") + } + + return nil +} + +// Execute provides the implementation of the plugin. +func (p *Plugin) Execute() error { + if err := p.playbooks(); err != nil { + return err + } + + if err := p.ansibleConfig(); err != nil { + return err + } + + if p.settings.PrivateKey != "" { + if err := p.privateKey(); err != nil { + return err + } + + defer os.Remove(p.settings.PrivateKeyFile) + } + + if p.settings.VaultPassword != "" { + if err := p.vaultPass(); err != nil { + return err + } + + defer os.Remove(p.settings.VaultPasswordFile) + } + + commands := []*exec.Cmd{ + p.versionCommand(), + } + + if p.settings.Requirements != "" { + commands = append(commands, p.requirementsCommand()) + } + + if p.settings.Galaxy != "" { + commands = append(commands, p.galaxyCommand()) + } + + for _, inventory := range p.settings.Inventories.Value() { + 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 +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..527b881 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,21 @@ +package plugin + +import ( + "github.com/drone-plugins/drone-plugin-lib/drone" +) + +// Plugin implements drone.Plugin to provide the plugin implementation. +type Plugin struct { + settings Settings + pipeline drone.Pipeline + network drone.Network +} + +// New initializes a plugin from the given Settings, Pipeline, and Network. +func New(settings Settings, pipeline drone.Pipeline, network drone.Network) drone.Plugin { + return &Plugin{ + settings: settings, + pipeline: pipeline, + network: network, + } +}