grim/convey

Merge the redux branch into default.

2021-12-20, Gary Kramlich
6b1dbda836d9
Merge the redux branch into default.

This officials abandons the 0.14.0 release and moves right on to 0.15.0 which
includes replacing docker with podman and simplifying many things.
  • +5 -9
    .hgignore
  • +20 -45
    ChangeLog
  • +87 -78
    README.md
  • +35 -107
    REFERENCE.md
  • +0 -20
    aws/README.md
  • +0 -29
    aws/aws.go
  • +0 -21
    aws/data/ecr-test.yml
  • +0 -74
    aws/ecr-get-login.go
  • +0 -57
    aws/parser.go
  • +0 -49
    color/x11.go
  • +25 -6
    config/config.go
  • +27 -48
    config/loader.go
  • +0 -86
    config/override_test.go
  • +72 -0
    config/raw.go
  • +16 -16
    config/tasks.go
  • +1 -1
    consts/consts.go
  • +0 -8
    convey.sublime-project
  • +42 -33
    convey.yml
  • +147 -146
    docker/README.md
  • +18 -98
    docker/build.go
  • +0 -88
    docker/build_test.go
  • +0 -65
    docker/docker.go
  • +11 -121
    docker/environment.go
  • +0 -58
    docker/environment_test.go
  • +2 -27
    docker/errors.go
  • +41 -0
    docker/export.go
  • +23 -0
    docker/export_test.go
  • +0 -79
    docker/healthcheck.go
  • +41 -0
    docker/import.go
  • +23 -0
    docker/import_test.go
  • +16 -46
    docker/login.go
  • +18 -21
    docker/login_test.go
  • +14 -36
    docker/logout.go
  • +6 -20
    docker/logout_test.go
  • +0 -124
    docker/parser.go
  • +0 -41
    docker/parser_test.go
  • +14 -41
    docker/pull.go
  • +25 -40
    docker/pull_test.go
  • +14 -40
    docker/push.go
  • +25 -40
    docker/push_test.go
  • +13 -45
    docker/remove.go
  • +23 -38
    docker/remove_test.go
  • +38 -384
    docker/run.go
  • +4 -18
    docker/run_test.go
  • +20 -48
    docker/tag.go
  • +34 -51
    docker/tag_test.go
  • +21 -0
    docker/tasks.go
  • +0 -79
    docker/util.go
  • +0 -89
    docker/util_test.go
  • +40 -24
    environment/default.go
  • +37 -75
    environment/environment.go
  • +56 -0
    environment/expand.go
  • +0 -47
    environment/merge.go
  • +28 -3
    exec/exec.go
  • +0 -7
    fs/copy.go
  • +0 -20
    fs/fs.go
  • +6 -0
    globals/globals.go
  • +18 -19
    go.mod
  • +26 -65
    go.sum
  • +0 -99
    kubectl/README.md
  • +0 -55
    kubectl/apply.go
  • +0 -31
    kubectl/apply_test.go
  • +0 -191
    kubectl/command.go
  • +0 -35
    kubectl/context.go
  • +0 -55
    kubectl/create.go
  • +0 -31
    kubectl/create_test.go
  • +0 -55
    kubectl/delete.go
  • +0 -31
    kubectl/delete_test.go
  • +0 -25
    kubectl/errors.go
  • +0 -33
    kubectl/kubectl.go
  • +0 -74
    kubectl/rollout.go
  • +0 -31
    kubectl/rollout_test.go
  • +0 -18
    loaders/bitbucket/bitbucket.go
  • +0 -10
    loaders/bitbucket/data/branch-image.yml
  • +0 -9
    loaders/bitbucket/data/branch-no-image.yml
  • +0 -15
    loaders/bitbucket/data/complex-global-image-with-login.yml
  • +0 -12
    loaders/bitbucket/data/complex-global-image.yml
  • +0 -14
    loaders/bitbucket/data/complex-step-image-with-login.yml
  • +0 -12
    loaders/bitbucket/data/complex-step-image.yml
  • +0 -11
    loaders/bitbucket/data/complex-step-simple-image.yml
  • +0 -13
    loaders/bitbucket/data/docker-mixed.yml
  • +0 -9
    loaders/bitbucket/data/docker-simple.yml
  • +0 -11
    loaders/bitbucket/data/simple.yml
  • +0 -277
    loaders/bitbucket/loader.go
  • +0 -514
    loaders/bitbucket/loader_test.go
  • +0 -128
    loaders/bitbucket/types.go
  • +0 -249
    loaders/bitbucket/unmarshal_test.go
  • +0 -19
    loaders/codebuild/codebuild.go
  • +0 -210
    loaders/codebuild/loader.go
  • +0 -41
    loaders/codebuild/types.go
  • +0 -101
    loaders/codebuild/unmarshal_test.go
  • +0 -288
    loaders/convey/convey.go
  • +0 -77
    loaders/convey/convey_test.go
  • +0 -87
    loaders/convey/default_plan_test.go
  • +0 -94
    loaders/convey/environment_test.go
  • +0 -40
    loaders/convey/errors.go
  • +0 -301
    loaders/convey/extends_test.go
  • +0 -54
    loaders/convey/plans.go
  • +0 -109
    loaders/convey/tasks.go
  • +0 -3
    logging/logging.go
  • +27 -200
    main.go
  • +44 -0
    metaplans/metaplans.go
  • +0 -44
    plans/metaplans.go
  • +13 -21
    plans/plans.go
  • +1 -1
    plans/plans_test.go
  • +5 -9
    plans/stages_test.go
  • +193 -0
    podman/README.md
  • +99 -0
    podman/build.go
  • +32 -0
    podman/build_test.go
  • +25 -0
    podman/errors.go
  • +60 -0
    podman/login.go
  • +44 -0
    podman/login_test.go
  • +44 -0
    podman/logout.go
  • +21 -0
    podman/logout_test.go
  • +51 -0
    podman/pull.go
  • +31 -0
    podman/pull_test.go
  • +51 -0
    podman/push.go
  • +31 -0
    podman/push_test.go
  • +59 -0
    podman/remove.go
  • +21 -0
    podman/remove_test.go
  • +187 -0
    podman/run.go
  • +50 -0
    podman/tag.go
  • +41 -0
    podman/tag_test.go
  • +18 -0
    podman/tasks.go
  • +124 -0
    runner/cmd.go
  • +0 -56
    runners/convey.go
  • +0 -235
    runners/graphviz.go
  • +0 -53
    runners/listenvironment.go
  • +0 -36
    runners/listmetaplans.go
  • +0 -36
    runners/listplans.go
  • +0 -43
    runners/listtasks.go
  • +0 -28
    runners/runners.go
  • +0 -34
    runners/showconfig.go
  • +0 -91
    runtime/cleanup.go
  • +0 -88
    runtime/containers.go
  • +0 -53
    runtime/containers_test.go
  • +54 -20
    runtime/runtime.go
  • +53 -0
    script/README.md
  • +37 -0
    script/file.go
  • +81 -0
    script/interpreter.go
  • +0 -107
    script/script.go
  • +0 -142
    script/script_test.go
  • +84 -0
    script/shell.go
  • +11 -0
    script/tasks.go
  • +0 -105
    ssh/agent.go
  • +0 -188
    ssh/agent_test.go
  • +0 -18
    ssh/ssh.go
  • +5 -5
    stages/stages.go
  • +1 -1
    stages/unmarshal_test.go
  • +0 -85
    state/state.go
  • +57 -40
    tasks/README.md
  • +14 -15
    tasks/clean.go
  • +2 -2
    tasks/clone.go
  • +1 -1
    tasks/clone_test.go
  • +5 -1
    tasks/count.go
  • +1 -1
    tasks/errors.go
  • +19 -7
    tasks/export.go
  • +5 -1
    tasks/fail.go
  • +20 -4
    tasks/import.go
  • +7 -11
    tasks/import_test.go
  • +5 -1
    tasks/noop.go
  • +0 -58
    tasks/subtask.go
  • +7 -7
    tasks/tasks.go
  • +0 -28
    tests/breakout.yml
  • +0 -15
    tests/clean.yml
  • +0 -35
    tests/codebuild/buildspec.yml
  • +0 -15
    tests/slow.yml
  • +0 -16
    tests/test-workdir.yml
  • +0 -27
    workspace/taskdir.go
  • +0 -82
    workspace/workspace.go
  • +1 -1
    yaml/stringslice.go
  • +1 -1
    yaml/stringslice_test.go
  • --- a/.hgignore Thu Aug 26 08:11:34 2021 -0500
    +++ b/.hgignore Mon Dec 20 12:33:09 2021 -0600
    @@ -1,11 +1,7 @@
    -syntax: regexp
    -^convey(-.+-amd64(\.exe)?)?$
    -\/junit\.xml$
    -\/(foo|bar|baz|qux|quux)\/
    -vendor\/
    -
    syntax: glob
    -*.sublime-workspace
    .*.swp
    -.convey
    -
    +convey
    +convey-*-darwin-*
    +convey-*-freebsd-*
    +convey-*-linux-*
    +convey-*-windows-*.exe
    --- a/ChangeLog Thu Aug 26 08:11:34 2021 -0500
    +++ b/ChangeLog Mon Dec 20 12:33:09 2021 -0600
    @@ -1,46 +1,21 @@
    -0.14.0-dev:
    - * Changed tasks names for bitbucket pipeline tasks.
    - * Abstracted the bitbucket pipeline script parsing to a library function.
    - * Fixed branch detection for bitbucket pipelines.
    - * Fixed an issue where the files for clean tasks were not having their
    - variables expanded.
    - * Fixed a bug where tasks weren't being validated. Fixed #160
    - * Set the workdir parameter for docker/run tasks to ${CONVEY_WORKSPACE} if
    - the image does not have a workdir set and the user has not specified one in
    - convey.yml. Fixed #167
    - * Add a top level convey.yml option for specifying the required version of
    - convey to use for the convey.yml. Fixed #168
    - * Command line arguments were not being passed to the command line in a
    - secure manner. You couldn't run a shell command, but you could break the
    - command. Fixed #162
    - * Add copies of all VCS environment variables without their prefix as well as
    - a VCS environment variable. Fixed #164
    - * Added a new aws/ecr-login task for logging into AWS ECR. Fixed #161
    - * Added kubectl/apply, kubectl/create, and kubectl/delete tasks. Fixed #170
    - * Added kubectl/rollout task.
    - * Added a new convey/noop task that does nothing. Fixed #174
    - * Fixed an issue where relative import paths wouldn't work right if the
    - convey.yml is in another directory. Fixed #176
    - * Split the docs up to make them easier to maintain. Fixed #177
    - * Added FreeBSD as a build target.
    - * Build static binaries and remove the alpine binary as it's no longer
    - necessary. PR #40 (Steve Wills)
    - * Deleted all existing images.
    - * Deprecated docker/import and docker/export and replaced them with
    - convey/import and convey/export.
    - * Removed deprecated command line options: --graphviz, --list-environment,
    - --list-meta-plans, --list-plans, --list-tasks, and --config.
    - * Removed the network since it doesn't actually protect the host from the
    - container.
    - * Removed the convey/extend task as it's very normal case is easily
    - replicated by plan/stage environment variables.
    - * Removed the deprecated always attribute on stages.
    - * Added the convey/fail task which is good for testing.
    - * Removed deprecated tasks formats. All tasks types must be defined via the
    - type attribute now.
    - * Added the convey/subtask task to simplify some configurations.
    - * Removed the tasks from the list plans output.
    - * Removed the plans from the list metaplans output.
    +0.15.0:
    + * The command line now requires a `run` command before a plan name even if
    + you're running the default plan. For example `convey run` for the default
    + plan, or `convey run plan1` to run plan1.
    + * Replaced Docker support with podman. This simplifies many many things, but
    + you do now need podman to run convey >= 0.15.0.
    + * All docker tasks have been marked as deprecated but will map themselves
    + into their podman replacement tasks, expect for docker/environment whose
    + behavior has been removed.
    + * The workspace has been changed from a container to a directory on the host.
    + This should solve a lot of issues that `docker cp` created, but this
    + directory is now created next to the convey.yml file that's being run so
    + disk space issues could arise.
    +
    +0.14.0: abandoned
    + * This version grew out of control and was past the point of saving. We
    + decided to throw the entire thing out and move on to 0.15.0 instead of
    + trying to save this ill conceived version.
    0.13.1: 20180114
    * Write warning, error, and fatal log messages to stderr. Fixed #156
    @@ -83,7 +58,7 @@
    0.11.0: 20171015
    * Added a quiet option to Remove tasks. Fixed #131. PR #31 (Eric Fritz)
    - * Added an environment task for updating environment varables. Fixed #128. PR #32 (Eric Fritz)
    + * Added an environment task for updating environment variables. Fixed #128. PR #32 (Eric Fritz)
    * Don't pass empty environment variables to Runs tasks. PR #35 (Eric Fritz)
    * Fix an infinite loop in variable expansion. Fixed #130. PR #33 (Eric Fritz)
    * Fix scripts being left around if convey receives SIGINT. Fixed #102. PR #34 (Eric Fritz)
    @@ -171,7 +146,7 @@
    (fixes #75)
    * Fixed an issue where the convey/go-build images would fail on go get if
    the package didn't have an sources files in it's root.
    - * Fixed all go get's to ignore vendor directories (fixes #73)
    + * Fixed all go gets to ignore vendor directories (fixes #73)
    * Fixed subshell's in script commands (fixes #51, #74)
    0.3.2: 20170330
    --- a/README.md Thu Aug 26 08:11:34 2021 -0500
    +++ b/README.md Mon Dec 20 12:33:09 2021 -0600
    @@ -1,20 +1,30 @@
    # Convey
    -[![GoDoc](https://godoc.org/bitbucket.org/rw_grim/convey?status.svg)](https://godoc.org/bitbucket.org/rw_grim/convey)
    -[![Go Report Card](https://goreportcard.com/badge/bitbucket.org/rw_grim/convey)](https://goreportcard.com/report/bitbucket.org/rw_grim/convey)
    +Convey is a pipeline for running containers and protecting the host as much as
    +possible. It accomplishes this goal by creating a volume and making it
    +available to each item in the pipeline.
    -Convey is a pipeline for running containers and protecting the host as much as possible. It accomplishes this goal by creating a volume and making it available to each item in the pipeline.
    -
    -Convey was created out of the need to build [Pidgin](https://pidgin.im) and its supporting libraries on multiple Linux distributions, Windows and MacOS. While there are many continuous integration tools out there, all of them still require maintenance and can only be used by the project itself.
    +Convey was created out of the need to build [Pidgin](https://pidgin.im) and its
    +supporting libraries on multiple Linux distributions, Windows and MacOS. While
    +there are many continuous integration tools out there, all of them still
    +require maintenance and can only be used by the project itself.
    -This becomes a problem when you have a community of plugin developers who would also like to build against all of its supported operating systems but can't and shouldn't need to put forth the effort and maintenance involved with using other available tools.
    -
    -This is where Convey's concept of [build containers](#markdown-header-concepts) comes into use. Once you have all the dependencies required to build something, like a Pidgin plugin, you are then able to share that Docker image with everyone using this system and they are then able to use that image to build against. So a project like Pidgin can supply build images containing the most recent releases with all of the build dependencies for multiple platforms and then plugin developers can use that image to test their plugins against.
    +This becomes a problem when you have a community of plugin developers who would
    +also like to build against all of its supported operating systems but can't and
    +shouldn't need to put forth the effort and maintenance involved with using
    +other available tools.
    -This also works when it comes to deployments. You can create an image to deploy to [bintray](https://bintray.com/) so that users are able to specify options in their convey.yml and not have to worry about the specifics of how it's uploaded.
    +This is where Convey's concept of [build containers](#markdown-header-concepts)
    +comes into use. Once you have all the dependencies required to build something,
    +like a Pidgin plugin, you are then able to share that container image with
    +everyone using this system and they are then able to use that image to build
    +against. So a project like Pidgin can supply build images containing the most
    +recent releases with all of the build dependencies for multiple platforms and
    +then plugin developers can use that image to test their plugins against.
    -If you still have doubts about how Convey can simplify your workflow and increase productivity, please contact me via [email](mailto:grim@reaperworld.com) or by opening an issue and let me know!
    -
    +If you still have doubts about how Convey can simplify your workflow and
    +increase productivity, please contact me via
    +[email](mailto:grim@reaperworld.com) or by opening an issue and let me know!
    ----
    @@ -26,98 +36,97 @@
    # Concepts
    -Convey is built around a concept that I call a build container. A build container is a container that has all of the dependencies to turn an input into something else. This could be source code or even just data processing. For example the `convey/go-build` image is a simple wrapper around the `golang` image that just builds a project instead of installing it. You can find more information on build containers [here](http://blog.terranillius.com/post/docker_builder_pattern/).
    -
    -Convey is built around this concept to allow you to build/process anything on any platform while at the same time protecting the host from a malicious config. This protection is provided by not exposing dangerous options like volume mounts or port forwards while running containers. This is accomplished by creating a volume a data volume at the start of the build plan and volume mounting it into containers as they are run. This allows you to pass state between modular tasks that can be reused between multiple plans.
    +Convey is built around a concept that I call a build container. A build
    +container is a container that has all of the dependencies to turn an input into
    +something else. This could be source code or even just data processing. For
    +example the `convey/go-build` image is a simple wrapper around the `golang`
    +image that just builds a project instead of installing it. You can find more
    +information on build containers
    +[here](http://blog.terranillius.com/post/docker_builder_pattern/).
    -Once built, convey can build itself entirely. This is done by using an import task to copy the source code into the workspace. Once that stage is complete, it runs another stage that builds for linux-x86_64, windows-x86_64, and darwin-x86_64 in parallel. The final stage will take those compiled binaries and export them back to the hosts file system.
    +Convey is built around this concept to allow you to build/process anything on
    +any platform while at the same time protecting the host from a malicious
    +config. This protection is provided by not exposing dangerous options like
    +volume mounts or port forwards while running containers. This is accomplished
    +by creating a volume a data volume at the start of the build plan and volume
    +mounting it into containers as they are run. This allows you to pass state
    +between modular tasks that can be reused between multiple plans.
    -Of course you don't have to export back to the host file system. Since these are just containers, you could instead upload them to your staging environment, packagecloud.io, bintray.com, whatever. Or you could export to the host and create another plan that will import the artifacts and then publish them.
    +Once built, convey can build itself entirely. This is done by using an import
    +task to copy the source code into the workspace. Once that stage is complete,
    +it runs another stage that builds for linux-x86_64, windows-x86_64, and
    +darwin-x86_64 in parallel. The final stage will take those compiled binaries
    +and export them back to the hosts file system.
    +
    +Of course you don't have to export back to the host file system. Since these
    +are just containers, you could instead upload them to your staging environment,
    +packagecloud.io, bintray.com, whatever. Or you could export to the host and
    +create another plan that will import the artifacts and then publish them.
    ----
    # Installation
    -There are a few ways you can install convey. The easiest is to grab it from the [downloads](https://bitbucket.org/rw_grim/convey/downloads/) section.
    +There are a few ways you can install convey. The easiest is to grab it from
    +[Source Forge](https://sourceforge.net/projects/pidgin/files/convey/) section.
    -If you want to use the version currently in development and have a golang environment setup, you can run `go install bitbucket.org/rw_grim/convey` and it'll install into your `$GOPATH`.
    +If you want to use the version currently in development and have a golang
    +environment setup, you can run `go install keep.imfreedom.org/grim/convey` and
    +it'll install into your `$GOPATH`.
    ----
    # Usage
    - usage: convey [<flags>] <command> [<args> ...]
    +## Basic
    +
    + Usage: convey <command>
    Convey is a container pipeline runner.
    Flags:
    - --help Show context-sensitive help (also try --help-long and --help-man).
    - --version Show application version.
    - --color Enable colorized output
    - -l, --config-loader=convey Select the configuration loader
    - -f, --config=CONFIG The config file name to use
    - -c, --cpu-shares=CPU-SHARES The amount of cpu shares to give to a run task
    - --docker-config=DOCKER-CONFIG
    - Location of docker client config files
    - -e, --env=ENV ... Set environment variables
    - -S, --force-sequential Don't run anything concurrently
    - -m, --memory=MEMORY The amount of memory to give the run task
    - --ssh-agent A shortcut for --ssh-identity=*
    - --ssh-identity=SSH-IDENTITY ...
    - Enable ssh-agent for the given identities
    - --timeout=15m The maximum amount of time a plan can run. 0 to disable. Units must be
    - specified.
    - -v, --verbose Be more verbose
    - --disable-deprecated Allow the user of deprecated features
    + -h, --help Show context-sensitive help.
    + --color Enable colorized output.
    + -v, --verbose Be more verbose
    Commands:
    - help [<command>...]
    - Show help.
    -
    - config
    - Show a dump of the config file
    + run Run plans
    + version Show the version and exit
    - environment
    - List the environment variables that are available
    + Run "convey <command> --help" for more information on a command.
    +
    +## Run Command
    +
    + Usage: convey run [<plans> ...]
    - graphviz
    - Output a graphviz diagram of the config file
    + Run plans
    +
    + Arguments:
    + [<plans> ...] The names of the plans to run
    - run* [<plan>...]
    - Run a plan or metaplan
    -
    - list environment
    - List the environment variables that are available.
    + Flags:
    + -h, --help Show context-sensitive help.
    + --color Enable colorized output.
    + -v, --verbose Be more verbose
    - list metaplans
    - List the metaplans defined in the configuration file.
    + -f, --config-file=FILE The config file to load
    + -S, --force-sequential Force concurrent stages to be ran sequentially
    + --keep-workspace Keep the workspace directory after running
    + --timeout=DURATION The maximum amount of time a plan can run. 0 to disable. Units must be
    + specified.
    + -e, --env=ENV,... Set an environment variable
    +
    +## Version Command
    +
    + Usage: convey version
    - list plans
    - List the plans defined in the configuration file.
    + Show the version and exit
    - list tasks
    - List the tasks defined in the configuration file.
    + Flags:
    + -h, --help Show context-sensitive help.
    + --color Enable colorized output.
    + -v, --verbose Be more verbose
    # Configuration
    -Convey supports multiple configuration loader which can be specified by the `-l` command line argument.
    -
    -# Config Loaders
    -
    -Convey supports multiple different types of configs that it can load. Currently supported loaders are convey and bitbucket.
    -
    -## Convey
    -
    -Documentation for the built in format can be found in [REFERENCE.md](REFERENCE.md).
    -The `convey` config is the one described above and is the default.
    -
    -# Bitbucket
    -
    -The `bitbucket` config is for [bitbucket-pipelines](https://bitbucket.org/product/features/pipelines) which will allow you to run your pipeline locally. Convey does a best attempt at emulating the pipelines environment, but your mileage may vary. If you're having problems, please open an issue.
    -
    -To use the bitbucket config loader, just run `convey -l bitbucket`. If there is a `bitbucket-pipelines.yml` file in the working directory, convey will happily run it.
    -
    -bitbucket pipelines does not have plans in the same sense that convey does, so if you do not specify one, convey will use the same heuristics that bitbucket-pipelines does to run the correct pipeline from the config.
    -
    -Also, since the `bitbucket-pipelines.yml` file is converted into the convey config format, `--graphviz`, `--list-plans`, and `--list-tasks` all work like normal as well.
    -
    +Documentation for this format can be found in [REFERENCE.md](REFERENCE.md).
    --- a/REFERENCE.md Thu Aug 26 08:11:34 2021 -0500
    +++ b/REFERENCE.md Mon Dec 20 12:33:09 2021 -0600
    @@ -1,7 +1,7 @@
    # Configuration
    -Configuring convey is done via a file named `convey.yml`. This file defines
    -the tasks as well as the plans. This document explains how that file is
    +Configuring convey is done via a file named `convey.yml`. This file defines
    +the tasks as well as the plans. This document explains how that file is
    structured and what options are available.
    # Examples
    @@ -13,83 +13,12 @@
    The complete reference for convey.yml can be found below.
    -## required-version
    -
    -The `required-version` field lets you specify a required version of convey
    -to run your `convey.yml`. Ranges are supported according to the semantics
    -defined [here](https://github.com/blang/semver#ranges). If a
    -`required_version` is not specified, convey will always attempt to run the
    -`convey.yml`.
    -
    -## Options
    -
    -The options section lets you fine tune a few things about your configuration.
    -
    -### default-plan
    -
    -`default-plan` is the name of the plan that should be run by default. When not
    -set, this defaults to `default`.
    -
    -### ssh-identities
    -
    -You can specify a list of SSH key fingerprints that are required for your run.
    -This is done by using the `ssh-identities` attribute which is a list of SSH
    -key fingerprints with an option checksum prefix. You can also specify `*` to
    -allow any key that's been added to the `ssh-agent`.
    -
    -`ssh-identities` are also supported in your override file. However, any
    -`ssh-identities` specified in your `convey.yml` will be overridden by the
    -values in the override file; they will not be merged.
    -
    -**NOTE** If running on docker4mac, there is an
    -[issue](https://github.com/docker/for-mac/issues/483) with xhyve (the
    -hypervisor that the Docker daemon runs in) where you can't volume mount the
    -`ssh-agent` socket. You can use this
    -[workaround](https://hub.docker.com/r/uber/ssh-agent-forward/) to expose the
    -`ssh-agent` via an alternative method.
    -
    -#### Examples
    -
    - options:
    - ssh-identities:
    - - ivZaDYamb5xdguIUUVT7DXvXwvE9JLsOkOkR3XAfzeI
    - - SHA256:Efrocgd+rvwjDAnHt2jZAcwDqeka0s8Vv7N3m08cVnA
    -
    -When using `*` you have to quote it because `*` treated specially in `yaml`.
    -
    - options:
    - ssh-identities:
    - - '*'
    -
    -## Extends
    -
    -The extends option allows you to define a base configuration file. The filepath
    -is relative to the current configuration file.
    -
    -The current file will inherit the options, tasks, and plans of the base
    -configuration file. If an option is supplied, or a task or plan with the same
    -name is defined in the current file, it replaces the inherited item.
    -
    -## Tasks
    -
    -The tasks section defines each task. By defining them separately from the
    -plan the tasks can be reused in multiple plans.
    -
    -There are many tasks types which can be chosen by the value of the `type`
    -attribute. See the links below for detailed information on each category of
    -tasks.
    -
    -* [aws](aws/README.md)
    -* [convey](tasks/README.md)
    -* [docker](docker/README.md)
    -* [kubectl](kubectl/README.md)
    -
    ## Environment
    The environment section defines a list of environment variables to set for the
    run.
    -It should be specified in a `NAME` or `NAME=VALUE` format. If no value is
    +It should be specified in a `NAME` or `NAME=VALUE` format. If no value is
    provided, the value of the variable from the host will be provided if it is
    available.
    @@ -104,9 +33,9 @@
    Since there are multiple places where environment variables can be set, they
    must all be merged together before being passed to a task.
    -Environment variables can be set at the `global-base`, `global`,
    -`global-override`, `plan`, `stage`, `task`, and command line levels. The are
    -merged in that order, with the last one taking precedence over the previous.
    +Environment variables can be set at the `config-file`, `plan`, `stage`, `task`,
    +and command line levels. They are merged in that order, with the last one
    +taking precedence over the previous.
    #### Examples
    @@ -118,13 +47,13 @@
    - FOO=bar
    tasks:
    env1:
    - type: docker/run
    - image: alpine:edge
    + type: podman/run
    + image: docker.io/alpine:edge
    environment:
    - FOO=baz
    env2:
    - type: docker/run
    - image: alpine:Edge
    + type: podman/run
    + image: docker.io/alpine:Edge
    plans:
    default:
    stages:
    @@ -132,21 +61,21 @@
    - env1
    - env2
    -In the next example, the stage is overriding the global value. So `env1` still
    -has a value of `baz` for the environment variable `FOO`, while `env2` has a
    -value of `qux`.
    +In the next example, the stage is overriding the config-file value. So `env1`
    +still has a value of `baz` for the environment variable `FOO`, while `env2` has
    +a value of `qux`.
    environment:
    - FOO=bar
    tasks:
    env1:
    - type: docker/run
    - image: alpine:edge
    + type: podman/run
    + image: docker.io/alpine:edge
    environment:
    - FOO=baz
    env2:
    - type: docker/run
    - image: alpine:Edge
    + type: podman/run
    + image: docker.io/alpine:Edge
    plans:
    default:
    stages:
    @@ -177,29 +106,21 @@
    ### Plan
    -Plans themselves are pretty straight forward. They group `stages` with an
    +Plans themselves are pretty straight forward. They group `stages` with an
    optional `environment`.
    ### Attributes
    | Name | Required | Description |
    | ----------- | -------- | ----------- |
    -| environment | | A list of environment variables to set. The should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. These environment variables will be applied on top of any that were set at the top-level. |
    +| environment | | A list of environment variables to set. They should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. These environment variables will be applied on top of any that were set at the config-file level. |
    | stages | Yes | A list of `stages` to be run as part of this `plan` in the order that they should be run in. |
    -| merge | | Whether or not to attempt to merge with a plan defined in the parent config file. |
    -
    -If a plan of the same name exists in a parent config file and merge is not set,
    -it will replace the plan in its entirety. If merge is set, then the stages
    -defined in this plan will overwrite stages of the parent plan with the same
    -name. This behavior also applies to environments. It is an error to attempt to
    -merge a stage that does not exist in the parent and new stages cannot be added
    -as their order would not be well defined.
    ----
    ### Stages
    -Stages group `tasks` together into a logical unit. They can be run
    +Stages group `tasks` together into a logical unit. They can be run
    sequentially or concurrently.
    #### Attributes
    @@ -208,27 +129,25 @@
    | ----------- | -------- | ------- | ----------- |
    | enabled | | true | Whether or not to run the stage at all. |
    | concurrent | | false | Whether or not the tasks should be run at the same time. |
    -| environment | | | A list of environment variables to set. The should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. These environment variables will be applied on top of any that were set at the top-level and the plan level. |
    -| name | | stageN | A name to give the stage.
    -| run | No | on-success | When a stage should be run. Possible values are `on-success`, `on-failure`, or `always`. Defaults to `on-success`. |
    -| tasks | Yes | | A list of task names to be run as part during this stage. If running sequentially they are run in the order that they are provided. |
    +| environment | | | A list of environment variables to set. The should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. These environment variables will be applied on top of any that were set at the top-level and the plan level. |
    +| name | | the idex of the stage | A name to give the stage.
    +| run | No | on-success | When a stage should be run. Possible values are `on-success`, `on-failure`, or `always`. |
    +| tasks | Yes | | A list of task names to be run as part during this stage. If running sequentially they are run in the order that they are provided. If you are just specifying a single task, you can just pass use it's name not in a list. |
    ----
    ## Meta Plans
    -Meta plans are used to group plans together and run them in serial. This is
    +Meta plans are used to group plans together and run them in serial. This is
    also possible via the command line by specifying multiple plans to run.
    -There is currently no support for nesting meta plans.
    -
    Meta plans contain a single attribute which is the list of `plans` to run.
    ### Attributes
    | Name | Required | Description |
    | ----------- | -------- | ----------- |
    -| plans | Yes | A list of `plans` to run in the order they should be run in. |
    +| plans | Yes | A list of `plans` to run in the order they should be run in. |
    ### Example
    @@ -238,3 +157,12 @@
    - plan1
    - plan2
    - plan3
    +
    +## Tasks
    +
    +The documentation for the available task types can be found below:
    +
    +* [Convey](tasks/README.md)
    +* [Docker](docker/README.md) (deprecated)
    +* [Podman](podman/README.md)
    +* [Script](script/README.md)
    --- a/aws/README.md Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,20 +0,0 @@
    -# AWS
    -
    -The aws package provides tasks for interacting with Amazon Web Services.
    -
    -## aws/ecr-login
    -
    -The `aws/ecr-login` task will log into AWS Elastic Container Registry. It
    -depends on the AWS cli package being installed and configured to work.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| region | | | The AWS region to used. |
    -
    -### Example
    -
    - login:
    - type: aws/ecr-login
    -
    --- a/aws/aws.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,29 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package aws contains the tasks for interacting with AWS.
    -package aws
    -
    -import (
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - // Tasks is a map of intrinsic tasks.
    - Tasks = map[string]tasks.Task{
    - "ecr-login": &ECRGetLogin{},
    - }
    -)
    --- a/aws/data/ecr-test.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,21 +0,0 @@
    ----
    -environment:
    - - REGION=us-east-2
    - - ACCOUNT_ID=1234567890
    -tasks:
    - ecr-login:
    - type: aws/ecr-login
    - tag:
    - type: docker/tag
    - source: alpine:edge
    - destination: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/alpine:edge
    - push:
    - type: docker/push
    - image: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/alpine:edge
    -plans:
    - default:
    - stages:
    - - tasks:
    - - ecr-login
    - - tag
    - - push
    --- a/aws/ecr-get-login.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,74 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package aws
    -
    -import (
    - "github.com/kballard/go-shellquote"
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ECRGetLogin represents an `aws ecr get-login` command.
    -type ECRGetLogin struct {
    - Region string `yaml:"region"`
    -}
    -
    -// Execute runs the `aws ecr get-login` command and calls docker login with the
    -// results.
    -func (ecr *ECRGetLogin) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - cmd := exec.NewGenerator("aws", "ecr", "get-login")
    - region := fullEnv.Map(ecr.Region)
    - if region != "" {
    - cmd.Append("--region", region)
    - }
    -
    - stdout, stderr, err := exec.RunOutput(name, cmd.Command(), rt.State.PlanTimeout)
    - if err != nil {
    - logger.Warnf("error: %s", stderr)
    -
    - return err
    - }
    -
    - argv, err := shellquote.Split(stdout)
    - if err != nil {
    - return err
    - }
    -
    - task, err := docker.ParseCommand(argv)
    - if err != nil {
    - return err
    - }
    -
    - return task.Execute(name, logger, env, rt)
    -}
    -
    -// New creates a aws/ecr-get-login task.
    -func (ecr *ECRGetLogin) New() tasks.Task {
    - return &ECRGetLogin{}
    -}
    -
    -// Valid checks that the build task is valid.
    -func (ecr *ECRGetLogin) Valid() error {
    - return nil
    -}
    --- a/aws/parser.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,57 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package aws
    -
    -import (
    - "fmt"
    -
    - "github.com/alecthomas/kingpin"
    -
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ParseCommand is a super stripped down kingpin parser that will parse aws
    -// command line options and return a convey task or an error
    -func ParseCommand(argv []string) (tasks.Task, error) {
    - app := kingpin.New("", "")
    -
    - ecr := app.Command("ecr", "")
    -
    - getLogin := ecr.Command("get-login", "")
    - region := getLogin.Flag("region", "").String()
    - getLogin.Flag("no-include-email", "").Bool()
    -
    - cmd, err := app.Parse(argv[1:])
    - if err != nil {
    - return nil, err
    - }
    -
    - var task tasks.Task
    -
    - switch cmd {
    - case "ecr get-login":
    - task = &ECRGetLogin{
    - Region: *region,
    - }
    - }
    -
    - if task != nil {
    - return task, nil
    - }
    -
    - return nil, fmt.Errorf("unable to parse aws command line '%v'", argv)
    -}
    --- a/color/x11.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,49 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package color
    -
    -var (
    - x11ColorList = []string{
    - "red",
    - "green",
    - "plum",
    - "blue",
    - "magenta",
    - "cyan",
    - "deeppink3",
    - "darkgreen",
    - "darkorchid2",
    - "lightblue",
    - "indigo",
    - "cyan4",
    - "SlateBlue",
    - "SteelBlue",
    - "LightSeaGreen",
    - "gold",
    - "goldenrod1",
    - "IndianRed",
    - "tomato",
    - "coral",
    - }
    -
    - x11ColorMap = map[string]string{}
    -)
    -
    -// X11 returns an x11 color based on the hash of the given string.
    -func X11(identifier string) string {
    - return lookup(identifier, x11ColorList, x11ColorMap)
    -}
    --- a/config/config.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/config/config.go Mon Dec 20 12:33:09 2021 -0600
    @@ -20,26 +20,45 @@
    import (
    "fmt"
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/metaplans"
    "keep.imfreedom.org/grim/convey/plans"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    // Config represents a full convey configuration.
    type Config struct {
    - Tasks map[string]tasks.Task
    - Plans map[string]plans.Plan
    - MetaPlans map[string]plans.MetaPlan
    - SSHIdentities []string
    - Environment []string
    + Tasks map[string]tasks.Task
    + Plans map[string]plans.Plan
    + MetaPlans map[string]metaplans.MetaPlan
    + Environment []string
    }
    // Valid returns an error if the command is valid or not.
    func (c *Config) Valid() error {
    for name, task := range c.Tasks {
    if err := task.Valid(); err != nil {
    - return fmt.Errorf("error: task %s : %s", name, err.Error())
    + return fmt.Errorf("task %s : %s", name, err.Error())
    + }
    +
    + if err := task.Deprecated(); err != nil {
    + log.Warnf("warning: task %s: %s", name, err.Error())
    }
    }
    return nil
    }
    +
    +// HasPlans checks if the given plans exist
    +func (c *Config) HasPlans(plans []string) error {
    + for _, name := range plans {
    + if _, found := c.Plans[name]; !found {
    + if _, found := c.MetaPlans[name]; !found {
    + return fmt.Errorf("no plan or metaplan named %q found", name)
    + }
    + }
    + }
    +
    + return nil
    +}
    --- a/config/loader.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/config/loader.go Mon Dec 20 12:33:09 2021 -0600
    @@ -17,67 +17,46 @@
    package config
    import (
    - "fmt"
    "io/ioutil"
    "os"
    - "path/filepath"
    - "strings"
    - "keep.imfreedom.org/grim/convey/runtime"
    + log "github.com/sirupsen/logrus"
    +
    + "gopkg.in/yaml.v2"
    )
    -// Loader defines all the functions that a config loader needs to implement.
    -type Loader interface {
    - Load(path, base string, data []byte, options []string, disableDeprecated bool) (*Config, error)
    - LoadOverride(path, base string, data []byte, config *Config, disableDeprecated bool)
    - Filenames() []string
    - OverrideSuffix() string
    - DefaultPlan() string
    - ResolvePlanName(plan string, cfg *Config, rt *runtime.Runtime) string
    -}
    +func LoadString(data []byte) (*Config, error) {
    + var cfg rawConfig
    -func determineFilename(filename string, loader Loader) string {
    - ext := filepath.Ext(filename)
    - base := strings.TrimSuffix(filename, ext)
    + err := yaml.Unmarshal(data, &cfg)
    + if err != nil {
    + return nil, err
    + }
    - return base + loader.OverrideSuffix() + ext
    + return cfg.process()
    }
    -func loadOverride(path, base string, loader Loader, cfg *Config, disableDeprecated bool) {
    - overrideFilename := determineFilename(base, loader)
    -
    - absName := filepath.Join(path, overrideFilename)
    - if _, err := os.Stat(absName); os.IsNotExist(err) {
    - // no override so bail
    - return
    - }
    -
    - data, err := ioutil.ReadFile(absName)
    +func LoadFile(filename string) (*Config, error) {
    + _, err := os.Stat(filename)
    if err != nil {
    - fmt.Printf("error in override file '%s' : %s", absName, err)
    - return
    + // If the user selected the default and it didn't exist, attempt the
    + // default with a .yaml extension.
    + if os.IsNotExist(err) && filename == "convey.yml" {
    + log.Infof("info: configuration file convey.yml not found, attempting convey.yaml instead.")
    + filename = "convey.yaml"
    + _, err = os.Stat(filename)
    + if err != nil {
    + return nil, err
    + }
    + } else {
    + return nil, err
    + }
    }
    - loader.LoadOverride(path, base, data, cfg, disableDeprecated)
    -}
    -
    -// LoadFile will determine the file name and override file name and then
    -// call the loaders methods to load them.
    -func LoadFile(file string, loader Loader, options []string, disableDeprecated bool) (*Config, error) {
    - // split the filename into our parts
    - path, base := filepath.Split(file)
    -
    - data, err := ioutil.ReadFile(file)
    + contents, err := ioutil.ReadFile(filename)
    if err != nil {
    - return nil, fmt.Errorf("failed to read config file '%s'", file)
    + return nil, err
    }
    - cfg, err := loader.Load(path, base, data, options, disableDeprecated)
    - if err != nil {
    - return cfg, err
    - }
    -
    - loadOverride(path, base, loader, cfg, disableDeprecated)
    -
    - return cfg, nil
    + return LoadString(contents)
    }
    --- a/config/override_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,86 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package config
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -type testLoader struct{}
    -
    -func (l *testLoader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*Config, error) {
    - return nil, nil
    -}
    -
    -func (l *testLoader) LoadOverride(path, base string, data []byte, cfg *Config, disableDeprecated bool) {
    -}
    -
    -func (l *testLoader) OverrideSuffix() string {
    - return "-test-override"
    -}
    -
    -func (l *testLoader) Filenames() []string {
    - return []string{"test-loader.yml"}
    -}
    -
    -func (l *testLoader) DefaultPlan() string {
    - return "default"
    -}
    -
    -func (l *testLoader) ResolvePlanName(plan string, cfg *Config, rt *runtime.Runtime) string {
    - return plan
    -}
    -
    -func TestDetermineFilenameDefault(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(
    - t,
    - determineFilename("convey.yml", l),
    - "convey"+l.OverrideSuffix()+".yml",
    - )
    -}
    -
    -func TestDetermineFilenameEmpty(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(t, determineFilename("", l), l.OverrideSuffix())
    -}
    -
    -func TestDetermineFilenameCustom(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(
    - t,
    - determineFilename("foo.yml", l),
    - "foo"+l.OverrideSuffix()+".yml",
    - )
    -}
    -
    -func TestDetermineFilenameUnicodeSnowman(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(
    - t,
    - determineFilename("☃.yml", l),
    - "☃"+l.OverrideSuffix()+".yml",
    - )
    -}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/config/raw.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,72 @@
    +package config
    +
    +import (
    + "fmt"
    +
    + "gopkg.in/yaml.v2"
    +
    + "keep.imfreedom.org/grim/convey/metaplans"
    + "keep.imfreedom.org/grim/convey/plans"
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +type rawConfig struct {
    + Environment []string
    + Tasks map[string]yaml.MapSlice
    + Plans map[string]plans.Plan
    + MetaPlans map[string]metaplans.MetaPlan `yaml:"meta-plans"`
    +}
    +
    +func (r *rawConfig) processTasks() (map[string]tasks.Task, error) {
    + realTasks := map[string]tasks.Task{}
    +
    + for name, rawTask := range r.Tasks {
    + taskType := ""
    +
    + for _, item := range rawTask {
    + if item.Key == "type" {
    + rawType, ok := item.Value.(string)
    + if !ok {
    + return nil, fmt.Errorf("the type value for task %q is not a string", name)
    + }
    + taskType = rawType
    + }
    + }
    +
    + if taskType == "" {
    + return nil, fmt.Errorf("task %q is missing its type", name)
    + }
    +
    + // look for the real task in the taskmap
    + templateTask, found := tasksMap[taskType]
    + if !found {
    + return nil, fmt.Errorf("task %q has unknown type %q", name, taskType)
    + }
    +
    + realTask, err := tasks.Clone(rawTask, templateTask)
    + if err != nil {
    + return nil, err
    + }
    +
    + realTasks[name] = realTask
    + }
    +
    + return realTasks, nil
    +}
    +
    +func (r *rawConfig) process() (*Config, error) {
    + cfg := Config{
    + Environment: r.Environment,
    + MetaPlans: r.MetaPlans,
    + Plans: r.Plans,
    + }
    +
    + tasks, err := r.processTasks()
    + if err != nil {
    + return nil, err
    + }
    +
    + cfg.Tasks = tasks
    +
    + return &cfg, nil
    +}
    --- a/config/tasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/config/tasks.go Mon Dec 20 12:33:09 2021 -0600
    @@ -17,38 +17,38 @@
    package config
    import (
    - "keep.imfreedom.org/grim/convey/aws"
    "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/kubectl"
    + "keep.imfreedom.org/grim/convey/podman"
    + "keep.imfreedom.org/grim/convey/script"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    // TaskMap is a type alias for a map of task names to tasks.
    -type TaskMap map[string]tasks.Task
    +type taskMap map[string]tasks.Task
    var (
    - // TasksMap is a lookup table for tasks
    - TasksMap = map[string]tasks.Task{}
    + // tasksMap is a lookup table for tasks
    + tasksMap = map[string]tasks.Task{}
    )
    func init() {
    - // add the aws tasks
    - for taskName, task := range aws.Tasks {
    - TasksMap["aws/"+taskName] = task
    - }
    -
    - // add the docker tasks
    + // add the old deprecated docker tasks
    for taskName, task := range docker.Tasks {
    - TasksMap["docker/"+taskName] = task
    + tasksMap[taskName] = task
    }
    // add the intrinsic convey tasks
    for taskName, task := range tasks.Tasks {
    - TasksMap["convey/"+taskName] = task
    + tasksMap[taskName] = task
    }
    - // add the kubectl tasks
    - for taskName, task := range kubectl.Tasks {
    - TasksMap["kubectl/"+taskName] = task
    + // add the podman tasks
    + for taskName, task := range podman.Tasks {
    + tasksMap[taskName] = task
    + }
    +
    + // add the script tasks
    + for taskName, task := range script.Tasks {
    + tasksMap[taskName] = task
    }
    }
    --- a/consts/consts.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/consts/consts.go Mon Dec 20 12:33:09 2021 -0600
    @@ -18,5 +18,5 @@
    const (
    // Version is the current version of convey
    - Version = "0.14.0-dev"
    + Version = "0.15.0-dev"
    )
    --- a/convey.sublime-project Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,8 +0,0 @@
    -{
    - "folders":
    - [
    - {
    - "path": "."
    - }
    - ]
    -}
    --- a/convey.yml Thu Aug 26 08:11:34 2021 -0500
    +++ b/convey.yml Mon Dec 20 12:33:09 2021 -0600
    @@ -1,10 +1,7 @@
    ---
    -required-version: 0.14.0-dev
    environment:
    - - CONVEY_VERSION=0.14.0-dev
    - - GO_IMAGE=golang:1.17-bullseye
    + - GO_IMAGE=docker.io/golang:1.17-bullseye
    tasks:
    - # tasks for the default plan
    import:
    type: convey/import
    files:
    @@ -15,53 +12,64 @@
    - convey-${CONVEY_VERSION}-darwin-amd64
    - convey-${CONVEY_VERSION}-freebsd-amd64
    - convey-${CONVEY_VERSION}-linux-amd64
    + - convey-${CONVEY_VERSION}-linux-arm64
    - convey-${CONVEY_VERSION}-windows-amd64.exe
    clean:
    type: convey/clean
    files:
    - convey-*
    go-test:
    - type: docker/run
    + type: podman/run
    image: ${GO_IMAGE}
    workdir: ${CONVEY_WORKSPACE}
    command: go test ./...
    - build:
    - type: docker/run
    + build-darwin-amd64:
    + type: podman/run
    image: ${GO_IMAGE}
    + workdir: ${CONVEY_WORKSPACE}
    environment:
    + - GOOS=darwin
    - GOARCH=amd64
    + command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}
    + build-freebsd-amd64:
    + type: podman/run
    + image: ${GO_IMAGE}
    workdir: ${CONVEY_WORKSPACE}
    - command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}${SUFFIX}
    - build-linux:
    - type: convey/subtask
    - base: build
    + environment:
    + - GOOS=freebsd
    + - GOARCH=amd64
    + command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}
    + build-linux-amd64:
    + type: podman/run
    + image: ${GO_IMAGE}
    + workdir: ${CONVEY_WORKSPACE}
    environment:
    - CGO_ENABLED=0
    - GOOS=linux
    - - SUFFIX=
    - build-windows:
    - type: convey/subtask
    - base: build
    + - GOARCH=amd64
    + command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}
    + build-linux-arm64:
    + type: podman/run
    + image: ${GO_IMAGE}
    + workdir: ${CONVEY_WORKSPACE}
    + environment:
    + - CGO_ENABLED=0
    + - GOOS=linux
    + - GOARCH=arm64
    + command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}
    + build-windows-amd64:
    + type: podman/run
    + image: ${GO_IMAGE}
    + workdir: ${CONVEY_WORKSPACE}
    environment:
    - GOOS=windows
    - - SUFFIX=.exe
    - build-darwin:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=darwin
    - - SUFFIX=
    - build-freebsd:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=freebsd
    - - SUFFIX=
    + - GOARCH=amd64
    + command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}.exe
    plans:
    default:
    stages:
    - - name: import-source
    + - name: import
    tasks:
    - import
    - name: testing
    @@ -70,10 +78,11 @@
    - name: build
    concurrent: true
    tasks:
    - - build-darwin
    - - build-freebsd
    - - build-linux
    - - build-windows
    + - build-darwin-amd64
    + - build-freebsd-amd64
    + - build-linux-amd64
    + - build-linux-arm64
    + - build-windows-amd64
    - name: export
    tasks:
    - export
    --- a/docker/README.md Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/README.md Mon Dec 20 12:33:09 2021 -0600
    @@ -1,83 +1,75 @@
    # Docker
    -The docker package provides tasks for interacting with docker.
    +Since version 0.15.0 convey no longer uses Docker and instead uses podman.
    +The Docker tasks are currently still available for backwards compatibility, but
    +will be removed in a future version. The do a best effort to apply their
    +original functionality to the podman tasks but not everything maps 100%. Please
    +see individual task documentation for additional information.
    -## docker/build Task
    +----
    +
    +## docker/build
    -A build task will build a docker image.
    +> The docker/build task has been deprecated since version 0.15.0. Use the
    +> podman/build task instead.
    +
    +This task will build a container image.
    ### Attributes
    +**NOTE:** arguments and files are not currently mapped into the podman/build
    +task.
    +
    | Name | Required | Default | Description |
    | ---------- | -------- | ------- | ----------- |
    -| arguments | | | A list of build arguments to pass to the build |
    -| dockerfile | Yes | | The dockerfile to use while building |
    -| files | | | A list of files that should be made available for the build |
    -| labels | | | A list of labels to set on the image being built |
    -| tag | | | The tag to apply to the image |
    -| tags | | | The list of tags as described above |
    -| target | | | The name of the target to build in a multi stage dockerfile |
    -
    -At least one tag must be supplied by either the `tag` or `tags` attribute. If both are
    -supplied, `tag` is inserted to the front of `tags`.
    +| arguments | | | A list of build arguments to pass to the build. |
    +| dockerfile | Yes | | The dockerfile to build. |
    +| files | | | A list of files that should be made available for the build. |
    +| labels | | | A list of labels to set on the container image being built. |
    +| tag | | | A tag to apply to the image. |
    +| tags | | | A list of tags to be applied to the image. |
    +| target | | | The name of the target to build in a multi stage dockerfile. |
    ### Example
    - build-image:
    + build:
    type: docker/build
    dockerfile: Dockerfile
    tag: myimage:latest
    ----
    -## docker/environment Task
    +## docker/environment
    +
    +> The docker/build task has been deprecated and has no replacement.
    -An environment task will read a file with environment variables and make them accessible to your plans.
    +The functionality of this task has been removed and the task now just fails.
    +If you have a use case for this task and would like to keep it, please file
    +and issue in our issue tracker.
    -A file is expected to have lines of the form `variable=value`. Empty lines are ignored. Files are read
    -from the workspace, so files generated by the plan do not need to be exported in order to be a target
    -of an environment task. From the other direction, it is necessary to import files on the host if they are
    -to be read by an environment task.
    +----
    +
    +## docker/export
    +
    +> The docker/export task has been deprecated since version 0.15.0. Use the
    +> convey/export task instead.
    +
    +This task will export files from the workspace to the host filesystem relative
    +to the convey.yml used for the run.
    ### Attributes
    -| Name | Required | Default | Description |
    -| ---------- | -------- | ------- | ----------- |
    -| from-file | | | A file that should be read. |
    -| from-files | | | A list of files that should be read. |
    -| prefix | | | A prefix to add to each variable read from a file |
    -
    -At least one file must be supplied by either the `from-file` or `from-files` attributes. If both are supplied,
    -`from-file` is inserted to the front of `from-files`. If the files being read are a result of environment variable
    -expansion, the order that the files are read are not guaranteed to be stable (or in the order supplied). Be cautious
    -of this if the environment files define overlapping variables.
    -
    -### Example
    -
    - inject-version:
    - type: docker/environment
    - from-file: version.txt
    - prefix: "APP_"
    -
    -----
    -
    -## docker/export Task
    -
    -An export task copies files from the volume to the host.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----- | -------- | ------- | ----------- |
    -| files | Yes | | A single filename or a list of files to copy from the workspace to the host. Files can be specified in one of two forms which can be mixed. The first is `source:destination` and the other is `filename` where filename will be used for both the host and the volume. |
    +| Name | Required | Default | Description |
    +| ------ | -------- | ------- | ----------- |
    +| files | Yes | | A single filename, source:destination, or a list of filenames and/or source:destinations. |
    ### Examples
    - export:
    + export-single:
    type: docker/export
    files: filename1
    - export:
    + export-multiple:
    type: docker/export
    files:
    - logo.png
    @@ -85,23 +77,27 @@
    ----
    -## docker/import Task
    +## docker/import
    -An import task copies files from the host to the volume. It has one required attribute named sources. This is a list of files relative to the directory that convey was run from that will be copied into the volume.
    +> The docker/import task has been deprecated since version 0.15.0. Use the
    +> convey/import task instead.
    +
    +This task will import files from the host filesystem relative to the convey.yml
    +used for the run and put them into the workspace.
    ### Attributes
    -| Name | Required | Default | Description |
    -| ----- | -------- | ------- | ----------- |
    -| files | Yes | | A single filename or a list of files to copy from the host to the workspace. Files can be specified in one of two forms which can be mixed. The first is `source:destination` and the other is `filename` where filename will be used for both the host and the volume. |
    +| Name | Required | Default | Description |
    +| ------ | -------- | ------- | ----------- |
    +| files | Yes | | A single filename, source:destination, or a list of filenames and/or source:destinations. |
    ### Examples
    - import:
    + import-single:
    type: docker/import
    files: filename1
    - import:
    + import-multiple:
    type: docker/import
    files:
    - Dockerfile
    @@ -109,17 +105,20 @@
    ----
    -## docker/login Task
    +## docker/login
    -A login task will run docker login to login to a registry.
    +> The docker/login task has been deprecated since version 0.15.0. Use the
    +> podman/login task instead.
    +
    +This task allows you to authorize to a container registry.
    ### Attributes
    -| Name | Required | Default | Description |
    -| -------- | -------- | ------- | ----------- |
    -| username | Yes | | The username to login with. |
    -| password | Yes | | The password to login with. |
    -| server | | | The server to login to. If omitted docker hub is used. |
    +| Name | Required | Default | Description |
    +| --------- | -------- | --------- | ----------- |
    +| username | Yes | | The username to use. |
    +| password | Yes | | The password to use. |
    +| server | | docker.io | The registry to login to. |
    ### Example
    @@ -130,37 +129,45 @@
    ----
    -## docker/logout Task
    +## docker/logout
    -A logout task will log you out from a Docker registry.
    +> The docker/logout task has been deprecated since version 0.15.0. Use the
    +> podman/logout task instead.
    +
    +This task allows you to unauthenticate from a container registry.
    ### Attributes
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| server | | | The server to logout from. If omitted docker hub is used. |
    +| Name | Required | Default | Description |
    +| --------- | -------- | --------- | ----------- |
    +| server | | docker.io | The registry to logout of. |
    ### Example
    registry-logout:
    type: docker/logout
    - server: registry.my.domain:5000
    + server: regsitry.my.domain:5000
    ----
    -## docker/pull Task
    +### docker/pull
    -A pull task pulls an image.
    +> The docker/pull task has been deprecated since version 0.15.0. Use the
    +> podman/pull task instead.
    +
    +This task allows you to pull down a container image. If the image is private,
    +be use to run a `docker/login` task first.
    ### Attributes
    | Name | Required | Default | Description |
    | ------ | -------- | ------- | ----------- |
    -| image | | | The name including the tag and registry of the image to pull. |
    -| images | | | A list of images as described above. |
    +| image | | | The name of the image include the tag. |
    +| images | | | A list of image names including the tags. |
    -At least one image must be supplied by either the `image` or `images` attribute. If both are
    -supplied, `image` is inserted to the front of `images`.
    +At least one image must be supplied by either the `image` or `images`
    +attribute. If both are specified, `image` will be prepended to the list from
    +`images`.
    ### Example
    @@ -170,88 +177,90 @@
    ----
    -## docker/push Task
    +### docker/push
    -A push task pushes an image.
    +> The docker/push task has been deprecated since version 0.15.0. Use the
    +> podman/push task instead.
    +
    +This task allows you to push a local container image to a registry. If the
    +repository on the registry is private, be use to run a `docker/login` task
    +first.
    ### Attributes
    | Name | Required | Default | Description |
    | ------ | -------- | ------- | ----------- |
    -| image | | | The name including the tag and registry of the image to push. |
    -| images | | | A list of images as described above. |
    +| image | | | The name of the image include the tag. |
    +| images | | | A list of image names including the tags. |
    -At least one image must be supplied by either the `image` or `images` attribute. If both are
    -supplied, `image` is inserted to the front of `images`.
    +At least one image must be supplied by either the `image` or `images`
    +attribute. If both are specified, `image` will be prepended to the list from
    +`images`.
    ### Example
    - push-image:
    + push-images:
    type: docker/push
    images:
    - - registry.my.domain:5000/newimage:master-latest
    - - registry.my.domain:5000/newimage:master-deadbeef
    + - registry.my.domain:5000/newimage:main-latest
    + - registry.my.domain:5000/newimage:main-deadbeef
    ----
    -## docker/remove Task
    +## docker/remove
    -A remove task removes an image.
    +> The docker/remove task has been deprecated since version 0.15.0. Use the
    +> podman/remove task instead.
    +
    +This task will remove an image from the local machine.
    ### Attributes
    +
    | Name | Required | Default | Description |
    | ------ | -------- | ------- | ----------- |
    -| image | | | The name including the tag and registry of the image to remove. |
    -| images | | | A list of images as described above. |
    -| quiet | | false | True if a missing image should not count as a task failure. |
    -
    -At least one image must be supplied by either the `image` or `images` attribute. If both are
    -supplied, `image` is inserted to the front of `images`.
    +| image | | | The name of the image include the tag. |
    +| images | | | A list of image names including the tags. |
    +| quiet | | false | True if a missing image should not count as a failure. |
    ### Example
    remove-image:
    type: docker/remove
    images:
    - - registry.my.domain:5000/newimage:master-latest
    - - registry.my.domain:5000/newimage:master-deadbeef
    + - registry.my.domain:5000/newimage:main-latest
    + - registry.my.domain:5000/newimage:main-deadbeef
    ----
    -## docker/run Task
    +## docker/run
    -A run task runs an image.
    +> The docker/run task has been deprecated since version 0.15.0. Use the
    +> podman/run task instead.
    +
    +This task will run a container with the workspace mounted.
    ### Attributes
    -| Name | Required | Default | Description |
    -| ----------- | -------- | ---------- | ----------- |
    -| command | | | The command to run in the container. This is only necessary if the image does not provide a command by default. |
    -| detach | | false | Will run the container in the background allowing other tasks to run. The container will be cleaned up on exit. If the image has a HEALTHCHECK specified in the Dockerfile, convey will wait until it is healthy before proceeding. |
    -| entrypoint | | | The entrypoint to use for the container. This is only necessary if the image does not provide one by default. |
    -| environment | | | A list of environment variables to set. The should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. |
    -| healthcheck | | | See the HealthCheck attributes below. |
    -| hostname | | | Will set the --network-alias argument in the docker run command. This is generally only useful to connect to service containers. |
    -| image | Yes | | The image including the registry and tag to run. |
    -| labels | | | A list of labels to set on the image being built |
    -| script | | | A list of commands to run in the image. Overrides entrypoint and command. |
    -| shell | | | When the script attribute is present, it will be ran in `/bin/sh` by default. You can use this attribute to choose a different shell. |
    -| user | | | The user to run as when executing the script or command inside the container. |
    -| workdir | | | The working directory to use in the container. |
    -| workspace | | /workspace | The path inside the container to mount the workspace volume. |
    +**NOTE:** the detach and healthcheck attributes are not mapped to the
    +podman/run task and are not expected to be in the future. If you have a use
    +case for this, please open an issue on our issue tracker.
    -### HealthCheck Attributes
    -
    -All of these attributes map directory to the `--health` arguments to `docker run`.
    -
    -| Name | Description |
    -| -------- | ----------- |
    -| command | The command to run as the health check. |
    -| interval | The interval with which to check the health. |
    -| retries | The number of attempts before giving up. |
    -| start-period | How long to wait before starting to check the container's health. |
    -| timeout | How long to wait for a health check to return before aborting. |
    +| Name | Required | Default | Description |
    +| ----------- | -------- | ------- | ----------- |
    +| command | | | The command to run in the container. This is only necessary if the image does not provide a command by default. |
    +| detach | | false | This is deprecated and no longer support. |
    +| entrypoint | | | The entrypoint to use for the container. This is only necessary if the image does not provide on by default or if you want to override the images default entrypoint. |
    +| environment | | | A list of environment variables to set. This should be specified in a `NAME` or `NAME=VALUE` format. |
    +| healthcheck | | | THis is deprecated and no longer support. |
    +| hostname | | | A custom hostname to set inside the container. |
    +| image | Yes | | The image include the registry and tag to run. |
    +| labels | | | A list of labels to set on the image being run. |
    +| script | | | A list of commands to run inside of the container. |
    +| shell | | | The shell to use with the script attribute above. |
    +| user | | | The user to run the container as. |
    +| workdir | | | The working directory inside the container. |
    +| workspace | | | The path to mount the convey workspace at inside of the container. |
    ### Example
    @@ -261,43 +270,35 @@
    type: docker/run
    image: golang:onbuild
    -A basic example using a standard image to do something else
    +A basic example using a a standard image to do something else.
    download-file:
    type: docker/run
    image: debian:jessie-slim
    script:
    - wget https://somedomain.tld/file1
    - - wget https://somedomain.tld/file1.sha256
    - - sha256sum -c file1.sha256
    -
    -A more compilicated example waiting for a detached nginx to start
    -
    - web-server:
    - type: docker/run
    - image: nginx:alpine
    - detach: true
    - command: wget http://localhost
    - interval: 1s
    + - wget https://somedomain.tld/file1.sha256sum
    + - sha256sum -c file1.sha256sum
    ----
    -## docker/tag Task
    +## docker/tag
    -A tag task will tag an existing image.
    +> The docker/tag task has been deprecated since version 0.15.0. Use the
    +> podman/tag task instead.
    +
    +This task will tag existing images on the host.
    ### Attributes
    -| Name | Required | Default | Description |
    -| ------------ | -------- | ---------- | ----------- |
    -| source | | | The source image, including registry and tag, to tag. |
    -| destination | | | The destination tag, including registry and tag, to tag. |
    -| destinations | | | The destination tags, including registry and tag, to tag. |
    +| Name | Required | Default | Description |
    +| ----------- | -------- | ------- | ----------- |
    +| source | Yes | | The full source image including the registry and tag to tag. |
    +| destination | Yes | | The destination tag include the registry and tag to create. |
    ### Example
    - tag-development:
    + tag-dev:
    type: docker/tag
    source: registry.my.domain:5000/newimage:latest
    - destination: registry.my.domain:5000/newimage:development
    -
    + destination: registry.my.domain:5000/newimage:dev
    --- a/docker/build.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/build.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,132 +1,52 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    - "path/filepath"
    + "fmt"
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/fs"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Build defines the options for building a docker image.
    type Build struct {
    + Arguments yaml.StringOrSlice `yaml:"arguments"`
    Dockerfile string `yaml:"dockerfile"`
    Files yaml.StringOrSlice `yaml:"files"`
    + Labels yaml.StringOrSlice `yaml:"labels"`
    Tag string `yaml:"tag"`
    Tags yaml.StringOrSlice `yaml:"tags"`
    Target string `yaml:"target"`
    - Labels yaml.StringOrSlice `yaml:"labels"`
    - Arguments yaml.StringOrSlice `yaml:"arguments"`
    +
    + realBuild *podman.Build
    }
    -// Execute runs the docker build command
    -func (b *Build) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - // create our task directory
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return err
    - }
    -
    - wsPath := rt.State.Workspace.Volume().Path()
    -
    - // import all of the declared files to our task directory
    - for _, filespec := range b.Files {
    - spec := fs.ParsePathSpec(filespec)
    - td.Import(filepath.Join(wsPath, spec.Source()), spec.Destination())
    - }
    -
    - dockerfile := fullEnv.Map(b.Dockerfile)
    -
    - tags, err := fullEnv.MapSlice(b.Tags)
    - if err != nil {
    - return err
    - }
    -
    - labels, err := fullEnv.MapSlice(b.Labels)
    - if err != nil {
    - return err
    - }
    -
    - arguments, err := fullEnv.MapSlice(b.Arguments)
    - if err != nil {
    - return err
    - }
    -
    - td.Import(dockerfile, filepath.Base(dockerfile))
    -
    - // create the basic command
    - cmd := exec.NewGenerator(
    - "build",
    - "-f", filepath.Join(td.Path(), filepath.Base(dockerfile)),
    - )
    -
    - // add any and all tags
    - for _, tag := range tags {
    - cmd.Append("-t", tag)
    - }
    -
    - // add any and all labels
    - for _, label := range labels {
    - cmd.Append("--label", label)
    - }
    -
    - // add any and all build arguments
    - for _, arg := range arguments {
    - cmd.Append("--build-arg", arg)
    - }
    -
    - // add the target if we have one
    - if b.Target != "" {
    - cmd.Append("--target", b.Target)
    - }
    -
    - // finally add the build context
    - cmd.Append(td.Path())
    -
    - return Docker(name, cmd.Command(), rt)
    +func (b *Build) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + return b.realBuild.Execute(name, logger, stageEnv, rt)
    }
    -// New creates a new docker build task.
    func (b *Build) New() tasks.Task {
    return &Build{}
    }
    -// Valid checks that the build task is valid.
    func (b *Build) Valid() error {
    - if b.Dockerfile == "" {
    - return errNoDockerFile
    - }
    -
    if b.Tag != "" {
    b.Tags = append([]string{b.Tag}, b.Tags...)
    }
    - if len(b.Tags) == 0 {
    - return errNoTag
    + b.realBuild = &podman.Build{
    + Annotations: b.Labels,
    + Containerfile: b.Dockerfile,
    + Tags: b.Tags,
    + Target: b.Target,
    }
    - return nil
    + return b.realBuild.Valid()
    }
    +
    +func (b *Build) Deprecated() error {
    + return fmt.Errorf("docker/build has been deprecated in favor of podman/build since 0.15.0")
    +}
    --- a/docker/build_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,88 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestBuildDockerfileRequired(t *testing.T) {
    - b := &Build{Tag: "foo"}
    - assert.EqualError(t, b.Valid(), errNoDockerFile.Error())
    -}
    -
    -func TestBuildTagRequired(t *testing.T) {
    - b := &Build{Dockerfile: "Dockerfile"}
    - assert.EqualError(t, b.Valid(), errNoTag.Error())
    -}
    -
    -func TestBuild(t *testing.T) {
    - b := &Build{
    - Dockerfile: "Dockerfile",
    - Tag: "tag",
    - }
    - assert.Nil(t, b.Valid())
    -}
    -
    -func TestBuildUnmarshalNormal(t *testing.T) {
    - data := `dockerfile: dockerfile
    -files:
    - - filename1
    -tag: tag
    -labels:
    - - label1
    -arguments:
    - - argument1
    -`
    -
    - b := Build{}
    - err := yaml.Unmarshal([]byte(data), &b)
    -
    - assert.Nil(t, err)
    - assert.Nil(t, b.Valid())
    -
    - assert.Equal(t, b.Dockerfile, "dockerfile")
    - assert.Equal(t, b.Files, cYaml.StringOrSlice{"filename1"})
    - assert.Equal(t, b.Tag, "tag")
    - assert.Equal(t, b.Labels, cYaml.StringOrSlice{"label1"})
    - assert.Equal(t, b.Arguments, cYaml.StringOrSlice{"argument1"})
    -}
    -
    -func TestBuildUnmarshalMissingDockerfile(t *testing.T) {
    - data := `tag: tag`
    -
    - b := Build{}
    - err := yaml.Unmarshal([]byte(data), &b)
    -
    - assert.Nil(t, err)
    - assert.EqualError(t, b.Valid(), errNoDockerFile.Error())
    -}
    -
    -func TestBuildUnmarshalMissingTag(t *testing.T) {
    - data := `dockerfile: dockerfile`
    -
    - b := Build{}
    - err := yaml.Unmarshal([]byte(data), &b)
    -
    - assert.Nil(t, err)
    - assert.EqualError(t, b.Valid(), errNoTag.Error())
    -}
    --- a/docker/docker.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,65 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package docker implements the docker task types.
    -package docker
    -
    -import (
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - // Tasks is a map of all docker tasks.
    - Tasks = map[string]tasks.Task{
    - "build": &Build{},
    - "login": &Login{},
    - "logout": &Logout{},
    - "pull": &Pull{},
    - "push": &Push{},
    - "remove": &Remove{},
    - "run": &Run{},
    - "tag": &Tag{},
    - "environment": &Environment{},
    - }
    -)
    -
    -func dockerCommand(cmdv []string, rt *runtime.Runtime) []string {
    - cmd := exec.NewGenerator()
    - cmd.Appendv(cmdv)
    -
    - // if we have a docker config prepend it first
    - if rt.State.DockerConfig != "" {
    - cmd.Prepend("--config", rt.State.DockerConfig)
    - }
    -
    - // finally prepend the actual docker command
    - cmd.Prepend("docker")
    -
    - return cmd.Command()
    -}
    -
    -// Docker runs a docker command.
    -func Docker(name string, cmdv []string, rt *runtime.Runtime) error {
    - return exec.Run(name, dockerCommand(cmdv, rt), rt.State.PlanTimeout)
    -}
    -
    -// DockerOutput runs a docker command but returns the output rather than
    -// outputting to the logger.
    -func DockerOutput(name string, cmdv []string, rt *runtime.Runtime) (string, string, error) {
    - return exec.RunOutput(name, dockerCommand(cmdv, rt), rt.State.PlanTimeout)
    -}
    --- a/docker/environment.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/environment.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,142 +1,32 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    "fmt"
    - "io/ioutil"
    - "path/filepath"
    - "strings"
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    - "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Environment contains options for loading environment variables from a file
    -// in the workspace.
    -type Environment struct {
    - File string `yaml:"from-file"`
    - Files yaml.StringOrSlice `yaml:"from-files"`
    - Prefix string `yaml:"prefix"`
    -}
    -
    -// Execute runs the environment task.
    -func (e *Environment) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := environment.New().Merge(env)
    - fullEnv.Merge(rt.Environment)
    -
    - prefix := fullEnv.Map(e.Prefix)
    -
    - files, err := fullEnv.MapSlice(e.Files)
    - if err != nil {
    - return err
    - }
    -
    - // create a directory in our state for the task
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return err
    - }
    -
    - for _, file := range files {
    - // Export the file into the temp directory and maintain the
    - // structure of the file (for ease of error messages, so we
    - // get the file a/b/c/env instead of env).
    -
    - e := tasks.Export{
    - Files: []string{file},
    - Path: td.Path(),
    - }
    -
    - err = e.Execute(name, logger, env, rt)
    - if err != nil {
    - return err
    - }
    +type Environment struct{}
    - // Process the entries of the file and apply them to the
    - // state's base environment immediately.
    - dest := filepath.Clean(filepath.Join(td.Path(), file))
    - entries, err := processFile(dest, file, prefix)
    - if err != nil {
    - return err
    - }
    -
    - env.MergeSlice(entries)
    - }
    -
    - return nil
    -}
    -
    -func processFile(path, name, prefix string) ([]string, error) {
    - data, err := ioutil.ReadFile(path)
    - if err != nil {
    - return nil, fmt.Errorf("failed to read environment file '%s'", name)
    - }
    -
    - entries := []string{}
    - for _, line := range strings.Split(string(data), "\n") {
    - // Allow blank lines
    - if len(strings.TrimSpace(line)) == 0 {
    - continue
    - }
    -
    - // Each non-empty line requires the form key=val. Split the
    - // key and the val, then uppercase the key and add the prefix.
    - // We don't care what form val takes, it will be treated as a
    - // string (does not need to be quoted).
    -
    - parts := strings.SplitN(line, "=", 2)
    - if len(parts) != 2 {
    - return nil, fmt.Errorf("malformed entry in environments file '%s'", line)
    - }
    -
    - var (
    - key = strings.TrimSpace(parts[0])
    - val = strings.TrimSpace(parts[1])
    - )
    -
    - if len(key) == 0 {
    - return nil, fmt.Errorf("malformed entry in environments file '%s'", line)
    - }
    -
    - entries = append(entries, fmt.Sprintf("%s%s=%s", prefix, strings.ToUpper(key), val))
    - }
    -
    - return entries, nil
    -}
    -
    -// New creates a new environment command.
    +// New creates a new Environment task.
    func (e *Environment) New() tasks.Task {
    return &Environment{}
    }
    -// Valid validate the environment command.
    +// Valid validates the task.
    func (e *Environment) Valid() error {
    - if e.File != "" {
    - e.Files = append([]string{e.File}, e.Files...)
    - }
    + return fmt.Errorf("The docker/environment tasks was removed in version 0.15.0")
    +}
    - if len(e.Files) == 0 {
    - return errNoFilesEnvironment
    - }
    -
    +// Executes the task
    +func (e *Environment) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    return nil
    }
    +
    +func (e *Environment) Deprecated() error {
    + return nil
    +}
    --- a/docker/environment_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,58 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestEnvironmentSimple(t *testing.T) {
    - i := &Environment{Files: cYaml.StringOrSlice{"foo"}}
    - assert.Nil(t, i.Valid())
    -}
    -
    -func TestEnvironmentFilesRequired(t *testing.T) {
    - i := &Environment{}
    - assert.EqualError(t, i.Valid(), errNoFilesEnvironment.Error())
    -}
    -
    -func TestEnvironmentUnmarshalString(t *testing.T) {
    - data := `from-files: filename`
    -
    - imp := Environment{}
    - err := yaml.Unmarshal([]byte(data), &imp)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, imp.Files, cYaml.StringOrSlice{"filename"})
    -}
    -
    -func TestEnvironmentUnmarshalStringSlice(t *testing.T) {
    - data := `from-files:
    - - filename1
    - - filename2`
    -
    - imp := Environment{}
    - err := yaml.Unmarshal([]byte(data), &imp)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, imp.Files, cYaml.StringOrSlice{"filename1", "filename2"})
    -}
    --- a/docker/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/errors.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,19 +1,3 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    @@ -21,15 +5,6 @@
    )
    var (
    - errNoDockerFile = errors.New("no dockerfile specified")
    - errNoTag = errors.New("no tag specified")
    - errWildcardWithDestination = errors.New("file list contains a wildcard with a destination")
    - errNoFiles = errors.New("no files specified")
    - errNoServer = errors.New("no server specified")
    - errNoUsername = errors.New("no username specified")
    - errNoImage = errors.New("no image specified")
    - errNoImages = errors.New("no images specified")
    - errNoSourceTag = errors.New("no source tag specified")
    - errNoDestinationTags = errors.New("no destination tags specified")
    - errNoFilesEnvironment = errors.New("no environment files specified")
    + errDetachRemoved = errors.New("the detach attribute was removed in convey 0.15.0")
    + errHealthCheckRemoved = errors.New("the healthcheck attribute was removed in convey 0.15.0")
    )
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/docker/export.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,41 @@
    +package docker
    +
    +import (
    + "fmt"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Export struct {
    + Files yaml.StringOrSlice `yaml:"files"`
    +
    + realExport *tasks.Export
    +}
    +
    +// New creates a new Export task.
    +func (e *Export) New() tasks.Task {
    + return &Export{}
    +}
    +
    +// Valid validates the export task.
    +func (e *Export) Valid() error {
    + e.realExport = &tasks.Export{
    + Files: e.Files,
    + }
    +
    + return e.realExport.Valid()
    +}
    +
    +// Executes the task
    +func (e *Export) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    + return e.realExport.Execute(name, logger, env, rt)
    +}
    +
    +func (e *Export) Deprecated() error {
    + return fmt.Errorf("docker/export has been deprecated in favor of convey/export since 0.15.0")
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/docker/export_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,23 @@
    +package docker
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +func TestExport(t *testing.T) {
    + e := &Export{
    + Files: []string{".:."},
    + }
    +
    + assert.NoError(t, e.Valid())
    +}
    +
    +func TestExportFilesRequired(t *testing.T) {
    + e := &Export{}
    +
    + assert.ErrorIs(t, e.Valid(), tasks.ErrNoFiles)
    +}
    --- a/docker/healthcheck.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,79 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    - "time"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// HealthCheck represents a docker health check.
    -type HealthCheck struct {
    - Command string `yaml:"command"`
    - Interval time.Duration `yaml:"interval"`
    - Retries int `yaml:"retries"`
    - StartPeriod time.Duration `yaml:"start-period"`
    - Timeout time.Duration `yaml:"timeout"`
    -}
    -
    -func containerHasHealthCheck(name, cid string, rt *runtime.Runtime, logger *log.Entry) (bool, error) {
    - cmdv := []string{
    - "inspect",
    - "-f",
    - "\"{{if .Config.Healthcheck}}true{{else}}false{{end}}\"",
    - cid,
    - }
    -
    - stdout, stderr, err := DockerOutput(name+"/healthcheck", cmdv, rt)
    - if err != nil {
    - logger.Error(stderr)
    -
    - return false, err
    - }
    -
    - if strings.TrimSpace(stdout) == "true" {
    - return true, nil
    - }
    -
    - return false, nil
    -}
    -
    -func containerIsHealthy(name, cid string, rt *runtime.Runtime, logger *log.Entry) (bool, error) {
    - cmdv := []string{
    - "inspect",
    - "-f",
    - "\"{{.State.Health.Status}}\"",
    - cid,
    - }
    -
    - stdout, stderr, err := DockerOutput(name+"/healthcheck", cmdv, rt)
    - if err != nil {
    - logger.Error(stderr)
    -
    - return false, err
    - }
    -
    - if strings.ToLower(strings.TrimSpace(stdout)) == "healthy" {
    - return true, err
    - }
    -
    - return false, nil
    -}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/docker/import.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,41 @@
    +package docker
    +
    +import (
    + "fmt"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Import struct {
    + Files yaml.StringOrSlice `yaml:"files"`
    +
    + realImport *tasks.Import
    +}
    +
    +// New creates a new Import task.
    +func (i *Import) New() tasks.Task {
    + return &Import{}
    +}
    +
    +// Valid validates the export task.
    +func (i *Import) Valid() error {
    + i.realImport = &tasks.Import{
    + Files: i.Files,
    + }
    +
    + return i.realImport.Valid()
    +}
    +
    +// Executes the task
    +func (i *Import) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    + return i.realImport.Execute(name, logger, env, rt)
    +}
    +
    +func (i *Import) Deprecated() error {
    + return fmt.Errorf("docker/import has been deprecated in favor of convey/import since 0.15.0")
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/docker/import_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,23 @@
    +package docker
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +func TestImport(t *testing.T) {
    + e := &Import{
    + Files: []string{".:."},
    + }
    +
    + assert.NoError(t, e.Valid())
    +}
    +
    +func TestImportFilesRequired(t *testing.T) {
    + e := &Import{}
    +
    + assert.ErrorIs(t, e.Valid(), tasks.ErrNoFiles)
    +}
    --- a/docker/login.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/login.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,72 +1,42 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    + "fmt"
    +
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    -// Login represents an login task for logging into a docker registry.
    type Login struct {
    + Server string `yaml:"server"`
    Username string `yaml:"username"`
    Password string `yaml:"password"`
    - Server string `yaml:"server"`
    +
    + realLogin *podman.Login
    }
    -// Execute runs the login task.
    -func (l *Login) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - username := fullEnv.Map(l.Username)
    - password := fullEnv.Map(l.Password)
    - server := fullEnv.Map(l.Server)
    -
    - cmd := exec.NewGenerator(
    - "login",
    - "-u", username,
    - "-p", password,
    - )
    -
    - if server != "" {
    - cmd.Append(server)
    - }
    -
    - return Docker(name, cmd.Command(), rt)
    +func (l *Login) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + return l.realLogin.Execute(name, logger, stageEnv, rt)
    }
    -// New creates a new login task.
    func (l *Login) New() tasks.Task {
    return &Login{}
    }
    -// Valid validate the login task.
    func (l *Login) Valid() error {
    - if l.Server == "" {
    - return errNoServer
    + l.realLogin = &podman.Login{
    + Registry: l.Server,
    + Username: l.Username,
    + Password: l.Password,
    }
    - if l.Username == "" {
    - return errNoUsername
    - }
    + return l.realLogin.Valid()
    +}
    - return nil
    +func (l *Login) Deprecated() error {
    + return fmt.Errorf("docker/login has been deprecated in favor of podman/login since 0.15.0")
    }
    --- a/docker/login_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/login_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,49 +1,46 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    "testing"
    "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/podman"
    )
    func TestLogin(t *testing.T) {
    l := &Login{
    - Server: "server",
    + Server: "docker.io",
    Username: "username",
    Password: "password",
    }
    - assert.Nil(t, l.Valid())
    + assert.NoError(t, l.Valid())
    }
    func TestLoginServerRequired(t *testing.T) {
    l := &Login{
    Username: "username",
    + Password: "password",
    }
    - assert.EqualError(t, l.Valid(), errNoServer.Error())
    + assert.ErrorIs(t, l.Valid(), podman.ErrNoRegistry)
    }
    func TestLoginUsernameRequired(t *testing.T) {
    l := &Login{
    - Server: "server",
    + Server: "docker.io",
    + Password: "password",
    }
    - assert.EqualError(t, l.Valid(), errNoUsername.Error())
    + assert.ErrorIs(t, l.Valid(), podman.ErrNoUsername)
    }
    +
    +func TestLoginPasswordRequired(t *testing.T) {
    + l := &Login{
    + Server: "docker.io",
    + Username: "username",
    + }
    +
    + assert.ErrorIs(t, l.Valid(), podman.ErrNoPassword)
    +}
    --- a/docker/logout.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/logout.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,60 +1,38 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    + "fmt"
    +
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    -// Logout represents a logout task which logs out of a docker registry.
    type Logout struct {
    Server string `yaml:"server"`
    +
    + realLogout *podman.Logout
    }
    -// Execute runs the logout task.
    -func (l *Logout) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - server := fullEnv.Map(l.Server)
    -
    - cmd := exec.NewGenerator("logout")
    -
    - if server != "" {
    - cmd.Append(server)
    - }
    -
    - return Docker(name, cmd.Command(), rt)
    +func (l *Logout) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + return l.realLogout.Execute(name, logger, stageEnv, rt)
    }
    -// New creates a new logout task.
    func (l *Logout) New() tasks.Task {
    return &Logout{}
    }
    -// Valid validates the logout task.
    func (l *Logout) Valid() error {
    - if l.Server == "" {
    - return errNoServer
    + l.realLogout = &podman.Logout{
    + Registry: l.Server,
    }
    - return nil
    + return l.realLogout.Valid()
    }
    +
    +func (l *Logout) Deprecated() error {
    + return fmt.Errorf("docker/logout has been deprecated in favor of podman/logout since 0.15.0")
    +}
    --- a/docker/logout_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/logout_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,37 +1,23 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    "testing"
    "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/podman"
    )
    func TestLogout(t *testing.T) {
    l := &Logout{
    - Server: "server",
    + Server: "docker.io",
    }
    - assert.Nil(t, l.Valid())
    + assert.NoError(t, l.Valid())
    }
    -func TestLogoutServerRequired(t *testing.T) {
    +func TestLoginRegistryRequired(t *testing.T) {
    l := &Logout{}
    - assert.EqualError(t, l.Valid(), errNoServer.Error())
    + assert.ErrorIs(t, l.Valid(), podman.ErrNoRegistry)
    }
    --- a/docker/parser.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,124 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "fmt"
    - "strings"
    -
    - "github.com/alecthomas/kingpin"
    -
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ParseCommand is a super stripped down kingpin parser that will parse docker
    -// command line options and return a convey task or an error
    -func ParseCommand(argv []string) (tasks.Task, error) {
    - app := kingpin.New("", "")
    -
    - login := app.Command("login", "")
    - loginUsername := login.Flag("username", "").Short('u').Default("").String()
    - loginPassword := login.Flag("password", "").Short('p').Default("").String()
    - // `aws ecr get-login adds environment variables to the login command,
    - // this handles that, but we ignore them.
    - login.Flag("env", "").Short('e').Default("").Strings()
    - loginServer := login.Arg("", "").String()
    -
    - logout := app.Command("logout", "")
    - logoutServer := logout.Arg("", "").String()
    -
    - build := app.Command("build", "")
    - buildTag := build.Flag("tag", "").Short('t').String()
    - buildContext := build.Arg("", "").String()
    -
    - push := app.Command("push", "")
    - pushImage := push.Arg("", "").Required().String()
    -
    - pull := app.Command("pull", "")
    - pullImage := pull.Arg("", "").Required().String()
    -
    - rmi := app.Command("rmi", "")
    - rmi.Flag("--force", "").Short('f').Default("false").Bool()
    - rmiImage := rmi.Arg("", "").Required().String()
    -
    - run := app.Command("run", "")
    - runEntryPoint := run.Flag("entrypoint", "").String()
    - runEnv := run.Flag("env", "").Short('e').Strings()
    - runImage := run.Arg("image", "").Required().String()
    - runWorkdir := run.Flag("workdir", "").Short('w').String()
    - runCommand := run.Arg("", "").Strings()
    -
    - tag := app.Command("tag", "")
    - tagSource := tag.Arg("src", "").Required().String()
    - tagDestination := tag.Arg("dest", "").Required().String()
    -
    - cmd, err := app.Parse(argv[1:])
    - if err != nil {
    - return nil, err
    - }
    -
    - var task tasks.Task
    -
    - switch cmd {
    - case "build":
    - task = &Build{
    - Tag: *buildTag,
    - Files: []string{*buildContext},
    - }
    - case "login":
    - task = &Login{
    - Username: *loginUsername,
    - Password: *loginPassword,
    - Server: *loginServer,
    - }
    - case "logout":
    - task = &Logout{
    - Server: *logoutServer,
    - }
    - case "pull":
    - task = &Pull{
    - Image: *pullImage,
    - }
    - case "push":
    - task = &Push{
    - Image: *pushImage,
    - }
    - case "rmi":
    - task = &Remove{
    - Image: *rmiImage,
    - }
    - case "run":
    - task = &Run{
    - Command: strings.Join(*runCommand, " "),
    - Image: *runImage,
    - EntryPoint: *runEntryPoint,
    - Environment: *runEnv,
    - WorkDir: *runWorkdir,
    - }
    - case "tag":
    - task = &Tag{
    - Source: *tagSource,
    - Destination: *tagDestination,
    - }
    - }
    -
    - if task != nil {
    - return task, nil
    - }
    -
    - return nil, fmt.Errorf("unable to parse docker command line '%v'", argv)
    -}
    --- a/docker/parser_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,41 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestECRGetLogin(t *testing.T) {
    - argv := strings.Split("docker login -u AWS -p password -e none https://1234567890.dkr.ecr.us-east-2.amazonaws.com", " ")
    -
    - task, err := ParseCommand(argv)
    - assert.Nil(t, err)
    - assert.NotNil(t, task)
    -
    - // make sure we can type assert into a Login command
    - assert.IsType(t, task, &Login{})
    - login := task.(*Login)
    -
    - // now check the login command
    - assert.Equal(t, login.Username, "AWS")
    - assert.Equal(t, login.Password, "password")
    - assert.Equal(t, login.Server, "https://1234567890.dkr.ecr.us-east-2.amazonaws.com")
    -}
    --- a/docker/pull.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/pull.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,71 +1,44 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    + "fmt"
    +
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Pull represents a docker pull task which pulls an image from a docker
    -// registry.
    type Pull struct {
    Image string `yaml:"image"`
    Images yaml.StringOrSlice `yaml:"images"`
    +
    + realPull *podman.Pull
    }
    -// Execute runs the pull task.
    -func (p *Pull) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - images, err := fullEnv.MapSlice(p.Images)
    - if err != nil {
    - return err
    - }
    -
    - for _, image := range images {
    - cmdv := []string{"pull", image}
    -
    - if err := Docker(name, cmdv, rt); err != nil {
    - return err
    - }
    - }
    -
    - return nil
    +func (p *Pull) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + return p.realPull.Execute(name, logger, stageEnv, rt)
    }
    -// New creates a new pull task.
    func (p *Pull) New() tasks.Task {
    return &Pull{}
    }
    -// Valid validates the pull task.
    func (p *Pull) Valid() error {
    if p.Image != "" {
    p.Images = append([]string{p.Image}, p.Images...)
    }
    - if len(p.Images) == 0 {
    - return errNoImages
    + p.realPull = &podman.Pull{
    + Tags: p.Images,
    }
    - return nil
    + return p.realPull.Valid()
    }
    +
    +func (p *Pull) Deprecated() error {
    + return fmt.Errorf("docker/pull has been deprecated in favor of podman/pull since 0.15.0")
    +}
    --- a/docker/pull_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/pull_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,19 +1,3 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    @@ -21,37 +5,38 @@
    "github.com/stretchr/testify/assert"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    + "keep.imfreedom.org/grim/convey/podman"
    + "keep.imfreedom.org/grim/convey/yaml"
    )
    -func TestPull(t *testing.T) {
    - p1 := &Pull{
    - Image: "image",
    +func TestPullImage(t *testing.T) {
    + p := &Pull{
    + Image: "foo",
    + }
    +
    + assert.NoError(t, p.Valid())
    +}
    +
    +func TestPullImages(t *testing.T) {
    + p := &Pull{
    + Images: []string{"foo", "bar"},
    }
    - assert.Nil(t, p1.Valid())
    +
    + assert.NoError(t, p.Valid())
    +}
    - p2 := &Pull{
    - Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
    +func TestPullImagesMerged(t *testing.T) {
    + p := &Pull{
    + Image: "foo",
    + Images: []string{"bar"},
    }
    - assert.Nil(t, p2.Valid())
    +
    + assert.NoError(t, p.Valid())
    + assert.Equal(t, p.Images, yaml.StringOrSlice{"foo", "bar"})
    }
    func TestPullImageRequired(t *testing.T) {
    - p1 := &Pull{}
    - assert.EqualError(t, p1.Valid(), errNoImages.Error())
    -
    - p2 := &Pull{
    - Images: cYaml.StringOrSlice{},
    - }
    - assert.EqualError(t, p2.Valid(), errNoImages.Error())
    -}
    + p := &Push{}
    -func TestPullValidNormalizesImages(t *testing.T) {
    - p := &Pull{
    - Image: "image1",
    - Images: cYaml.StringOrSlice{"image2", "image3"},
    - }
    -
    - assert.Nil(t, p.Valid())
    - assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
    + assert.ErrorIs(t, p.Valid(), podman.ErrNoTags)
    }
    --- a/docker/push.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/push.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,70 +1,44 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    + "fmt"
    +
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Push represents a docker push task which push an image to a docker registry.
    type Push struct {
    Image string `yaml:"image"`
    Images yaml.StringOrSlice `yaml:"images"`
    +
    + realPush *podman.Push
    }
    -// Execute runs the push task.
    -func (p *Push) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - images, err := fullEnv.MapSlice(p.Images)
    - if err != nil {
    - return err
    - }
    -
    - for _, image := range images {
    - cmdv := []string{"push", image}
    -
    - if err := Docker(name, cmdv, rt); err != nil {
    - return err
    - }
    - }
    -
    - return nil
    +func (p *Push) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + return p.realPush.Execute(name, logger, stageEnv, rt)
    }
    -// New creates a new push task.
    func (p *Push) New() tasks.Task {
    return &Push{}
    }
    -// Valid validates the push task.
    func (p *Push) Valid() error {
    if p.Image != "" {
    p.Images = append([]string{p.Image}, p.Images...)
    }
    - if len(p.Images) == 0 {
    - return errNoImages
    + p.realPush = &podman.Push{
    + Tags: p.Images,
    }
    - return nil
    + return p.realPush.Valid()
    }
    +
    +func (p *Push) Deprecated() error {
    + return fmt.Errorf("docker/push has been deprecated in favor of podman/push since 0.15.0")
    +}
    --- a/docker/push_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/push_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,19 +1,3 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    @@ -21,37 +5,38 @@
    "github.com/stretchr/testify/assert"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    + "keep.imfreedom.org/grim/convey/podman"
    + "keep.imfreedom.org/grim/convey/yaml"
    )
    -func TestPush(t *testing.T) {
    - p1 := &Push{
    - Image: "image",
    +func TestPushImage(t *testing.T) {
    + p := &Push{
    + Image: "foo",
    + }
    +
    + assert.NoError(t, p.Valid())
    +}
    +
    +func TestPushImages(t *testing.T) {
    + p := &Push{
    + Images: []string{"foo", "bar"},
    }
    - assert.Nil(t, p1.Valid())
    +
    + assert.NoError(t, p.Valid())
    +}
    - p2 := &Push{
    - Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
    +func TestPushImagesMerged(t *testing.T) {
    + p := &Push{
    + Image: "foo",
    + Images: []string{"bar"},
    }
    - assert.Nil(t, p2.Valid())
    +
    + assert.NoError(t, p.Valid())
    + assert.Equal(t, p.Images, yaml.StringOrSlice{"foo", "bar"})
    }
    func TestPushImageRequired(t *testing.T) {
    - p1 := &Push{}
    - assert.EqualError(t, p1.Valid(), errNoImages.Error())
    -
    - p2 := &Push{
    - Images: cYaml.StringOrSlice{},
    - }
    - assert.EqualError(t, p2.Valid(), errNoImages.Error())
    -}
    + p := &Push{}
    -func TestPushValidNormalizesImages(t *testing.T) {
    - p := &Push{
    - Image: "image1",
    - Images: cYaml.StringOrSlice{"image2", "image3"},
    - }
    -
    - assert.Nil(t, p.Valid())
    - assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
    + assert.ErrorIs(t, p.Valid(), podman.ErrNoTags)
    }
    --- a/docker/remove.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/remove.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,77 +1,45 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    - "strings"
    + "fmt"
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Remove represents a docker rm task which deletes a local docker image.
    type Remove struct {
    Image string `yaml:"image"`
    Images yaml.StringOrSlice `yaml:"images"`
    Quiet bool `yaml:"quiet"`
    +
    + realRemove *podman.Remove
    }
    -// Execute runs the remove task.
    -func (r *Remove) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - images, err := fullEnv.MapSlice(r.Images)
    - if err != nil {
    - return err
    - }
    -
    - for _, image := range images {
    - cmdv := []string{"rmi", image}
    -
    - if _, stderr, err := DockerOutput(name, cmdv, rt); err != nil {
    - if strings.Contains(stderr, "No such image") && r.Quiet {
    - continue
    - }
    -
    - return err
    - }
    - }
    -
    - return nil
    +func (r *Remove) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + return r.realRemove.Execute(name, logger, stageEnv, rt)
    }
    -// New creates a new remove task.
    func (r *Remove) New() tasks.Task {
    return &Remove{}
    }
    -// Valid validates the remove task.
    func (r *Remove) Valid() error {
    if r.Image != "" {
    r.Images = append([]string{r.Image}, r.Images...)
    }
    - if len(r.Images) == 0 {
    - return errNoImages
    + r.realRemove = &podman.Remove{
    + Tags: r.Images,
    }
    - return nil
    + return r.realRemove.Valid()
    }
    +
    +func (r *Remove) Deprecated() error {
    + return fmt.Errorf("docker/remove has been deprecated in favor of podman/remove since 0.15.0")
    +}
    --- a/docker/remove_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/remove_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,19 +1,3 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    @@ -21,37 +5,38 @@
    "github.com/stretchr/testify/assert"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    + "keep.imfreedom.org/grim/convey/podman"
    + "keep.imfreedom.org/grim/convey/yaml"
    )
    -func TestRemove(t *testing.T) {
    - p1 := &Remove{
    - Image: "image",
    +func TestRemoveImage(t *testing.T) {
    + r := &Remove{
    + Image: "foo",
    }
    - assert.Nil(t, p1.Valid())
    - p2 := &Remove{
    - Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
    - }
    - assert.Nil(t, p2.Valid())
    + assert.NoError(t, r.Valid())
    }
    -func TestRemoveImageRequired(t *testing.T) {
    - p1 := &Remove{}
    - assert.EqualError(t, p1.Valid(), errNoImages.Error())
    +func TestRemoveImages(t *testing.T) {
    + r := &Remove{
    + Images: []string{"foo", "bar"},
    + }
    - p2 := &Remove{
    - Images: cYaml.StringOrSlice{},
    - }
    - assert.EqualError(t, p2.Valid(), errNoImages.Error())
    + assert.NoError(t, r.Valid())
    }
    -func TestRemoveValidNormalizesImages(t *testing.T) {
    - p := &Remove{
    - Image: "image1",
    - Images: cYaml.StringOrSlice{"image2", "image3"},
    +func TestRemoveMerge(t *testing.T) {
    + r := &Remove{
    + Image: "foo",
    + Images: []string{"bar", "baz"},
    }
    - assert.Nil(t, p.Valid())
    - assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
    + assert.NoError(t, r.Valid())
    + assert.Equal(t, r.Images, yaml.StringOrSlice{"foo", "bar", "baz"})
    }
    +
    +func TestRemoveImagesRequired(t *testing.T) {
    + r := &Remove{}
    +
    + assert.ErrorIs(t, r.Valid(), podman.ErrNoTags)
    +}
    --- a/docker/run.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/run.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,418 +1,72 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    "fmt"
    - "io/ioutil"
    - "os"
    - "os/user"
    - "path/filepath"
    - "strings"
    - "time"
    - "github.com/kballard/go-shellquote"
    - "github.com/satori/go.uuid"
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/normalize"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Run represents a docker run task which will run a container.
    type Run struct {
    Command string `yaml:"command"`
    Detach bool `yaml:"detach"`
    - EntryPoint string `yaml:"entrypoint"`
    + Entrypoint string `yaml:"entrypoint"`
    Environment yaml.StringOrSlice `yaml:"environment"`
    - HealthCheck HealthCheck `yaml:"healthcheck"`
    + HealthCheck interface{} `yaml:"healthcheck"`
    Hostname string `yaml:"hostname"`
    Image string `yaml:"image"`
    Labels yaml.StringOrSlice `yaml:"labels"`
    Script yaml.StringOrSlice `yaml:"script"`
    Shell string `yaml:"shell"`
    User string `yaml:"user"`
    - WorkDir string `yaml:"workdir"`
    - WorkSpace string `yaml:"workspace"`
    -}
    -
    -// UnmarshalYAML is a custom yaml unmarshaller for run tasks.
    -func (r *Run) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawRun Run
    - raw := rawRun{Shell: "/bin/sh", HealthCheck: HealthCheck{}}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *r = Run(raw)
    -
    - return nil
    -}
    -
    -func (r *Run) ReadEnvironment() []string {
    - return []string(r.Environment)
    -}
    -
    -// writeScript will write a shell script to the given tempfile for the given commands
    -func (r *Run) writeScript(name string, rt *runtime.Runtime, fullEnv *environment.Environment) (string, string, string, error) {
    - // create our task directory if it doesn't exist already
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return "", "", "", err
    - }
    -
    - // create the temp file to write the script to
    - scriptFile := filepath.Join(td.Path(), "script")
    -
    - entryPoint := r.Shell
    - if entryPoint == "" {
    - entryPoint = "/bin/sh"
    - }
    -
    - // Scripts must retain order, so don't use st.MapSlice to
    - // expand things (which results in a non-deterministically
    - // ordered slice of the expanded input). It also doesn't
    - // make sense to expand things here anyway - use a loop in
    - // bash if you need that kind of control.
    - scripts, err := fullEnv.MapSlice(r.Script)
    - if err != nil {
    - if nErr := os.Remove(scriptFile); nErr != nil {
    - fmt.Printf("error removing file: %s\n", nErr)
    - }
    -
    - return "", "", "", err
    - }
    -
    - // set the run command argument to the script file
    - commandArg := scriptFile
    -
    - // write the script to the file
    - err = ioutil.WriteFile(scriptFile, []byte(strings.Join(scripts, "\n")), 0700)
    - if err != nil {
    - return "", "", "", err
    - }
    -
    - // return it all
    - return scriptFile, entryPoint, commandArg, nil
    -}
    -
    -// detach will run the container detached
    -func (r *Run) detach(name string, cmdv []string, rt *runtime.Runtime, logger *log.Entry) error {
    - stdout, stderr, err := DockerOutput(name, cmdv, rt)
    - if err != nil {
    - logger.Errorf("%s", stderr)
    -
    - return err
    - }
    -
    - cid := strings.TrimSpace(stdout)
    -
    - rt.Cleanup(func(logger *log.Entry) {
    - logger.Debugf("stopping container %s", cid)
    + Workdir string `yaml:"workdir"`
    + Workspace string `yaml:"workspace"`
    - err = StopContainer(cid, logger, rt)
    - if err != nil {
    - logger.Warnf("failed to stop container %s: %s", cid, err.Error())
    - } else {
    - logger.Infof("stopped container %s", cid)
    - }
    - })
    -
    - logger.Infof("started detached container %s", cid)
    - logger.Infof("checking for healthcheck")
    -
    - // check if the container has a health check
    - hasHealth, err := containerHasHealthCheck(name, cid, rt, logger)
    - if err != nil {
    - return err
    - }
    -
    - if hasHealth {
    - healthChan := make(chan error)
    -
    - logger.Infof("waiting for container to go healthy")
    -
    - go func() {
    - duration := 5 * time.Second
    -
    - for {
    - // check the health
    - healthy, err := containerIsHealthy(name, cid, rt, logger)
    - if err != nil {
    - healthChan <- err
    - }
    -
    - if healthy {
    - healthChan <- nil
    - }
    -
    - logger.Infof("container still not healthy, waiting %v", duration)
    -
    - time.Sleep(duration)
    - }
    - }()
    -
    - err := <-healthChan
    - if err != nil {
    - return err
    - }
    -
    - logger.Infof("container is ready")
    - } else {
    - logger.Infof("no healthcheck found")
    - }
    -
    - return nil
    -}
    -
    -func (r *Run) buildCommandHealthCheck(cmd *exec.Generator) {
    - // add the healthcheck command if we got one
    - if r.HealthCheck.Command != "" {
    - cmd.Append("--health-cmd", r.HealthCheck.Command)
    - }
    -
    - // add the provided interval
    - if r.HealthCheck.Interval != time.Duration(0) {
    - cmd.Append("--health-interval", r.HealthCheck.Interval.String())
    - }
    -
    - // add the provided retries
    - if r.HealthCheck.Retries != 0 {
    - cmd.Append("--health-retries", fmt.Sprintf("%d", r.HealthCheck.Retries))
    - }
    -
    - // add the provided start period
    - if r.HealthCheck.StartPeriod != time.Duration(0) {
    - cmd.Append("--health-start-period", r.HealthCheck.StartPeriod.String())
    - }
    -
    - // add the provided timeout
    - if r.HealthCheck.Timeout != time.Duration(0) {
    - cmd.Append("--health-timeout", r.HealthCheck.Timeout.String())
    - }
    + realRun *podman.Run
    }
    -// Execute runs the run task.
    -func (r *Run) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().MergeSlice(r.Environment).Merge(rt.Environment)
    -
    - // create an id for this container
    - runID := uuid.NewV4().String()[0:6]
    -
    - // Map the image name so we can use it elsewhere
    - image := fullEnv.Map(r.Image)
    - if image == "" {
    - return errNoImage
    - }
    -
    - // build the command
    - cmd := exec.NewGenerator(
    - "run", "--rm",
    - "--name", runID,
    - )
    -
    - // add the hostname if specified
    - hostname := fullEnv.Map(r.Hostname)
    - if hostname != "" {
    - cmd.Append("--network-alias", hostname)
    - }
    -
    - // assign a default workspace location
    - workSpace := r.WorkSpace
    - if workSpace == "" {
    - workSpace = "/workspace"
    - }
    -
    - workspaceMount := fullEnv.Map(workSpace)
    -
    - cmd.Append(
    - "-v", rt.State.Workspace.Volume().Path()+":"+workspaceMount,
    - "-e", "CONVEY_WORKSPACE="+workspaceMount,
    - )
    -
    - // we do workDir after workspaceMount so we can put workspaceMount into an
    - // environment that workDir will be mapped against.
    - wsEnv := fullEnv.Copy()
    - wsEnv.Set("CONVEY_WORKSPACE", workspaceMount)
    -
    - workdir := wsEnv.Map(r.WorkDir)
    - if workdir != "" {
    - cmd.Append("-w", workdir)
    - } else {
    - id, nErr := ImageID(image, logger, rt)
    - if nErr != nil {
    - return nErr
    - }
    -
    - if id != "" {
    - // Check if the image does _not_ have a workdir set. If it doesn't,
    - // set workdir to the convey workspace
    - cmdv := []string{
    - "inspect",
    - "--format",
    - "{{.Config.WorkingDir}}",
    - image,
    - }
    - stdout, stderr, nErr := DockerOutput("checkWorkDir", cmdv, rt)
    - if nErr != nil {
    - logger.Errorf("%s", stderr)
    -
    - return nErr
    - }
    -
    - if strings.TrimSpace(stdout) == "" {
    - cmd.Append("-w", workspaceMount)
    - }
    - }
    - }
    -
    - // detach if necessary
    - if r.Detach {
    - cmd.Append("-d")
    - }
    -
    - // add cpushares if necessary
    - if rt.State.CPUShares != "" {
    - cmd.Append("--cpu-shares", rt.State.CPUShares)
    - }
    -
    - // add memory constraints if necessary
    - if rt.State.Memory != "" {
    - cmd.Append("--memory", rt.State.Memory)
    - }
    -
    - // grab the current user's UID and GID
    - user, err := user.Current()
    - if err != nil {
    - return err
    - }
    - cmd.Append("-e", "UID="+user.Uid, "-e", "GID="+user.Gid)
    -
    - // set user if one was specified
    - username := fullEnv.Map(r.User)
    - if username != "" {
    - cmd.Append("--user", username)
    - }
    -
    - // initialize some variables
    - var scriptFile string
    - entryPoint := r.EntryPoint
    - commandArg := fullEnv.Map(r.Command)
    -
    - // if we're using a script defined in the yaml, create it and override
    - // some variables
    - if len(r.Script) > 0 {
    - scriptFile, entryPoint, commandArg, err = r.writeScript(name, rt, fullEnv)
    - if err != nil {
    - return err
    - }
    -
    - if scriptFile != "" {
    - cmd.Append("-v", scriptFile+":"+scriptFile)
    - }
    -
    - }
    -
    - if entryPoint != "" {
    - cmd.Append("--entrypoint", entryPoint)
    - }
    -
    - // add a label to the container
    - cmd.Append(
    - "--label",
    - normalize.Normalize(fmt.Sprintf("convey-%d-task=%s", os.Getpid(), name)),
    - )
    -
    - // run through any user supplied labels
    - labels, err := fullEnv.MapSlice(r.Labels)
    - if err != nil {
    - return err
    - }
    - for _, label := range labels {
    - cmd.Append("--label", label)
    - }
    -
    - // add the ssh agent stuff
    - if rt.State.EnableSSHAgent {
    - authSock := os.Getenv("SSH_AUTH_SOCK")
    - cmd.Append(
    - "-e", "SSH_AUTH_SOCK",
    - "-v", authSock+":"+authSock,
    - )
    - }
    -
    - // add the health check stuff
    - r.buildCommandHealthCheck(cmd)
    -
    - // now add all non-empty environment variables
    - for _, env := range fullEnv.Prune().Items() {
    - cmd.Append("-e", env)
    - }
    -
    - // add the image to the command
    - cmd.Append(image)
    -
    - // append the command if we have one
    - if commandArg != "" {
    - args, err := shellquote.Split(commandArg)
    - if err != nil {
    - return err
    - }
    -
    - cmd.Append(args...)
    - }
    -
    - logger.Infof("running container with id %s", runID)
    -
    - //
    - // Everything after this should be mostly dead code
    - //
    -
    - if r.Detach {
    - return r.detach(name, cmd.Command(), rt, logger)
    - }
    -
    - // Mark running so we can detach this container form the network
    - // when we've got a signal to shutdown. An active network cannot
    - // be removed, so we need to track things to kill along with it.
    - // This is no longer a problem once the subprocess returns.
    -
    - rt.Containers.MarkRunning(runID)
    - defer rt.Containers.UnmarkRunning(runID)
    -
    - // run the command
    - return Docker(name, cmd.Command(), rt)
    -}
    -
    -// New creates a new run task.
    +// New creates a new Run task.
    func (r *Run) New() tasks.Task {
    return &Run{}
    }
    -// Valid validates the run task.
    +// Valid validates the export task.
    func (r *Run) Valid() error {
    - if r.Image == "" {
    - return errNoImage
    + if r.Detach == true {
    + return errDetachRemoved
    + }
    +
    + if r.HealthCheck != nil {
    + return errHealthCheckRemoved
    }
    - return nil
    + r.realRun = &podman.Run{
    + Annotations: r.Labels,
    + Command: r.Command,
    + Entrypoint: r.Entrypoint,
    + Environment: r.Environment,
    + Hostname: r.Hostname,
    + Image: r.Image,
    + Script: r.Script,
    + Shell: r.Shell,
    + User: r.User,
    + Workdir: r.Workdir,
    + Workspace: r.Workspace,
    + }
    +
    + return r.realRun.Valid()
    }
    +
    +// Executes the task
    +func (r *Run) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    + return r.realRun.Execute(name, logger, env, rt)
    +}
    +
    +func (r *Run) Deprecated() error {
    + return fmt.Errorf("docker/run has been deprecated in favor of podman/run since 0.15.0")
    +}
    --- a/docker/run_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/run_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,25 +1,11 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    "testing"
    "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/podman"
    )
    func TestRun(t *testing.T) {
    @@ -27,11 +13,11 @@
    Image: "image",
    }
    - assert.Nil(t, r.Valid())
    + assert.NoError(t, r.Valid())
    }
    func TestRunImageRequired(t *testing.T) {
    r := &Run{}
    - assert.EqualError(t, r.Valid(), errNoImage.Error())
    + assert.ErrorIs(t, r.Valid(), podman.ErrNoImage)
    }
    --- a/docker/tag.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/tag.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,77 +1,49 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    + "fmt"
    +
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/podman"
    "keep.imfreedom.org/grim/convey/runtime"
    "keep.imfreedom.org/grim/convey/tasks"
    "keep.imfreedom.org/grim/convey/yaml"
    )
    -// Tag represents a docker tag command.
    type Tag struct {
    Source string `yaml:"source"`
    Destination string `yaml:"destination"`
    Destinations yaml.StringOrSlice `yaml:"destinations"`
    +
    + realTag *podman.Tag
    }
    -// Execute runs the execute task.
    -func (t *Tag) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - source := fullEnv.Map(t.Source)
    -
    - destinations, err := fullEnv.MapSlice(t.Destinations)
    - if err != nil {
    - return err
    - }
    -
    - for _, destination := range destinations {
    - cmdv := []string{"tag", source, destination}
    -
    - if err := Docker(name, cmdv, rt); err != nil {
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// New creates a new docker tag task.
    +// New creates a new Tag task.
    func (t *Tag) New() tasks.Task {
    return &Tag{}
    }
    -// Valid validates the tag task.
    +// Valid validates the Tag task.
    func (t *Tag) Valid() error {
    - if t.Source == "" {
    - return errNoSourceTag
    - }
    -
    if t.Destination != "" {
    t.Destinations = append([]string{t.Destination}, t.Destinations...)
    }
    - if len(t.Destinations) == 0 {
    - return errNoDestinationTags
    + t.realTag = &podman.Tag{
    + Image: t.Source,
    + Targets: t.Destinations,
    }
    - return nil
    + return t.realTag.Valid()
    }
    +
    +// Executes the task
    +func (t *Tag) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    + return t.realTag.Execute(name, logger, env, rt)
    +}
    +
    +func (t *Tag) Deprecated() error {
    + return fmt.Errorf("docker/tag has been deprecated in favor of podman/tag since 0.15.0")
    +}
    --- a/docker/tag_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/docker/tag_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -1,19 +1,3 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    package docker
    import (
    @@ -21,53 +5,52 @@
    "github.com/stretchr/testify/assert"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    + "keep.imfreedom.org/grim/convey/podman"
    + "keep.imfreedom.org/grim/convey/yaml"
    )
    -func TestTag(t *testing.T) {
    - p1 := &Tag{
    - Source: "source",
    - Destination: "destination",
    +func TestTagSingleDestination(t *testing.T) {
    + tag := Tag{
    + Source: "foo",
    + Destination: "bar",
    }
    - assert.Nil(t, p1.Valid())
    +
    + assert.NoError(t, tag.Valid())
    + assert.Equal(t, tag.Destinations, yaml.StringOrSlice{"bar"})
    +}
    +
    +func TestTagMultipleDestinations(t *testing.T) {
    + tag := Tag{
    + Source: "foo",
    + Destinations: []string{"bar", "baz"},
    + }
    - p2 := &Tag{
    - Source: "source",
    - Destinations: cYaml.StringOrSlice{"dest1", "dest2", "dest3"},
    + assert.NoError(t, tag.Valid())
    +}
    +
    +func TestTagMergedDestinations(t *testing.T) {
    + tag := Tag{
    + Source: "foo",
    + Destination: "bar",
    + Destinations: []string{"baz"},
    }
    - assert.Nil(t, p2.Valid())
    +
    + assert.NoError(t, tag.Valid())
    + assert.Equal(t, tag.Destinations, yaml.StringOrSlice{"bar", "baz"})
    }
    func TestTagSourceRequired(t *testing.T) {
    - tag := &Tag{
    - Destination: "destination",
    - }
    -
    - assert.EqualError(t, tag.Valid(), errNoSourceTag.Error())
    -}
    -
    -func TestTagDestinationRequired(t *testing.T) {
    - p1 := &Tag{
    - Source: "source",
    + tag := Tag{
    + Destination: "bar",
    }
    - assert.EqualError(t, p1.Valid(), errNoDestinationTags.Error())
    -
    - p2 := &Tag{
    - Source: "source",
    - Destinations: cYaml.StringOrSlice{},
    - }
    -
    - assert.EqualError(t, p2.Valid(), errNoDestinationTags.Error())
    + assert.ErrorIs(t, tag.Valid(), podman.ErrNoImage)
    }
    -func TestTagValidNormalizesDestinations(t *testing.T) {
    - p := &Tag{
    - Source: "source",
    - Destination: "dest1",
    - Destinations: cYaml.StringOrSlice{"dest2", "dest3"},
    +func TestTagDestinationsRequired(t *testing.T) {
    + tag := Tag{
    + Source: "foo",
    }
    - assert.Nil(t, p.Valid())
    - assert.Equal(t, p.Destinations, cYaml.StringOrSlice{"dest1", "dest2", "dest3"})
    + assert.ErrorIs(t, tag.Valid(), podman.ErrNoTargets)
    }
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/docker/tasks.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,21 @@
    +package docker
    +
    +import (
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +var (
    + Tasks = map[string]tasks.Task{
    + "docker/build": &Build{},
    + "docker/import": &Import{},
    + "docker/environment": &Environment{},
    + "docker/export": &Export{},
    + "docker/login": &Login{},
    + "docker/logout": &Logout{},
    + "docker/pull": &Pull{},
    + "docker/push": &Push{},
    + "docker/remove": &Remove{},
    + "docker/run": &Run{},
    + "docker/tag": &Tag{},
    + }
    +)
    --- a/docker/util.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,79 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ParseImage will convert an image like python:3 to it's registry, name, and tag
    -func ParseImage(image string) (string, string, string) {
    - registry := ""
    - tag := ""
    -
    - // split the image into two pieces on the first /
    - parts := strings.SplitN(image, "/", 2)
    - if len(parts) == 2 {
    - if strings.Contains(parts[0], ".") || strings.Contains(parts[0], ":") || parts[0] == "localhost" {
    - registry = parts[0]
    - image = parts[1]
    - }
    - }
    -
    - parts = strings.SplitN(image, ":", 2)
    -
    - name := parts[0]
    - if len(parts) > 1 {
    - tag = parts[1]
    - }
    -
    - return registry, name, tag
    -}
    -
    -// StopContainer will call 'docker stop' on the given container id
    -func StopContainer(cid string, logger *log.Entry, rt *runtime.Runtime) error {
    - cmdv := []string{"stop", cid}
    -
    - _, stderr, err := DockerOutput("stop container", cmdv, rt)
    - if err != nil {
    - return err
    - }
    -
    - if stderr != "" {
    - logger.Warnf("%s", strings.TrimSpace(stderr))
    - }
    -
    - return nil
    -}
    -
    -// ImageID will return the ID of the given image if it's available locally
    -func ImageID(tag string, logger *log.Entry, rt *runtime.Runtime) (string, error) {
    - cmdv := []string{"image", "ls", "-q", tag}
    -
    - stdout, stderr, err := DockerOutput("get image id", cmdv, rt)
    - if err != nil {
    - logger.Errorf("error: %s", stderr)
    -
    - return "", err
    - }
    -
    - return strings.TrimSpace(stdout), nil
    -}
    --- a/docker/util_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,89 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -type parseImageData struct {
    - input string
    - registry string
    - name string
    - tag string
    -}
    -
    -func TestParseImage(t *testing.T) {
    - data := []parseImageData{
    - {
    - input: "",
    - registry: "",
    - name: "",
    - tag: "",
    - }, {
    - input: "python",
    - registry: "",
    - name: "python",
    - tag: "",
    - }, {
    - input: "python:3",
    - registry: "",
    - name: "python",
    - tag: "3",
    - }, {
    - input: "convey/workspace",
    - registry: "",
    - name: "convey/workspace",
    - tag: "",
    - }, {
    - input: "localhost/convey/workspace",
    - registry: "localhost",
    - name: "convey/workspace",
    - tag: "",
    - }, {
    - input: "localhost:5000/convey/workspace",
    - registry: "localhost:5000",
    - name: "convey/workspace",
    - tag: "",
    - }, {
    - input: "registry.docker.io/python:3",
    - registry: "registry.docker.io",
    - name: "python",
    - tag: "3",
    - }, {
    - input: "registry.docker.io/rw_grim/convey:latest",
    - registry: "registry.docker.io",
    - name: "rw_grim/convey",
    - tag: "latest",
    - }, {
    - input: "registry.my.tld:5000/something/fancy:commit:deadbeef",
    - registry: "registry.my.tld:5000",
    - name: "something/fancy",
    - tag: "commit:deadbeef",
    - },
    - }
    -
    - for _, test := range data {
    - registry, name, tag := ParseImage(test.input)
    -
    - assert.Equal(t, registry, test.registry)
    - assert.Equal(t, name, test.name)
    - assert.Equal(t, tag, test.tag)
    - }
    -}
    --- a/environment/default.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/environment/default.go Mon Dec 20 12:33:09 2021 -0600
    @@ -22,13 +22,26 @@
    "strings"
    "time"
    - "bitbucket.org/rw_grim/govcs"
    - "bitbucket.org/rw_grim/govcs/hg"
    + "hg.sr.ht/~grim/govcs"
    + "hg.sr.ht/~grim/govcs/hg"
    + "keep.imfreedom.org/grim/convey/consts"
    "keep.imfreedom.org/grim/convey/normalize"
    )
    -func (e *Environment) loadVCS(wd string) error {
    +// setEnvOS will add the given envar by name only to the environment but also
    +// put value into the OS's environmanet variable of the same name.
    +func (e Environment) setEnvOS(name, value string) error {
    + if err := os.Setenv(name, value); err != nil {
    + return err
    + }
    +
    + e[name] = ""
    +
    + return nil
    +}
    +
    +func (e Environment) loadVCS(wd string) error {
    repo, err := govcs.Detect(wd)
    if err != nil {
    return nil
    @@ -36,38 +49,41 @@
    name := strings.ToUpper(repo.Name())
    - e.Set("VCS", name)
    - e.Set("COMMIT", repo.Commit())
    - e.Set("COMMIT_SHORT", repo.ShortCommit())
    - e.Set("COMMIT", repo.Commit())
    - e.Set("BRANCH", repo.Branch())
    - e.Set("BRANCH_NORMALIZED", normalize.Normalize(repo.Branch()))
    - e.Set("REMOTE", repo.Remote(""))
    + vars := map[string]string{
    + "BRANCH": repo.Branch(),
    + "BRANCH_NORMALIZED": normalize.Normalize(repo.Branch()),
    + "COMMIT": repo.Commit(),
    + "COMMIT_SHORT": repo.ShortCommit(),
    + "REMOTE": repo.Remote(""),
    + "VCS": name,
    - e.Set(name+"_COMMIT", repo.Commit())
    - e.Set(name+"_COMMIT_SHORT", repo.ShortCommit())
    - e.Set(name+"_COMMIT", repo.Commit())
    - e.Set(name+"_BRANCH", repo.Branch())
    - e.Set(name+"_BRANCH_NORMALIZED", normalize.Normalize(repo.Branch()))
    - e.Set(name+"_REMOTE", repo.Remote(""))
    + name + "_BRANCH": repo.Branch(),
    + name + "_BRANCH_NORMALIZED": normalize.Normalize(repo.Branch()),
    + name + "_COMMIT": repo.Commit(),
    + name + "_COMMIT_SHORT": repo.ShortCommit(),
    + name + "_REMOTE": repo.Remote(""),
    + }
    // check for vcs specific values. If this grows past 1, make it a switch
    // type.
    if hg, ok := repo.(*hg.Mercurial); ok {
    - e.Set("BOOKMARK", hg.Bookmark())
    - e.Set(name+"_BOOKMARK", hg.Bookmark())
    + vars["BOOKMARK"] = hg.Bookmark()
    + vars["HG_BOOKMARK"] = hg.Bookmark()
    + }
    +
    + for k, v := range vars {
    + if err := e.setEnvOS(k, v); err != nil {
    + return err
    + }
    }
    return nil
    }
    // LoadDefaults will load the default environment variables for a convey run.
    -func (e *Environment) LoadDefaults(wd string) error {
    - oldHome := os.Getenv("HOME")
    - e.Set("HOME", "/tmp")
    - defer e.Set("HOME", oldHome)
    -
    - e.Set("RUN_TIME", time.Now().UTC().Format("2006-01-02T15:04:05-0700"))
    +func (e Environment) LoadDefaults(wd string) error {
    + e["RUN_TIME"] = time.Now().UTC().Format("2006-01-02T15:04:05-0700")
    + e["CONVEY_VERSION"] = consts.Version
    err := e.loadVCS(wd)
    if err != nil {
    --- a/environment/environment.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/environment/environment.go Mon Dec 20 12:33:09 2021 -0600
    @@ -18,24 +18,10 @@
    package environment
    import (
    - "fmt"
    - "os"
    "strings"
    - "sync"
    )
    -type EnvironmentReader interface {
    - ReadEnvironment() []string
    -}
    -
    -type Environment struct {
    - mutex sync.Mutex
    -
    - items []string
    -
    - expandables []string
    - delimiter string
    -}
    +type Environment map[string]string
    // splitEqual takes a string like "foo=bar" and returns it as ("foo", "bar")
    func splitEqual(value string) (string, string) {
    @@ -47,80 +33,56 @@
    return parts[0], parts[1]
    }
    -func New(items ...string) *Environment {
    - if items == nil {
    - items = []string{}
    - }
    -
    - return &Environment{
    - items: items,
    - }
    -}
    +func New(items ...string) Environment {
    + r := Environment{}
    -func (e *Environment) Copy() *Environment {
    - return New(e.Items()...)
    -}
    -
    -func NewExpandable(expandables []string, delimiter string, items ...string) *Environment {
    - return &Environment{
    - items: items,
    -
    - expandables: expandables,
    - delimiter: delimiter,
    - }
    -}
    -
    -func (e *Environment) Items() []string {
    - return e.items
    + return r.MergeSlice(items)
    }
    -func (e *Environment) Set(name, value string) {
    - if err := os.Setenv(name, value); err != nil {
    - fmt.Printf("error setting environment variable '%s': %s\n", name, err)
    - } else {
    - e.mutex.Lock()
    - e.items = append(e.items, name)
    - e.mutex.Unlock()
    +func (e Environment) Copy() Environment {
    + r := Environment{}
    +
    + for k, v := range e {
    + r[k] = v
    }
    +
    + return r
    }
    -// Prune removes items from the environment if they don't have a value and are
    -// not in the OS's environment.
    -func (e *Environment) Prune() *Environment {
    - e.mutex.Lock()
    - defer e.mutex.Unlock()
    -
    - pruned := []string{}
    -
    - for _, val := range e.items {
    - k, v := splitEqual(val)
    - if v == "" && os.Getenv(k) == "" {
    - continue
    - }
    -
    - pruned = append(pruned, val)
    +// Merge will merge another environment into the current environment
    +// overwriting any existing items
    +func (e Environment) Merge(env Environment) Environment {
    + for k, v := range env {
    + e[k] = v
    }
    - e.items = pruned
    -
    return e
    }
    -// Merge will merge another environment into the current environment
    -func (e *Environment) Merge(env *Environment) *Environment {
    - return e.MergeSlice(env.items)
    -}
    -
    -func (e *Environment) MergeSlice(items []string) *Environment {
    - e.items = Merge(e.items, items)
    +// MergeSlice will merge a slice of strings in key or key=value formats.
    +func (e Environment) MergeSlice(items []string) Environment {
    + for _, item := range items {
    + k, v := splitEqual(item)
    + e[k] = v
    + }
    return e
    }
    -func (e *Environment) MapSlice(slice []string) ([]string, error) {
    - return SliceMapper(slice, e.Items())
    -}
    +func (e Environment) All() []string {
    + r := make([]string, len(e))
    +
    + idx := 0
    -func (e *Environment) Map(key string) string {
    - return Map(key, e.Items())
    + for k, v := range e {
    + if v != "" {
    + r[idx] = k + "=" + v
    + } else {
    + r[idx] = k
    + }
    +
    + idx++
    + }
    +
    + return r
    }
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/environment/expand.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,56 @@
    +package environment
    +
    +import (
    + "os"
    +
    + log "github.com/sirupsen/logrus"
    +)
    +
    +func (e Environment) expandMapper(name string) string {
    + // We might have something like $(pwd), in which case we don't want
    + // to wrap the name in `{}`, which would cause it to be ${}(pwd) and
    + // cannot be evaluated by a run task.
    +
    + if name == "" {
    + return "$"
    + }
    +
    + if val, found := e[name]; found {
    + if val != "" {
    + return val
    + }
    +
    + return os.Getenv(name)
    + }
    +
    + return ""
    +}
    +
    +// Expand looks for the given key and returns the value if one is found
    +// recursing if the value contains another variable.
    +func (e Environment) Expand(key string) string {
    + last := os.Expand(key, e.expandMapper)
    + n_tries := 10
    +
    + for i := 0; i < n_tries; i++ {
    + next := os.Expand(last, e.expandMapper)
    + if next == last {
    + return next
    + }
    + last = next
    + }
    +
    + log.Warnf("failed to fully expand %q after %d recursions", key, n_tries)
    +
    + return ""
    +}
    +
    +func (e Environment) Expandv(items []string) []string {
    + expanded := make([]string, len(items))
    +
    + for idx, item := range items {
    + expanded[idx] = e.Expand(item)
    + }
    +
    + return expanded
    +}
    --- a/environment/merge.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,47 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package environment provides utilities for managing environment variables.
    -package environment
    -
    -import ()
    -
    -// Merge combines two environments and returns the result.
    -func Merge(orig, update []string) []string {
    - env := map[string]string{}
    -
    - for _, val := range orig {
    - k, v := splitEqual(val)
    - env[k] = v
    - }
    -
    - for _, val := range update {
    - k, v := splitEqual(val)
    - env[k] = v
    - }
    -
    - ret := []string{}
    - for k, v := range env {
    - val := k
    - if v != "" {
    - val += "=" + v
    - }
    -
    - ret = append(ret, val)
    - }
    -
    - return ret
    -}
    --- a/exec/exec.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/exec/exec.go Mon Dec 20 12:33:09 2021 -0600
    @@ -20,6 +20,7 @@
    import (
    "bufio"
    "fmt"
    + "io"
    "os/exec"
    "sync"
    "time"
    @@ -38,7 +39,19 @@
    errCollector = newLogCollector(logger, log.ErrorLevel)
    )
    - return run(name, cmdv, timeout, &sync.WaitGroup{}, outCollector, errCollector)
    + return run(name, cmdv, nil, timeout, &sync.WaitGroup{}, outCollector, errCollector)
    +}
    +
    +// RunWithStdin runs the command specified in argv and passes stdin to the
    +// process's stdin. stderr and stdout are logged to a new log adapter.
    +func RunWithStdin(name string, cmdv []string, stdin io.Reader, timeout time.Duration) error {
    + var (
    + logger = logging.NewAdapter(name)
    + outCollector = newLogCollector(logger, log.InfoLevel)
    + errCollector = newLogCollector(logger, log.ErrorLevel)
    + )
    +
    + return run(name, cmdv, stdin, timeout, &sync.WaitGroup{}, outCollector, errCollector)
    }
    // RunOutput works just like Run but returns stdout and stderr instead of
    @@ -50,13 +63,13 @@
    errCollector = newStringCollector(wg)
    )
    - err := run(name, cmdv, timeout, wg, outCollector.handler, errCollector.handler)
    + err := run(name, cmdv, nil, timeout, wg, outCollector.handler, errCollector.handler)
    wg.Wait()
    return outCollector.output, errCollector.output, err
    }
    -func run(name string, cmdv []string, timeout time.Duration, wg *sync.WaitGroup, outHandler, errHandler collector) error {
    +func run(name string, cmdv []string, stdin io.Reader, timeout time.Duration, wg *sync.WaitGroup, outHandler, errHandler collector) error {
    logger := logging.NewAdapter(name)
    logger.Debugf("running command \"%v\"", cmdv)
    @@ -75,6 +88,18 @@
    return err
    }
    + if stdin != nil {
    + stdinPipe, err := cmd.StdinPipe()
    + if err != nil {
    + return err
    + }
    +
    + go func() {
    + defer stdinPipe.Close()
    + io.Copy(stdinPipe, stdin)
    + }()
    + }
    +
    // at this point we're committed to running the log handlers so increment
    // the waitgroup
    wg.Add(2)
    --- a/fs/copy.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/fs/copy.go Mon Dec 20 12:33:09 2021 -0600
    @@ -22,8 +22,6 @@
    "os"
    "path/filepath"
    "strings"
    -
    - log "github.com/sirupsen/logrus"
    )
    // CopyFile copies the contents from src to dst atomically.
    @@ -65,11 +63,8 @@
    dst = absDst
    err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
    - log.Debugf("walk: path %q", path)
    -
    // make sure we're don't descend into our destination directory.
    if strings.HasPrefix(path, absDst) {
    - log.Debugf("%q is in the destination %q", path, absDst)
    return nil
    }
    @@ -77,8 +72,6 @@
    // destination name
    realDst := filepath.Join(dst, strings.TrimPrefix(path, src))
    - log.Debugf("src %q; dst %q; realDst %q", path, dst, realDst)
    -
    // if path is a directory, create it
    if info.IsDir() {
    err := os.Mkdir(realDst, info.Mode())
    --- a/fs/fs.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,20 +0,0 @@
    -// Convey
    -// Copyright 2016-2020 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package path provides utilities for handling filesystem paths and
    -// objects.
    -
    -package fs
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/globals/globals.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,6 @@
    +package globals
    +
    +type Globals struct {
    + Color bool `kong:"flag,help='Enable colorized output.',default='1'"`
    + Verbose bool `kong:"flag,help='Be more verbose',short='v',default='0'"`
    +}
    --- a/go.mod Thu Aug 26 08:11:34 2021 -0500
    +++ b/go.mod Mon Dec 20 12:33:09 2021 -0600
    @@ -1,28 +1,27 @@
    module keep.imfreedom.org/grim/convey
    require (
    - bitbucket.org/rw_grim/govcs v0.0.0-20190727184842-ba0d24484add
    - github.com/alecthomas/kingpin v2.2.6+incompatible
    - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
    - github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
    - github.com/blang/semver v3.5.1+incompatible
    - github.com/davecgh/go-spew v1.1.1
    - github.com/emirpasic/gods v1.12.0
    - github.com/go-yaml/yaml v2.1.0+incompatible
    + github.com/alecthomas/kong v0.2.22
    github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
    - github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
    - github.com/kr/pretty v0.1.0 // indirect
    - github.com/mattn/go-colorable v0.1.8 // indirect
    - github.com/mattn/go-isatty v0.0.13 // indirect
    github.com/mattn/go-zglob v0.0.3
    github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
    - github.com/satori/go.uuid v1.2.0
    + github.com/opencontainers/selinux v1.10.0
    github.com/sirupsen/logrus v1.8.1
    - github.com/stretchr/objx v0.1.1 // indirect
    - github.com/stretchr/testify v1.4.0
    - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
    - golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
    - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
    + github.com/stretchr/testify v1.7.0
    + gopkg.in/yaml.v2 v2.4.0
    + hg.sr.ht/~grim/govcs v0.0.0-20211118104632-69c28523ced0
    )
    -go 1.13
    +require (
    + github.com/davecgh/go-spew v1.1.1 // indirect
    + github.com/kr/pretty v0.1.0 // indirect
    + github.com/mattn/go-colorable v0.1.12 // indirect
    + github.com/mattn/go-isatty v0.0.14 // indirect
    + github.com/pkg/errors v0.9.1 // indirect
    + github.com/pmezard/go-difflib v1.0.0 // indirect
    + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
    + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
    + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
    +)
    +
    +go 1.17
    --- a/go.sum Thu Aug 26 08:11:34 2021 -0500
    +++ b/go.sum Mon Dec 20 12:33:09 2021 -0600
    @@ -1,90 +1,51 @@
    -bitbucket.org/rw_grim/govcs v0.0.0-20190727184842-ba0d24484add h1:UrZ36ILYSiSY3lfbIVA0J0/oQ9qw6tWB9XycaEPSyAM=
    -bitbucket.org/rw_grim/govcs v0.0.0-20190727184842-ba0d24484add/go.mod h1:V6eZobI0I0CV3C1piPcqutimEoUr3zeIHiWJUKLqfnE=
    -github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
    -github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
    -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
    -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
    -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
    -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
    -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
    -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
    -github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
    -github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
    -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
    -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
    +github.com/alecthomas/kong v0.2.22 h1:lRcQYT2/yJ+coDNA5ws0mRL0pwSqjbP/6AcRkyKhomk=
    +github.com/alecthomas/kong v0.2.22/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0=
    +github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
    +github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
    github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
    -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
    -github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
    -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
    github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
    github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
    -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
    -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
    -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
    github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
    github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
    github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
    github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
    github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
    -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
    -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
    -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
    -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
    -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
    -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
    -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
    -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
    -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
    -github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
    -github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
    +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
    +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
    +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
    +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
    github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
    github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
    -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
    -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
    github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
    github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
    +github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU=
    +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
    +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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
    -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
    -github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
    -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
    github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
    github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
    github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
    -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
    github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
    github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
    -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
    -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
    -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
    -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
    -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
    -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
    -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
    -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
    -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
    -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
    -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
    -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
    +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
    golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
    -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
    -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
    -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
    +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
    +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
    gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
    gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
    +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
    +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
    +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
    +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
    +hg.sr.ht/~grim/govcs v0.0.0-20211118104632-69c28523ced0 h1:lwsa9zayd6v1hap0h4hxyv7Ops4xe28/D4rnI4yeGVE=
    +hg.sr.ht/~grim/govcs v0.0.0-20211118104632-69c28523ced0/go.mod h1:cWLo/6lMAb12s8HGhwVislGtL0OaLHEUFmY8ZHVQPiM=
    --- a/kubectl/README.md Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,99 +0,0 @@
    -# kubectl
    -
    -The kubectl package provides tasks for interacting with kubernetes via kubectl.
    -It supplies the create, apply, and delete commands. It provides a few
    -templating engines that can be used to template your manifests before they're
    -used.
    -
    -If `none` or no engine is specified then no templating is done. An engine of `environment` will do normal variable swapping with the environment variables that convey knows about in the format of `${VARNAME}` or `$VARNAME`.
    -
    -----
    -
    -## kubectl/apply Task
    -
    -An apply task will apply kubernetes manifests to the cluster.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------| -------- | ---------- | ----------- |
    -| context | | | The kubernetes context to use. |
    -| files | Yes | | The list of manifest files to apply. |
    -| namespace | | | The kubernetes namespace to use. |
    -| selector | | | The list of selectors to use. |
    -| engine | | | The template engine to use. |
    -
    -### Example
    -
    - apply:
    - type: kubectl/apply
    - files:
    - - manifest.yml
    -
    -----
    -
    -## kubectl/create Task
    -
    -A create task will create kubernetes manifests on the cluster.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------| -------- | ---------- | ----------- |
    -| context | | | The kubernetes context to use. |
    -| files | Yes | | The list of manifest files to apply. |
    -| namespace | | | The kubernetes namespace to use. |
    -| selector | | | The selector to use. |
    -| engine | | | The template engine to use. |
    -
    -### Example
    -
    - apply:
    - type: kubectl/create
    - files:
    - - manifest.yml
    -
    -----
    -
    -## kubectl/delete Task
    -
    -A delete task will delete kubernetes manifests from the cluster.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------| -------- | ---------- | ----------- |
    -| context | | | The kubernetes context to use. |
    -| files | Yes | | The list of manifest files to apply. |
    -| namespace | | | The kubernetes namespace to use. |
    -| selector | | | The selector to use. |
    -| engine | | | The template engine to use. |
    -
    -### Example
    -
    - apply:
    - type: kubectl/delete
    - files:
    - - manifest.yml
    -
    ----
    -
    -## kubectl/rollout Task
    -
    -A rollout task will run `kubectl status` to wait for a rollout to complete.
    -
    -###
    -
    -| Name | Required | Default | Description |
    -| --------- | -------- | ------- | ----------- |
    -| context | | | The context to use. |
    -| namespace | | | The namespace to use. |
    -| target | Yes | | The rollout target to wait for. |
    -
    -### Example
    -
    - wait-for-deploy:
    - type: kubectl/rollout
    - namespace: prod
    - target: deployment/www
    -
    --- a/kubectl/apply.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Apply is a task for calling `kubectl apply`.
    -type Apply CRUDCommand
    -
    -// Execute runs `kubectl apply` with the given arguments.
    -func (a *Apply) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - cmd := &CRUDCommand{
    - Context: a.Context,
    - Namespace: a.Namespace,
    - Files: a.Files,
    - Selector: a.Selector,
    - Engine: a.Engine,
    - }
    -
    - return cmd.Execute(name, "apply", logger, env, rt)
    -}
    -
    -// New creates a new kubectl/apply task.
    -func (a *Apply) New() tasks.Task {
    - return &Apply{}
    -}
    -
    -// Valid check if the apply task is valid
    -func (a *Apply) Valid() error {
    - if len(a.Files) <= 0 {
    - return errNoFiles
    - }
    -
    - return nil
    -}
    --- a/kubectl/apply_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestApplyFilesRequired(t *testing.T) {
    - a := &Apply{}
    - assert.NotNil(t, a.Valid())
    -
    - a = &Apply{Files: []string{"foo.yml"}}
    - assert.Nil(t, a.Valid())
    -}
    --- a/kubectl/command.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,191 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "io/ioutil"
    - "os"
    - "path/filepath"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    - "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -// CRUDCommand represents a call to a `kubectl` command to manage deployments.
    -type CRUDCommand struct {
    - Context string `yaml:"context"`
    - Files yaml.StringOrSlice `yaml:"files"`
    - Namespace string `yaml:"namespace"`
    - Selector yaml.StringOrSlice `yaml:"selector"`
    - Engine string `yaml:"engine"`
    -}
    -
    -// templateNone just copies files from the workspace into the scratch directory.
    -func (c *CRUDCommand) templateNone(name, scratchDir string, cmd *exec.Generator, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - // update the files we're going to run by joining them with our scratch directory
    - for _, file := range c.Files {
    - realFile := env.Map(file)
    -
    - cmd.Append("-f", filepath.Join(scratchDir, realFile))
    - }
    -
    - // now create an export task to get our files out of the workspace
    - export := &tasks.Export{
    - Files: c.Files,
    - Path: scratchDir,
    - }
    -
    - // make sure the export task is valid
    - err := export.Valid()
    - if err != nil {
    - return err
    - }
    -
    - // run the export
    - return export.Execute(name, logger, env, rt)
    -}
    -
    -func templateEnvironmentFile(filename string, env *environment.Environment) error {
    - // read the file
    - raw, err := ioutil.ReadFile(filename)
    - if err != nil {
    - return err
    - }
    -
    - // replace the environment variables in it
    - data := env.Map(string(raw))
    -
    - // write the templated data back out
    - return ioutil.WriteFile(filename, []byte(data), 0700)
    -}
    -
    -func templateEnvironmentDirectory(directory string, env *environment.Environment) error {
    - files, err := ioutil.ReadDir(directory)
    - if err != nil {
    - return err
    - }
    -
    - for _, file := range files {
    - absFile := filepath.Join(directory, file.Name())
    - err = templateEnvironmentFile(absFile, env)
    - if err != nil {
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// templateEnvironment will copy files from the workspace into the scratch
    -// directory and replace environment variables with those that convey knows
    -// about.
    -func (c *CRUDCommand) templateEnvironment(name, scratchDir string, cmd *exec.Generator, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - // run the normal export which we will update in place.
    - err := c.templateNone(name, scratchDir, cmd, logger, env, rt)
    - if err != nil {
    - return err
    - }
    -
    - // now iterate the files and replace all their environment variables with those that we have
    - for _, file := range c.Files {
    - // resolve the filename if it's templated
    - resolvedFile := env.Map(file)
    -
    - // figure out the full path to it
    - absFile := filepath.Join(scratchDir, resolvedFile)
    -
    - // check if we're looking at a file or a directory
    - entry, err := os.Stat(absFile)
    - if err != nil {
    - return err
    - }
    -
    - if entry.IsDir() {
    - err = templateEnvironmentDirectory(absFile, env)
    - } else {
    - err = templateEnvironmentFile(absFile, env)
    - }
    - if err != nil {
    - return err
    - }
    -
    - }
    -
    - return nil
    -}
    -
    -// Execute runs the given `kubectl` command with the given arguments.
    -func (c *CRUDCommand) Execute(name, action string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - // if we have a context use it
    - if c.Context != "" {
    - err := useContext(name, c.Context, logger, env, rt.State.PlanTimeout)
    - if err != nil {
    - return err
    - }
    - }
    -
    - // now build the apply command line
    - cmd := exec.NewGenerator("kubectl", action)
    -
    - namespace := fullEnv.Map(c.Namespace)
    - if namespace != "" {
    - cmd.Append("-n", namespace)
    - }
    -
    - selectors, err := fullEnv.MapSlice(c.Selector)
    - if err != nil {
    - return err
    - }
    - for _, selector := range selectors {
    - if selector != "" {
    - cmd.Append("-l", selector)
    - }
    - }
    -
    - // create our scratch directory
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return err
    - }
    -
    - // run our files through the template engine
    - switch c.Engine {
    - case "":
    - fallthrough
    - case "none":
    - err = c.templateNone(name, td.Path(), cmd, logger, fullEnv, rt)
    - case "env":
    - fallthrough
    - case "environment":
    - err = c.templateEnvironment(name, td.Path(), cmd, logger, fullEnv, rt)
    - }
    -
    - // if there was an error with templating bail
    - if err != nil {
    - return err
    - }
    -
    - // finally run the command
    - return exec.Run(name, cmd.Command(), rt.State.PlanTimeout)
    -}
    --- a/kubectl/context.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,35 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "time"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    -)
    -
    -// useContext calls `kubectl config use-context` for the given context
    -func useContext(name, context string, logger *log.Entry, env *environment.Environment, timeout time.Duration) error {
    - realContext := env.Map(context)
    -
    - cmdv := []string{"kubectl", "config", "use-context", realContext}
    -
    - return exec.Run(name, cmdv, timeout)
    -}
    --- a/kubectl/create.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Create is a task for calling `kubectl create`.
    -type Create CRUDCommand
    -
    -// Execute runs `kubectl create` with the given arguments.
    -func (c *Create) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - cmd := &CRUDCommand{
    - Context: c.Context,
    - Namespace: c.Namespace,
    - Files: c.Files,
    - Selector: c.Selector,
    - Engine: c.Engine,
    - }
    -
    - return cmd.Execute(name, "create", logger, env, rt)
    -}
    -
    -// New creates a new kubectl/create task.
    -func (c *Create) New() tasks.Task {
    - return &Create{}
    -}
    -
    -// Valid check if the apply task is valid
    -func (c *Create) Valid() error {
    - if len(c.Files) <= 0 {
    - return errNoFiles
    - }
    -
    - return nil
    -}
    --- a/kubectl/create_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestCreateFilesRequired(t *testing.T) {
    - c := &Create{}
    - assert.NotNil(t, c.Valid())
    -
    - c = &Create{Files: []string{"foo.yml"}}
    - assert.Nil(t, c.Valid())
    -}
    --- a/kubectl/delete.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Delete is a task for calling `kubectl delete`.
    -type Delete CRUDCommand
    -
    -// Execute runs `kubectl delete` with the given arguments.
    -func (d *Delete) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - cmd := &CRUDCommand{
    - Context: d.Context,
    - Namespace: d.Namespace,
    - Files: d.Files,
    - Selector: d.Selector,
    - Engine: d.Engine,
    - }
    -
    - return cmd.Execute(name, "delete", logger, env, rt)
    -}
    -
    -// New creates a new kubectl/create task.
    -func (d *Delete) New() tasks.Task {
    - return &Delete{}
    -}
    -
    -// Valid check if the apply task is valid
    -func (d *Delete) Valid() error {
    - if len(d.Files) <= 0 {
    - return errNoFiles
    - }
    -
    - return nil
    -}
    --- a/kubectl/delete_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestDeleteFilesRequired(t *testing.T) {
    - d := &Delete{}
    - assert.NotNil(t, d.Valid())
    -
    - d = &Delete{Files: []string{"foo.yml"}}
    - assert.Nil(t, d.Valid())
    -}
    --- a/kubectl/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,25 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "errors"
    -)
    -
    -var (
    - errNoFiles = errors.New("no files specified")
    -)
    --- a/kubectl/kubectl.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,33 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package kubectl contains the tasks for interacting with kubernetes via
    -// kubectl.
    -package kubectl
    -
    -import (
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - // Tasks is a map of kubectl tasks.
    - Tasks = map[string]tasks.Task{
    - "apply": &Apply{},
    - "create": &Create{},
    - "delete": &Delete{},
    - "rollout": &Rollout{},
    - }
    -)
    --- a/kubectl/rollout.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,74 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "errors"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// CRUDCommand represents a call to a `kubectl` command to manage deployments.
    -type Rollout struct {
    - Context string `yaml:"context"`
    - Namespace string `yaml:"namespace"`
    - Target string `yaml:"target"`
    -}
    -
    -func (r *Rollout) New() tasks.Task {
    - return &Rollout{}
    -}
    -
    -func (r *Rollout) Valid() error {
    - if r.Target == "" {
    - return errors.New("no target specified")
    - }
    -
    - return nil
    -}
    -
    -// Execute runs the given `kubectl` command with the given arguments.
    -func (r *Rollout) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - // if we have a context use it
    - if r.Context != "" {
    - err := useContext(name, r.Context, logger, env, rt.State.PlanTimeout)
    - if err != nil {
    - return err
    - }
    - }
    -
    - // now build the command line
    - cmd := exec.NewGenerator("kubectl", "rollout", "status")
    -
    - namespace := fullEnv.Map(r.Namespace)
    - if namespace != "" {
    - cmd.Append("-n", namespace)
    - }
    -
    - // add the target
    - cmd.Append(r.Target)
    -
    - // finally run the command
    - return exec.Run(name, cmd.Command(), rt.State.PlanTimeout)
    -}
    --- a/kubectl/rollout_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestRolloutTargetRequired(t *testing.T) {
    - r := &Rollout{}
    - assert.NotNil(t, r.Valid())
    -
    - r = &Rollout{Target: "deployment/www"}
    - assert.Nil(t, r.Valid())
    -}
    --- a/loaders/bitbucket/bitbucket.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,18 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package bitbucket providers a config loader for bitbucket-pipelines.yml.
    -package bitbucket
    --- a/loaders/bitbucket/data/branch-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,10 +0,0 @@
    ----
    -# This is a simple bitbucket-pipelines.yml that uses the branch format to
    -# setup the build
    -pipelines:
    - branches:
    - develop:
    - - step:
    - image: library/alpine
    - script:
    - - true
    --- a/loaders/bitbucket/data/branch-no-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,9 +0,0 @@
    ----
    -# This is a simple bitbucket-pipelines.yml that does not specify an image and
    -# should cause a failure.
    -pipelines:
    - branches:
    - develop:
    - - step:
    - script:
    - - true
    --- a/loaders/bitbucket/data/complex-global-image-with-login.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,15 +0,0 @@
    -image:
    - name: registry.docker.io/python:3
    - username: foo
    - password: bar
    - email: email
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-global-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,12 +0,0 @@
    -image:
    - name: python:3
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-step-image-with-login.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,14 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - image:
    - name: registry.docker.io/python:3
    - username: user
    - password: pass
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-step-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,12 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - image:
    - name: python:3
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-step-simple-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,11 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - image: python:3
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/docker-mixed.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,13 +0,0 @@
    -image: gliderlabs/alpine:edge
    -pipelines:
    - default:
    - - step:
    - script:
    - - hostname
    - - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
    - - date +%s
    - - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
    - - docker push atlassian/my-app:$BITBUCKET_COMMIT
    - - date +%s
    -options:
    - docker: true
    --- a/loaders/bitbucket/data/docker-simple.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,9 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - script:
    - - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
    - - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
    - - docker push atlassian/my-app:$BITBUCKET_COMMIT
    -options:
    - docker: true
    --- a/loaders/bitbucket/data/simple.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,11 +0,0 @@
    -image: python:3
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/loader.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,277 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "fmt"
    - "os"
    - "path/filepath"
    -
    - "bitbucket.org/rw_grim/govcs"
    - "github.com/go-yaml/yaml"
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/script"
    - "keep.imfreedom.org/grim/convey/stages"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -const (
    - defaultPlan = "default"
    -)
    -
    -// Loader is a loader.Loader that loads bitbucket-pipelines.yml files
    -type Loader struct{}
    -
    -func addPipeline(name string, defImage image, pipelines []pipeline, cfg *config.Config) error {
    - plan := plans.Plan{
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Tasks: []string{"import"},
    - Run: "on-success",
    - },
    - },
    - }
    -
    - for idx, pipeline := range pipelines {
    - loginTask := ""
    - logoutTask := ""
    -
    - img := defImage
    - if pipeline.Steps.Image.Name != "" {
    - img = pipeline.Steps.Image
    -
    - // if the step has an image with a username, we need to add the tasks to login and logout
    - if img.Username != "" {
    - registry, _, _ := docker.ParseImage(img.Name)
    -
    - // create and add the login task to the stage
    - loginTask = fmt.Sprintf("%s-%d-login", name, idx)
    - cfg.Tasks[loginTask] = &docker.Login{
    - Username: img.Username,
    - Password: img.Password,
    - Server: registry,
    - }
    -
    - // create the logout task, but store the name so we can add it after the other tasks
    - logoutTask = fmt.Sprintf("%s-%d-logout", name, idx)
    - cfg.Tasks[logoutTask] = &docker.Logout{
    - Server: registry,
    - }
    - }
    - } else if img.Username != "" {
    - // if we're using the global image and it has a username, we need to add the tasks to the stage
    - loginTask = "login"
    - logoutTask = "logout"
    - }
    -
    - // if we have a login task, add it to the tasks for this stage
    - if loginTask != "" {
    - plan.Stages[idx].Tasks = append(plan.Stages[idx].Tasks, loginTask)
    - }
    -
    - parsedTasks, err := script.Parse(img.Name, "/bin/sh", pipeline.Steps.Script)
    - if err != nil {
    - return err
    - }
    -
    - for idx, task := range parsedTasks {
    - taskName := fmt.Sprintf("%s-%d", name, idx)
    - cfg.Tasks[taskName] = task
    -
    - plan.Stages[0].Tasks = append(plan.Stages[0].Tasks, taskName)
    -
    - }
    -
    - // if we have a logout task, add it to the tasks for this stage
    - if logoutTask != "" {
    - plan.Stages[idx].Tasks = append(plan.Stages[idx].Tasks, logoutTask)
    - }
    -
    - }
    -
    - cfg.Plans[name] = plan
    -
    - return nil
    -}
    -
    -func addPipelines(base string, defImage image, pipelines map[string][]pipeline, cfg *config.Config) error {
    - for name, branchPipeline := range pipelines {
    - if len(branchPipeline) > 0 {
    - planName := fmt.Sprintf("%s-%s", base, name)
    - err := addPipeline(planName, defImage, branchPipeline, cfg)
    - if err != nil {
    - return err
    - }
    - }
    - }
    -
    - return nil
    -}
    -
    -// Load loads the given filename and returns a config.Config for it.
    -func (l *Loader) Load(base, path string, data []byte, options []string, disableDeprecated bool) (*config.Config, error) {
    - var pipeline bitbucketPipelines
    - err := yaml.Unmarshal(data, &pipeline)
    - if err != nil {
    - return nil, err
    - }
    -
    - cfg := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - },
    - Plans: map[string]plans.Plan{},
    - }
    -
    - // store the default image
    - defImage := pipeline.Image
    -
    - // if the default image has a username, add a login/logout task
    - if defImage.Username != "" {
    - registry, _, _ := docker.ParseImage(defImage.Name)
    - cfg.Tasks["login"] = &docker.Login{
    - Username: defImage.Username,
    - Password: defImage.Password,
    - Server: registry,
    - }
    -
    - cfg.Tasks["logout"] = &docker.Logout{
    - Server: registry,
    - }
    - }
    -
    - // add the default pipelines
    - if len(pipeline.Pipelines.Default) > 0 {
    - err = addPipeline("default", defImage, pipeline.Pipelines.Default, cfg)
    - if err != nil {
    - return nil, err
    - }
    - }
    -
    - err = addPipelines("bookmark", defImage, pipeline.Pipelines.Bookmarks, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = addPipelines("branch", defImage, pipeline.Pipelines.Branches, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = addPipelines("custom", defImage, pipeline.Pipelines.Custom, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = addPipelines("tag", defImage, pipeline.Pipelines.Tags, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - return cfg, nil
    -}
    -
    -// LoadOverride loads the given override file into the config.
    -func (l *Loader) LoadOverride(base, path string, data []byte, cfg *config.Config, disableDeprecated bool) {
    -}
    -
    -// Filenames returns the list for filenames that this loader supports.
    -func (l *Loader) Filenames() []string {
    - return []string{"bitbucket-pipelines.yml"}
    -}
    -
    -// OverrideSuffix returns the suffix for override files.
    -func (l *Loader) OverrideSuffix() string {
    - return "-override"
    -}
    -
    -// DefaultPlan returns the default plan name.
    -func (l *Loader) DefaultPlan() string {
    - vcs, err := govcs.Detect(".")
    - if err != nil {
    - log.Error(err)
    - return defaultPlan
    - }
    -
    - branch := vcs.Branch()
    -
    - if branch == "master" || branch == "default" {
    - return defaultPlan
    - }
    -
    - return "branch-" + branch
    -}
    -
    -// ResolvePlanName resolves the plan name if the one in the config contains a
    -// wildcard.
    -func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, rt *runtime.Runtime) string {
    - if plan != "" {
    - // try to shortcut if we can
    - if plan == defaultPlan {
    - return defaultPlan
    - }
    -
    - customPlan := fmt.Sprintf("custom-%s", plan)
    - if _, found := cfg.Plans[customPlan]; found {
    - return customPlan
    - }
    -
    - // we couldn't find the plan, so just return it so convey can return
    - // an error saying it wasn't found.
    - return plan
    - }
    -
    - // we're in auto discovery mode! woo?
    -
    - matchMapper := map[string][]string{
    - "branch": {},
    - }
    -
    - // figure out the we're looking for
    - if gitBranch := os.Getenv("GIT_BRANCH"); gitBranch != "" {
    - matchMapper["branch"] = append(matchMapper["branch"], gitBranch)
    - }
    - if hgBranch := os.Getenv("HG_BRANCH"); hgBranch != "" {
    - matchMapper["branch"] = append(matchMapper["branch"], hgBranch)
    - }
    - if hgBookmark := os.Getenv("HG_BOOKMARK"); hgBookmark != "" {
    - matchMapper["bookmark"] = append(matchMapper["bookmark"], hgBookmark)
    - }
    -
    - for name := range cfg.Plans {
    - for prefix, matchers := range matchMapper {
    - for _, matcher := range matchers {
    - calculatedName := fmt.Sprintf("%s-%s", prefix, matcher)
    - fmt.Printf("checking %s against %s\n", calculatedName, name)
    - if matched, _ := filepath.Match(name, calculatedName); matched {
    - return name
    - }
    - }
    - }
    - }
    -
    - return defaultPlan
    -}
    --- a/loaders/bitbucket/loader_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,514 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "io/ioutil"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/stages"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -func TestLoaderSimple(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/simple.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexGlobalImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-global-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexGlobalImageWithLogin(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-global-image-with-login.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "login": &docker.Login{
    - Username: "foo",
    - Password: "bar",
    - Server: "registry.docker.io",
    - },
    - "default-0": &docker.Run{
    - Image: "registry.docker.io/python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - "logout": &docker.Logout{
    - Server: "registry.docker.io",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Run: "on-success",
    - Concurrent: false,
    - Environment: nil,
    - Tasks: []string{"import", "login", "default-0", "logout"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexStepSimpleImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-step-simple-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexStepImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-step-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexStepImageWithLogin(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-step-image-with-login.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0-login": &docker.Login{
    - Username: "user",
    - Password: "pass",
    - Server: "registry.docker.io",
    - },
    - "default-0": &docker.Run{
    - Image: "registry.docker.io/python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - "default-0-logout": &docker.Logout{
    - Server: "registry.docker.io",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0-login", "default-0", "default-0-logout"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderBranchNoImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/branch-no-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - err = cfg.Valid()
    - assert.NotNil(t, err)
    -}
    -
    -func TestLoaderBranchImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/branch-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "branch-develop-0": &docker.Run{
    - Image: "library/alpine",
    - WorkDir: "/workspace",
    - Script: []string{
    - "true",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "branch-develop": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "branch-develop-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderDockerMixed(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/docker-mixed.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "gliderlabs/alpine:edge",
    - Script: []string{"hostname"},
    - WorkDir: "/workspace",
    - Shell: "/bin/sh",
    - },
    - "default-1": &docker.Login{
    - Username: "$DOCKER_USERNAME",
    - Password: "$DOCKER_PASSWORD",
    - },
    - "default-2": &docker.Run{
    - Image: "gliderlabs/alpine:edge",
    - Script: []string{"date +%s"},
    - WorkDir: "/workspace",
    - Shell: "/bin/sh",
    - },
    - "default-3": &docker.Build{
    - Files: []string{"."},
    - Tag: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - "default-4": &docker.Push{
    - Image: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - "default-5": &docker.Run{
    - Image: "gliderlabs/alpine:edge",
    - Script: []string{"date +%s"},
    - WorkDir: "/workspace",
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{
    - "import",
    - "default-0",
    - "default-1",
    - "default-2",
    - "default-3",
    - "default-4",
    - "default-5",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderDockerSimple(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/docker-simple.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Login{
    - Username: "$DOCKER_USERNAME",
    - Password: "$DOCKER_PASSWORD",
    - },
    - "default-1": &docker.Build{
    - Files: []string{"."},
    - Tag: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - "default-2": &docker.Push{
    - Image: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{
    - "import",
    - "default-0",
    - "default-1",
    - "default-2",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    --- a/loaders/bitbucket/types.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,128 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "github.com/go-yaml/yaml"
    -)
    -
    -type image struct {
    - Name string `yaml:"name"`
    - Username string `yaml:"username"`
    - Password string `yaml:"password"`
    - EMail string `yaml:"email"`
    -}
    -
    -func (i *image) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - // first check if this is the basic format where it's just a name
    - name := ""
    - err := unmarshal(&name)
    - if err == nil {
    - *i = image{
    - Name: name,
    - }
    -
    - return nil
    - }
    -
    - // if we got a yaml type error, try to unmarshal it as the struct
    - if _, ok := err.(*yaml.TypeError); ok {
    - type rawImage image
    - raw := rawImage{}
    -
    - if err = unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *i = image(raw)
    -
    - return nil
    - }
    -
    - return err
    -}
    -
    -type step struct {
    - Image image `yaml:"image"`
    - Script []string `yaml:"script"`
    - Services []string `yaml:"services"`
    -}
    -
    -type pipeline struct {
    - Steps step `yaml:"step"`
    -}
    -
    -type pipelines struct {
    - Bookmarks map[string][]pipeline `yaml:"bookmarks"`
    - Branches map[string][]pipeline `yaml:"branches"`
    - Custom map[string][]pipeline `yaml:"custom"`
    - Default []pipeline `yaml:"default"`
    - Tags map[string][]pipeline `yaml:"tags"`
    -}
    -
    -type service struct {
    - Image image `yaml:"image"`
    - Environment []string `yaml:"environment"`
    - Username string `yaml:"username"`
    - Password string `yaml:"password"`
    -}
    -
    -type definition struct {
    - Services map[string]service `yaml:"services"`
    -}
    -
    -type options struct {
    - Docker bool `yaml:"docker"`
    -}
    -
    -type bitbucketPipelines struct {
    - Image image `yaml:"image"`
    - Clone clone `yaml:"clone"`
    - Pipelines pipelines `yaml:"pipelines"`
    - Definitions definition `yaml:"definitions"`
    - Options options `yaml:"options"`
    -}
    -
    -func (b *bitbucketPipelines) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawBitbucketPipelines bitbucketPipelines
    - raw := rawBitbucketPipelines{Clone: clone{}}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *b = bitbucketPipelines(raw)
    -
    - return nil
    -}
    -
    -type clone struct {
    - Depth int `yaml:"depth"`
    -}
    -
    -func (c *clone) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawClone clone
    - raw := rawClone{Depth: 50}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *c = clone(raw)
    -
    - return nil
    -}
    --- a/loaders/bitbucket/unmarshal_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,249 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestUnmarshalSimple(t *testing.T) {
    - yamlData := `image: python:3
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests`
    -
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Image: image{
    - Name: "python:3",
    - },
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalMultipleBranches(t *testing.T) {
    - yamlData := `image: pidgin/builder-debian:stretch
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - ./autogen.sh --enable-debug --enable-gtk-doc
    - - make -s -j$(nproc)
    - - make -s -j$(nproc) distcheck
    - branches:
    - release-2.x.y:
    - - step:
    - image: pidgin/release-builder:release-2.x.y
    - script:
    - - set -ex
    - - ./autogen.sh --enable-debug
    - - make -s -j$(nproc)
    - - make -s -j$(nproc) check
    - - make distcheck`
    -
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Image: image{
    - Name: "pidgin/builder-debian:stretch",
    - },
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "set -ex",
    - "./autogen.sh --enable-debug --enable-gtk-doc",
    - "make -s -j$(nproc)",
    - "make -s -j$(nproc) distcheck",
    - },
    - },
    - },
    - },
    - Branches: map[string][]pipeline{
    - "release-2.x.y": {
    - {
    - Steps: step{
    - Image: image{
    - Name: "pidgin/release-builder:release-2.x.y",
    - },
    - Script: []string{
    - "set -ex",
    - "./autogen.sh --enable-debug",
    - "make -s -j$(nproc)",
    - "make -s -j$(nproc) check",
    - "make distcheck",
    - },
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalWithDocker(t *testing.T) {
    - yamlData := `pipelines:
    - default:
    - - step:
    - script:
    - - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
    - - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
    - - docker push atlassian/my-app:$BITBUCKET_COMMIT
    -options:
    - docker: true
    -`
    -
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD",
    - "docker build -t atlassian/my-app:$BITBUCKET_COMMIT .",
    - "docker push atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - },
    - },
    - },
    - },
    - Options: options{
    - Docker: true,
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalComplexGlobalImage(t *testing.T) {
    - yamlData := `pipelines:
    -image:
    - name: foobar
    -pipelines:
    - default:
    - - step:
    - script:
    - - true
    - `
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Image: image{
    - Name: "foobar",
    - },
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "true",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalComplexStepImage(t *testing.T) {
    - yamlData := `pipelines:
    -pipelines:
    - default:
    - - step:
    - image:
    - name: name
    - username: user
    - password: pass
    - email: test@example.com
    - script:
    - - true
    - `
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Image: image{
    - Name: "name",
    - Username: "user",
    - Password: "pass",
    - EMail: "test@example.com",
    - },
    - Script: []string{
    - "true",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    --- a/loaders/codebuild/codebuild.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,19 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package codebuild contains the convey load for AWS CodeBuild
    -// https://aws.amazon.com/codebuild/
    -package codebuild
    --- a/loaders/codebuild/loader.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,210 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package codebuild
    -
    -import (
    - "fmt"
    - "path/filepath"
    - "strings"
    -
    - "github.com/go-yaml/yaml"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/script"
    - "keep.imfreedom.org/grim/convey/stages"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Loader defines an AWS CodeBuild configuration.
    -type Loader struct {
    - region string
    - image string
    - accountID string
    -}
    -
    -func (l *Loader) loadOptions(options []string) error {
    - for _, opt := range options {
    - parts := strings.SplitN(opt, "=", 2)
    - if len(parts) != 2 {
    - return fmt.Errorf("invalid option '%s'", opt)
    - }
    -
    - switch parts[0] {
    - case "region":
    - l.region = parts[1]
    - case "image":
    - l.image = parts[1]
    - case "account-id":
    - l.accountID = parts[1]
    - }
    - }
    -
    - if l.image == "" {
    - return fmt.Errorf("no image specified")
    - }
    -
    - return nil
    -}
    -
    -// Load loads an AWS CodeBuild buildspec.yml.
    -func (l *Loader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*config.Config, error) {
    - err := l.loadOptions(options)
    - if err != nil {
    - return nil, err
    - }
    -
    - var cb CodeBuild
    - err = yaml.Unmarshal(data, &cb)
    - if err != nil {
    - return nil, err
    - }
    -
    - cfg := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - },
    - Plans: map[string]plans.Plan{},
    - }
    -
    - // create our plan and add our tasks
    - plan := plans.Plan{
    - Stages: []stages.Stage{
    - {
    - Name: "import",
    - Enabled: true,
    - Run: "on-success",
    - Tasks: []string{"import"},
    - },
    - },
    - }
    -
    - // TODO put the right image here
    - err = l.addPhases(cb, cfg, l.image, &plan)
    - if err != nil {
    - return nil, err
    - }
    -
    - l.createExportTask(cb, cfg, &plan)
    -
    - cfg.Plans["default"] = plan
    -
    - return cfg, nil
    -}
    -
    -func (l *Loader) addPhase(cb CodeBuild, cfg *config.Config, phaseName, imageName string, plan *plans.Plan) error {
    - phase, ok := cb.Phases[phaseName]
    - // ignore phases that are not in the yaml
    - if !ok {
    - return nil
    - }
    -
    - parsedTasks, err := script.Parse(imageName, "/bin/sh", phase.Commands)
    - if err != nil {
    - return err
    - }
    -
    - // create our stage
    - stage := stages.Stage{
    - Name: phaseName,
    - Enabled: true,
    - Run: "on-success",
    - }
    -
    - // now add the tasks to the config and the stage
    - for idx, task := range parsedTasks {
    - name := fmt.Sprintf("%s-%d", phaseName, idx)
    - cfg.Tasks[name] = task
    -
    - stage.Tasks = append(stage.Tasks, name)
    - }
    -
    - // add the stage to the plan
    - plan.Stages = append(plan.Stages, stage)
    -
    - return nil
    -}
    -
    -func (l *Loader) addPhases(cb CodeBuild, cfg *config.Config, imageName string, plan *plans.Plan) error {
    - phases := []string{"install", "pre_build", "build", "post_build"}
    -
    - // load all of the phases scripts into a single slice
    - for _, phase := range phases {
    - err := l.addPhase(cb, cfg, phase, imageName, plan)
    - if err != nil {
    - return err
    - }
    -
    - }
    -
    - return nil
    -}
    -
    -func (l *Loader) createExportTask(cb CodeBuild, cfg *config.Config, plan *plans.Plan) {
    - if len(cb.Artifacts.Files) == 0 {
    - return
    - }
    -
    - export := &tasks.Export{
    - Files: cb.Artifacts.Files,
    - }
    -
    - cfg.Tasks["artifacts"] = export
    -
    - if cb.Artifacts.DiscardPaths == "yes" {
    - for idx, name := range cb.Artifacts.Files {
    - cb.Artifacts.Files[idx] = filepath.Base(name)
    - }
    - }
    -
    - exportStage := stages.Stage{
    - Name: "artifacts",
    - Enabled: true,
    - Run: "on-success",
    - Tasks: []string{"artifacts"},
    - }
    -
    - plan.Stages = append(plan.Stages, exportStage)
    -}
    -
    -// LoadOverride just satisfies the Loader interface an is not implemented.
    -func (l *Loader) LoadOverride(path, base string, data []byte, config *config.Config, disableDeprecated bool) {
    -}
    -
    -// Filenames returns the filenames defining AWS CodeBuild configurations.
    -func (l *Loader) Filenames() []string {
    - return []string{"buildspec.yml"}
    -}
    -
    -// OverrideSuffix just satisfies the Loader interface and is not used.
    -func (l *Loader) OverrideSuffix() string {
    - return ""
    -}
    -
    -// DefaultPlan returns the default plan to use when an AWS CodeBuild
    -// configuration is loaded.
    -func (l *Loader) DefaultPlan() string {
    - return "default"
    -}
    -
    -// ResolvePlanName satisfies the LoaderInterface and returns the default value.
    -func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, rt *runtime.Runtime) string {
    - return plan
    -}
    --- a/loaders/codebuild/types.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,41 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package codebuild
    -
    -type environment struct {
    - Variables map[string]string `yaml:"variables"`
    - ParameterStore map[string]string `yaml:"parameter-store"`
    -}
    -
    -type phase struct {
    - Commands []string `yaml:"commands"`
    -}
    -
    -type artifacts struct {
    - Files []string `yaml:"files"`
    - DiscardPaths string `yaml:"discard-paths"`
    - BaseDirectory string `yaml:"base-directory"`
    -}
    -
    -// CodeBuild defines the data structure for the top level AWS CodeBuild
    -// configuration.
    -type CodeBuild struct {
    - Version string `yaml:"version"`
    - Environment environment `yaml:"env"`
    - Phases map[string]phase `yaml:"phases"`
    - Artifacts artifacts `yaml:"artifacts"`
    -}
    --- a/loaders/codebuild/unmarshal_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,101 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package codebuild
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestUnmarshalSimple(t *testing.T) {
    - yamlData := `version: 0.2
    -env:
    - variables:
    - JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64"
    - parameter-store:
    - LOGIN_PASSWORD: "dockerLoginPassword"
    -phases:
    - install:
    - commands:
    - - apt-get update -y
    - - apt-get install -y maven
    - pre_build:
    - commands:
    - - echo Nothing to do in the pre_build phase...
    - build:
    - commands:
    - - echo Build started on $(date)
    - - mvn install
    - post_build:
    - commands:
    - - echo Build completed on $(date)
    -artifacts:
    - files:
    - - target/messageUtil-1.0.jar
    - discard-paths: yes`
    -
    - var actual CodeBuild
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := CodeBuild{
    - Version: "0.2",
    - Environment: environment{
    - Variables: map[string]string{
    - "JAVA_HOME": "/usr/lib/jvm/java-8-openjdk-amd64",
    - },
    - ParameterStore: map[string]string{
    - "LOGIN_PASSWORD": "dockerLoginPassword",
    - },
    - },
    - Phases: map[string]phase{
    - "install": {
    - Commands: []string{
    - "apt-get update -y",
    - "apt-get install -y maven",
    - },
    - },
    - "pre_build": {
    - Commands: []string{
    - "echo Nothing to do in the pre_build phase...",
    - },
    - },
    - "build": {
    - Commands: []string{
    - "echo Build started on $(date)",
    - "mvn install",
    - },
    - },
    - "post_build": {
    - Commands: []string{
    - "echo Build completed on $(date)",
    - },
    - },
    - },
    - Artifacts: artifacts{
    - Files: []string{
    - "target/messageUtil-1.0.jar",
    - },
    - DiscardPaths: "yes",
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    --- a/loaders/convey/convey.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,288 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package convey provides the config loader for convey configurations.
    -package convey
    -
    -import (
    - "fmt"
    - "path/filepath"
    -
    - "github.com/blang/semver"
    - "github.com/go-yaml/yaml"
    - log "github.com/sirupsen/logrus"
    -
    - cConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/consts"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -type options struct {
    - DefaultPlan string `yaml:"default-plan"`
    - SSHIdentities cYaml.StringOrSlice `yaml:"ssh-identities"`
    -}
    -
    -type config struct {
    - RequiredVersion string `yaml:"required-version"`
    - Extends string `yaml:"extends"`
    - Tasks map[string]yaml.MapSlice `yaml:"tasks"`
    - Plans map[string]plans.Plan `yaml:"plans"`
    - MetaPlans map[string]plans.MetaPlan `yaml:"meta-plans"`
    - Environment cYaml.StringOrSlice `yaml:"environment"`
    - Options options `yaml:"options"`
    -}
    -
    -type override struct {
    - Environment cYaml.StringOrSlice `yaml:"environment"`
    - Options options `yaml:"options"`
    -}
    -
    -// Loader is a loader.Loader for loading convey.yml files.
    -type Loader struct {
    - logger *log.Entry
    - defaultPlan string
    - depth int
    - fileLoader func(string, *Loader) (*cConfig.Config, error)
    -}
    -
    -func (l *Loader) loadBase(cfg config, path string, options []string, disableDeprecated bool) (*cConfig.Config, error) {
    - // see if we're extending something - partially load a base
    - // config object if so that we'll modify; otherwise use an
    - // empty base config object that we'll set in a similar way
    - if cfg.Extends != "" {
    - if l.depth >= MaxExtendsDepth {
    - return nil, ErrMaxExtendsDepth
    - }
    -
    - extendsAbsName := filepath.Join(path, cfg.Extends)
    -
    - l.depth++
    - baseConfig, err := l.loadFile(extendsAbsName, options, disableDeprecated)
    - l.depth--
    -
    - // We can safely ignore no plans and no tasks errors here
    - // as we're ensured to also get a valid base config back.
    - // This is a bit of a strange idiom, but is still used in
    - // places like io.Reader (return non-zero n on error).
    - if err != nil && err != ErrNoPlans && err != ErrNoTasks {
    - return nil, err
    - }
    -
    - return baseConfig, nil
    - }
    -
    - return &cConfig.Config{
    - Tasks: map[string]tasks.Task{},
    - Plans: map[string]plans.Plan{},
    - MetaPlans: map[string]plans.MetaPlan{},
    - SSHIdentities: []string{},
    - }, nil
    -}
    -
    -func (l *Loader) loadPlans(baseConfig *cConfig.Config, cfg config) error {
    - // iterate through each plan and do any cleanup we need to
    - for _, plan := range cfg.Plans {
    - // set stage names for any that are missing them
    - for idx := range plan.Stages {
    - if plan.Stages[idx].Name == "" {
    - plan.Stages[idx].Name = fmt.Sprintf("stage-%d", idx)
    - }
    - }
    - }
    -
    - // If the plan has the merge attribute set, try to overwrite
    - // stages of a plan that is already declared with the same name.
    - // If no such plan exists, raise an error (you're trying to
    - // overwrite a stage of nothing, which is almost certainly an error.
    - for name, plan := range cfg.Plans {
    - if plan.Merge {
    - base, ok := baseConfig.Plans[name]
    - if !ok {
    - return fmt.Errorf("cannot merge with unknown plan '%s'", name)
    - }
    -
    - base, err := mergePlan(base, plan, name)
    - if err != nil {
    - return err
    - }
    -
    - baseConfig.Plans[name] = base
    - } else {
    - baseConfig.Plans[name] = plan
    - }
    - }
    -
    - return nil
    -}
    -
    -// Load loads the given filename and returns it as a config.Config.
    -func (l *Loader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*cConfig.Config, error) {
    - if l.logger == nil {
    - l.logger = logging.NewAdapter("config loader")
    - }
    -
    - // load the raw config
    - cfg := config{}
    - err := yaml.Unmarshal(data, &cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - if cfg.RequiredVersion != "" {
    - verRange, nErr := semver.ParseRange(cfg.RequiredVersion)
    - if nErr != nil {
    - return nil, nErr
    - }
    -
    - curVer, nErr := semver.Make(consts.Version)
    - if nErr != nil {
    - return nil, nErr
    - }
    -
    - if !verRange(curVer) {
    - nErr = fmt.Errorf(
    - "convey version %s required, but %s is in use",
    - cfg.RequiredVersion,
    - consts.Version,
    - )
    -
    - return nil, nErr
    - }
    - }
    -
    - // get our base config
    - baseConfig, err := l.loadBase(cfg, path, options, disableDeprecated)
    - if err != nil {
    - return nil, err
    - }
    -
    - // turn the raw tasks into real tasks
    - realTasks, err := loadTasks(path, cfg.Tasks, l.logger, disableDeprecated)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = l.loadPlans(baseConfig, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - // store the default plan in the loader
    - l.defaultPlan = cfg.Options.DefaultPlan
    -
    - // tasks, plans, and metaplans are all name => * maps, and
    - // if we're extending something we want to inherit all of these
    - // EXCEPT for things that we are explicitly overriding by using
    - // the same name. Do a shallow merge here
    -
    - for name, task := range realTasks {
    - baseConfig.Tasks[name] = task
    - }
    -
    - for name, metaPlan := range cfg.MetaPlans {
    - baseConfig.MetaPlans[name] = metaPlan
    - }
    -
    - baseConfig.Environment = environment.Merge(baseConfig.Environment, cfg.Environment)
    -
    - // Check if the default plan is being overridden
    - if cfg.Options.DefaultPlan != "" {
    - l.defaultPlan = cfg.Options.DefaultPlan
    - }
    -
    - // don't clobber ssh-identities with an empty list
    - if len(cfg.Options.SSHIdentities) > 0 {
    - baseConfig.SSHIdentities = cfg.Options.SSHIdentities
    - }
    -
    - // Return the base config at this point, but maybe possibly return an
    - // error that we're missing something in order to proceed. This gets
    - // kind of weird: if we're calling Load recursively through LoadFile,
    - // as is the case with extending a config file, we're going to ignore
    - // these errors but need the actual config (we may define tasks and
    - // plans in an extending config).
    -
    - if len(baseConfig.Tasks) == 0 {
    - return baseConfig, ErrNoTasks
    - }
    -
    - if len(baseConfig.Plans) == 0 {
    - return baseConfig, ErrNoPlans
    - }
    -
    - return baseConfig, nil
    -}
    -
    -// Load loads the given filename and returns it as a config.Config.
    -func (l *Loader) loadFile(path string, options []string, disableDeprecated bool) (*cConfig.Config, error) {
    - if l.fileLoader == nil {
    - return cConfig.LoadFile(path, l, options, disableDeprecated)
    - }
    -
    - return l.fileLoader(path, l)
    -}
    -
    -// LoadOverride loads the given override file into the given config.Config.
    -func (l *Loader) LoadOverride(path, base string, data []byte, config *cConfig.Config, disableDeprecated bool) {
    - var overrideData override
    -
    - err := yaml.Unmarshal(data, &overrideData)
    - if err != nil {
    - return
    - }
    -
    - config.Environment = environment.Merge(config.Environment, overrideData.Environment)
    -
    - // Check if the default plan is being overridden
    - if overrideData.Options.DefaultPlan != "" {
    - l.defaultPlan = overrideData.Options.DefaultPlan
    - }
    -
    - // if there are ssh-identities in the override they need to replace the
    - // ones in the normal config file.
    - if len(overrideData.Options.SSHIdentities) > 0 {
    - config.SSHIdentities = overrideData.Options.SSHIdentities
    - }
    -}
    -
    -// Filenames returns a string slice of filenames that are valid for this loader.
    -func (l *Loader) Filenames() []string {
    - return []string{"convey.yml", "convey.yaml"}
    -}
    -
    -// OverrideSuffix returns the suffix for override filenames.
    -func (l *Loader) OverrideSuffix() string {
    - return "-override"
    -}
    -
    -// DefaultPlan returns the default plan name.
    -func (l *Loader) DefaultPlan() string {
    - if l.defaultPlan != "" {
    - return l.defaultPlan
    - }
    -
    - return "default"
    -}
    -
    -// ResolvePlanName resolves a plan name if wildcards are supported.
    -func (l *Loader) ResolvePlanName(plan string, cfg *cConfig.Config, rt *runtime.Runtime) string {
    - return plan
    -}
    --- a/loaders/convey/convey_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,77 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - realConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/state"
    -)
    -
    -/* OverrideSuffix tests */
    -func TestOverrideSuffix(t *testing.T) {
    - l := Loader{}
    -
    - assert.Equal(t, l.OverrideSuffix(), "-override")
    -}
    -
    -/* ResolvePlanName tests */
    -func TestResolvePlanName(t *testing.T) {
    - l := Loader{}
    - cfg := &realConfig.Config{}
    - rt := runtime.New(&state.State{})
    -
    - tests := []string{
    - "",
    - "foo",
    - "*",
    - }
    -
    - for _, name := range tests {
    - assert.Equal(t, l.ResolvePlanName(name, cfg, rt), name)
    - }
    -}
    -
    -/* required fields tests */
    -func TestTasksRequired(t *testing.T) {
    - data := `plans:
    - default:
    - name: foo
    -`
    -
    - l := Loader{}
    -
    - _, err := l.Load(".", ".", []byte(data), []string{}, true)
    - assert.EqualError(t, err, ErrNoTasks.Error())
    -}
    -
    -func TestPlansRequired(t *testing.T) {
    - data := `tasks:
    - foo:
    - type: docker/run
    - image: foobar
    -`
    -
    - l := Loader{}
    -
    - _, err := l.Load(".", ".", []byte(data), []string{}, true)
    - assert.EqualError(t, err, ErrNoPlans.Error())
    -}
    --- a/loaders/convey/default_plan_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,87 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestDefaultPlan(t *testing.T) {
    - l := Loader{}
    -
    - assert.Equal(t, l.DefaultPlan(), "default")
    -}
    -
    -func TestDefaultPlanSet(t *testing.T) {
    - l := Loader{
    - defaultPlan: "something else",
    - }
    -
    - assert.Equal(t, l.DefaultPlan(), "something else")
    -}
    -
    -func TestDefaultPlanFromConfig(t *testing.T) {
    - data := `options:
    - default-plan: overridden-plan
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary
    -plans:
    - overridden-plan:
    - stages:
    - - tasks:
    - - foo
    -`
    -
    - l := Loader{}
    - cfg, err := l.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, l.defaultPlan, "overridden-plan")
    - assert.NotNil(t, cfg)
    -}
    -
    -func TestDefaultPlanFromOverride(t *testing.T) {
    - data := `tasks:
    - foo:
    - type: docker/run
    - image: imaginary
    -plans:
    - overridden-plan:
    - stages:
    - - tasks:
    - - foo
    -`
    -
    - l := Loader{}
    - cfg, err := l.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, l.defaultPlan, "")
    -
    - overrideData := `
    -options:
    - default-plan: abcxyz
    -`
    -
    - l.LoadOverride(".", ".", []byte(overrideData), cfg, true)
    -
    - assert.Equal(t, l.DefaultPlan(), "abcxyz")
    -}
    --- a/loaders/convey/environment_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,94 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -var baseData = `tasks:
    - foo:
    - type: docker/run
    - image: notreal
    -plans:
    - default:
    - stages:
    - - tasks:
    - - foo
    -`
    -
    -/* These test ensure that the convey yaml config properly loads environment
    - * settings.
    - */
    -
    -func TestEnvironmentUnmarshalGlobalSlice(t *testing.T) {
    - data := `environment:
    - - foo=bar
    - - baz
    -` + baseData
    -
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.ElementsMatch(t, cfg.Environment, []string{"foo=bar", "baz"})
    -}
    -
    -func TestEnvironmentUnmarshalGlobalString(t *testing.T) {
    - data := `environment: foo=bar
    -` + baseData
    -
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, cfg.Environment, []string{"foo=bar"})
    -}
    -
    -func TestEnvironmentUnmarshalGlobalOverrideSlice(t *testing.T) {
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(baseData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Empty(t, cfg.Environment)
    -
    - overrideData := `
    -environment:
    - - foo=bar
    - - baz
    -`
    - loader.LoadOverride(".", ".", []byte(overrideData), cfg, true)
    -
    - assert.ElementsMatch(t, cfg.Environment, []string{"foo=bar", "baz"})
    -}
    -
    -func TestEnvironmentUnmarshalGlobalOverrideString(t *testing.T) {
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(baseData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Empty(t, cfg.Environment)
    -
    - overrideData := `
    -environment: foo=bar
    -`
    - loader.LoadOverride(".", ".", []byte(overrideData), cfg, true)
    -
    - assert.Equal(t, cfg.Environment, []string{"foo=bar"})
    -}
    --- a/loaders/convey/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,40 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "errors"
    -)
    -
    -const (
    - // MaxExtendsDepth is the maximum depth that configs can extend each other.
    - MaxExtendsDepth = 50
    -)
    -
    -var (
    - // ErrNoTasks is used to represent a config that doesn't have any tasks
    - // defined.
    - ErrNoTasks = errors.New("no tasks specified")
    -
    - // ErrNoPlans is used to represent a config that doesn't have any plans
    - // defined.
    - ErrNoPlans = errors.New("no plans specified")
    -
    - // ErrMaxExtendsDepth is returned when a cycle has been found in configs
    - // that extend each other.
    - ErrMaxExtendsDepth = errors.New("exceeded allowed depth for extended configs")
    -)
    --- a/loaders/convey/extends_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,301 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "fmt"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - cConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/docker"
    -)
    -
    -func TestExtendNoClash(t *testing.T) {
    - baseData := `
    -environment:
    - - x=1
    - - y=2
    - - z=3
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    - bar:
    - type: docker/run
    - image: imaginary2
    - baz:
    - type: docker/run
    - image: imaginary3
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo, bar, baz]
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -environment:
    - - w=4
    -tasks:
    - bonk:
    - type: docker/run
    - image: imaginary4
    -plans:
    - plan2:
    - stages:
    - - tasks: [foo, bonk]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Len(t, cfg.Plans, 2)
    - assert.Contains(t, cfg.Plans, "plan1")
    - assert.Contains(t, cfg.Plans, "plan2")
    -
    - assert.ElementsMatch(
    - t,
    - cfg.Environment,
    - []string{"x=1", "y=2", "z=3", "w=4"},
    - )
    - assert.Len(t, cfg.Tasks, 4)
    - assert.Contains(t, cfg.Tasks, "foo")
    - assert.Contains(t, cfg.Tasks, "bar")
    - assert.Contains(t, cfg.Tasks, "baz")
    - assert.Contains(t, cfg.Tasks, "bonk")
    -}
    -
    -func TestExtendExtendsAndOverwrite(t *testing.T) {
    - baseData := `
    -environment:
    - - x=1
    - - y=2
    - - z=3
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    - bar:
    - type: docker/run
    - image: imaginary2
    - baz:
    - type: docker/run
    - image: imaginary3
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo, bar, baz]
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -environment:
    - - z=4
    -tasks:
    - baz:
    - type: docker/run
    - image: imaginary4
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo, baz]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.ElementsMatch(t, cfg.Environment, []string{"x=1", "y=2", "z=4"})
    -
    - assert.Len(t, cfg.Plans, 1)
    - assert.Contains(t, cfg.Plans, "plan1")
    -
    - assert.Len(t, cfg.Tasks, 3)
    - assert.Contains(t, cfg.Tasks, "foo")
    - assert.Contains(t, cfg.Tasks, "bar")
    - assert.Contains(t, cfg.Tasks, "baz")
    -
    - assert.Equal(t, cfg.Tasks["baz"].(*docker.Run).Image, "imaginary4")
    - assert.ElementsMatch(
    - t,
    - cfg.Plans["plan1"].Stages[0].Tasks,
    - []string{"foo", "baz"},
    - )
    -}
    -
    -func TestExtendExtendDefault(t *testing.T) {
    - baseData := `
    -options:
    - default-plan: plan1
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo]
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -options:
    - default-plan: plan2
    -tasks:
    - bar:
    - type: docker/run
    - image: imaginary1
    -plans:
    - plan2:
    - stages:
    - - tasks: [bar]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - _, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, loader.defaultPlan, "plan2")
    -}
    -
    -func TestExtendExtendTasksOnly(t *testing.T) {
    - baseData := `
    -options:
    - default-plan: plan1
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -plans:
    - plan:
    - stages:
    - - tasks: [foo]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - assert.Len(t, cfg.Plans, 1)
    - assert.Contains(t, cfg.Plans, "plan")
    -
    - assert.Len(t, cfg.Tasks, 1)
    - assert.Contains(t, cfg.Tasks, "foo")
    -}
    -
    -func TestExtendExtendNoTasks(t *testing.T) {
    - baseData := ``
    - extendedData := `extends: base.yml`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - _, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.EqualError(t, err, ErrNoTasks.Error())
    -}
    -
    -func TestExtendExtendNoPlans(t *testing.T) {
    - baseData := `
    -options:
    - default-plan: plan1
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    -`
    -
    - extendedData := `extends: base.yaml`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.EqualError(t, err, ErrNoPlans.Error())
    - assert.Len(t, cfg.Tasks, 1)
    - assert.Contains(t, cfg.Tasks, "foo")
    -}
    -
    -func TestExtendLoop(t *testing.T) {
    - sameData := `
    -extends: convey.yaml
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(sameData), []string{}, true)
    - },
    - }
    -
    - _, err := loader.Load(".", ".", []byte(sameData), []string{}, true)
    -
    - assert.EqualError(t, err, ErrMaxExtendsDepth.Error())
    -}
    -
    -func TestExtendFilename(t *testing.T) {
    - data := `
    -extends: ../base.yaml
    -`
    -
    - calledWith := ""
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - calledWith = name
    - return nil, fmt.Errorf("early out")
    - },
    - }
    -
    - loader.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Equal(t, calledWith, "../base.yaml")
    -}
    --- a/loaders/convey/plans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,54 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "fmt"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/stages"
    -)
    -
    -// mergePlan modifies parent in-place so that any stage declared in child
    -// will overwrite the stage of the same name in parent. Return an error if
    -// a stage exists in child but not parent. Environments are also merged.
    -func mergePlan(parent, child plans.Plan, name string) (plans.Plan, error) {
    - for _, stage := range child.Stages {
    - if !replaceStage(parent, stage) {
    - return plans.Plan{}, fmt.Errorf(
    - "cannot overwrite stage '%s' in plan '%s' (no such stage in parent)",
    - stage.Name,
    - name,
    - )
    - }
    - }
    -
    - parent.Environment = environment.Merge(parent.Environment, child.Environment)
    - return parent, nil
    -}
    -
    -func replaceStage(plan plans.Plan, stage stages.Stage) bool {
    - for i, match := range plan.Stages {
    - if match.Name == stage.Name {
    - plan.Stages[i] = stage
    - return true
    - }
    - }
    -
    - return false
    -}
    --- a/loaders/convey/tasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,109 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "fmt"
    -
    - "github.com/go-yaml/yaml"
    - log "github.com/sirupsen/logrus"
    -
    - cConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - deprecatedTasks map[string]string = map[string]string{
    - "docker/import": "convey/import",
    - "docker/export": "convey/export",
    - }
    -)
    -
    -func loadTask(name string, yamlTask yaml.MapSlice, logger *log.Entry, disableDeprecated bool) (tasks.Task, error) {
    - // task is a yaml.MapSlice since we can't unmarshal it into the proper data
    - // structure right away, so we have to walk it and grab the attributes
    - // we're interested in.
    - rawType := ""
    -
    - for _, item := range yamlTask {
    - if key, ok := item.Key.(string); ok && key == "type" {
    - if value, ok := item.Value.(string); ok {
    - rawType = value
    - }
    - }
    - }
    -
    - task, ok := cConfig.TasksMap[rawType]
    - // if we didn't find the task, check deprecated and old naming formats
    - if !ok {
    - if depTask, found := deprecatedTasks[rawType]; found {
    - if disableDeprecated {
    - return nil, fmt.Errorf("tasks %q not found", rawType)
    - }
    - log.Warnf("task %q is deprecated and replaced with %q", rawType, depTask)
    - newTask, ok := cConfig.TasksMap[depTask]
    - if !ok {
    - return nil, fmt.Errorf(
    - "task %q is deprecated, but can't find task %q that replaces it",
    - rawType,
    - depTask,
    - )
    - }
    - task = newTask
    - } else {
    - return nil, fmt.Errorf("task type %q not found", rawType)
    - }
    - }
    -
    - realTask, err := tasks.CloneTask(yamlTask, task)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = realTask.Valid()
    - if err != nil {
    - return nil, fmt.Errorf("%s: %s", name, err.Error())
    - }
    -
    - return realTask, nil
    -}
    -
    -func loadTasks(path string, raw map[string]yaml.MapSlice, logger *log.Entry, disableDeprecated bool) (map[string]tasks.Task, error) {
    - realTasks := map[string]tasks.Task{}
    -
    - for name, task := range raw {
    - realTask, err := loadTask(name, task, logger, disableDeprecated)
    - if err != nil {
    - return nil, err
    - }
    -
    - realTasks[name] = realTask
    - }
    -
    - // now resolve any subtasks
    - for name, task := range realTasks {
    - if subtask, ok := task.(*tasks.SubTask); ok {
    - if parent, found := realTasks[subtask.Base]; found {
    - subtask.Parent = parent
    - } else {
    - return nil, fmt.Errorf("failed to find parent task %q for task %q", subtask.Base, name)
    - }
    - }
    - }
    -
    - return realTasks, nil
    -}
    --- a/logging/logging.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/logging/logging.go Mon Dec 20 12:33:09 2021 -0600
    @@ -35,6 +35,3 @@
    log.SetFormatter(NewFormatter(color))
    }
    -
    -func Shutdown() {
    -}
    --- a/main.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/main.go Mon Dec 20 12:33:09 2021 -0600
    @@ -19,226 +19,53 @@
    import (
    "fmt"
    "os"
    - "path/filepath"
    "runtime/debug"
    - "strings"
    - "github.com/alecthomas/kingpin"
    + "github.com/alecthomas/kong"
    - "keep.imfreedom.org/grim/convey/config"
    "keep.imfreedom.org/grim/convey/consts"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/loaders/bitbucket"
    - "keep.imfreedom.org/grim/convey/loaders/codebuild"
    - "keep.imfreedom.org/grim/convey/loaders/convey"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/runners"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/ssh"
    - "keep.imfreedom.org/grim/convey/state"
    + "keep.imfreedom.org/grim/convey/globals"
    + "keep.imfreedom.org/grim/convey/runner"
    )
    -var (
    - app = kingpin.New("convey", "Convey is a container pipeline runner.").Version(consts.Version)
    +type VersionCmd struct{}
    - color = app.Flag("color", "Enable colorized output").Default("true").Bool()
    - configLoader = app.Flag("config-loader", "Select the configuration loader").Short('l').Default("convey").Enum("convey", "bitbucket", "codebuild")
    - configFile = app.Flag("config", "The config file name to use").Short('f').String()
    - cpuShares = app.Flag("cpu-shares", "The amount of cpu shares to give to a run task").Short('c').String()
    - dockerConfig = app.Flag("docker-config", "Location of docker client config files").String()
    - env = app.Flag("env", "Set environment variables").Short('e').Strings()
    - forceSequential = app.Flag("force-sequential", "Don't run anything concurrently").Short('S').Default("False").Bool()
    - keep = app.Flag("keep", "Keep the workspace volume").Short('k').Hidden().Default("False").Bool()
    - memory = app.Flag("memory", "The amount of memory to give the run task").Short('m').String()
    - sshAgent = app.Flag("ssh-agent", "A shortcut for --ssh-identity=*").Default("false").Bool()
    - sshIdentities = app.Flag("ssh-identity", "Enable ssh-agent for the given identities").Strings()
    - planTimeout = app.Flag("timeout", "The maximum amount of time a plan can run. 0 to disable. Units must be specified.").Default("15m").Duration()
    - verbose = app.Flag("verbose", "Be more verbose").Short('v').Default("False").Bool()
    - disableDeprecated = app.Flag("disable-deprecated", "Allow the use of deprecated features").Default("False").Bool()
    - options = app.Flag("opt", "Options to pass to the config loader").Short('o').Strings()
    +func (c *VersionCmd) Run(g *globals.Globals) error {
    + fmt.Printf("convey %s\n", consts.Version)
    - // simple commands
    - _ = app.Command("config", "Show a dump of the config file")
    - _ = app.Command("environment", "List the environment variables that are available")
    - _ = app.Command("graphviz", "Output a graphviz diagram of the config file")
    -
    - // run command
    - run = app.Command("run", "Run a plan or metaplan").Default()
    - planNames = run.Arg("plan", "The plan or list of plans to run in specified order").Strings()
    + if g.Verbose {
    + fmt.Printf("additional information...\n")
    + }
    - // list commands
    - ls = app.Command("list", "List information").Alias("ls")
    - _ = ls.Command("environment", "List the environment variables that are available.").Alias("env")
    - _ = ls.Command("metaplans", "List the metaplans defined in the configuration file.")
    - _ = ls.Command("plans", "List the plans defined in the configuration file.")
    - _ = ls.Command("tasks", "List the tasks defined in the configuration file.")
    -)
    -
    -// determineLoader will return the config loader to use.
    -func determineLoader() config.Loader {
    - switch *configLoader {
    - case "bitbucket":
    - return &bitbucket.Loader{}
    - case "codebuild":
    - return &codebuild.Loader{}
    - default:
    - return &convey.Loader{}
    - }
    + return nil
    }
    -// determineRunner will return the runner that handles the given command.
    -func determineRunner(command string) runners.Runner {
    - switch command {
    - case "graphviz":
    - return &runners.Graphviz{}
    - case "list environment":
    - return &runners.ListEnvironment{}
    - case "list metaplans":
    - return &runners.ListMetaPlans{}
    - case "list plans":
    - return &runners.ListPlans{}
    - case "list tasks":
    - return &runners.ListTasks{}
    - case "config":
    - return &runners.ShowConfig{}
    - default:
    - return &runners.Convey{}
    - }
    -}
    -
    -// resolvePlans will run through all of the plans in the config and resolve
    -// their names via the loader.
    -func resolvePlans(cfg *config.Config, loader config.Loader, rt *runtime.Runtime) []string {
    - realPlans := []string{}
    -
    - for _, planName := range *planNames {
    - if metaPlan, found := cfg.MetaPlans[planName]; found {
    - realPlans = append(realPlans, metaPlan.Plans...)
    - } else {
    - realPlans = append(realPlans, loader.ResolvePlanName(planName, cfg, rt))
    - }
    - }
    +var cli struct {
    + globals.Globals
    - return realPlans
    -}
    -
    -// loadConfig will load the config and it's path or an error.
    -func loadConfig(loader config.Loader) (*config.Config, string, error) {
    - // if a config file was not provided search for the loader's default file
    - if *configFile == "" {
    - for _, filename := range loader.Filenames() {
    - if _, err := os.Stat(filename); os.IsNotExist(err) {
    - continue
    - }
    -
    - *configFile = filename
    - break
    - }
    - }
    -
    - // now make sure we found a config file
    - if *configFile == "" {
    - err := fmt.Errorf("config file not found, looking for %s", strings.Join(loader.Filenames(), ","))
    - return nil, "", err
    - }
    -
    - // figure out the path to the config file
    - cfgPath, err := filepath.Abs(filepath.Dir(*configFile))
    - if err != nil {
    - return nil, "", err
    - }
    -
    - cfg, err := config.LoadFile(*configFile, loader, *options, *disableDeprecated)
    - if err != nil {
    - return nil, "", err
    - }
    -
    - return cfg, cfgPath, nil
    + Run runner.RunnerCmd `kong:"cmd,help='Run plans'"`
    + Version VersionCmd `kong:"cmd,help='Show the version and exit'"`
    }
    func gomain() int {
    - args := os.Args[1:]
    -
    - command, err := app.Parse(args)
    - if err != nil {
    - app.Usage(args)
    - return 1
    - }
    -
    - // setup logging
    - logging.Setup(*color, *verbose)
    - defer logging.Shutdown()
    -
    - // create a logger to use now that logging is set up
    - logger := logging.NewAdapter("convey")
    + ctx := kong.Parse(
    + &cli,
    + kong.Name("convey"),
    + kong.Description("Convey is a container pipeline runner."),
    + kong.UsageOnError(),
    + kong.ConfigureHelp(kong.HelpOptions{
    + Compact: true,
    + Summary: true,
    + }),
    + )
    - // now load the config
    - loader := determineLoader()
    -
    - cfg, cfgPath, err := loadConfig(loader)
    + err := ctx.Run(&cli.Globals)
    if err != nil {
    - logger.Errorf("Could not load config: %s", err)
    - return 1
    - }
    -
    - // find our default environment variables and then update them with the
    - // values from the command line
    - defEnv := environment.New()
    - if err := defEnv.LoadDefaults(cfgPath); err != nil {
    - logger.Errorf("Could not initialize environment: %s", err)
    + fmt.Fprintf(os.Stderr, "error: %s\n", err)
    return 1
    }
    - // if the user specified the shortcut, add * to the list of acceptable keys
    - if *sshAgent {
    - *sshIdentities = append(*sshIdentities, "*")
    - }
    -
    - // now merge in the keys from the config
    - *sshIdentities = append(*sshIdentities, cfg.SSHIdentities...)
    -
    - // now check if we have any keys and make sure one of them is usable
    - enableSSHAgent, err := ssh.ShouldEnable(*sshIdentities)
    - if err != nil {
    - logger.Errorf("Could not configure ssh agent: %s", err)
    - return 1
    - }
    -
    - runner := determineRunner(command)
    -
    - if len(*planNames) == 0 {
    - *planNames = []string{loader.DefaultPlan()}
    - }
    -
    - // create our state
    - st := state.New()
    -
    - // set the state's variables and validate it
    - st.CfgPath = cfgPath
    - st.KeepWorkspace = *keep
    - st.DisableDeprecated = *disableDeprecated
    - st.ForceSequential = *forceSequential
    - st.EnableSSHAgent = enableSSHAgent
    - st.PlanTimeout = *planTimeout
    - st.DockerConfig = *dockerConfig
    - st.CPUShares = *cpuShares
    - st.Memory = *memory
    -
    - if err := st.Valid(); err != nil {
    - logger.Errorf("Error validating convey state: %s", err)
    -
    - return 1
    - }
    -
    - // finally create our runtime
    - fullEnv := environment.New().Merge(defEnv).MergeSlice(*env)
    - rt := runtime.NewWithEnvironment(st, fullEnv)
    - defer rt.Shutdown()
    -
    - // resolve the plan name with the load and options
    - realPlans := resolvePlans(cfg, loader, rt)
    -
    - return runner.Run(cfg, realPlans, *env, rt)
    + return 0
    }
    func main() {
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/metaplans/metaplans.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,44 @@
    +// Convey
    +// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    +//
    +// This program is free software: you can redistribute it and/or modify
    +// it under the terms of the GNU General Public License as published by
    +// the Free Software Foundation, either version 3 of the License, or
    +// (at your option) any later version.
    +//
    +// This program is distributed in the hope that it will be useful,
    +// but WITHOUT ANY WARRANTY; without even the implied warranty of
    +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    +// GNU General Public License for more details.
    +//
    +// You should have received a copy of the GNU General Public License
    +// along with this program. If not, see <http://www.gnu.org/licenses/>.
    +
    +package metaplans
    +
    +import (
    + "fmt"
    +)
    +
    +// MetaPlan is a representation of a meta plan.
    +type MetaPlan struct {
    + Plans []string `yaml:"plans"`
    +}
    +
    +// UnmarshalYAML is a custom yaml unmarshaller for MetaPlan's.
    +func (m *MetaPlan) UnmarshalYAML(unmarshal func(interface{}) error) error {
    + type rawMetaPlan MetaPlan
    + raw := rawMetaPlan{}
    +
    + if err := unmarshal(&raw); err != nil {
    + return err
    + }
    +
    + if len(raw.Plans) == 0 {
    + return fmt.Errorf("no plans specified")
    + }
    +
    + *m = MetaPlan(raw)
    +
    + return nil
    +}
    --- a/plans/metaplans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,44 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package plans
    -
    -import (
    - "fmt"
    -)
    -
    -// MetaPlan is a representation of a meta plan.
    -type MetaPlan struct {
    - Plans []string `yaml:"plans"`
    -}
    -
    -// UnmarshalYAML is a custom yaml unmarshaller for MetaPlan's.
    -func (m *MetaPlan) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawMetaPlan MetaPlan
    - raw := rawMetaPlan{}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - if len(raw.Plans) == 0 {
    - return fmt.Errorf("no plans specified")
    - }
    -
    - *m = MetaPlan(raw)
    -
    - return nil
    -}
    --- a/plans/plans.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/plans/plans.go Mon Dec 20 12:33:09 2021 -0600
    @@ -24,7 +24,6 @@
    log "github.com/sirupsen/logrus"
    - "keep.imfreedom.org/grim/convey/docker"
    "keep.imfreedom.org/grim/convey/environment"
    "keep.imfreedom.org/grim/convey/logging"
    "keep.imfreedom.org/grim/convey/runtime"
    @@ -42,40 +41,33 @@
    type Plan struct {
    Environment yaml.StringOrSlice `yaml:"environment"`
    Stages []stages.Stage `yaml:"stages"`
    - Merge bool `yaml:"merge"`
    logger *log.Entry
    }
    -func (p *Plan) haveRunTask(taskMap map[string]tasks.Task) bool {
    - for _, stage := range p.Stages {
    - for _, taskName := range stage.Tasks {
    - task := taskMap[taskName]
    - if _, isRun := task.(*docker.Run); isRun {
    - return true
    - }
    - }
    - }
    -
    - return false
    -}
    -
    // Execute runs the plan.
    -func (p *Plan) Execute(path string, tasks map[string]tasks.Task, env *environment.Environment, rt *runtime.Runtime) error {
    +func (p *Plan) Execute(path string, tasks map[string]tasks.Task, configEnv environment.Environment, rt *runtime.Runtime) error {
    if err := p.Valid(); err != nil {
    return err
    }
    - planEnv := env.Copy().Merge(rt.Environment).MergeSlice(p.Environment)
    + planEnv := configEnv.Copy().MergeSlice(p.Environment).Merge(rt.Environment)
    p.logger = logging.NewAdapter(path)
    // set a flag on whether or not we've failed
    var planErr error
    - for _, stage := range p.Stages {
    + for idx, stage := range p.Stages {
    start := time.Now()
    - absStageName := fmt.Sprintf("%s/%s", path, stage.Name)
    +
    + absStageName := ""
    + if stage.Name != "" {
    + absStageName = fmt.Sprintf("%s/%s", path, stage.Name)
    + } else {
    + absStageName = fmt.Sprintf("%s/%d", path, idx)
    + }
    +
    stageLogger := logging.NewAdapter(absStageName)
    if stage.ShouldRun(planErr) {
    @@ -84,8 +76,8 @@
    err := stage.Execute(absStageName, stageLogger, tasks, planEnv, rt)
    elapsed := time.Since(start)
    - rt.State.PlanTimeout -= elapsed
    - p.logger.Debugf("remaining plantime: %s", rt.State.PlanTimeout)
    + rt.Timeout -= elapsed
    + p.logger.Debugf("remaining plantime: %s", rt.Timeout)
    if err != nil {
    stageLogger.Error("stage failed")
    --- a/plans/plans_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/plans/plans_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -19,8 +19,8 @@
    import (
    "testing"
    - "github.com/go-yaml/yaml"
    "github.com/stretchr/testify/assert"
    + "gopkg.in/yaml.v2"
    cYaml "keep.imfreedom.org/grim/convey/yaml"
    )
    --- a/plans/stages_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/plans/stages_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -18,25 +18,21 @@
    import (
    "testing"
    + "time"
    "github.com/stretchr/testify/assert"
    "keep.imfreedom.org/grim/convey/environment"
    "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/state"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    func runPlan(path string, p Plan, tasks map[string]tasks.Task) error {
    - st := state.New()
    - defer st.Destroy()
    + env := environment.New()
    + rt := runtime.New(env, "", false, false, 0*time.Second)
    + defer rt.Shutdown()
    - return p.Execute(
    - path,
    - tasks,
    - environment.New(),
    - runtime.New(st),
    - )
    + return p.Execute(path, tasks, environment.New(), rt)
    }
    func TestPlanStagesNone(t *testing.T) {
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/README.md Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,193 @@
    +# Podman
    +
    +Podman is a container runtime that runs daemonless as a normal user. It has
    +been adopted as the only container run time since convey 0.15.0.
    +
    +----
    +
    +## podman/build
    +
    +This task will build a container image.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ------------- | -------- | ------------------- | ----------- |
    +| annotations | | | A list of annotations for the container image. |
    +| containerfile | Yes | | The containerfile to build. |
    +| context | | ${CONVEY_WORKSPACE} | The context to pass to podman build. This path needs to build under the convey workspace which means all files for the build must first be imported into the workspace. |
    +| tags | | | A list of tags or a single tag to be applied to the image. |
    +| target | | | The name of the target to build in a multi stage containerfile. |
    +
    +### Example
    +
    + build:
    + type: podman/build
    + containerfile: Dockerfile
    + tags: myimage:latest
    +
    +----
    +
    +## podman/login
    +
    +This task allows you to authorize to a container registry.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| --------- | -------- | --------- | ----------- |
    +| password | Yes | | The password to use. |
    +| registry | | docker.io | The registry to login to. |
    +| username | Yes | | The username to use. |
    +
    +### Example
    +
    + registry-login:
    + type: podman/login
    + username: superuser1
    + password: abc123
    +
    +----
    +
    +## podman/logout
    +
    +This task allows you to unauthenticate from a container registry.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| -------- | -------- | --------- | ----------- |
    +| registry | | docker.io | The registry to logout of. |
    +
    +### Example
    +
    + registry-logout:
    + type: podman/logout
    + registry: regsitry.my.domain:5000
    +
    +----
    +
    +### podman/pull
    +
    +This task allows you to pull down a container image. If the image is private,
    +be use to run a `podman/login` task first.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ---- | -------- | ------- | ----------- |
    +| tags | Yes | | A list of image names or a single image name including the tags. |
    +
    +### Example
    +
    + pull-alpine:
    + type: podman/pull
    + tags: gliderlabs/alpine:edge
    +
    +----
    +
    +### podman/push
    +
    +This task allows you to push a local container image to a registry. If the
    +repository on the registry is private, be use to run a `podman/login` task
    +first.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ---- | -------- | ------- | ----------- |
    +| tags | Yes | | A list of image names or a single image name including the tags. |
    +
    +At least one image must be supplied by either the `image` or `images`
    +attribute. If both are specified, `image` will be prepended to the list from
    +`images`.
    +
    +### Example
    +
    + push-images:
    + type: podman/push
    + tags:
    + - registry.my.domain:5000/newimage:main-latest
    + - registry.my.domain:5000/newimage:main-deadbeef
    +
    +----
    +
    +## podman/remove
    +
    +This task will remove an image from the local machine.
    +
    +### Attributes
    +
    +
    +| Name | Required | Default | Description |
    +| ----- | -------- | ------- | ----------- |
    +| tags | | | A list of image names including the tags. |
    +| quiet | | false | True if a missing image should not count as a failure. |
    +
    +### Example
    +
    + remove-image:
    + type: podman/remove
    + tags:
    + - registry.my.domain:5000/newimage:main-latest
    + - registry.my.domain:5000/newimage:main-deadbeef
    +
    +----
    +
    +## podman/run
    +
    +This task will run a container with the workspace mounted.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ----------- | -------- | ------- | ----------- |
    +| annotations | | | A list of annotations to set on the container being run. |
    +| command | | | The command to run in the container. This is only necessary if the image does not provide a command by default. |
    +| entrypoint | | | The entrypoint to use for the container. This is only necessary if the image does not provide on by default or if you want to override the images default entrypoint. |
    +| environment | | | A list of environment variables to set. This should be specified in a `NAME` or `NAME=VALUE` format. |
    +| hostname | | | A custom hostname to set inside the container. |
    +| image | Yes | | The image include the registry and tag to run. |
    +| script | | | A list of commands to run inside of the container. |
    +| shell | | | The shell to use with the script attribute above. |
    +| user | | | The user to run the container as. |
    +| workdir | | | The working directory inside the container. |
    +| workspace | | | The path to mount the convey workspace at inside of the container. |
    +
    +### Example
    +
    +A basic example where the image knows everything to do.
    +
    + build-golang:
    + type: podman/run
    + image: golang:onbuild
    +
    +A basic example using a a standard image to do something else.
    +
    + download-file:
    + type: podman/run
    + image: debian:bookworm
    + script:
    + - wget https://somedomain.tld/file1
    + - wget https://somedomain.tld/file1.sha256sum
    + - sha256sum -c file1.sha256sum
    +
    +----
    +
    +## podman/tag
    +
    +This task will tag existing images on the host.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ------- | -------- | ------- | ----------- |
    +| image | Yes | | The full source image including the registry and tag to tag. |
    +| targets | Yes | | The destination tag include the registry and tag to create. |
    +
    +### Example
    +
    + tag-dev:
    + type: podman/tag
    + image: registry.my.domain:5000/newimage:latest
    + targets: registry.my.domain:5000/newimage:dev
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/build.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,99 @@
    +package podman
    +
    +import (
    + "path/filepath"
    + "strings"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Build struct {
    + Annotations yaml.StringOrSlice `yaml:"annotations"`
    + Containerfile string `yaml:"containerfile"`
    + Context string `yaml:"context"`
    + Tags yaml.StringOrSlice `yaml:"tags"`
    + Target string `yaml:"target"`
    +}
    +
    +func (b *Build) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + ws, err := rt.Workspace()
    + if err != nil {
    + return err
    + }
    +
    + context := filepath.Join(ws.Path(), env.Expand(b.Context))
    + if !strings.HasSuffix(context, string(filepath.Separator)) {
    + context += string(filepath.Separator)
    + }
    + log.Infof("resolved context: %q", context)
    + log.Infof("wspath: %q", ws.Path())
    + if !strings.HasPrefix(context, ws.Path()) {
    + return ErrContextOutsideOfWorkspace
    + }
    +
    + generator := exec.NewGenerator(
    + "podman",
    + "build",
    + "--tag", env.Expand(b.Tags[0]),
    + "--file", env.Expand(b.Containerfile),
    + )
    +
    + for _, annotation := range b.Annotations {
    + generator.Append("--annotation", annotation)
    + }
    +
    + if b.Target != "" {
    + generator.Append("--target", b.Target)
    + }
    +
    + generator.Append(context)
    +
    + err = exec.Run(name, generator.Command(), rt.Timeout)
    + if err != nil {
    + return err
    + }
    +
    + // if we were give more than one tag run a tag test to do the rest
    + if len(b.Tags) > 1 {
    + t := Tag{
    + Image: b.Tags[0],
    + Targets: b.Tags[1:],
    + }
    +
    + return t.Execute(name, logger, stageEnv, rt)
    + }
    +
    + return nil
    +}
    +
    +func (b *Build) New() tasks.Task {
    + return &Build{}
    +}
    +
    +func (b *Build) Valid() error {
    + if b.Containerfile == "" {
    + return ErrNoContainerfile
    + }
    +
    + if len(b.Tags) == 0 {
    + return ErrNoTags
    + }
    +
    + if b.Context == "" {
    + b.Context = "."
    + }
    +
    + return nil
    +}
    +
    +func (b *Build) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/build_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,32 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +)
    +
    +func TestBuild(t *testing.T) {
    + b := &Build{
    + Containerfile: "Dockerfile",
    + Tags: []string{"foo:latest"},
    + }
    +
    + assert.NoError(t, b.Valid())
    +}
    +
    +func TestBuildContainerfileRequired(t *testing.T) {
    + b := &Build{
    + Tags: []string{"foo:latest"},
    + }
    +
    + assert.ErrorIs(t, b.Valid(), ErrNoContainerfile)
    +}
    +
    +func TestBuildTagsRequired(t *testing.T) {
    + b := &Build{
    + Containerfile: "Dockerfile",
    + }
    +
    + assert.ErrorIs(t, b.Valid(), ErrNoTags)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/errors.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,25 @@
    +package podman
    +
    +import (
    + "errors"
    +)
    +
    +var (
    + // common errors
    + ErrNoImage = errors.New("no image specified")
    +
    + // build errors
    + ErrNoContainerfile = errors.New("no container file specified")
    + ErrContextOutsideOfWorkspace = errors.New("context is outside of the workspace")
    +
    + // login/logout errors
    + ErrNoRegistry = errors.New("no registry specified")
    + ErrNoUsername = errors.New("no username provided")
    + ErrNoPassword = errors.New("no password provided")
    +
    + // pull/push errors
    + ErrNoTags = errors.New("no tags specified")
    +
    + // tag errors
    + ErrNoTargets = errors.New("no targets specified")
    +)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/login.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,60 @@
    +package podman
    +
    +import (
    + "strings"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +type Login struct {
    + Registry string `yaml:"registry"`
    + Username string `yaml:"username"`
    + Password string `yaml:"password"`
    +}
    +
    +func (l *Login) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + registry := env.Expand(l.Registry)
    + username := env.Expand(l.Username)
    + stdin := strings.NewReader(env.Expand(l.Password))
    +
    + generator := exec.NewGenerator(
    + "podman",
    + "login",
    + "--username", username,
    + "--password-stdin",
    + registry,
    + )
    +
    + return exec.RunWithStdin(name, generator.Command(), stdin, rt.Timeout)
    +}
    +
    +func (l *Login) New() tasks.Task {
    + return &Login{}
    +}
    +
    +func (l *Login) Valid() error {
    + if l.Registry == "" {
    + return ErrNoRegistry
    + }
    +
    + if l.Username == "" {
    + return ErrNoUsername
    + }
    +
    + if l.Password == "" {
    + return ErrNoPassword
    + }
    +
    + return nil
    +}
    +
    +func (l *Login) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/login_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,44 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +)
    +
    +func TestLogin(t *testing.T) {
    + l := &Login{
    + Registry: "docker.io",
    + Username: "username",
    + Password: "password",
    + }
    +
    + assert.NoError(t, l.Valid())
    +}
    +
    +func TestLoginRegistryRequired(t *testing.T) {
    + l := &Login{
    + Username: "username",
    + Password: "password",
    + }
    +
    + assert.ErrorIs(t, l.Valid(), ErrNoRegistry)
    +}
    +
    +func TestLoginUsernameRequired(t *testing.T) {
    + l := &Login{
    + Registry: "docker.io",
    + Password: "password",
    + }
    +
    + assert.ErrorIs(t, l.Valid(), ErrNoUsername)
    +}
    +
    +func TestLoginPasswordRequired(t *testing.T) {
    + l := &Login{
    + Registry: "docker.io",
    + Username: "username",
    + }
    +
    + assert.ErrorIs(t, l.Valid(), ErrNoPassword)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/logout.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,44 @@
    +package podman
    +
    +import (
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +type Logout struct {
    + Registry string `yaml:"registry"`
    +}
    +
    +func (l *Logout) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + registry := env.Expand(l.Registry)
    +
    + generator := exec.NewGenerator(
    + "podman",
    + "logout",
    + registry,
    + )
    +
    + return exec.Run(name, generator.Command(), rt.Timeout)
    +}
    +
    +func (l *Logout) New() tasks.Task {
    + return &Logout{}
    +}
    +
    +func (l *Logout) Valid() error {
    + if l.Registry == "" {
    + return ErrNoRegistry
    + }
    +
    + return nil
    +}
    +
    +func (l *Logout) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/logout_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,21 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +)
    +
    +func TestLogout(t *testing.T) {
    + l := &Logout{
    + Registry: "docker.io",
    + }
    +
    + assert.NoError(t, l.Valid())
    +}
    +
    +func TestLogoutRegistryRequired(t *testing.T) {
    + l := &Logout{}
    +
    + assert.ErrorIs(t, l.Valid(), ErrNoRegistry)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/pull.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,51 @@
    +package podman
    +
    +import (
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Pull struct {
    + Tags yaml.StringOrSlice `yaml:"tags"`
    +}
    +
    +func (p *Pull) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + tags := env.Expandv(p.Tags)
    + for _, tag := range tags {
    + generator := exec.NewGenerator(
    + "podman",
    + "pull",
    + tag,
    + )
    +
    + err := exec.Run(name, generator.Command(), rt.Timeout)
    + if err != nil {
    + return err
    + }
    + }
    +
    + return nil
    +}
    +
    +func (p *Pull) New() tasks.Task {
    + return &Pull{}
    +}
    +
    +func (p *Pull) Valid() error {
    + if len(p.Tags) == 0 {
    + return ErrNoTags
    + }
    +
    + return nil
    +}
    +
    +func (p *Pull) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/pull_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,31 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +func TestPullString(t *testing.T) {
    + p := &Pull{
    + Tags: yaml.StringOrSlice{"foo"},
    + }
    +
    + assert.NoError(t, p.Valid())
    +}
    +
    +func TestPullSlice(t *testing.T) {
    + p := &Pull{
    + Tags: []string{"foo", "bar"},
    + }
    +
    + assert.NoError(t, p.Valid())
    +}
    +
    +func TestPullTagsRequired(t *testing.T) {
    + p := &Push{}
    +
    + assert.ErrorIs(t, p.Valid(), ErrNoTags)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/push.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,51 @@
    +package podman
    +
    +import (
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Push struct {
    + Tags yaml.StringOrSlice `yaml:"tags"`
    +}
    +
    +func (p *Push) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + tags := env.Expandv(p.Tags)
    + for _, tag := range tags {
    + generator := exec.NewGenerator(
    + "podman",
    + "push",
    + tag,
    + )
    +
    + err := exec.Run(name, generator.Command(), rt.Timeout)
    + if err != nil {
    + return err
    + }
    + }
    +
    + return nil
    +}
    +
    +func (p *Push) New() tasks.Task {
    + return &Push{}
    +}
    +
    +func (p *Push) Valid() error {
    + if len(p.Tags) == 0 {
    + return ErrNoTags
    + }
    +
    + return nil
    +}
    +
    +func (p *Push) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/push_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,31 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +func TestPushString(t *testing.T) {
    + p := &Push{
    + Tags: yaml.StringOrSlice{"foo"},
    + }
    +
    + assert.NoError(t, p.Valid())
    +}
    +
    +func TestPushSlice(t *testing.T) {
    + p := &Push{
    + Tags: []string{"foo", "bar"},
    + }
    +
    + assert.NoError(t, p.Valid())
    +}
    +
    +func TestPushTagsRequired(t *testing.T) {
    + p := &Push{}
    +
    + assert.ErrorIs(t, p.Valid(), ErrNoTags)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/remove.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,59 @@
    +package podman
    +
    +import (
    + "fmt"
    + "strings"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Remove struct {
    + Tags yaml.StringOrSlice `yaml:"tags"`
    + Quiet bool `yaml:"quiet"`
    +}
    +
    +func (r *Remove) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + tags := env.Expandv(r.Tags)
    + for _, tag := range tags {
    + generator := exec.NewGenerator(
    + "podman",
    + "rmi",
    + tag,
    + )
    +
    + _, stderr, err := exec.RunOutput(name, generator.Command(), rt.Timeout)
    + if err != nil {
    + if r.Quiet && strings.Contains(stderr, "image not known") {
    + continue
    + }
    +
    + return fmt.Errorf("%s", strings.TrimSpace(stderr))
    + }
    + }
    +
    + return nil
    +}
    +
    +func (r *Remove) New() tasks.Task {
    + return &Remove{}
    +}
    +
    +func (r *Remove) Valid() error {
    + if len(r.Tags) == 0 {
    + return ErrNoTags
    + }
    +
    + return nil
    +}
    +
    +func (r *Remove) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/remove_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,21 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +)
    +
    +func TestRemove(t *testing.T) {
    + r := &Remove{
    + Tags: []string{"foo"},
    + }
    +
    + assert.NoError(t, r.Valid())
    +}
    +
    +func TestRemoveTagsRequired(t *testing.T) {
    + r := &Remove{}
    +
    + assert.ErrorIs(t, r.Valid(), ErrNoTags)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/run.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,187 @@
    +package podman
    +
    +import (
    + "os"
    + "path/filepath"
    + "strings"
    +
    + "github.com/kballard/go-shellquote"
    + "github.com/opencontainers/selinux/go-selinux"
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Run struct {
    + Annotations yaml.StringOrSlice `yaml:"annotations"`
    + Command string `yaml:"command"`
    + Entrypoint string `yaml:"entrypoint"`
    + Environment yaml.StringOrSlice `yaml:"environment"`
    + Hostname string `yaml:"hostname"`
    + Image string `yaml:"image"`
    + Script yaml.StringOrSlice `yaml:"script"`
    + Shell string `yaml:"shell"`
    + User string `yaml:"user"`
    + Workdir string `yaml:"workdir"`
    + Workspace string `yaml:"workspace"`
    +
    + commandv []string
    +}
    +
    +func (r *Run) buildScript(cfgPath string, env environment.Environment) (string, error) {
    + script := env.Expandv(r.Script)
    +
    + fp, err := os.CreateTemp(cfgPath, ".convey-script-")
    + if err != nil {
    + return "", err
    + }
    +
    + if _, err = fp.Write([]byte(strings.Join(script, "\n"))); err != nil {
    + return "", err
    + }
    +
    + if err := fp.Chmod(0700); err != nil {
    + return "", err
    + }
    +
    + if err := fp.Close(); err != nil {
    + return "", err
    + }
    +
    + return fp.Name(), nil
    +}
    +
    +func (r *Run) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + // Create a new environment based on the stage's environment. Then merge
    + // the task's environment overriding anything from the stage. Finally merge
    + // the runtime environment which holds the environment from the command line.
    + env := stageEnv.Copy().MergeSlice(r.Environment).Merge(rt.Environment)
    +
    + // Figure out where we're mounting the workspace. This should be done
    + // before workdir is expanded as work is typically set to CONVEY_WORKSPACE.
    + workspace := env.Expand(r.Workspace)
    + if workspace == "" {
    + workspace = "/workspace"
    + }
    +
    + hostWorkspace, err := rt.Workspace()
    + if err != nil {
    + return err
    + }
    +
    + env["CONVEY_WORKSPACE"] = workspace
    +
    + volume := hostWorkspace.Path() + ":" + workspace
    + if selinux.GetEnabled() {
    + // We use lower zed because it creates a shared selinux label that can
    + // be used by multiple containers and this task could be in a stage
    + // that is running tasks concurrently.
    + volume += ":z"
    + }
    +
    + generator := exec.NewGenerator(
    + "podman",
    + "run",
    + "--rm",
    + "--volume", volume,
    + )
    +
    + // if the user provided a script, generate the file and set all of our
    + // attributes appropriately.
    + if len(r.Script) > 0 {
    + // Overwrite any entry point that the user provided. Validate set
    + // /bin/sh if the user didn't provide one.
    + r.Entrypoint = r.Shell
    +
    + scriptFile, err := r.buildScript(rt.ConfigPath, env)
    + if err != nil {
    + os.Remove(scriptFile)
    + return err
    + }
    +
    + defer os.Remove(scriptFile)
    +
    + scriptFileBase := filepath.Base(scriptFile)
    +
    + r.commandv = []string{"/" + scriptFileBase}
    +
    + volume := scriptFile + ":/" + scriptFileBase
    + if selinux.GetEnabled() {
    + volume += ":z"
    + }
    +
    + generator.Append("--volume", volume)
    + }
    +
    + // Add any annotations that the user specified.
    + for _, annotation := range r.Annotations {
    + generator.Append("--annotation", annotation)
    + }
    +
    + // Add the entrypoint is one was specified.
    + if r.Entrypoint != "" {
    + generator.Append("--entrypoint", env.Expand(r.Entrypoint))
    + }
    +
    + // Now add all the given environment variables
    + for _, name := range env.All() {
    + generator.Append("--env", name)
    + }
    +
    + // Add the hostname if one was specified.
    + if r.Hostname != "" {
    + generator.Append("--hostname", env.Expand(r.Hostname))
    + }
    +
    + // Add the user if one was specified.
    + if r.User != "" {
    + generator.Append("--user", env.Expand(r.User))
    + }
    +
    + // Add the workdir if one was specified.
    + if r.Workdir != "" {
    + generator.Append("--workdir", env.Expand(r.Workdir))
    + }
    +
    + // Finally append the image name.
    + generator.Append(env.Expand(r.Image))
    +
    + if len(r.commandv) > 0 {
    + generator.Appendv(env.Expandv(r.commandv))
    + }
    +
    + return exec.Run(name, generator.Command(), rt.Timeout)
    +}
    +
    +func (r *Run) New() tasks.Task {
    + return &Run{}
    +}
    +
    +func (r *Run) Valid() error {
    + if r.Image == "" {
    + return ErrNoImage
    + }
    +
    + if r.Command != "" {
    + cmdv, err := shellquote.Split(r.Command)
    + if err != nil {
    + return err
    + }
    +
    + r.commandv = cmdv
    + }
    +
    + if r.Shell == "" {
    + r.Shell = "/bin/sh"
    + }
    +
    + return nil
    +}
    +
    +func (r *Run) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/tag.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,50 @@
    +package podman
    +
    +import (
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Tag struct {
    + Image string `yaml:"image"`
    + Targets yaml.StringOrSlice `yaml:"targets"`
    +}
    +
    +func (t *Tag) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + env := stageEnv.Copy().Merge(rt.Environment)
    +
    + generator := exec.NewGenerator(
    + "podman",
    + "tag",
    + t.Image,
    + )
    +
    + generator.Appendv(env.Expandv(t.Targets))
    +
    + return exec.Run(name, generator.Command(), rt.Timeout)
    +}
    +
    +func (t *Tag) New() tasks.Task {
    + return &Tag{}
    +}
    +
    +func (t *Tag) Valid() error {
    + if t.Image == "" {
    + return ErrNoImage
    + }
    +
    + if len(t.Targets) == 0 {
    + return ErrNoTargets
    + }
    +
    + return nil
    +}
    +
    +func (t *Tag) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/tag_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,41 @@
    +package podman
    +
    +import (
    + "testing"
    +
    + "github.com/stretchr/testify/assert"
    +)
    +
    +func TestTagSingleTarget(t *testing.T) {
    + tag := &Tag{
    + Image: "foo",
    + Targets: []string{"bar"},
    + }
    +
    + assert.NoError(t, tag.Valid())
    +}
    +
    +func TestTagMultipleTargets(t *testing.T) {
    + tag := &Tag{
    + Image: "foo",
    + Targets: []string{"bar", "baz"},
    + }
    +
    + assert.NoError(t, tag.Valid())
    +}
    +
    +func TestTagImageRequired(t *testing.T) {
    + tag := &Tag{
    + Targets: []string{"bar"},
    + }
    +
    + assert.ErrorIs(t, tag.Valid(), ErrNoImage)
    +}
    +
    +func TestTagTargetRequired(t *testing.T) {
    + tag := &Tag{
    + Image: "foo",
    + }
    +
    + assert.ErrorIs(t, tag.Valid(), ErrNoTargets)
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/podman/tasks.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,18 @@
    +package podman
    +
    +import (
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +var (
    + Tasks = map[string]tasks.Task{
    + "podman/build": &Build{},
    + "podman/login": &Login{},
    + "podman/logout": &Logout{},
    + "podman/pull": &Pull{},
    + "podman/push": &Push{},
    + "podman/remove": &Remove{},
    + "podman/run": &Run{},
    + "podman/tag": &Tag{},
    + }
    +)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/runner/cmd.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,124 @@
    +package runner
    +
    +import (
    + "context"
    + "fmt"
    + "os"
    + "os/signal"
    + "path/filepath"
    + "syscall"
    + "time"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/config"
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/globals"
    + "keep.imfreedom.org/grim/convey/logging"
    + "keep.imfreedom.org/grim/convey/plans"
    + "keep.imfreedom.org/grim/convey/runtime"
    +)
    +
    +type RunnerCmd struct {
    + ConfigFile string `kong:"flag,help='The config file to load',placeholder='FILE',short='f',default='convey.yml'"`
    + ForceSequential bool `kong:"flag,help='Force concurrent stages to be ran sequentially',short='S',default='false'"`
    + KeepWorkspace bool `kong:"flag,help='Keep the workspace directory after running',default='false'"`
    + Timeout time.Duration `kong:"flag,help='The maximum amount of time a plan can run. 0 to disable. Units must be specified.',placeholder='DURATION',default='15m'"`
    + Environment []string `kong:"flag,name='env',help='Set an environment variable',short='e',placeholder='ENV'"`
    +
    + Plans []string `kong:"arg,help='The names of the plans to run',default='default'"`
    +}
    +
    +func (c *RunnerCmd) runPlan(name string, cfg *config.Config, plan plans.Plan) error {
    + configPath, _ := filepath.Split(c.ConfigFile)
    + if configPath == "" {
    + cwd, err := os.Getwd()
    + if err != nil {
    + return err
    + }
    +
    + configPath = cwd
    + }
    +
    + env := environment.New(c.Environment...)
    + rt := runtime.New(env, configPath, c.ForceSequential, c.KeepWorkspace, c.Timeout)
    + defer rt.Shutdown()
    +
    + configEnv := environment.New(cfg.Environment...)
    +
    + return plan.Execute(name, cfg.Tasks, configEnv, rt)
    +}
    +
    +func (c *RunnerCmd) runPlans(ctx context.Context, cfg *config.Config) error {
    + errChan := make(chan error, 1)
    +
    + cancelled := false
    +
    + go func() {
    + for _, name := range c.Plans {
    + plan := cfg.Plans[name]
    +
    + if err := c.runPlan(name, cfg, plan); err != nil {
    + errChan <- err
    + return
    + }
    +
    + if cancelled {
    + break
    + }
    + }
    + errChan <- nil
    + }()
    +
    + for {
    + select {
    + case err := <-errChan:
    + return err
    + case <-ctx.Done():
    + cancelled = true
    + }
    + }
    +}
    +
    +func (c *RunnerCmd) Run(g *globals.Globals) error {
    + logging.Setup(g.Color, g.Verbose)
    +
    + cfg, err := config.LoadFile(c.ConfigFile)
    + if err != nil {
    + return err
    + }
    +
    + err = cfg.Valid()
    + if err != nil {
    + return fmt.Errorf("config is invalid: %s", err)
    + }
    +
    + err = cfg.HasPlans(c.Plans)
    + if err != nil {
    + return err
    + }
    +
    + signalChan := make(chan os.Signal, 1)
    + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
    +
    + errChan := make(chan error, 1)
    +
    + ctx, cancel := context.WithCancel(context.Background())
    +
    + go func(ctx context.Context, cfg *config.Config) {
    + errChan <- c.runPlans(ctx, cfg)
    + }(ctx, cfg)
    +
    + for {
    + select {
    + case err := <-errChan:
    + return err
    + case s := <-signalChan:
    + log.Infof("caught signal %s, exiting...", s)
    + cancel()
    + break
    + }
    + }
    +
    + return nil
    +}
    --- a/runners/convey.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,56 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// Convey is the normal Convey Runner.
    -type Convey struct{}
    -
    -// Run runs the Convey Runner.
    -func (c *Convey) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - logger := logging.NewAdapter("convey-runner")
    - /* now look for the plan */
    - for _, planName := range plans {
    - if plan, found := cfg.Plans[planName]; found {
    - err := plan.Valid()
    - if err != nil {
    - logger.Errorf("%s", err)
    -
    - return 1
    - }
    -
    - err = plan.Execute(planName, cfg.Tasks, environment.New(cfg.Environment...), rt)
    - if err != nil {
    - logger.Errorf("%s", err)
    -
    - return 1
    - }
    - } else {
    - logger.Fatalf("plan %s not found", planName)
    -
    - return 1
    - }
    - }
    -
    - return 0
    -}
    --- a/runners/graphviz.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,235 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "bytes"
    - "fmt"
    - "regexp"
    - "text/template"
    -
    - "keep.imfreedom.org/grim/convey/color"
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -var graphvizTemplate = `
    -digraph {
    - graph[rankdir="LR"]
    -
    - node[style="filled,rounded" shape="rect"]
    -
    - # add the meta plans
    - node[shape="rect" fillcolor="plum"]
    -{{range .MetaPlans}} {{.Normalized}}_start[label="{{.Name}} start"]
    - {{.Normalized}}_finish[label="{{.Name}} finish"]
    -{{end}}
    -
    - # add a start and finish node for each plan
    - node[shape="component" fillcolor="palegreen"]
    -{{range .Plans}} {{.Normalized}}_start[label="{{.Name}} start"]
    - {{.Normalized}}_finish[label="{{.Name}} finish"]
    -{{end}}
    -
    - # add the tasks
    - node[style="filled,rounded" shape="rect" fillcolor="powderblue"]
    -{{range .Tasks}} {{.Normalized}}[label="{{.Name}}"]
    -{{end}}
    -
    -{{range .Plans}}
    -
    - # add the stages for {{.Name}}
    - edge[color="{{.Color}}"]{{$planNormalized := .Normalized }}
    - {{- range .Stages}}
    - {{$planNormalized}}_{{.Normalized}} [label="{{.Name}}" shape="octagon" fillcolor="{{if .Enabled}}lightpink1{{else}}gray{{end}}"{{if eq ".Run" "always"}} style="bold,filled,rounded"{{end}}]{{end}}
    -
    - # {{.Name}} happy path
    - {{.Normalized}}_start{{range .Stages}} -> {{$planNormalized}}_{{.Normalized}}{{if .Concurrent}} -> { {{range .Tasks}} {{.}} {{end}} }{{else}}{{range .Tasks}} -> {{.}}{{end}}{{end}}{{end}} -> {{.Normalized}}_finish
    - # {{.Name}} unhappy path
    - {{ range .Stages}}{{$stage := .}}{{range .FallThroughs}}{{$planNormalized}}_{{$stage.Normalized}} -> {{$planNormalized}}_{{.Normalized}} [style="dotted"]
    - {{end}}{{end -}}
    -{{- end}}
    -
    -{{range .MetaPlans}}
    - # metaplan {{.Name}}
    - edge[color="{{.Color}}" style="dashed,bold"]
    - {{.Normalized}}_start -> {{range .Plans}}{{.Normalized}}_start
    - {{.Normalized}}_finish -> {{end}} {{.Normalized}}_finish
    -{{end}}
    -}
    -`
    -
    -var normalizeRegex *regexp.Regexp
    -
    -type (
    - // Graphviz is a Runner that will create a graphviz output for the config
    - // that has been loaded.
    - Graphviz struct{}
    -
    - graphvizTask struct {
    - Normalized string
    - Name string
    - }
    -
    - graphvizStage struct {
    - Normalized string
    - Name string
    - Enabled bool
    - Concurrent bool
    - Run string
    - Tasks []string
    - FallThroughs []graphvizStage
    - }
    -
    - graphvizPlan struct {
    - Normalized string
    - Name string
    - Color string
    - Stages []graphvizStage
    - }
    -
    - graphvizMetaPlan struct {
    - Normalized string
    - Name string
    - Color string
    - Plans []graphvizPlan
    - }
    -)
    -
    -func normalize(prefix, str string) string {
    - if normalizeRegex == nil {
    - normalizeRegex = regexp.MustCompile("[^a-zA-Z0-9_]")
    - }
    -
    - return prefix + "_" + normalizeRegex.ReplaceAllString(str, "_")
    -}
    -
    -func (g *Graphviz) getTasks(cfg *config.Config) []graphvizTask {
    - tasks := []graphvizTask{}
    -
    - for name := range cfg.Tasks {
    - task := graphvizTask{
    - Normalized: normalize("task", name),
    - Name: name,
    - }
    - tasks = append(tasks, task)
    - }
    -
    - return tasks
    -}
    -
    -func (g *Graphviz) getMetaPlans(cfg *config.Config) []graphvizMetaPlan {
    - metaPlans := []graphvizMetaPlan{}
    -
    - for name, cMetaPlan := range cfg.MetaPlans {
    - metaPlan := graphvizMetaPlan{
    - Normalized: normalize("metaplan", name),
    - Name: name,
    - Color: color.X11(name),
    - }
    -
    - for _, name := range cMetaPlan.Plans {
    - if plan, found := cfg.Plans[name]; found {
    - metaPlan.Plans = append(metaPlan.Plans, g.plan(name, plan))
    - }
    - }
    -
    - metaPlans = append(metaPlans, metaPlan)
    - }
    -
    - return metaPlans
    -}
    -
    -func (g Graphviz) plan(name string, plan plans.Plan) graphvizPlan {
    - gvizPlan := graphvizPlan{
    - Normalized: normalize("plan", name),
    - Name: name,
    - Color: color.X11(name),
    - Stages: []graphvizStage{},
    - }
    -
    - for idx, stage := range plan.Stages {
    - gvizStage := graphvizStage{
    - Normalized: normalize("stage", stage.Name),
    - Name: stage.Name,
    - Enabled: stage.Enabled,
    - Concurrent: stage.Concurrent,
    - Run: stage.Run,
    - Tasks: []string{},
    - }
    -
    - for _, task := range stage.Tasks {
    - gvizStage.Tasks = append(gvizStage.Tasks, normalize("task", task))
    - }
    -
    - gvizPlan.Stages = append(gvizPlan.Stages, gvizStage)
    -
    - // if this always runs, if it's not the first add it to the previous stages' fallthroughs
    - if stage.Run == "always" && idx > 0 {
    - for i := 0; i < idx; i++ {
    - gvizPlan.Stages[i].FallThroughs = append(gvizPlan.Stages[idx-1].FallThroughs, gvizStage)
    - }
    - }
    - }
    -
    - return gvizPlan
    -}
    -
    -func (g *Graphviz) getPlans(cfg *config.Config) []graphvizPlan {
    - plans := []graphvizPlan{}
    -
    - for name, plan := range cfg.Plans {
    - plans = append(plans, g.plan(name, plan))
    - }
    -
    - return plans
    -}
    -
    -// Run runs the graphviz runner.
    -func (g *Graphviz) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - logger := logging.NewAdapter("graphviz")
    -
    - params := map[string]interface{}{
    - "Tasks": g.getTasks(cfg),
    - "Plans": g.getPlans(cfg),
    - "MetaPlans": g.getMetaPlans(cfg),
    - }
    -
    - // Load the template
    - tmpl, err := template.New("graphviz").Parse(graphvizTemplate)
    - if err != nil {
    - logger.Fatalf("error: %s", err)
    -
    - return 1
    - }
    -
    - // Run the template
    - output := new(bytes.Buffer)
    - err = tmpl.Execute(output, params)
    - if err != nil {
    - logger.Fatalf("error: %s", err)
    -
    - return 1
    - }
    -
    - // Output the template
    - fmt.Print(output)
    -
    - return 0
    -}
    --- a/runners/listenvironment.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,53 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    - "os"
    - "sort"
    - "strings"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListEnvironment is a runner that will output the environment.
    -type ListEnvironment struct{}
    -
    -// Run runs the ListEnvironment runner.
    -func (l *ListEnvironment) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - fmt.Printf("Builtins:\n")
    - stateEnv := rt.Environment.Items()
    - sort.Strings(stateEnv)
    - for _, key := range stateEnv {
    - fmt.Printf(" %s=%s\n", key, os.Getenv(key))
    - }
    -
    - fmt.Printf("Config:\n")
    - cfgEnv := cfg.Environment
    - sort.Strings(cfgEnv)
    - for _, env := range cfgEnv {
    - if strings.Contains(env, "=") {
    - fmt.Printf(" %s\n", env)
    - } else {
    - fmt.Printf(" %s=%s\n", env, os.Getenv(env))
    - }
    - }
    -
    - return 0
    -}
    --- a/runners/listmetaplans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,36 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListMetaPlans is a Runner that outputs the metaplans for the loaded config.
    -type ListMetaPlans struct{}
    -
    -// Run runs the ListMetaPlans runner.
    -func (l *ListMetaPlans) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - for metaPlanName, _ := range cfg.MetaPlans {
    - fmt.Printf("%s\n", metaPlanName)
    - }
    -
    - return 0
    -}
    --- a/runners/listplans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,36 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListPlans is a Runner that outputs the plans in the loaded config.
    -type ListPlans struct{}
    -
    -// Run runs the ListPlans Runner.
    -func (l *ListPlans) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - for planName, _ := range cfg.Plans {
    - fmt.Printf("%s\n", planName)
    - }
    -
    - return 0
    -}
    --- a/runners/listtasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,43 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    - "sort"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListTasks is a Runner that will output the tasks defined in a config.
    -type ListTasks struct{}
    -
    -// Run runs the ListTasks runner.
    -func (l *ListTasks) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - tasks := []string{}
    - for taskName := range config.TasksMap {
    - tasks = append(tasks, taskName)
    - }
    -
    - sort.Strings(tasks)
    - for _, taskName := range tasks {
    - fmt.Printf(" %s\n", taskName)
    - }
    -
    - return 0
    -}
    --- a/runners/runners.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,28 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package runners contains all of the runners for convey.
    -package runners
    -
    -import (
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// Runner is an interface for defining something that convey can run.
    -type Runner interface {
    - Run(cfg *config.Config, plan []string, env []string, rt *runtime.Runtime) int
    -}
    --- a/runners/showconfig.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,34 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "github.com/davecgh/go-spew/spew"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ShowConfig is a Runner that will dump the config to stdout.
    -type ShowConfig struct{}
    -
    -// Run runs the ShowConfig runner.
    -func (sc *ShowConfig) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - spew.Dump(cfg)
    -
    - return 0
    -}
    --- a/runtime/cleanup.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,91 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runtime
    -
    -import (
    - "os"
    - "os/signal"
    - "sync"
    - "syscall"
    -
    - "github.com/emirpasic/gods/stacks/arraystack"
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/logging"
    -)
    -
    -// cleanupList is a simple structure for keeping track of functions to call
    -// on cleanup.
    -type cleanupList struct {
    - mutex sync.Mutex
    - functions *arraystack.Stack
    -}
    -
    -// newCleanupList create and initializes a cleanupList
    -func newCleanupList() *cleanupList {
    - return &cleanupList{
    - functions: arraystack.New(),
    - }
    -}
    -
    -// Add adds a function to the cleanup list.
    -func (cl *cleanupList) Add(fn func(l *log.Entry)) {
    - cl.mutex.Lock()
    - defer cl.mutex.Unlock()
    -
    - cl.functions.Push(fn)
    -}
    -
    -// run will run all of the cleanup functions in the list
    -func (cl *cleanupList) run() {
    - cl.mutex.Lock()
    - defer cl.mutex.Unlock()
    -
    - logger := logging.NewAdapter("cleanup")
    -
    - it := cl.functions.Iterator()
    - for it.Next() {
    - fn := it.Value().(func(*log.Entry))
    -
    - fn(logger)
    - }
    -}
    -
    -// cleanupOnSignal will call State.Destroy on SIGINT or SIGTERM
    -func (rt *Runtime) cleanupOnSignal() {
    - // create a channel to receive the signal
    - ch := make(chan os.Signal, 1)
    -
    - go func() {
    - // block until we get a signal, we don't care what it is so we just
    - // throw it away
    - <-ch
    -
    - // call the destroy method
    - rt.Shutdown()
    -
    - // exit with an exit code of 1
    - os.Exit(1)
    - }()
    -
    - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
    -}
    -
    -// Cleanup registers a function to be called when the state is being torn down.
    -func (rt *Runtime) Cleanup(fn func(l *log.Entry)) {
    - rt.cleanupList.Add(fn)
    -}
    --- a/runtime/containers.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,88 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runtime
    -
    -import (
    - "sync"
    -)
    -
    -type Containers struct {
    - running map[string]struct{}
    - detached map[string]struct{}
    - mutex sync.RWMutex
    -}
    -
    -func NewContainers() *Containers {
    - return &Containers{
    - running: map[string]struct{}{},
    - detached: map[string]struct{}{},
    - }
    -}
    -
    -// MarkRunning will add the given container name into the list
    -// of containers currently running. This falls through directly to
    -// the root state so that states wrapping the global one do not have
    -// to sync additional running container names.
    -func (c *Containers) MarkRunning(name string) {
    - c.mutex.Lock()
    - defer c.mutex.Unlock()
    - c.running[name] = struct{}{}
    -}
    -
    -// UnmarkRunning removes a container from the list of running containers.
    -func (c *Containers) UnmarkRunning(name string) {
    - c.mutex.Lock()
    - defer c.mutex.Unlock()
    - delete(c.running, name)
    -}
    -
    -// GetRunning returns a list of all running containers.
    -func (c *Containers) GetRunning() []string {
    - c.mutex.RLock()
    - defer c.mutex.RUnlock()
    -
    - names := []string{}
    - for key := range c.running {
    - names = append(names, key)
    - }
    -
    - return names
    -}
    -
    -// MarkDetached will add the given container name into the list
    -// of containers running in detached mode which must be shut down
    -// at the end of the plan. This falls through directly to the root
    -// state so that states wrapping the global one do not have to sync
    -// additional detached container names.
    -func (c *Containers) MarkDetached(name string) {
    - c.mutex.Lock()
    - defer c.mutex.Unlock()
    - c.detached[name] = struct{}{}
    -}
    -
    -// GetDetached returns a list of all detached containers.
    -func (c *Containers) GetDetached() []string {
    - c.mutex.RLock()
    - defer c.mutex.RUnlock()
    -
    - names := []string{}
    - for key := range c.detached {
    - names = append(names, key)
    - }
    -
    - return names
    -}
    --- a/runtime/containers_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,53 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runtime
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestContainersRunning(t *testing.T) {
    - containers := NewContainers()
    - assert.Empty(t, containers.GetRunning())
    -
    - containers.MarkRunning("foo")
    - assert.Len(t, containers.GetRunning(), 1)
    - assert.ElementsMatch(t, containers.GetRunning(), []string{"foo"})
    -
    - containers.MarkRunning("bar")
    - assert.Len(t, containers.GetRunning(), 2)
    - assert.ElementsMatch(t, containers.GetRunning(), []string{"foo", "bar"})
    -
    - containers.UnmarkRunning("bar")
    - assert.Len(t, containers.GetRunning(), 1)
    - assert.ElementsMatch(t, containers.GetRunning(), []string{"foo"})
    -}
    -
    -func TestContainersDetached(t *testing.T) {
    - containers := NewContainers()
    - assert.Empty(t, containers.GetDetached())
    -
    - containers.MarkDetached("foo")
    - assert.Len(t, containers.GetDetached(), 1)
    - assert.ElementsMatch(t, containers.GetDetached(), []string{"foo"})
    -
    - containers.MarkDetached("bar")
    - assert.Len(t, containers.GetDetached(), 2)
    - assert.ElementsMatch(t, containers.GetDetached(), []string{"foo", "bar"})
    -}
    --- a/runtime/runtime.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/runtime/runtime.go Mon Dec 20 12:33:09 2021 -0600
    @@ -17,41 +17,75 @@
    package runtime
    import (
    + "io/ioutil"
    + "time"
    +
    log "github.com/sirupsen/logrus"
    "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/state"
    + "keep.imfreedom.org/grim/convey/fs"
    )
    type Runtime struct {
    - State *state.State
    - cleanupList *cleanupList
    - Environment *environment.Environment
    - Containers *Containers
    -}
    + Environment environment.Environment
    -func New(st *state.State) *Runtime {
    - return NewWithEnvironment(st, environment.New())
    + ConfigPath string
    + ForceSequential bool
    + KeepWorkspace bool
    + Timeout time.Duration
    +
    + workspace *fs.Directory
    }
    -func NewWithEnvironment(st *state.State, env *environment.Environment) *Runtime {
    +func New(env environment.Environment, configPath string, forceSequential bool, keepWorkspace bool, timeout time.Duration) *Runtime {
    rt := &Runtime{
    - State: st,
    - cleanupList: newCleanupList(),
    - Environment: env,
    - Containers: NewContainers(),
    + Environment: env,
    + ConfigPath: configPath,
    + ForceSequential: forceSequential,
    + KeepWorkspace: keepWorkspace,
    + Timeout: timeout,
    }
    - rt.cleanupList.Add(func(*log.Entry) {
    - st.Destroy()
    - })
    -
    - rt.cleanupOnSignal()
    + rt.Environment.LoadDefaults(configPath)
    return rt
    }
    +func (rt *Runtime) Workspace() (*fs.Directory, error) {
    + if rt.workspace == nil {
    + path, err := ioutil.TempDir(rt.ConfigPath, ".convey-")
    + if err != nil {
    + return nil, err
    + }
    +
    + dir, err := fs.NewDirectory(path)
    + if err != nil {
    + return nil, err
    + }
    +
    + rt.workspace = dir
    +
    + log.Debugf("created workspace %q", path)
    + }
    +
    + return rt.workspace, nil
    +}
    +
    func (rt *Runtime) Shutdown() {
    - // run the registered cleanup functions
    - rt.cleanupList.run()
    + if rt.workspace != nil {
    + if !rt.KeepWorkspace {
    + err := rt.workspace.Destroy()
    + if err != nil {
    + log.Errorf(
    + "failed to remove workspace %q: %s",
    + rt.workspace.Path(),
    + err,
    + )
    + } else {
    + log.Debugf("removed workspace %q", rt.workspace.Path())
    + }
    + } else {
    + log.Infof("keeping workspace %q as requested", rt.workspace.Path())
    + }
    + }
    }
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/script/README.md Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,53 @@
    +# Script
    +
    +Script is a set of tasks for running scripts on the host. These can be
    +dangerous and should be used sparingly.
    +
    +----
    +
    +## script/shell
    +
    +Available on UNIX like platforms including BSD, macOS, and Linux, but will
    +raise an error if attempted to be used on Windows.
    +
    +Either a filename or commands must be specified, but specifying both is an
    +error as Convey will not know which one you want to prioritize. When commands
    +are specified, a temporary file will be created containing the given commands.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ----------- | -------- | ------- | ----------- |
    +| commands | | | A list of commands to run as the script. |
    +| environment | | | A list of environment variables to set. This should be specified in a `NAME` or `NAME=VALUE` format. |
    +| filename | | | The name of a script file to run. The file must exist in the directory containing the current convey configuration file or a subdirectory. |
    +| shell | | | The shell to use. If not specified the users default shell will be used. |
    +
    +### Example
    +
    +Run an existing script
    +
    + run-script:
    + type: script/shell
    + filename: script.sh
    +
    +Run an ahhoc script with a single command. The commands parameter will work
    +with a single string or a list of strings
    +
    + uptime:
    + type: script/shell
    + commands: uptime
    +
    + uname:
    + type: script/shell
    + commands:
    + - uname -a
    +
    +Run an adhoc script in Z Shell
    +
    + zshell:
    + type: script/shell
    + shell: /bin/zsh
    + commands:
    + - uname
    + - uptime
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/script/file.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,37 @@
    +package script
    +
    +import (
    + "os"
    + "runtime"
    + "strings"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    +)
    +
    +func sliceToFile(commands []string, path string, env environment.Environment) (string, error) {
    + script := env.Expandv(commands)
    +
    + fp, err := os.CreateTemp(path, ".convey-script-")
    + if err != nil {
    + return "", err
    + }
    +
    + lineEnding := "\n"
    + if runtime.GOOS == "windows" {
    + lineEnding = "\r\n"
    + }
    +
    + if _, err = fp.Write([]byte(strings.Join(script, lineEnding))); err != nil {
    + return "", err
    + }
    +
    + if err := fp.Chmod(0700); err != nil {
    + return "", err
    + }
    +
    + if err := fp.Close(); err != nil {
    + return "", err
    + }
    +
    + return fp.Name(), nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/script/interpreter.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,81 @@
    +package script
    +
    +import (
    + "fmt"
    + "os"
    + "os/exec"
    + "os/user"
    + "runtime"
    + "strings"
    +)
    +
    +func windowsInterpreter() (string, error) {
    + intrepreter := os.Getenv("COMSPEC")
    + if intrepreter == "" {
    + intrepreter = "cmd.exe"
    + }
    +
    + return intrepreter, nil
    +}
    +
    +func darwinInterpreter() (string, error) {
    + user, err := user.Current()
    + if err != nil {
    + return "", err
    + }
    +
    + path := "Local/Default/User/" + user.Username
    + args := []string{"localhost", "-read", path, "UserShell"}
    + buffer, err := exec.Command("dscl", args...).Output()
    + if err != nil {
    + return "", err
    + }
    +
    + output := strings.TrimSpace(string(buffer))
    + parts := strings.Split(output, " ")
    +
    + if len(parts) != 2 {
    + return "", fmt.Errorf("unexpected output from dscl: %q", output)
    + }
    +
    + return strings.TrimSpace(parts[1]), nil
    +}
    +
    +func posixInterpreter() (string, error) {
    + user, err := user.Current()
    + if err != nil {
    + return "", err
    + }
    +
    + buffer, err := exec.Command("getent", "passwd", user.Uid).Output()
    + if err != nil {
    + return "", err
    + }
    +
    + output := strings.TrimSpace(string(buffer))
    + fields := strings.Split(output, ":")
    + if len(fields) < 6 {
    + return "", fmt.Errorf("unexpected output from getent: %q", output)
    + }
    +
    + intrepreter := fields[6]
    + if intrepreter == "" {
    + return "", fmt.Errorf("no shell found for user %q", user.Uid)
    + }
    +
    + return intrepreter, nil
    +}
    +
    +func findInterpreter(given string) (string, error) {
    + if given != "" {
    + return given, nil
    + }
    +
    + if runtime.GOOS == "windows" {
    + return windowsInterpreter()
    + } else if runtime.GOOS == "darwin" {
    + return darwinInterpreter()
    + } else {
    + return posixInterpreter()
    + }
    +}
    --- a/script/script.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,107 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package script
    -
    -import (
    - "strings"
    -
    - "github.com/kballard/go-shellquote"
    -
    - "keep.imfreedom.org/grim/convey/aws"
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ParseFunc defines a function that can parse a shell command into a convey
    -// task.
    -type ParseFunc func(argv []string) (tasks.Task, error)
    -
    -var (
    - commands = map[string]ParseFunc{
    - "docker": docker.ParseCommand,
    - "aws": aws.ParseCommand,
    - }
    -)
    -
    -// Parse will process a slice of commands and turn it into the proper tasks for
    -// convey to run.
    -func Parse(image, shell string, script []string) ([]tasks.Task, error) {
    - newTasks := []tasks.Task{}
    - currentScript := []string{}
    -
    - for _, cmd := range script {
    - // trim whitespace from the command
    - clean := strings.TrimSpace(cmd)
    -
    - // handle weirdo commands that are entirely wrapped in $()
    - if strings.HasPrefix(clean, "$(") && strings.HasSuffix(clean, ")") {
    - clean = strings.TrimPrefix(clean, "$(")
    - clean = strings.TrimSuffix(clean, ")")
    - }
    -
    - // handle weirdo commands that are entirely wrapped in `'s
    - if strings.HasPrefix(clean, "`") && strings.HasSuffix(clean, "`") {
    - clean = strings.TrimPrefix(clean, "`")
    - clean = strings.TrimSuffix(clean, "`")
    - }
    -
    - argv, err := shellquote.Split(clean)
    - if err != nil {
    - return nil, err
    - }
    -
    - if len(argv) == 0 {
    - continue
    - }
    -
    - currentCmd := strings.TrimSpace(argv[0])
    - if parser, ok := commands[currentCmd]; ok {
    - if len(currentScript) > 0 {
    - newTasks = append(newTasks, &docker.Run{
    - Shell: shell,
    - Script: currentScript,
    - Image: image,
    - WorkDir: "/workspace",
    - })
    -
    - // reset our script
    - currentScript = []string{}
    - }
    -
    - task, err := parser(argv)
    - if err != nil {
    - return nil, err
    - }
    -
    - newTasks = append(newTasks, task)
    - } else {
    - currentScript = append(currentScript, cmd)
    - }
    - }
    -
    - // if we have any unfinished script commands create a task for them
    - if len(currentScript) > 0 {
    - newTasks = append(newTasks, &docker.Run{
    - Shell: shell,
    - Script: currentScript,
    - Image: image,
    - WorkDir: "/workspace",
    - })
    - }
    -
    - return newTasks, nil
    -}
    --- a/script/script_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,142 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package script
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "keep.imfreedom.org/grim/convey/aws"
    - "keep.imfreedom.org/grim/convey/docker"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestEmpty(t *testing.T) {
    - parsedTasks, err := Parse("", "", []string{})
    -
    - assert.Nil(t, err)
    - assert.Empty(t, parsedTasks)
    -}
    -
    -func TestSimple(t *testing.T) {
    - commands := []string{
    - "#!/bin/sh",
    - "false",
    - }
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 1)
    -
    - assert.IsType(t, parsedTasks[0], &docker.Run{})
    -}
    -
    -func TestSubShellUnknown(t *testing.T) {
    - commands := []string{"$(true)"}
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 1)
    -
    - assert.IsType(t, parsedTasks[0], &docker.Run{})
    -
    - run := parsedTasks[0].(*docker.Run)
    - assert.Equal(t, run.Script, cYaml.StringOrSlice([]string{"$(true)"}))
    -}
    -
    -func TestBackTicksUnknown(t *testing.T) {
    - commands := []string{"`true`"}
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 1)
    -
    - assert.IsType(t, parsedTasks[0], &docker.Run{})
    -
    - run := parsedTasks[0].(*docker.Run)
    - assert.Equal(t, run.Script, cYaml.StringOrSlice([]string{"`true`"}))
    -}
    -
    -func TestDockerThenScript(t *testing.T) {
    - commands := []string{
    - "docker push foo",
    - "pwd",
    - }
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 2)
    -
    - assert.IsType(t, parsedTasks[0], &docker.Push{})
    - assert.IsType(t, parsedTasks[1], &docker.Run{})
    -}
    -
    -func TestScriptThenDocker(t *testing.T) {
    - commands := []string{
    - "pwd",
    - "docker push foo",
    - }
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 2)
    -
    - assert.IsType(t, parsedTasks[0], &docker.Run{})
    - assert.IsType(t, parsedTasks[1], &docker.Push{})
    -}
    -
    -func TestMixed(t *testing.T) {
    - commands := []string{
    - "pwd",
    - "docker push foo",
    - "pwd",
    - }
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 3)
    -
    - assert.IsType(t, parsedTasks[0], &docker.Run{})
    - assert.IsType(t, parsedTasks[1], &docker.Push{})
    - assert.IsType(t, parsedTasks[2], &docker.Run{})
    -}
    -
    -func TestAWSECRGetLogin(t *testing.T) {
    - commands := []string{
    - "aws ecr get-login",
    - "aws ecr get-login --region us-west-2",
    - "$(aws ecr get-login)",
    - "`aws ecr get-login`",
    - }
    -
    - parsedTasks, err := Parse("", "", commands)
    -
    - assert.Nil(t, err)
    - assert.Len(t, parsedTasks, 4)
    -
    - assert.IsType(t, parsedTasks[0], &aws.ECRGetLogin{})
    - assert.IsType(t, parsedTasks[1], &aws.ECRGetLogin{})
    - assert.IsType(t, parsedTasks[2], &aws.ECRGetLogin{})
    - assert.IsType(t, parsedTasks[3], &aws.ECRGetLogin{})
    -}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/script/shell.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,84 @@
    +package script
    +
    +import (
    + "fmt"
    + "os"
    + "path/filepath"
    + gort "runtime"
    + "strings"
    +
    + log "github.com/sirupsen/logrus"
    +
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/exec"
    + "keep.imfreedom.org/grim/convey/runtime"
    + "keep.imfreedom.org/grim/convey/tasks"
    + "keep.imfreedom.org/grim/convey/yaml"
    +)
    +
    +type Shell struct {
    + Commands yaml.StringOrSlice `yaml:"commands"`
    + Environment yaml.StringOrSlice `yaml:"environment"`
    + Filename string `yaml:"filename"`
    + Shell string `yaml:"shell"`
    +}
    +
    +func (s *Shell) Execute(name string, logger *log.Entry, stageEnv environment.Environment, rt *runtime.Runtime) error {
    + // Create a new environment based on the stage's environment. Then merge
    + // the task's environment overriding anything from the stage. Finally merge
    + // the runtime environment which holds the environment from the command line.
    + env := stageEnv.Copy().MergeSlice(s.Environment).Merge(rt.Environment)
    +
    + filename := filepath.Join(rt.ConfigPath, s.Filename)
    + if !strings.HasPrefix(filename, rt.ConfigPath) {
    + return fmt.Errorf("script is outside of the configuration files directory")
    + }
    + if filename == "" {
    + file, err := sliceToFile(s.Commands, rt.ConfigPath, env)
    + if err != nil {
    + return err
    + }
    +
    + filename = file
    + defer os.Remove(filename)
    + }
    +
    + shell, err := findInterpreter(s.Shell)
    + if err != nil {
    + return err
    + }
    +
    + if shell == "" {
    + return fmt.Errorf("failed to determine which shell to use")
    + }
    +
    + return exec.Run(name, []string{shell, filename}, rt.Timeout)
    +}
    +
    +func (s *Shell) New() tasks.Task {
    + return &Shell{}
    +}
    +
    +func (s *Shell) Valid() error {
    + if gort.GOOS == "windows" {
    + return fmt.Errorf("this task is not supported on windows")
    + }
    +
    + if s.Shell == "" {
    + s.Shell = "/bin/sh"
    + }
    +
    + if len(s.Commands) == 0 && s.Filename == "" {
    + return fmt.Errorf("a filename or at least one command must be specified")
    + }
    +
    + if len(s.Commands) > 0 && s.Filename != "" {
    + return fmt.Errorf("only one of filename and commands may be specified")
    + }
    +
    + return nil
    +}
    +
    +func (s *Shell) Deprecated() error {
    + return nil
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/script/tasks.go Mon Dec 20 12:33:09 2021 -0600
    @@ -0,0 +1,11 @@
    +package script
    +
    +import (
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +var (
    + Tasks = map[string]tasks.Task{
    + "script/shell": &Shell{},
    + }
    +)
    --- a/ssh/agent.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,105 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package ssh
    -
    -import (
    - "fmt"
    - "io"
    - "net"
    - "os"
    - "strings"
    -
    - "golang.org/x/crypto/ssh"
    - "golang.org/x/crypto/ssh/agent"
    -)
    -
    -// KeysAvailable returns True if there is at least one key that the ssh agent
    -// knows about, otherwise false. err will be set if there is an error along
    -// the way.
    -func KeysAvailable(identites []string) (bool, error) {
    - sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
    - if err != nil {
    - return false, err
    - }
    -
    - return keysAvailable(sshAgent, identites)
    -}
    -
    -func keysAvailable(rw io.ReadWriter, identities []string) (bool, error) {
    - keys, err := agent.NewClient(rw).List()
    - if err != nil {
    - return false, err
    - }
    -
    - // this runs in O(m * n) but thankful the number of keys should be low.
    - for _, identity := range identities {
    - if identity == "*" {
    - // if the user doesn't care which key and we have a key, return true
    - if len(keys) > 0 {
    - return true, nil
    - }
    -
    - // if the user doesn't care which key but we have no keys, return an error
    - return false, fmt.Errorf("no ssh keys available")
    - }
    -
    - uIdentity := strings.ToUpper(identity)
    - for _, pubKey := range keys {
    - fpSHA256 := strings.ToUpper(ssh.FingerprintSHA256(pubKey))
    - fpMD5 := strings.ToUpper(ssh.FingerprintLegacyMD5(pubKey))
    -
    - if strings.HasPrefix(uIdentity, "SHA256:") {
    - if uIdentity == fpSHA256 {
    - return true, nil
    - }
    - } else if strings.HasPrefix(uIdentity, "MD5:") {
    - if uIdentity[4:] == fpMD5 {
    - return true, nil
    - }
    - } else {
    - // no known prefix check sha256 then md5, then give up
    - if uIdentity == fpSHA256[7:] || uIdentity == fpMD5 {
    - return true, nil
    - }
    - }
    - }
    - }
    -
    - if err == nil {
    - err = fmt.Errorf("no usable ssh identities found")
    - }
    -
    - return false, err
    -}
    -
    -// ShouldEnable returns true if the ssh agent should be enabled.
    -func ShouldEnable(identities []string) (bool, error) {
    - if len(identities) <= 0 {
    - return false, nil
    - }
    -
    - haveKeys, err := KeysAvailable(identities)
    - if err != nil {
    - return false, fmt.Errorf("error talking to ssh-agent: %s", err)
    - }
    -
    - if !haveKeys {
    - return false, fmt.Errorf("no keys available in ssh-agent")
    - }
    -
    - return true, nil
    -}
    --- a/ssh/agent_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,188 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package ssh
    -
    -import (
    - "crypto/ecdsa"
    - "crypto/elliptic"
    - "crypto/rand"
    - "net"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "golang.org/x/crypto/ssh"
    - "golang.org/x/crypto/ssh/agent"
    -)
    -
    -func setupAgent(keys []agent.AddedKey) net.Conn {
    - a := agent.NewKeyring()
    -
    - for _, key := range keys {
    - err := a.Add(key)
    - if err != nil {
    - panic(err)
    - }
    - }
    -
    - c1, c2 := net.Pipe()
    -
    - go func() {
    - defer c2.Close()
    -
    - err := agent.ServeAgent(a, c2)
    - if err != nil {
    - panic(err)
    - }
    - }()
    -
    - return c1
    -}
    -
    -func generateKey() *ecdsa.PrivateKey {
    - priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
    - if err != nil {
    - panic(err)
    - }
    -
    - return priv
    -}
    -
    -func TestKeysAvailableEmpty(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{}),
    - []string{},
    - )
    -
    - assert.False(t, avail)
    - assert.NotNil(t, err)
    -}
    -
    -func TestKeysAvailableWildcardNoKeys(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{}),
    - []string{"*"},
    - )
    -
    - assert.False(t, avail)
    - assert.NotNil(t, err)
    -}
    -
    -func TestKeysAvailableWildcardWithKeys(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: generateKey()},
    - }),
    - []string{"*"},
    - )
    -
    - assert.True(t, avail)
    - assert.Nil(t, err)
    -}
    -
    -func TestKeysAvailableUnknownFingerprint(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: generateKey()},
    - }),
    - []string{"SHA256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"},
    - )
    -
    - assert.False(t, avail)
    - assert.NotNil(t, err)
    -}
    -
    -func TestKeysAvailableFingerprintMD5NoPrefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{ssh.FingerprintLegacyMD5(sshPub)},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    -
    -func TestKeysAvailableFingerprintMD5Prefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{"MD5:" + ssh.FingerprintLegacyMD5(sshPub)},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    -
    -func TestKeysAvailableFingerprintSHA256Prefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{ssh.FingerprintSHA256(sshPub)},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    -
    -func TestKeysAvailableFingerprintSHA256NoPrefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - // FingerprintSHA256 always has the prefix so we strip it off for testing
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{ssh.FingerprintSHA256(sshPub)[7:]},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    --- a/ssh/ssh.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,18 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package ssh contains helpers for handling ssh-agent sockets.
    -package ssh
    --- a/stages/stages.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/stages/stages.go Mon Dec 20 12:33:09 2021 -0600
    @@ -72,10 +72,10 @@
    }
    // Execute runs the stage.
    -func (s *Stage) Execute(path string, logger *log.Entry, taskMap map[string]tasks.Task, env *environment.Environment, rt *runtime.Runtime) error {
    - stageEnv := env.Copy().MergeSlice(env.Items()).MergeSlice(s.Environment)
    +func (s *Stage) Execute(path string, logger *log.Entry, taskMap map[string]tasks.Task, env environment.Environment, rt *runtime.Runtime) error {
    + stageEnv := env.Copy().Merge(env).MergeSlice(s.Environment)
    - if s.Concurrent && !rt.State.ForceSequential {
    + if s.Concurrent && !rt.ForceSequential {
    taskRes := make(chan error)
    stageRes := make(chan error)
    @@ -125,7 +125,7 @@
    return nil
    }
    -func (s *Stage) runTask(path, name string, stageEnv *environment.Environment, taskMap map[string]tasks.Task, rt *runtime.Runtime) error {
    +func (s *Stage) runTask(path, name string, stageEnv environment.Environment, taskMap map[string]tasks.Task, rt *runtime.Runtime) error {
    absTaskName := fmt.Sprintf("%s/%s", path, name)
    taskLogger := logging.NewAdapter(fmt.Sprintf("%s/%s", path, name))
    @@ -133,7 +133,7 @@
    task, found := taskMap[name]
    if !found {
    - taskLogger.Fatal("failed, task not found")
    + taskLogger.Error("failed, task not found")
    return fmt.Errorf("task %s not found", absTaskName)
    }
    --- a/stages/unmarshal_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/stages/unmarshal_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -19,8 +19,8 @@
    import (
    "testing"
    - "github.com/go-yaml/yaml"
    "github.com/stretchr/testify/assert"
    + "gopkg.in/yaml.v2"
    )
    func TestUnmarshalDefaults(t *testing.T) {
    --- a/state/state.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,85 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package state contains the type that maintain the state during a run.
    -package state
    -
    -import (
    - "fmt"
    - "os"
    - "time"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/workspace"
    -)
    -
    -// State holds all of the runtime data during a run.
    -type State struct {
    - logger *log.Entry
    -
    - CfgPath string
    - Workspace *workspace.Workspace
    - KeepWorkspace bool
    -
    - DisableDeprecated bool
    - ForceSequential bool
    - EnableSSHAgent bool
    - PlanTimeout time.Duration
    -
    - DockerConfig string
    - CPUShares string
    - Memory string
    -}
    -
    -// New creates a new state.
    -func New() *State {
    - pwd, err := os.Getwd()
    - if err != nil {
    - panic(err)
    - }
    -
    - ws, err := workspace.New(pwd)
    - if err != nil {
    - panic(err)
    - }
    -
    - return &State{
    - Workspace: ws,
    - logger: logging.NewAdapter("state"),
    - }
    -}
    -
    -// Destroy will clean up the given state and run any registered cleanup
    -// functions
    -func (st *State) Destroy() {
    - // finally remove the workspace if requested
    - if !st.KeepWorkspace {
    - st.Workspace.Destroy()
    - }
    -}
    -
    -// Valid validates whether the state is correct or not.
    -func (st *State) Valid() error {
    - if st.EnableSSHAgent {
    - if val := os.Getenv("SSH_AUTH_SOCK"); val == "" {
    - return fmt.Errorf("ssh-agent forwarding requested, but agent not running")
    - }
    - }
    -
    - return nil
    -}
    --- a/tasks/README.md Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/README.md Mon Dec 20 12:33:09 2021 -0600
    @@ -6,13 +6,14 @@
    ## convey/clean Task
    -A clean task will remove files from the host relative and limited to the working directory of convey.
    +A clean task will remove files from the host relative and limited to the
    +directory containing the convey.yml for the run.
    ### Attributes
    | Name | Required | Default | Description |
    | --------- | -------- | ------- | ----------- |
    -| files | Yes | | A list of filenames relative to the convey working directory to remove. |
    +| files | Yes | | A list of filenames relative to the convey.yml file to remove. |
    ### Example
    @@ -25,10 +26,35 @@
    ----
    +## convey/export Task
    +
    +An export task will move files from the workspace to the host underneath the
    +directory containing the convey.yml file was found.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| --------- | -------- | ------- | ----------- |
    +| files | Yes | | A single filename or a list of files to copy from the workspace to the host. Files can be specified in one of two forms which can be mixed. The first is `source:destination` and the other is `filename` where filename will be used for both the host and the volume. |
    +
    +### Examples
    +
    + export:
    + type: convey/export
    + files: filename1
    +
    + export:
    + type: convey/export
    + files:
    + - logo.png
    + - binary:binary-linux-x86_64
    +
    +----
    +
    ## convey/fail Task
    -The fail task just automatically fails. It's similar to convey/noop in that it's
    -mostly used for debugging.
    +The fail task just automatically fails. It's similar to `convey/noop` in that
    +it's mostly used for debugging.
    ### Attributes
    @@ -41,9 +67,35 @@
    ----
    +## convey/import Task
    +
    +An import task copies files from the host to the workspace. It has one required
    +attribute named files. This is a list of files relative to the directory
    +containing the convey.yml file that will be copied into the workspace.
    +
    +### Attributes
    +
    +| Name | Required | Default | Description |
    +| ----- | -------- | ------- | ----------- |
    +| files | Yes | | A single filename or a list of files to copy from the host to the workspace. Files can be specified in one of two forms which can be mixed. The first is `source:destination` and the other is `filename` where filename will be used for both the host and the volume. |
    +
    +### Examples
    +
    + import:
    + type: convey/import
    + files: filename1
    +
    + import:
    + type: convey/import
    + files:
    + - Dockerfile
    + - src:source
    +
    +----
    +
    ## convey/noop Task
    -A noop task does nothing. It is a "No Operation" task. It's used primarily in
    +A noop task does nothing. It is a "No Operation" task. It's used primarily in
    testing, but could be used for stubbing stuff out or debugging.
    ### Attributes
    @@ -54,38 +106,3 @@
    clean:
    type: convey/noop
    -
    -----
    -
    -### convey/subtask Task
    -
    -A subtask lets reuse existing tasks by setting the environment.
    -
    -As of right now, a subtask can **NOT** override an environment variable in it's
    -parent task, but you can mimic that but having all subtasks setting the
    -environment variable.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| -------- | -------- | ------- | ----------- |
    -| base | Yes | | The name of the parent task |
    -
    -### Example
    -
    - build:
    - type: docker/run
    - command: go build -o foo-${GOOS}-${GOARCH}
    - environment:
    - - GOARCH=amd64
    - linux:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=linux
    - windows:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=windows
    -
    --- a/tasks/clean.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/clean.go Mon Dec 20 12:33:09 2021 -0600
    @@ -35,10 +35,10 @@
    Files yaml.StringOrSlice `yaml:"files"`
    }
    -func sanitizeFile(base, file string, env []string) (string, error) {
    - filename, err := environment.Mapper(file, env)
    - if err != nil {
    - return "", err
    +func sanitizeFile(base, file string, env environment.Environment) (string, error) {
    + filename := env.Expand(file)
    + if filename == "" {
    + return "", fmt.Errorf("no file name provided")
    }
    pathname, err := filepath.Abs(filename)
    @@ -53,7 +53,7 @@
    return pathname, nil
    }
    -func sanitizeFiles(base string, files, env []string) ([]string, error) {
    +func sanitizeFiles(base string, files, env environment.Environment) ([]string, error) {
    list := []string{}
    for _, file := range files {
    sanitized, err := sanitizeFile(base, file, env)
    @@ -68,20 +68,15 @@
    }
    // Execute runs the clean task.
    -func (c *Clean) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    +func (c *Clean) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    fullEnv := environment.New()
    fullEnv.Merge(env)
    fullEnv.Merge(rt.Environment)
    - wd, err := os.Getwd()
    - if err != nil {
    - return err
    - }
    -
    for _, pattern := range c.Files {
    - expandedPattern, err := environment.Mapper(pattern, fullEnv.Items())
    - if err != nil {
    - return err
    + expandedPattern := fullEnv.Expand(pattern)
    + if expandedPattern == "" {
    + continue
    }
    matches, err := zglob.Glob(expandedPattern)
    @@ -96,7 +91,7 @@
    }
    for _, path := range matches {
    - sanitized, err := sanitizeFile(wd, path, fullEnv.Items())
    + sanitized, err := sanitizeFile(rt.ConfigPath, path, fullEnv)
    if err != nil {
    return err
    }
    @@ -125,3 +120,7 @@
    return nil
    }
    +
    +func (c *Clean) Deprecated() error {
    + return nil
    +}
    --- a/tasks/clone.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/clone.go Mon Dec 20 12:33:09 2021 -0600
    @@ -17,13 +17,13 @@
    package tasks
    import (
    - "github.com/go-yaml/yaml"
    + "gopkg.in/yaml.v2"
    )
    // CloneTask creates a task of the given type from the given payload. It
    // does this by creating a fresh instance of a task of the target type,
    // then marshalling/unmarshalling the payload to that type.
    -func CloneTask(task interface{}, taskType Task) (Task, error) {
    +func Clone(task interface{}, taskType Task) (Task, error) {
    rawTask, err := yaml.Marshal(task)
    if err != nil {
    return nil, err
    --- a/tasks/clone_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/clone_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -28,7 +28,7 @@
    files := []string{"one", "two", "three"}
    task := &Clean{Files: files}
    - clone, err := CloneTask(task, taskType)
    + clone, err := Clone(task, taskType)
    assert.Nil(t, err)
    assert.NotNil(t, clone)
    --- a/tasks/count.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/count.go Mon Dec 20 12:33:09 2021 -0600
    @@ -29,7 +29,7 @@
    }
    // Execute runs the count task.
    -func (c *Count) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    +func (c *Count) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    c.Count += 1
    return nil
    @@ -44,3 +44,7 @@
    func (c *Count) Valid() error {
    return nil
    }
    +
    +func (c *Count) Deprecated() error {
    + return nil
    +}
    --- a/tasks/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/errors.go Mon Dec 20 12:33:09 2021 -0600
    @@ -21,5 +21,5 @@
    )
    var (
    - errNoFiles = errors.New("no files specified")
    + ErrNoFiles = errors.New("no files specified")
    )
    --- a/tasks/export.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/export.go Mon Dec 20 12:33:09 2021 -0600
    @@ -30,7 +30,6 @@
    type Export struct {
    Files yaml.StringOrSlice `yaml:"files"`
    - Path string `yaml:"path"`
    }
    // New creates a new Export task.
    @@ -41,28 +40,37 @@
    // Valid validates the export task.
    func (e *Export) Valid() error {
    if len(e.Files) == 0 {
    - return errNoFiles
    + return ErrNoFiles
    }
    return nil
    }
    // Executes the task
    -func (e *Export) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    +func (e *Export) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    fullEnv := env.Copy().Merge(rt.Environment)
    + workspace, err := rt.Workspace()
    + if err != nil {
    + return err
    + }
    +
    for _, pattern := range e.Files {
    - pattern = fullEnv.Map(pattern)
    + pattern := fullEnv.Expand(pattern)
    + if pattern == "" {
    + continue
    + }
    +
    spec := fs.ParsePathSpec(pattern)
    - matches, err := filepath.Glob(filepath.Join(rt.State.Workspace.Volume().Path(), spec.Source()))
    + matches, err := filepath.Glob(filepath.Join(workspace.Path(), spec.Source()))
    if err != nil {
    return err
    }
    for _, match := range matches {
    - realSrc := strings.TrimPrefix(match, rt.State.Workspace.Volume().Path())
    - err = rt.State.Workspace.Volume().Export(realSrc, spec.Destination())
    + realSrc := strings.TrimPrefix(match, workspace.Path())
    + err = workspace.Export(realSrc, spec.Destination())
    if err != nil {
    return err
    }
    @@ -71,3 +79,7 @@
    return nil
    }
    +
    +func (e *Export) Deprecated() error {
    + return nil
    +}
    --- a/tasks/fail.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/fail.go Mon Dec 20 12:33:09 2021 -0600
    @@ -29,7 +29,7 @@
    type Fail struct{}
    // Execute runs the fail task.
    -func (c *Fail) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    +func (c *Fail) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    return fmt.Errorf("convey/fail task always fails")
    }
    @@ -42,3 +42,7 @@
    func (c *Fail) Valid() error {
    return nil
    }
    +
    +func (f *Fail) Deprecated() error {
    + return nil
    +}
    --- a/tasks/import.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/import.go Mon Dec 20 12:33:09 2021 -0600
    @@ -41,18 +41,25 @@
    // Valid validates the export task.
    func (i *Import) Valid() error {
    if len(i.Files) == 0 {
    - return errNoFiles
    + return ErrNoFiles
    }
    return nil
    }
    // Executes the task
    -func (i *Import) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    +func (i *Import) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    + fullEnv := env.Copy().Merge(rt.Environment)
    +
    for _, pattern := range i.Files {
    + pattern := fullEnv.Expand(pattern)
    + if pattern == "" {
    + continue
    + }
    +
    spec := fs.ParsePathSpec(pattern)
    - matches, err := filepath.Glob(filepath.Join(rt.State.CfgPath, spec.Source()))
    + matches, err := filepath.Glob(filepath.Join(rt.ConfigPath, spec.Source()))
    if err != nil {
    return err
    }
    @@ -61,7 +68,12 @@
    }
    for _, match := range matches {
    - err = rt.State.Workspace.Volume().Import(match, spec.Destination())
    + ws, err := rt.Workspace()
    + if err != nil {
    + return err
    + }
    +
    + err = ws.Import(match, spec.Destination())
    if err != nil {
    return err
    }
    @@ -70,3 +82,7 @@
    return nil
    }
    +
    +func (i *Import) Deprecated() error {
    + return nil
    +}
    --- a/tasks/import_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/import_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -20,6 +20,7 @@
    "os"
    "path/filepath"
    "testing"
    + "time"
    "github.com/stretchr/testify/assert"
    @@ -27,34 +28,29 @@
    "keep.imfreedom.org/grim/convey/fs"
    "keep.imfreedom.org/grim/convey/logging"
    "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/state"
    )
    func testImport(t *testing.T, files []string) {
    - pwd, err := os.Getwd()
    - assert.Nil(t, err)
    -
    - // setup our state
    - st := state.New()
    - st.CfgPath = pwd
    -
    env := environment.New()
    // create our runtime
    - rt := runtime.NewWithEnvironment(st, env)
    + rt := runtime.New(environment.New(), "", false, false, 0*time.Second)
    defer rt.Shutdown()
    // create our task and run it.
    importTask := &Import{Files: files}
    - err = importTask.Execute("", logging.NewAdapter("test"), env, rt)
    + err := importTask.Execute("", logging.NewAdapter("test"), env, rt)
    assert.Nil(t, err)
    for _, file := range files {
    spec := fs.ParsePathSpec(file)
    - _, err = os.Stat(filepath.Join(st.Workspace.Volume().Path(), spec.Destination()))
    + ws, err := rt.Workspace()
    + assert.Nil(t, err)
    +
    + _, err = os.Stat(filepath.Join(ws.Path(), spec.Destination()))
    assert.NotEqual(t, err, os.IsNotExist(err))
    }
    }
    --- a/tasks/noop.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/noop.go Mon Dec 20 12:33:09 2021 -0600
    @@ -27,7 +27,7 @@
    type Noop struct{}
    // Execute runs the noop task.
    -func (c *Noop) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    +func (c *Noop) Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error {
    return nil
    }
    @@ -40,3 +40,7 @@
    func (c *Noop) Valid() error {
    return nil
    }
    +
    +func (n *Noop) Deprecated() error {
    + return nil
    +}
    --- a/tasks/subtask.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,58 +0,0 @@
    -// Convey
    -// Copyright 2016-2020 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package tasks
    -
    -import (
    - "fmt"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -type SubTask struct {
    - Base string `yaml:"base"`
    - Parent Task
    - Environment yaml.StringOrSlice `yaml:"environment"`
    -}
    -
    -func (t *SubTask) New() Task {
    - return &SubTask{}
    -}
    -
    -func (t *SubTask) Valid() error {
    - if t.Base == "" {
    - return fmt.Errorf("no base task specified")
    - }
    -
    - return nil
    -}
    -
    -func (t *SubTask) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy()
    -
    - _ = t.Parent.(environment.EnvironmentReader)
    - if e, ok := t.Parent.(environment.EnvironmentReader); ok {
    - fullEnv.MergeSlice(e.ReadEnvironment())
    - }
    -
    - fullEnv.MergeSlice(t.Environment).Merge(rt.Environment)
    -
    - return t.Parent.Execute(name, logger, fullEnv, rt)
    -}
    --- a/tasks/tasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/tasks/tasks.go Mon Dec 20 12:33:09 2021 -0600
    @@ -27,18 +27,18 @@
    var (
    // Tasks is the list of tasks defined in this package.
    Tasks = map[string]Task{
    - "clean": &Clean{},
    - "export": &Export{},
    - "fail": &Fail{},
    - "import": &Import{},
    - "noop": &Noop{},
    - "subtask": &SubTask{},
    + "convey/clean": &Clean{},
    + "convey/export": &Export{},
    + "convey/fail": &Fail{},
    + "convey/import": &Import{},
    + "convey/noop": &Noop{},
    }
    )
    // Task is an interface for defining a task that can be run.
    type Task interface {
    - Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error
    + Execute(name string, logger *log.Entry, env environment.Environment, rt *runtime.Runtime) error
    New() Task
    Valid() error
    + Deprecated() error
    }
    --- a/tests/breakout.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,28 +0,0 @@
    -tasks:
    - import-bad:
    - type: convey/import
    - files: /etc/ssl:/ssl
    - import:
    - type: convey/import
    - files: breakout.yml
    - export-bad:
    - type: convey/export
    - files: breakout.yml:/
    - export:
    - type: convey/export
    - files: breakout.yml:qux/quux/free.yml
    -plans:
    - default:
    - stages:
    - - tasks:
    - - import
    - - export
    - import-bad:
    - stages:
    - - tasks:
    - - import-bad
    - export-bad:
    - stages:
    - - tasks:
    - - import
    - - export-bad
    --- a/tests/clean.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,15 +0,0 @@
    -# This is a simple test that makes sure the clean task can remove files that
    -# have an environment variable in their name.
    ----
    -environment:
    - - HOST_PATH=.
    -tasks:
    - clean:
    - type: convey/clean
    - files:
    - - ${HOST_PATH}/dirty
    - - ${HOST_PATH}/**/bar.baz
    -plans:
    - default:
    - stages:
    - - tasks: clean
    --- a/tests/codebuild/buildspec.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,35 +0,0 @@
    -version: 0.2
    -
    -env:
    - variables:
    - JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64"
    - parameter-store:
    - LOGIN_PASSWORD: "dockerLoginPassword"
    -
    -phases:
    - install:
    - commands:
    - - echo Entered the install phase...
    - - apt-get update -y
    - - apt-get install -y maven
    - pre_build:
    - commands:
    - - echo Entered the pre_build phase...
    - - docker login –u User –p $LOGIN_PASSWORD
    - build:
    - commands:
    - - echo Entered the build phase...
    - - echo Build started on `date`
    - - mvn install
    - post_build:
    - commands:
    - - echo Entered the post_build phase...
    - - echo Build completed on `date`
    -artifacts:
    - files:
    - - target/messageUtil-1.0.jar
    - discard-paths: yes
    -cache:
    - paths:
    - - '/root/.m2/**/*'
    -
    --- a/tests/slow.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,15 +0,0 @@
    -tasks:
    - slow:
    - type: docker/run
    - image: alpine:edge
    - script: sleep 5
    -
    -plans:
    - default:
    - stages:
    - - name: first
    - tasks: slow
    - - name: second
    - tasks: slow
    - - name: third
    - tasks: slow
    --- a/tests/test-workdir.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,16 +0,0 @@
    -# This test just ensure that we can set the workdir to ${CONVEY_WORKSPACE}
    -# successfully
    -
    -tasks:
    - basic:
    - type: docker/run
    - image: alpine:edge
    - workdir: ${CONVEY_WORKSPACE}
    - script:
    - - pwd
    - - ls
    -plans:
    - default:
    - stages:
    - - tasks: basic
    -
    --- a/workspace/taskdir.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,27 +0,0 @@
    -// Convey
    -// Copyright 2016-2020 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package workspace
    -
    -import (
    - "path/filepath"
    -
    - "keep.imfreedom.org/grim/convey/fs"
    -)
    -
    -func (ws *Workspace) CreateTaskDirectory(name string) (*fs.Directory, error) {
    - return fs.NewDirectory(filepath.Join(ws.path, name))
    -}
    --- a/workspace/workspace.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,82 +0,0 @@
    -// Convey
    -// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package workspace
    -
    -import (
    - "io/ioutil"
    - "os"
    - "path/filepath"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/fs"
    -)
    -
    -type Workspace struct {
    - parent string
    - root string
    - path string
    - volume *fs.Directory
    -}
    -
    -func New(parent string) (*Workspace, error) {
    - root := filepath.Join(parent, ".convey")
    - err := os.MkdirAll(root, 0700)
    - if err != nil {
    - return nil, err
    - }
    -
    - path, err := ioutil.TempDir(root, "")
    - if err != nil {
    - return nil, err
    - }
    -
    - volume, err := fs.NewDirectory(filepath.Join(path, "volume"))
    - if err != nil {
    - return nil, err
    - }
    -
    - workspace := &Workspace{
    - parent: parent,
    - root: root,
    - path: path,
    - volume: volume,
    - }
    -
    - return workspace, nil
    -}
    -
    -func (ws *Workspace) Destroy() {
    - if err := os.RemoveAll(ws.path); err != nil {
    - log.Errorf("error removing workspace: %s", err)
    - return
    - }
    -
    - // try to remove the .convey directory. This will only succeed when
    - // our state is the only one and that's intentional.
    - if err := os.Remove(ws.root); err != nil {
    - log.Errorf("error removing %s: %s", ws.root, err)
    - }
    -}
    -
    -func (ws *Workspace) Path() string {
    - return ws.path
    -}
    -
    -func (ws *Workspace) Volume() *fs.Directory {
    - return ws.volume
    -}
    --- a/yaml/stringslice.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/yaml/stringslice.go Mon Dec 20 12:33:09 2021 -0600
    @@ -18,7 +18,7 @@
    package yaml
    import (
    - "github.com/go-yaml/yaml"
    + "gopkg.in/yaml.v2"
    )
    // StringOrSlice is a []string type alias that has a custom UnmarshalYAML
    --- a/yaml/stringslice_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/yaml/stringslice_test.go Mon Dec 20 12:33:09 2021 -0600
    @@ -19,8 +19,8 @@
    import (
    "testing"
    - "github.com/go-yaml/yaml"
    "github.com/stretchr/testify/assert"
    + "gopkg.in/yaml.v2"
    )
    func unmarshal(t *testing.T, str string) StringOrSlice {