From ba28c39a7d9141ac4d1b56c9719c8e84567ff529 Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Mon, 11 Jan 2021 21:54:49 +0100 Subject: [PATCH] refactor: update dependencies and use plugin boilerplate --- cmd/drone-docker-buildx/config.go | 261 ++++++++++++++++++ cmd/drone-docker-buildx/main.go | 329 +++-------------------- docker.go | 392 ---------------------------- docker_test.go | 1 - go.mod | 5 +- go.sum | 22 +- daemon.go => plugin/daemon.go | 8 +- plugin/docker.go | 233 +++++++++++++++++ plugin/docker_test.go | 1 + plugin/impl.go | 203 ++++++++++++++ plugin/plugin.go | 21 ++ tags.go => plugin/tags.go | 2 +- tags_test.go => plugin/tags_test.go | 2 +- 13 files changed, 788 insertions(+), 692 deletions(-) create mode 100644 cmd/drone-docker-buildx/config.go delete mode 100644 docker.go delete mode 100644 docker_test.go rename daemon.go => plugin/daemon.go (80%) create mode 100644 plugin/docker.go create mode 100644 plugin/docker_test.go create mode 100644 plugin/impl.go create mode 100644 plugin/plugin.go rename tags.go => plugin/tags.go (99%) rename tags_test.go => plugin/tags_test.go (99%) diff --git a/cmd/drone-docker-buildx/config.go b/cmd/drone-docker-buildx/config.go new file mode 100644 index 0000000..daa7d3c --- /dev/null +++ b/cmd/drone-docker-buildx/config.go @@ -0,0 +1,261 @@ +package main + +import ( + "github.com/thegeeklab/drone-docker-buildx/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.BoolFlag{ + Name: "dry-run", + Usage: "dry run disables docker push", + EnvVars: []string{"PLUGIN_DRY_RUN"}, + Destination: &settings.Dryrun, + }, + &cli.StringFlag{ + Name: "remote.url", + Usage: "git remote url", + EnvVars: []string{"DRONE_REMOTE_URL"}, + Destination: &settings.Build.Remote, + }, + &cli.StringFlag{ + Name: "commit.sha", + Usage: "git commit sha", + EnvVars: []string{"DRONE_COMMIT_SHA"}, + Value: "00000000", + Destination: &settings.Build.Name, + }, + &cli.StringFlag{ + Name: "commit.ref", + Usage: "git commit ref", + EnvVars: []string{"DRONE_COMMIT_REF"}, + Destination: &settings.Build.Ref, + }, + &cli.StringFlag{ + Name: "daemon.mirror", + Usage: "docker daemon registry mirror", + EnvVars: []string{"PLUGIN_MIRROR", "DOCKER_PLUGIN_MIRROR"}, + Destination: &settings.Daemon.Mirror, + }, + &cli.StringFlag{ + Name: "daemon.storage-driver", + Usage: "docker daemon storage driver", + EnvVars: []string{"PLUGIN_STORAGE_DRIVER"}, + Destination: &settings.Daemon.StorageDriver, + }, + &cli.StringFlag{ + Name: "daemon.storage-path", + Usage: "docker daemon storage path", + Value: "/var/lib/docker", + EnvVars: []string{"PLUGIN_STORAGE_PATH"}, + Destination: &settings.Daemon.StoragePath, + }, + &cli.StringFlag{ + Name: "daemon.bip", + Usage: "docker daemon bride ip address", + EnvVars: []string{"PLUGIN_BIP"}, + Destination: &settings.Daemon.Bip, + }, + &cli.StringFlag{ + Name: "daemon.mtu", + Usage: "docker daemon custom mtu setting", + EnvVars: []string{"PLUGIN_MTU"}, + Destination: &settings.Daemon.MTU, + }, + &cli.StringSliceFlag{ + Name: "daemon.dns", + Usage: "docker daemon dns server", + EnvVars: []string{"PLUGIN_CUSTOM_DNS"}, + Destination: &settings.Daemon.DNS, + }, + &cli.StringSliceFlag{ + Name: "daemon.dns-search", + Usage: "docker daemon dns search domains", + EnvVars: []string{"PLUGIN_CUSTOM_DNS_SEARCH"}, + Destination: &settings.Daemon.DNSSearch, + }, + &cli.BoolFlag{ + Name: "daemon.insecure", + Usage: "docker daemon allows insecure registries", + EnvVars: []string{"PLUGIN_INSECURE"}, + Destination: &settings.Daemon.Insecure, + }, + &cli.BoolFlag{ + Name: "daemon.ipv6", + Usage: "docker daemon IPv6 networking", + EnvVars: []string{"PLUGIN_IPV6"}, + Destination: &settings.Daemon.IPv6, + }, + &cli.BoolFlag{ + Name: "daemon.experimental", + Usage: "docker daemon Experimental mode", + EnvVars: []string{"PLUGIN_EXPERIMENTAL"}, + Destination: &settings.Daemon.Experimental, + }, + &cli.BoolFlag{ + Name: "daemon.debug", + Usage: "docker daemon executes in debug mode", + EnvVars: []string{"PLUGIN_DEBUG", "DOCKER_LAUNCH_DEBUG"}, + Destination: &settings.Daemon.Debug, + }, + &cli.BoolFlag{ + Name: "daemon.off", + Usage: "don't start the docker daemon", + EnvVars: []string{"PLUGIN_DAEMON_OFF"}, + Destination: &settings.Daemon.Disabled, + }, + &cli.StringFlag{ + Name: "dockerfile", + Usage: "build dockerfile", + Value: "Dockerfile", + EnvVars: []string{"PLUGIN_DOCKERFILE"}, + Destination: &settings.Build.Dockerfile, + }, + &cli.StringFlag{ + Name: "context", + Usage: "build context", + Value: ".", + EnvVars: []string{"PLUGIN_CONTEXT"}, + Destination: &settings.Build.Context, + }, + &cli.StringSliceFlag{ + Name: "tags", + Usage: "build tags", + Value: cli.NewStringSlice([]string{"latest"}...), + EnvVars: []string{"PLUGIN_TAG", "PLUGIN_TAGS"}, + FilePath: ".tags", + Destination: &settings.Build.Tags, + }, + &cli.BoolFlag{ + Name: "tags.auto", + Usage: "default build tags", + EnvVars: []string{"PLUGIN_DEFAULT_TAGS", "PLUGIN_AUTO_TAG"}, + Destination: &settings.Build.TagsAuto, + }, + &cli.StringFlag{ + Name: "tags.suffix", + Usage: "default build tags with suffix", + EnvVars: []string{"PLUGIN_DEFAULT_SUFFIX", "PLUGIN_AUTO_TAG_SUFFIX"}, + Destination: &settings.Build.TagsSuffix, + }, + &cli.StringSliceFlag{ + Name: "args", + Usage: "build args", + EnvVars: []string{"PLUGIN_BUILD_ARGS"}, + Destination: &settings.Build.Args, + }, + &cli.StringSliceFlag{ + Name: "args-from-env", + Usage: "build args", + EnvVars: []string{"PLUGIN_BUILD_ARGS_FROM_ENV"}, + Destination: &settings.Build.ArgsEnv, + }, + &cli.BoolFlag{ + Name: "quiet", + Usage: "quiet docker build", + EnvVars: []string{"PLUGIN_QUIET"}, + Destination: &settings.Build.Quiet, + }, + &cli.StringFlag{ + Name: "target", + Usage: "build target", + EnvVars: []string{"PLUGIN_TARGET"}, + Destination: &settings.Build.Target, + }, + &cli.StringSliceFlag{ + Name: "cache-from", + Usage: "images to consider as cache sources", + EnvVars: []string{"PLUGIN_CACHE_FROM"}, + Destination: &settings.Build.CacheFrom, + }, + &cli.BoolFlag{ + Name: "squash", + Usage: "squash the layers at build time", + EnvVars: []string{"PLUGIN_SQUASH"}, + Destination: &settings.Build.Squash, + }, + &cli.BoolFlag{ + Name: "pull-image", + Usage: "force pull base image at build time", + EnvVars: []string{"PLUGIN_PULL_IMAGE"}, + Value: true, + Destination: &settings.Build.Pull, + }, + &cli.BoolFlag{ + Name: "compress", + Usage: "compress the build context using gzip", + EnvVars: []string{"PLUGIN_COMPRESS"}, + Destination: &settings.Build.Compress, + }, + &cli.StringFlag{ + Name: "repo", + Usage: "docker repository", + EnvVars: []string{"PLUGIN_REPO"}, + Destination: &settings.Build.Repo, + }, + &cli.StringFlag{ + Name: "docker.registry", + Usage: "docker registry", + Value: "https://index.docker.io/v1/", + EnvVars: []string{"PLUGIN_REGISTRY", "DOCKER_REGISTRY"}, + Destination: &settings.Login.Registry, + }, + &cli.StringFlag{ + Name: "docker.username", + Usage: "docker username", + EnvVars: []string{"PLUGIN_USERNAME", "DOCKER_USERNAME"}, + Destination: &settings.Login.Username, + }, + &cli.StringFlag{ + Name: "docker.password", + Usage: "docker password", + EnvVars: []string{"PLUGIN_PASSWORD", "DOCKER_PASSWORD"}, + Destination: &settings.Login.Password, + }, + &cli.StringFlag{ + Name: "docker.email", + Usage: "docker email", + EnvVars: []string{"PLUGIN_EMAIL", "DOCKER_EMAIL"}, + Destination: &settings.Login.Email, + }, + &cli.StringFlag{ + Name: "docker.config", + Usage: "docker json dockerconfig content", + EnvVars: []string{"PLUGIN_CONFIG", "DOCKER_PLUGIN_CONFIG"}, + Destination: &settings.Login.Config, + }, + &cli.BoolFlag{ + Name: "docker.purge", + Usage: "docker should cleanup images", + EnvVars: []string{"PLUGIN_PURGE"}, + Value: true, + Destination: &settings.Cleanup, + }, + &cli.StringFlag{ + Name: "repo.branch", + Usage: "repository default branch", + EnvVars: []string{"DRONE_REPO_BRANCH"}, + Destination: &settings.Build.Branch, + }, + &cli.BoolFlag{ + Name: "no-cache", + Usage: "do not use cached intermediate containers", + EnvVars: []string{"PLUGIN_NO_CACHE"}, + Destination: &settings.Build.NoCache, + }, + &cli.StringSliceFlag{ + Name: "add-host", + Usage: "additional host:IP mapping", + EnvVars: []string{"PLUGIN_ADD_HOST"}, + Destination: &settings.Build.AddHost, + }, + &cli.StringSliceFlag{ + Name: "platforms", + Usage: "arget platform for build", + EnvVars: []string{"PLUGIN_PLATFORMS"}, + Destination: &settings.Build.Platforms, + }, + } +} diff --git a/cmd/drone-docker-buildx/main.go b/cmd/drone-docker-buildx/main.go index 866d1b2..c7afafe 100644 --- a/cmd/drone-docker-buildx/main.go +++ b/cmd/drone-docker-buildx/main.go @@ -4,308 +4,61 @@ import ( "os" "github.com/joho/godotenv" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/thegeeklab/drone-docker-buildx/plugin" + "github.com/urfave/cli/v2" - docker "github.com/drone-plugins/drone-docker" + "github.com/drone-plugins/drone-plugin-lib/errors" + "github.com/drone-plugins/drone-plugin-lib/urfave" ) -var ( - version = "unknown" -) +var version = "unknown" func main() { - // Load env-file if it exists first - if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" { - godotenv.Load(env) + settings := &plugin.Settings{} + + if _, err := os.Stat("/run/drone/env"); err == nil { + godotenv.Overload("/run/drone/env") } - app := cli.NewApp() - app.Name = "drone-docker-buildx" - app.Usage = "Build docker container with DinD and buildx." - app.Action = run - app.Version = version - app.Flags = []cli.Flag{ - cli.BoolFlag{ - Name: "dry-run", - Usage: "dry run disables docker push", - EnvVar: "PLUGIN_DRY_RUN", - }, - cli.StringFlag{ - Name: "remote.url", - Usage: "git remote url", - EnvVar: "DRONE_REMOTE_URL", - }, - cli.StringFlag{ - Name: "commit.sha", - Usage: "git commit sha", - EnvVar: "DRONE_COMMIT_SHA", - Value: "00000000", - }, - cli.StringFlag{ - Name: "commit.ref", - Usage: "git commit ref", - EnvVar: "DRONE_COMMIT_REF", - }, - cli.StringFlag{ - Name: "daemon.mirror", - Usage: "docker daemon registry mirror", - EnvVar: "PLUGIN_MIRROR,DOCKER_PLUGIN_MIRROR", - }, - cli.StringFlag{ - Name: "daemon.storage-driver", - Usage: "docker daemon storage driver", - EnvVar: "PLUGIN_STORAGE_DRIVER", - }, - cli.StringFlag{ - Name: "daemon.storage-path", - Usage: "docker daemon storage path", - Value: "/var/lib/docker", - EnvVar: "PLUGIN_STORAGE_PATH", - }, - cli.StringFlag{ - Name: "daemon.bip", - Usage: "docker daemon bride ip address", - EnvVar: "PLUGIN_BIP", - }, - cli.StringFlag{ - Name: "daemon.mtu", - Usage: "docker daemon custom mtu setting", - EnvVar: "PLUGIN_MTU", - }, - cli.StringSliceFlag{ - Name: "daemon.dns", - Usage: "docker daemon dns server", - EnvVar: "PLUGIN_CUSTOM_DNS", - }, - cli.StringSliceFlag{ - Name: "daemon.dns-search", - Usage: "docker daemon dns search domains", - EnvVar: "PLUGIN_CUSTOM_DNS_SEARCH", - }, - cli.BoolFlag{ - Name: "daemon.insecure", - Usage: "docker daemon allows insecure registries", - EnvVar: "PLUGIN_INSECURE", - }, - cli.BoolFlag{ - Name: "daemon.ipv6", - Usage: "docker daemon IPv6 networking", - EnvVar: "PLUGIN_IPV6", - }, - cli.BoolFlag{ - Name: "daemon.experimental", - Usage: "docker daemon Experimental mode", - EnvVar: "PLUGIN_EXPERIMENTAL", - }, - cli.BoolFlag{ - Name: "daemon.debug", - Usage: "docker daemon executes in debug mode", - EnvVar: "PLUGIN_DEBUG,DOCKER_LAUNCH_DEBUG", - }, - cli.BoolFlag{ - Name: "daemon.off", - Usage: "don't start the docker daemon", - EnvVar: "PLUGIN_DAEMON_OFF", - }, - cli.StringFlag{ - Name: "dockerfile", - Usage: "build dockerfile", - Value: "Dockerfile", - EnvVar: "PLUGIN_DOCKERFILE", - }, - cli.StringFlag{ - Name: "context", - Usage: "build context", - Value: ".", - EnvVar: "PLUGIN_CONTEXT", - }, - cli.StringSliceFlag{ - Name: "tags", - Usage: "build tags", - Value: &cli.StringSlice{"latest"}, - EnvVar: "PLUGIN_TAG,PLUGIN_TAGS", - FilePath: ".tags", - }, - cli.BoolFlag{ - Name: "tags.auto", - Usage: "default build tags", - EnvVar: "PLUGIN_DEFAULT_TAGS,PLUGIN_AUTO_TAG", - }, - cli.StringFlag{ - Name: "tags.suffix", - Usage: "default build tags with suffix", - EnvVar: "PLUGIN_DEFAULT_SUFFIX,PLUGIN_AUTO_TAG_SUFFIX", - }, - cli.StringSliceFlag{ - Name: "args", - Usage: "build args", - EnvVar: "PLUGIN_BUILD_ARGS", - }, - cli.StringSliceFlag{ - Name: "args-from-env", - Usage: "build args", - EnvVar: "PLUGIN_BUILD_ARGS_FROM_ENV", - }, - cli.BoolFlag{ - Name: "quiet", - Usage: "quiet docker build", - EnvVar: "PLUGIN_QUIET", - }, - cli.StringFlag{ - Name: "target", - Usage: "build target", - EnvVar: "PLUGIN_TARGET", - }, - cli.StringSliceFlag{ - Name: "cache-from", - Usage: "images to consider as cache sources", - EnvVar: "PLUGIN_CACHE_FROM", - }, - cli.BoolFlag{ - Name: "squash", - Usage: "squash the layers at build time", - EnvVar: "PLUGIN_SQUASH", - }, - cli.BoolTFlag{ - Name: "pull-image", - Usage: "force pull base image at build time", - EnvVar: "PLUGIN_PULL_IMAGE", - }, - cli.BoolFlag{ - Name: "compress", - Usage: "compress the build context using gzip", - EnvVar: "PLUGIN_COMPRESS", - }, - cli.StringFlag{ - Name: "repo", - Usage: "docker repository", - EnvVar: "PLUGIN_REPO", - }, - cli.StringFlag{ - Name: "docker.registry", - Usage: "docker registry", - Value: "https://index.docker.io/v1/", - EnvVar: "PLUGIN_REGISTRY,DOCKER_REGISTRY", - }, - cli.StringFlag{ - Name: "docker.username", - Usage: "docker username", - EnvVar: "PLUGIN_USERNAME,DOCKER_USERNAME", - }, - cli.StringFlag{ - Name: "docker.password", - Usage: "docker password", - EnvVar: "PLUGIN_PASSWORD,DOCKER_PASSWORD", - }, - cli.StringFlag{ - Name: "docker.email", - Usage: "docker email", - EnvVar: "PLUGIN_EMAIL,DOCKER_EMAIL", - }, - cli.StringFlag{ - Name: "docker.config", - Usage: "docker json dockerconfig content", - EnvVar: "PLUGIN_CONFIG,DOCKER_PLUGIN_CONFIG", - }, - cli.BoolTFlag{ - Name: "docker.purge", - Usage: "docker should cleanup images", - EnvVar: "PLUGIN_PURGE", - }, - cli.StringFlag{ - Name: "repo.branch", - Usage: "repository default branch", - EnvVar: "DRONE_REPO_BRANCH", - }, - cli.BoolFlag{ - Name: "no-cache", - Usage: "do not use cached intermediate containers", - EnvVar: "PLUGIN_NO_CACHE", - }, - cli.StringSliceFlag{ - Name: "add-host", - Usage: "additional host:IP mapping", - EnvVar: "PLUGIN_ADD_HOST", - }, - cli.StringSliceFlag{ - Name: "platforms", - Usage: "arget platform for build", - EnvVar: "PLUGIN_PLATFORMS", - }, + app := &cli.App{ + Name: "drone-docker-buildx", + Usage: "build docker container with DinD and buildx", + Version: version, + Flags: append(settingsFlags(settings), urfave.Flags()...), + Action: run(settings), } if err := app.Run(os.Args); err != nil { - logrus.Fatal(err) + errors.HandleExit(err) } } -func run(c *cli.Context) error { - plugin := docker.Plugin{ - Dryrun: c.Bool("dry-run"), - Cleanup: c.BoolT("docker.purge"), - Login: docker.Login{ - Registry: c.String("docker.registry"), - Username: c.String("docker.username"), - Password: c.String("docker.password"), - Email: c.String("docker.email"), - Config: c.String("docker.config"), - }, - Build: docker.Build{ - Remote: c.String("remote.url"), - Name: c.String("commit.sha"), - Dockerfile: c.String("dockerfile"), - Context: c.String("context"), - Tags: c.StringSlice("tags"), - Platforms: c.StringSlice("platforms"), - Args: c.StringSlice("args"), - ArgsEnv: c.StringSlice("args-from-env"), - Target: c.String("target"), - Squash: c.Bool("squash"), - Pull: c.BoolT("pull-image"), - CacheFrom: c.StringSlice("cache-from"), - Compress: c.Bool("compress"), - Repo: c.String("repo"), - NoCache: c.Bool("no-cache"), - AddHost: c.StringSlice("add-host"), - Quiet: c.Bool("quiet"), - }, - Daemon: docker.Daemon{ - Registry: c.String("docker.registry"), - Mirror: c.String("daemon.mirror"), - StorageDriver: c.String("daemon.storage-driver"), - StoragePath: c.String("daemon.storage-path"), - Insecure: c.Bool("daemon.insecure"), - Disabled: c.Bool("daemon.off"), - IPv6: c.Bool("daemon.ipv6"), - Debug: c.Bool("daemon.debug"), - Bip: c.String("daemon.bip"), - DNS: c.StringSlice("daemon.dns"), - DNSSearch: c.StringSlice("daemon.dns-search"), - MTU: c.String("daemon.mtu"), - Experimental: c.Bool("daemon.experimental"), - }, - } +func run(settings *plugin.Settings) cli.ActionFunc { + return func(ctx *cli.Context) error { + urfave.LoggingFromContext(ctx) - if c.Bool("tags.auto") { - if docker.UseDefaultTag( // return true if tag event or default branch - c.String("commit.ref"), - c.String("repo.branch"), - ) { - tag, err := docker.DefaultTagSuffix( - c.String("commit.ref"), - c.String("tags.suffix"), - ) - if err != nil { - logrus.Printf("cannot build docker image for %s, invalid semantic version", c.String("commit.ref")) - return err + 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 } - plugin.Build.Tags = tag - } else { - logrus.Printf("skipping automated docker build for %s", c.String("commit.ref")) - return nil - } - } - return plugin.Exec() + 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/docker.go b/docker.go deleted file mode 100644 index 7bf9d44..0000000 --- a/docker.go +++ /dev/null @@ -1,392 +0,0 @@ -package docker - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "time" -) - -type ( - // Daemon defines Docker daemon parameters. - Daemon struct { - Registry string // Docker registry - Mirror string // Docker registry mirror - Insecure bool // Docker daemon enable insecure registries - StorageDriver string // Docker daemon storage driver - StoragePath string // Docker daemon storage path - Disabled bool // DOcker daemon is disabled (already running) - Debug bool // Docker daemon started in debug mode - Bip string // Docker daemon network bridge IP address - DNS []string // Docker daemon dns server - DNSSearch []string // Docker daemon dns search domain - MTU string // Docker daemon mtu setting - IPv6 bool // Docker daemon IPv6 networking - Experimental bool // Docker daemon enable experimental mode - } - - // Login defines Docker login parameters. - Login struct { - Registry string // Docker registry address - Username string // Docker registry username - Password string // Docker registry password - Email string // Docker registry email - Config string // Docker Auth Config - } - - // Build defines Docker build parameters. - Build struct { - Remote string // Git remote URL - Name string // Docker build using default named tag - Dockerfile string // Docker build Dockerfile - Context string // Docker build context - Tags []string // Docker build tags - Platforms []string // Docker build target platforms - Args []string // Docker build args - ArgsEnv []string // Docker build args from env - Target string // Docker build target - Squash bool // Docker build squash - Pull bool // Docker build pull - CacheFrom []string // Docker build cache-from - Compress bool // Docker build compress - Repo string // Docker build repository - NoCache bool // Docker build no-cache - AddHost []string // Docker build add-host - Quiet bool // Docker build quiet - } - - // Plugin defines the Docker plugin parameters. - Plugin struct { - Login Login // Docker login configuration - Build Build // Docker build configuration - Daemon Daemon // Docker daemon configuration - Dryrun bool // Docker push is skipped - Cleanup bool // Docker purge is enabled - } -) - -// Exec executes the plugin step -func (p Plugin) Exec() error { - // start the Docker daemon server - if !p.Daemon.Disabled { - p.startDaemon() - } - - // poll the docker daemon until it is started. This ensures the daemon is - // ready to accept connections before we proceed. - for i := 0; i < 15; i++ { - cmd := commandInfo() - err := cmd.Run() - if err == nil { - break - } - time.Sleep(time.Second * 1) - } - - // Create Auth Config File - if p.Login.Config != "" { - os.MkdirAll(dockerHome, 0600) - - path := filepath.Join(dockerHome, "config.json") - err := ioutil.WriteFile(path, []byte(p.Login.Config), 0600) - if err != nil { - return fmt.Errorf("error writing config.json: %s", err) - } - } - - // login to the Docker registry - if p.Login.Password != "" { - cmd := commandLogin(p.Login) - err := cmd.Run() - if err != nil { - return fmt.Errorf("error authenticating: %s", err) - } - } - - switch { - case p.Login.Password != "": - fmt.Println("Detected registry credentials") - case p.Login.Config != "": - fmt.Println("Detected registry credentials file") - default: - fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.") - } - - if p.Build.Squash && !p.Daemon.Experimental { - fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...") - p.Build.Squash = false - } - - // add proxy build args - addProxyBuildArgs(&p.Build) - - var cmds []*exec.Cmd - cmds = append(cmds, commandVersion()) // docker version - cmds = append(cmds, commandInfo()) // docker info - cmds = append(cmds, commandBuilder()) - cmds = append(cmds, commandBuildx()) - - // pre-pull cache images - for _, img := range p.Build.CacheFrom { - cmds = append(cmds, commandPull(img)) - } - - cmds = append(cmds, commandBuild(p.Build)) // docker build - - for _, tag := range p.Build.Tags { - cmds = append(cmds, commandTag(p.Build, tag)) // docker tag - - if !p.Dryrun { - cmds = append(cmds, commandPush(p.Build, tag)) // docker push - } - } - - if p.Cleanup { - cmds = append(cmds, commandRmi(p.Build.Name)) // docker rmi - cmds = append(cmds, commandPrune()) // docker system prune -f - } - - // execute all commands in batch mode. - for _, cmd := range cmds { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - trace(cmd) - - err := cmd.Run() - if err != nil && isCommandPull(cmd.Args) { - fmt.Printf("Could not pull cache-from image %s. Ignoring...\n", cmd.Args[2]) - } else if err != nil && isCommandPrune(cmd.Args) { - fmt.Printf("Could not prune system containers. Ignoring...\n") - } else if err != nil && isCommandRmi(cmd.Args) { - fmt.Printf("Could not remove image %s. Ignoring...\n", cmd.Args[2]) - } else if err != nil { - return err - } - } - - return nil -} - -// helper function to create the docker login command. -func commandLogin(login Login) *exec.Cmd { - if login.Email != "" { - return commandLoginEmail(login) - } - return exec.Command( - dockerExe, "login", - "-u", login.Username, - "-p", login.Password, - login.Registry, - ) -} - -// helper to check if args match "docker pull " -func isCommandPull(args []string) bool { - return len(args) > 2 && args[1] == "pull" -} - -func commandPull(repo string) *exec.Cmd { - return exec.Command(dockerExe, "pull", repo) -} - -func commandLoginEmail(login Login) *exec.Cmd { - return exec.Command( - dockerExe, "login", - "-u", login.Username, - "-p", login.Password, - "-e", login.Email, - login.Registry, - ) -} - -// helper function to create the docker info command. -func commandVersion() *exec.Cmd { - return exec.Command(dockerExe, "version") -} - -// helper function to create the docker info command. -func commandInfo() *exec.Cmd { - return exec.Command(dockerExe, "info") -} - -func commandBuilder() *exec.Cmd { - return exec.Command(dockerExe, "buildx", "create", "--use") -} - -func commandBuildx() *exec.Cmd { - return exec.Command(dockerExe, "buildx", "ls") -} - -// helper function to create the docker build command. -func commandBuild(build Build) *exec.Cmd { - args := []string{ - "buildx", - "build", - "--load", - "--rm=true", - "-f", build.Dockerfile, - "-t", build.Name, - } - - args = append(args, build.Context) - if build.Squash { - args = append(args, "--squash") - } - if build.Compress { - args = append(args, "--compress") - } - if build.Pull { - args = append(args, "--pull=true") - } - if build.NoCache { - args = append(args, "--no-cache") - } - for _, arg := range build.CacheFrom { - args = append(args, "--cache-from", arg) - } - for _, arg := range build.ArgsEnv { - addProxyValue(&build, arg) - } - for _, arg := range build.Args { - args = append(args, "--build-arg", arg) - } - for _, host := range build.AddHost { - args = append(args, "--add-host", host) - } - if build.Target != "" { - args = append(args, "--target", build.Target) - } - if build.Quiet { - args = append(args, "--quiet") - } - - if len(build.Platforms) > 0 { - args = append(args, "--platform", strings.Join(build.Platforms[:], ",")) - } - - return exec.Command(dockerExe, args...) -} - -// helper function to add proxy values from the environment -func addProxyBuildArgs(build *Build) { - addProxyValue(build, "http_proxy") - addProxyValue(build, "https_proxy") - addProxyValue(build, "no_proxy") -} - -// helper function to add the upper and lower case version of a proxy value. -func addProxyValue(build *Build, key string) { - value := getProxyValue(key) - - if len(value) > 0 && !hasProxyBuildArg(build, key) { - build.Args = append(build.Args, fmt.Sprintf("%s=%s", key, value)) - build.Args = append(build.Args, fmt.Sprintf("%s=%s", strings.ToUpper(key), value)) - } -} - -// helper function to get a proxy value from the environment. -// -// assumes that the upper and lower case versions of are the same. -func getProxyValue(key string) string { - value := os.Getenv(key) - - if len(value) > 0 { - return value - } - - return os.Getenv(strings.ToUpper(key)) -} - -// helper function that looks to see if a proxy value was set in the build args. -func hasProxyBuildArg(build *Build, key string) bool { - keyUpper := strings.ToUpper(key) - - for _, s := range build.Args { - if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { - return true - } - } - - return false -} - -// helper function to create the docker tag command. -func commandTag(build Build, tag string) *exec.Cmd { - var ( - source = build.Name - target = fmt.Sprintf("%s:%s", build.Repo, tag) - ) - return exec.Command( - dockerExe, "tag", source, target, - ) -} - -// helper function to create the docker push command. -func commandPush(build Build, tag string) *exec.Cmd { - target := fmt.Sprintf("%s:%s", build.Repo, tag) - return exec.Command(dockerExe, "push", target) -} - -// helper function to create the docker daemon command. -func commandDaemon(daemon Daemon) *exec.Cmd { - args := []string{ - "--data-root", daemon.StoragePath, - "--host=unix:///var/run/docker.sock", - } - - if daemon.StorageDriver != "" { - args = append(args, "-s", daemon.StorageDriver) - } - if daemon.Insecure && daemon.Registry != "" { - args = append(args, "--insecure-registry", daemon.Registry) - } - if daemon.IPv6 { - args = append(args, "--ipv6") - } - if len(daemon.Mirror) != 0 { - args = append(args, "--registry-mirror", daemon.Mirror) - } - if len(daemon.Bip) != 0 { - args = append(args, "--bip", daemon.Bip) - } - for _, dns := range daemon.DNS { - args = append(args, "--dns", dns) - } - for _, dnsSearch := range daemon.DNSSearch { - args = append(args, "--dns-search", dnsSearch) - } - if len(daemon.MTU) != 0 { - args = append(args, "--mtu", daemon.MTU) - } - if daemon.Experimental { - args = append(args, "--experimental") - } - return exec.Command(dockerdExe, args...) -} - -// helper to check if args match "docker prune" -func isCommandPrune(args []string) bool { - return len(args) > 3 && args[2] == "prune" -} - -func commandPrune() *exec.Cmd { - return exec.Command(dockerExe, "system", "prune", "-f") -} - -// helper to check if args match "docker rmi" -func isCommandRmi(args []string) bool { - return len(args) > 2 && args[1] == "rmi" -} - -func commandRmi(tag string) *exec.Cmd { - return exec.Command(dockerExe, "rmi", tag) -} - -// trace writes each command to stdout with the command wrapped in an xml -// tag so that it can be extracted and displayed in the logs. -func trace(cmd *exec.Cmd) { - fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " ")) -} diff --git a/docker_test.go b/docker_test.go deleted file mode 100644 index 1cdc3ff..0000000 --- a/docker_test.go +++ /dev/null @@ -1 +0,0 @@ -package docker diff --git a/go.mod b/go.mod index b892cb2..f0f63a5 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ -module github.com/drone-plugins/drone-docker +module github.com/thegeeklab/drone-docker-buildx require ( github.com/coreos/go-semver v0.3.0 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/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.7.0 - github.com/urfave/cli v1.22.5 + github.com/urfave/cli/v2 v2.3.0 golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 // indirect ) diff --git a/go.sum b/go.sum index 829e79f..64fb071 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng 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/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pkg/errors v0.8.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= @@ -17,16 +22,29 @@ 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/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 v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= -github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 h1:DQmQoKxQWtyybCtX/3dIuDBcAhFszqq8YiNeS6sNu1c= golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +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/daemon.go b/plugin/daemon.go similarity index 80% rename from daemon.go rename to plugin/daemon.go index 5976cd1..0705fb5 100644 --- a/daemon.go +++ b/plugin/daemon.go @@ -1,6 +1,4 @@ -// +build !windows - -package docker +package plugin import ( "io/ioutil" @@ -12,8 +10,8 @@ const dockerdExe = "/usr/local/bin/dockerd" const dockerHome = "/root/.docker/" func (p Plugin) startDaemon() { - cmd := commandDaemon(p.Daemon) - if p.Daemon.Debug { + cmd := commandDaemon(p.settings.Daemon) + if p.settings.Daemon.Debug { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } else { diff --git a/plugin/docker.go b/plugin/docker.go new file mode 100644 index 0000000..c4ca0dd --- /dev/null +++ b/plugin/docker.go @@ -0,0 +1,233 @@ +package plugin + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/urfave/cli/v2" +) + +// helper function to create the docker login command. +func commandLogin(login Login) *exec.Cmd { + if login.Email != "" { + return commandLoginEmail(login) + } + return exec.Command( + dockerExe, "login", + "-u", login.Username, + "-p", login.Password, + login.Registry, + ) +} + +// helper to check if args match "docker pull " +func isCommandPull(args []string) bool { + return len(args) > 2 && args[1] == "pull" +} + +func commandPull(repo string) *exec.Cmd { + return exec.Command(dockerExe, "pull", repo) +} + +func commandLoginEmail(login Login) *exec.Cmd { + return exec.Command( + dockerExe, "login", + "-u", login.Username, + "-p", login.Password, + "-e", login.Email, + login.Registry, + ) +} + +// helper function to create the docker info command. +func commandVersion() *exec.Cmd { + return exec.Command(dockerExe, "version") +} + +// helper function to create the docker info command. +func commandInfo() *exec.Cmd { + return exec.Command(dockerExe, "info") +} + +func commandBuilder() *exec.Cmd { + return exec.Command(dockerExe, "buildx", "create", "--use") +} + +func commandBuildx() *exec.Cmd { + return exec.Command(dockerExe, "buildx", "ls") +} + +// helper function to create the docker build command. +func commandBuild(build Build) *exec.Cmd { + args := []string{ + "buildx", + "build", + "--load", + "--rm=true", + "-f", build.Dockerfile, + "-t", build.Name, + } + + args = append(args, build.Context) + if build.Squash { + args = append(args, "--squash") + } + if build.Compress { + args = append(args, "--compress") + } + if build.Pull { + args = append(args, "--pull=true") + } + if build.NoCache { + args = append(args, "--no-cache") + } + for _, arg := range build.CacheFrom.Value() { + args = append(args, "--cache-from", arg) + } + for _, arg := range build.ArgsEnv.Value() { + addProxyValue(&build, arg) + } + for _, arg := range build.Args.Value() { + args = append(args, "--build-arg", arg) + } + for _, host := range build.AddHost.Value() { + args = append(args, "--add-host", host) + } + if build.Target != "" { + args = append(args, "--target", build.Target) + } + if build.Quiet { + args = append(args, "--quiet") + } + + if len(build.Platforms.Value()) > 0 { + args = append(args, "--platform", strings.Join(build.Platforms.Value()[:], ",")) + } + + return exec.Command(dockerExe, args...) +} + +// helper function to add proxy values from the environment +func addProxyBuildArgs(build *Build) { + addProxyValue(build, "http_proxy") + addProxyValue(build, "https_proxy") + addProxyValue(build, "no_proxy") +} + +// helper function to add the upper and lower case version of a proxy value. +func addProxyValue(build *Build, key string) { + value := getProxyValue(key) + + if len(value) > 0 && !hasProxyBuildArg(build, key) { + args := build.Args.Value() + args = append(build.Args.Value(), fmt.Sprintf("%s=%s", key, value)) + args = append(build.Args.Value(), fmt.Sprintf("%s=%s", strings.ToUpper(key), value)) + build.Args = *cli.NewStringSlice(args...) + } +} + +// helper function to get a proxy value from the environment. +// +// assumes that the upper and lower case versions of are the same. +func getProxyValue(key string) string { + value := os.Getenv(key) + + if len(value) > 0 { + return value + } + + return os.Getenv(strings.ToUpper(key)) +} + +// helper function that looks to see if a proxy value was set in the build args. +func hasProxyBuildArg(build *Build, key string) bool { + keyUpper := strings.ToUpper(key) + + for _, s := range build.Args.Value() { + if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { + return true + } + } + + return false +} + +// helper function to create the docker tag command. +func commandTag(build Build, tag string) *exec.Cmd { + var ( + source = build.Name + target = fmt.Sprintf("%s:%s", build.Repo, tag) + ) + return exec.Command( + dockerExe, "tag", source, target, + ) +} + +// helper function to create the docker push command. +func commandPush(build Build, tag string) *exec.Cmd { + target := fmt.Sprintf("%s:%s", build.Repo, tag) + return exec.Command(dockerExe, "push", target) +} + +// helper function to create the docker daemon command. +func commandDaemon(daemon Daemon) *exec.Cmd { + args := []string{ + "--data-root", daemon.StoragePath, + "--host=unix:///var/run/docker.sock", + } + + if daemon.StorageDriver != "" { + args = append(args, "-s", daemon.StorageDriver) + } + if daemon.Insecure && daemon.Registry != "" { + args = append(args, "--insecure-registry", daemon.Registry) + } + if daemon.IPv6 { + args = append(args, "--ipv6") + } + if len(daemon.Mirror) != 0 { + args = append(args, "--registry-mirror", daemon.Mirror) + } + if len(daemon.Bip) != 0 { + args = append(args, "--bip", daemon.Bip) + } + for _, dns := range daemon.DNS.Value() { + args = append(args, "--dns", dns) + } + for _, dnsSearch := range daemon.DNSSearch.Value() { + args = append(args, "--dns-search", dnsSearch) + } + if len(daemon.MTU) != 0 { + args = append(args, "--mtu", daemon.MTU) + } + if daemon.Experimental { + args = append(args, "--experimental") + } + return exec.Command(dockerdExe, args...) +} + +// helper to check if args match "docker prune" +func isCommandPrune(args []string) bool { + return len(args) > 3 && args[2] == "prune" +} + +func commandPrune() *exec.Cmd { + return exec.Command(dockerExe, "system", "prune", "-f") +} + +// helper to check if args match "docker rmi" +func isCommandRmi(args []string) bool { + return len(args) > 2 && args[1] == "rmi" +} + +func commandRmi(tag string) *exec.Cmd { + return exec.Command(dockerExe, "rmi", tag) +} + +// trace writes each command to stdout with the command wrapped in an xml +// tag so that it can be extracted and displayed in the logs. +func trace(cmd *exec.Cmd) { + fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " ")) +} diff --git a/plugin/docker_test.go b/plugin/docker_test.go new file mode 100644 index 0000000..b0736c3 --- /dev/null +++ b/plugin/docker_test.go @@ -0,0 +1 @@ +package plugin diff --git a/plugin/impl.go b/plugin/impl.go new file mode 100644 index 0000000..59e6e22 --- /dev/null +++ b/plugin/impl.go @@ -0,0 +1,203 @@ +package plugin + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +// Daemon defines Docker daemon parameters. +type Daemon struct { + Registry string // Docker registry + Mirror string // Docker registry mirror + Insecure bool // Docker daemon enable insecure registries + StorageDriver string // Docker daemon storage driver + StoragePath string // Docker daemon storage path + Disabled bool // DOcker daemon is disabled (already running) + Debug bool // Docker daemon started in debug mode + Bip string // Docker daemon network bridge IP address + DNS cli.StringSlice // Docker daemon dns server + DNSSearch cli.StringSlice // Docker daemon dns search domain + MTU string // Docker daemon mtu setting + IPv6 bool // Docker daemon IPv6 networking + Experimental bool // Docker daemon enable experimental mode +} + +// Login defines Docker login parameters. +type Login struct { + Registry string // Docker registry address + Username string // Docker registry username + Password string // Docker registry password + Email string // Docker registry email + Config string // Docker Auth Config +} + +// Build defines Docker build parameters. +type Build struct { + Remote string // Git remote URL + Name string // Git commit sha used as docker default named tag + Ref string // Git commit ref + Branch string // Git repository branch + Dockerfile string // Docker build Dockerfile + Context string // Docker build context + TagsAuto bool // Docker build auto tag + TagsSuffix string // Docker build tags with suffix + Tags cli.StringSlice // Docker build tags + Platforms cli.StringSlice // Docker build target platforms + Args cli.StringSlice // Docker build args + ArgsEnv cli.StringSlice // Docker build args from env + Target string // Docker build target + Squash bool // Docker build squash + Pull bool // Docker build pull + CacheFrom cli.StringSlice // Docker build cache-from + Compress bool // Docker build compress + Repo string // Docker build repository + NoCache bool // Docker build no-cache + AddHost cli.StringSlice // Docker build add-host + Quiet bool // Docker build quiet +} + +// Settings for the Plugin. +type Settings struct { + Daemon Daemon + Login Login + Build Build + Dryrun bool + Cleanup bool +} + +// Validate handles the settings validation of the plugin. +func (p *Plugin) Validate() error { + p.settings.Daemon.Registry = p.settings.Login.Registry + + if p.settings.Build.TagsAuto { + // return true if tag event or default branch + if UseDefaultTag( + p.settings.Build.Ref, + p.settings.Build.Branch, + ) { + tag, err := DefaultTagSuffix( + p.settings.Build.Ref, + p.settings.Build.TagsSuffix, + ) + if err != nil { + logrus.Printf("cannot build docker image for %s, invalid semantic version", p.settings.Build.Ref) + return err + } + p.settings.Build.Tags = *cli.NewStringSlice(tag...) + } else { + logrus.Printf("skipping automated docker build for %s", p.settings.Build.Ref) + return nil + } + } + + return nil +} + +// Execute provides the implementation of the plugin. +func (p *Plugin) Execute() error { + // start the Docker daemon server + if !p.settings.Daemon.Disabled { + p.startDaemon() + } + + // poll the docker daemon until it is started. This ensures the daemon is + // ready to accept connections before we proceed. + for i := 0; i < 15; i++ { + cmd := commandInfo() + err := cmd.Run() + if err == nil { + break + } + time.Sleep(time.Second * 1) + } + + // Create Auth Config File + if p.settings.Login.Config != "" { + os.MkdirAll(dockerHome, 0600) + + path := filepath.Join(dockerHome, "config.json") + err := ioutil.WriteFile(path, []byte(p.settings.Login.Config), 0600) + if err != nil { + return fmt.Errorf("error writing config.json: %s", err) + } + } + + // login to the Docker registry + if p.settings.Login.Password != "" { + cmd := commandLogin(p.settings.Login) + err := cmd.Run() + if err != nil { + return fmt.Errorf("error authenticating: %s", err) + } + } + + switch { + case p.settings.Login.Password != "": + fmt.Println("Detected registry credentials") + case p.settings.Login.Config != "": + fmt.Println("Detected registry credentials file") + default: + fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.") + } + + if p.settings.Build.Squash && !p.settings.Daemon.Experimental { + fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...") + p.settings.Build.Squash = false + } + + // add proxy build args + addProxyBuildArgs(&p.settings.Build) + + var cmds []*exec.Cmd + cmds = append(cmds, commandVersion()) // docker version + cmds = append(cmds, commandInfo()) // docker info + cmds = append(cmds, commandBuilder()) + cmds = append(cmds, commandBuildx()) + + // pre-pull cache images + for _, img := range p.settings.Build.CacheFrom.Value() { + cmds = append(cmds, commandPull(img)) + } + + cmds = append(cmds, commandBuild(p.settings.Build)) // docker build + + for _, tag := range p.settings.Build.Tags.Value() { + cmds = append(cmds, commandTag(p.settings.Build, tag)) // docker tag + + if !p.settings.Dryrun { + cmds = append(cmds, commandPush(p.settings.Build, tag)) // docker push + } + } + + if p.settings.Cleanup { + cmds = append(cmds, commandRmi(p.settings.Build.Name)) // docker rmi + cmds = append(cmds, commandPrune()) // docker system prune -f + } + + // execute all commands in batch mode. + for _, cmd := range cmds { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + trace(cmd) + + err := cmd.Run() + if err != nil && isCommandPull(cmd.Args) { + fmt.Printf("Could not pull cache-from image %s. Ignoring...\n", cmd.Args[2]) + } else if err != nil && isCommandPrune(cmd.Args) { + fmt.Printf("Could not prune system containers. Ignoring...\n") + } else if err != nil && isCommandRmi(cmd.Args) { + fmt.Printf("Could not remove image %s. Ignoring...\n", cmd.Args[2]) + } else if 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, + } +} diff --git a/tags.go b/plugin/tags.go similarity index 99% rename from tags.go rename to plugin/tags.go index 32114d9..76f7b56 100644 --- a/tags.go +++ b/plugin/tags.go @@ -1,4 +1,4 @@ -package docker +package plugin import ( "fmt" diff --git a/tags_test.go b/plugin/tags_test.go similarity index 99% rename from tags_test.go rename to plugin/tags_test.go index 6c3feb2..89b65da 100644 --- a/tags_test.go +++ b/plugin/tags_test.go @@ -1,4 +1,4 @@ -package docker +package plugin import ( "reflect"