Start of the refactor
* Remove aws, docker, and kubectl commands
* Remove all config loaders and rewrote the convey loader
* Remove all of the runners and rewrote the convey runner
* Remove all ssh stuff
* Move metaplans to their own package
* Replace kingpin with kong for command line processing
--- 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 @@
-The aws package provides tasks for interacting with Amazon Web Services.
-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.
-| Name | Required | Default | Description |
-| ------ | -------- | ------- | ----------- |
-| region | | | The AWS region to used. |
--- 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 @@
-// 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.
- "keep.imfreedom.org/grim/convey/tasks"
- // 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:
- - ACCOUNT_ID=1234567890
- destination: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/alpine:edge
- image: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/alpine:edge
--- 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 @@
-// 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/>.
- "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
-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)
- cmd.Append("--region", region)
- stdout, stderr, err := exec.RunOutput(name, cmd.Command(), rt.State.PlanTimeout)
- logger.Warnf("error: %s", stderr)
- argv, err := shellquote.Split(stdout)
- task, err := docker.ParseCommand(argv)
- return task.Execute(name, logger, env, rt)
-// New creates a aws/ecr-get-login task.
-func (ecr *ECRGetLogin) New() tasks.Task {
-// Valid checks that the build task is valid.
-func (ecr *ECRGetLogin) Valid() error {
--- 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 @@
-// 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/>.
- "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:])
- return nil, fmt.Errorf("unable to parse aws command line '%v'", argv)
--- a/config/config.go Thu Aug 26 08:11:34 2021 -0500
+++ b/config/config.go Tue Oct 05 01:14:12 2021 -0500
@@ -20,26 +20,39 @@
+ "keep.imfreedom.org/grim/convey/metaplans" "keep.imfreedom.org/grim/convey/plans"
"keep.imfreedom.org/grim/convey/tasks"
// Config represents a full convey configuration.
- Tasks map[string]tasks.Task
- Plans map[string]plans.Plan
- MetaPlans map[string]plans.MetaPlan
+ Tasks map[string]tasks.Task + Plans map[string]plans.Plan + MetaPlans map[string]metaplans.MetaPlan // 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("error: task %q : %q", name, err.Error()) +// 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) --- a/config/loader.go Thu Aug 26 08:11:34 2021 -0500
+++ b/config/loader.go Tue Oct 05 01:14:12 2021 -0500
@@ -17,67 +17,27 @@
- "keep.imfreedom.org/grim/convey/runtime"
+ "github.com/go-yaml/yaml" -// Loader defines all the functions that a config loader needs to implement.
- Load(path, base string, data []byte, options []string, disableDeprecated bool) (*Config, error)
- LoadOverride(path, base string, data []byte, config *Config, disableDeprecated bool)
- OverrideSuffix() string
- ResolvePlanName(plan string, cfg *Config, rt *runtime.Runtime) string
+func LoadString(data []byte) (*Config, error) { -func determineFilename(filename string, loader Loader) string {
- ext := filepath.Ext(filename)
- base := strings.TrimSuffix(filename, ext)
+ err := yaml.Unmarshal(data, &cfg) - return base + loader.OverrideSuffix() + ext
-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) {
- data, err := ioutil.ReadFile(absName)
+func LoadFile(filename string) (*Config, error) { + contents, err := ioutil.ReadFile(filename) - fmt.Printf("error in override file '%s' : %s", absName, err)
- loader.LoadOverride(path, base, data, cfg, disableDeprecated)
+ return LoadString(contents)
-// 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)
- return nil, fmt.Errorf("failed to read config file '%s'", file)
- cfg, err := loader.Load(path, base, data, options, disableDeprecated)
- loadOverride(path, base, loader, cfg, disableDeprecated)
--- 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 @@
-// 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/>.
- "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) {
-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 {
-func (l *testLoader) ResolvePlanName(plan string, cfg *Config, rt *runtime.Runtime) string {
-func TestDetermineFilenameDefault(t *testing.T) {
- determineFilename("convey.yml", l),
- "convey"+l.OverrideSuffix()+".yml",
-func TestDetermineFilenameEmpty(t *testing.T) {
- assert.Equal(t, determineFilename("", l), l.OverrideSuffix())
-func TestDetermineFilenameCustom(t *testing.T) {
- determineFilename("foo.yml", l),
- "foo"+l.OverrideSuffix()+".yml",
-func TestDetermineFilenameUnicodeSnowman(t *testing.T) {
- determineFilename("☃.yml", l),
- "☃"+l.OverrideSuffix()+".yml",
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/config/raw.go Tue Oct 05 01:14:12 2021 -0500
@@ -0,0 +1,72 @@
+ "github.com/go-yaml/yaml" + "keep.imfreedom.org/grim/convey/metaplans" + "keep.imfreedom.org/grim/convey/plans" + "keep.imfreedom.org/grim/convey/tasks" + 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 { + for _, item := range rawTask { + if item.Key == "type" { + rawType, ok := item.Value.(string) + return nil, fmt.Errorf("the type value for task %q is not a string", name) + return nil, fmt.Errorf("task %q is missing its type", name) + // look for the real task in the taskmap + templateTask, found := tasksMap[taskType] + return nil, fmt.Errorf("task %q has unknown type %q", name, taskType) + realTask, err := tasks.Clone(rawTask, templateTask) + realTasks[name] = realTask +func (r *rawConfig) process() (*Config, error) { + Environment: r.Environment, + MetaPlans: r.MetaPlans, + tasks, err := r.processTasks() --- a/config/tasks.go Thu Aug 26 08:11:34 2021 -0500
+++ b/config/tasks.go Tue Oct 05 01:14:12 2021 -0500
@@ -17,38 +17,20 @@
- "keep.imfreedom.org/grim/convey/aws"
- "keep.imfreedom.org/grim/convey/docker"
- "keep.imfreedom.org/grim/convey/kubectl"
"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 - // TasksMap is a lookup table for tasks
- TasksMap = map[string]tasks.Task{}
+ // tasksMap is a lookup table for tasks + tasksMap = map[string]tasks.Task{}
- for taskName, task := range aws.Tasks {
- TasksMap["aws/"+taskName] = task
- // add the docker tasks
- for taskName, task := range docker.Tasks {
- TasksMap["docker/"+taskName] = task
// add the intrinsic convey tasks
for taskName, task := range tasks.Tasks {
- TasksMap["convey/"+taskName] = task
- // add the kubectl tasks
- for taskName, task := range kubectl.Tasks {
- TasksMap["kubectl/"+taskName] = task
+ tasksMap["convey/"+taskName] = task --- a/consts/consts.go Thu Aug 26 08:11:34 2021 -0500
+++ b/consts/consts.go Tue Oct 05 01:14:12 2021 -0500
@@ -18,5 +18,5 @@
// Version is the current version of convey
--- 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 @@
--- a/convey.yml Thu Aug 26 08:11:34 2021 -0500
+++ b/convey.yml Tue Oct 05 01:14:12 2021 -0500
@@ -20,43 +20,43 @@
- workdir: ${CONVEY_WORKSPACE}
- workdir: ${CONVEY_WORKSPACE}
- command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}${SUFFIX}
+ # workdir: ${CONVEY_WORKSPACE} + # command: go test ./... + # workdir: ${CONVEY_WORKSPACE} + # command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}${SUFFIX} --- a/docker/README.md Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +0,0 @@
-The docker package provides tasks for interacting with docker.
-A build task will build a docker image.
-| 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`.
-----
-## docker/environment Task
-An environment task will read a file with environment variables and make them accessible to your plans.
-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.
-| 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.
- type: docker/environment
-----
-An export task copies files from the volume to the host.
-| 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. |
- - binary:binary-linux-x86_64
-----
-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.
-| 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. |
-----
-A login task will run docker login to login to a registry.
-| 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. |
-----
-A logout task will log you out from a Docker registry.
-| Name | Required | Default | Description |
-| ------ | -------- | ------- | ----------- |
-| server | | | The server to logout from. If omitted docker hub is used. |
- server: registry.my.domain:5000
-----
-A pull task pulls an image.
-| 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. |
-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: gliderlabs/alpine:edge
-----
-A push task pushes an image.
-| 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. |
-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`.
- - registry.my.domain:5000/newimage:master-latest
- - registry.my.domain:5000/newimage:master-deadbeef
-----
-A remove task removes an image.
-| 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`.
- - registry.my.domain:5000/newimage:master-latest
- - registry.my.domain:5000/newimage:master-deadbeef
-----
-A run task runs an image.
-| 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. |
-### HealthCheck Attributes
-All of these attributes map directory to the `--health` arguments to `docker run`.
-| -------- | ----------- |
-| 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. |
-A basic example where the image knows everything to do.
-A basic example using a standard image to do something else
- image: debian:jessie-slim
- - 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
- command: wget http://localhost
-----
-A tag task will tag an existing image.
-| 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. |
- source: registry.my.domain:5000/newimage:latest
- destination: registry.my.domain:5000/newimage:development
--- a/docker/build.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-// 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/>.
- 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/runtime"
- "keep.imfreedom.org/grim/convey/tasks"
- "keep.imfreedom.org/grim/convey/yaml"
-// Build defines the options for building a docker image.
- Dockerfile string `yaml:"dockerfile"`
- Files yaml.StringOrSlice `yaml:"files"`
- Tag string `yaml:"tag"`
- Tags yaml.StringOrSlice `yaml:"tags"`
- Target string `yaml:"target"`
- Labels yaml.StringOrSlice `yaml:"labels"`
- Arguments yaml.StringOrSlice `yaml:"arguments"`
-// 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)
- 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)
- labels, err := fullEnv.MapSlice(b.Labels)
- arguments, err := fullEnv.MapSlice(b.Arguments)
- td.Import(dockerfile, filepath.Base(dockerfile))
- // create the basic command
- cmd := exec.NewGenerator(
- "-f", filepath.Join(td.Path(), filepath.Base(dockerfile)),
- // add any and all tags
- for _, tag := range tags {
- // 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
- cmd.Append("--target", b.Target)
- // finally add the build context
- return Docker(name, cmd.Command(), rt)
-// New creates a new docker build task.
-func (b *Build) New() tasks.Task {
-// Valid checks that the build task is valid.
-func (b *Build) Valid() error {
- if b.Dockerfile == "" {
- b.Tags = append([]string{b.Tag}, b.Tags...)
--- 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 @@
-// 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/>.
- "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) {
- Dockerfile: "Dockerfile",
- assert.Nil(t, b.Valid())
-func TestBuildUnmarshalNormal(t *testing.T) {
- data := `dockerfile: dockerfile
- err := yaml.Unmarshal([]byte(data), &b)
- 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) {
- err := yaml.Unmarshal([]byte(data), &b)
- assert.EqualError(t, b.Valid(), errNoDockerFile.Error())
-func TestBuildUnmarshalMissingTag(t *testing.T) {
- data := `dockerfile: dockerfile`
- err := yaml.Unmarshal([]byte(data), &b)
- 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 @@
-// 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.
- "keep.imfreedom.org/grim/convey/exec"
- "keep.imfreedom.org/grim/convey/runtime"
- "keep.imfreedom.org/grim/convey/tasks"
- // Tasks is a map of all docker tasks.
- Tasks = map[string]tasks.Task{
- "environment": &Environment{},
-func dockerCommand(cmdv []string, rt *runtime.Runtime) []string {
- cmd := exec.NewGenerator()
- // 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
-// 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
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-// 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/>.
- 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
-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)
- // create a directory in our state for the task
- td, err := rt.State.Workspace.CreateTaskDirectory(name)
- 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).
- err = e.Execute(name, logger, env, rt)
- // 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)
- env.MergeSlice(entries)
-func processFile(path, name, prefix string) ([]string, error) {
- data, err := ioutil.ReadFile(path)
- return nil, fmt.Errorf("failed to read environment file '%s'", name)
- for _, line := range strings.Split(string(data), "\n") {
- if len(strings.TrimSpace(line)) == 0 {
- // 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)
- return nil, fmt.Errorf("malformed entry in environments file '%s'", line)
- key = strings.TrimSpace(parts[0])
- val = strings.TrimSpace(parts[1])
- return nil, fmt.Errorf("malformed entry in environments file '%s'", line)
- entries = append(entries, fmt.Sprintf("%s%s=%s", prefix, strings.ToUpper(key), val))
-// New creates a new environment command.
-func (e *Environment) New() tasks.Task {
-// Valid validate the environment command.
-func (e *Environment) Valid() error {
- e.Files = append([]string{e.File}, e.Files...)
- return errNoFilesEnvironment
--- 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 @@
-// 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/>.
- "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) {
- assert.EqualError(t, i.Valid(), errNoFilesEnvironment.Error())
-func TestEnvironmentUnmarshalString(t *testing.T) {
- data := `from-files: filename`
- err := yaml.Unmarshal([]byte(data), &imp)
- assert.Equal(t, imp.Files, cYaml.StringOrSlice{"filename"})
-func TestEnvironmentUnmarshalStringSlice(t *testing.T) {
- err := yaml.Unmarshal([]byte(data), &imp)
- assert.Equal(t, imp.Files, cYaml.StringOrSlice{"filename1", "filename2"})
--- a/docker/errors.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-// 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/>.
- 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")
--- 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 @@
-// 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/>.
- 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) {
- "\"{{if .Config.Healthcheck}}true{{else}}false{{end}}\"",
- stdout, stderr, err := DockerOutput(name+"/healthcheck", cmdv, rt)
- if strings.TrimSpace(stdout) == "true" {
-func containerIsHealthy(name, cid string, rt *runtime.Runtime, logger *log.Entry) (bool, error) {
- "\"{{.State.Health.Status}}\"",
- stdout, stderr, err := DockerOutput(name+"/healthcheck", cmdv, rt)
- if strings.ToLower(strings.TrimSpace(stdout)) == "healthy" {
--- a/docker/login.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-// 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/>.
- 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"
-// Login represents an login task for logging into a docker registry.
- Username string `yaml:"username"`
- Password string `yaml:"password"`
- Server string `yaml:"server"`
-// 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(
- return Docker(name, cmd.Command(), rt)
-// New creates a new login task.
-func (l *Login) New() tasks.Task {
-// Valid validate the login task.
-func (l *Login) Valid() error {
--- a/docker/login_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestLogin(t *testing.T) {
- assert.Nil(t, l.Valid())
-func TestLoginServerRequired(t *testing.T) {
- assert.EqualError(t, l.Valid(), errNoServer.Error())
-func TestLoginUsernameRequired(t *testing.T) {
- assert.EqualError(t, l.Valid(), errNoUsername.Error())
--- a/docker/logout.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-// 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/>.
- 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"
-// Logout represents a logout task which logs out of a docker registry.
- Server string `yaml:"server"`
-// 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")
- return Docker(name, cmd.Command(), rt)
-// New creates a new logout task.
-func (l *Logout) New() tasks.Task {
-// Valid validates the logout task.
-func (l *Logout) Valid() error {
--- a/docker/logout_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestLogout(t *testing.T) {
- assert.Nil(t, l.Valid())
-func TestLogoutServerRequired(t *testing.T) {
- assert.EqualError(t, l.Valid(), errNoServer.Error())
--- 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 @@
-// 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/>.
- "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:])
- Files: []string{*buildContext},
- Username: *loginUsername,
- Password: *loginPassword,
- Command: strings.Join(*runCommand, " "),
- EntryPoint: *runEntryPoint,
- Destination: *tagDestination,
- 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 @@
-// 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/>.
- "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)
- // make sure we can type assert into a Login command
- assert.IsType(t, 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
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-// 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/>.
- 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"
-// Pull represents a docker pull task which pulls an image from a docker
- Image string `yaml:"image"`
- Images yaml.StringOrSlice `yaml:"images"`
-// 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)
- for _, image := range images {
- cmdv := []string{"pull", image}
- if err := Docker(name, cmdv, rt); err != nil {
-// New creates a new pull task.
-func (p *Pull) New() tasks.Task {
-// Valid validates the pull task.
-func (p *Pull) Valid() error {
- p.Images = append([]string{p.Image}, p.Images...)
- if len(p.Images) == 0 {
--- a/docker/pull_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
- cYaml "keep.imfreedom.org/grim/convey/yaml"
-func TestPull(t *testing.T) {
- assert.Nil(t, p1.Valid())
- Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
- assert.Nil(t, p2.Valid())
-func TestPullImageRequired(t *testing.T) {
- assert.EqualError(t, p1.Valid(), errNoImages.Error())
- Images: cYaml.StringOrSlice{},
- assert.EqualError(t, p2.Valid(), errNoImages.Error())
-func TestPullValidNormalizesImages(t *testing.T) {
- Images: cYaml.StringOrSlice{"image2", "image3"},
- assert.Nil(t, p.Valid())
- assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
--- a/docker/push.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-// 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/>.
- 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"
-// Push represents a docker push task which push an image to a docker registry.
- Image string `yaml:"image"`
- Images yaml.StringOrSlice `yaml:"images"`
-// 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)
- for _, image := range images {
- cmdv := []string{"push", image}
- if err := Docker(name, cmdv, rt); err != nil {
-// New creates a new push task.
-func (p *Push) New() tasks.Task {
-// Valid validates the push task.
-func (p *Push) Valid() error {
- p.Images = append([]string{p.Image}, p.Images...)
- if len(p.Images) == 0 {
--- a/docker/push_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
- cYaml "keep.imfreedom.org/grim/convey/yaml"
-func TestPush(t *testing.T) {
- assert.Nil(t, p1.Valid())
- Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
- assert.Nil(t, p2.Valid())
-func TestPushImageRequired(t *testing.T) {
- assert.EqualError(t, p1.Valid(), errNoImages.Error())
- Images: cYaml.StringOrSlice{},
- assert.EqualError(t, p2.Valid(), errNoImages.Error())
-func TestPushValidNormalizesImages(t *testing.T) {
- Images: cYaml.StringOrSlice{"image2", "image3"},
- assert.Nil(t, p.Valid())
- assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
--- a/docker/remove.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-// 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/>.
- 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"
-// Remove represents a docker rm task which deletes a local docker image.
- Image string `yaml:"image"`
- Images yaml.StringOrSlice `yaml:"images"`
- Quiet bool `yaml:"quiet"`
-// 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)
- 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 {
-// New creates a new remove task.
-func (r *Remove) New() tasks.Task {
-// Valid validates the remove task.
-func (r *Remove) Valid() error {
- r.Images = append([]string{r.Image}, r.Images...)
- if len(r.Images) == 0 {
--- a/docker/remove_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
- cYaml "keep.imfreedom.org/grim/convey/yaml"
-func TestRemove(t *testing.T) {
- assert.Nil(t, p1.Valid())
- Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
- assert.Nil(t, p2.Valid())
-func TestRemoveImageRequired(t *testing.T) {
- assert.EqualError(t, p1.Valid(), errNoImages.Error())
- Images: cYaml.StringOrSlice{},
- assert.EqualError(t, p2.Valid(), errNoImages.Error())
-func TestRemoveValidNormalizesImages(t *testing.T) {
- Images: cYaml.StringOrSlice{"image2", "image3"},
- assert.Nil(t, p.Valid())
- assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
--- a/docker/run.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,418 +0,0 @@
-// 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/>.
- "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/runtime"
- "keep.imfreedom.org/grim/convey/tasks"
- "keep.imfreedom.org/grim/convey/yaml"
-// Run represents a docker run task which will run a container.
- Command string `yaml:"command"`
- Detach bool `yaml:"detach"`
- EntryPoint string `yaml:"entrypoint"`
- Environment yaml.StringOrSlice `yaml:"environment"`
- HealthCheck HealthCheck `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 {
- raw := rawRun{Shell: "/bin/sh", HealthCheck: HealthCheck{}}
- if err := unmarshal(&raw); err != 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)
- // create the temp file to write the script to
- scriptFile := filepath.Join(td.Path(), "script")
- // 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 nErr := os.Remove(scriptFile); nErr != nil {
- fmt.Printf("error removing file: %s\n", nErr)
- // 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)
- 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)
- logger.Errorf("%s", stderr)
- cid := strings.TrimSpace(stdout)
- rt.Cleanup(func(logger *log.Entry) {
- logger.Debugf("stopping container %s", cid)
- err = StopContainer(cid, logger, rt)
- logger.Warnf("failed to stop container %s: %s", cid, err.Error())
- 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)
- healthChan := make(chan error)
- logger.Infof("waiting for container to go healthy")
- duration := 5 * time.Second
- healthy, err := containerIsHealthy(name, cid, rt, logger)
- logger.Infof("container still not healthy, waiting %v", duration)
- logger.Infof("container is ready")
- logger.Infof("no healthcheck found")
-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())
-// 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)
- cmd := exec.NewGenerator(
- // add the hostname if specified
- hostname := fullEnv.Map(r.Hostname)
- cmd.Append("--network-alias", hostname)
- // assign a default workspace location
- workSpace := r.WorkSpace
- workSpace = "/workspace"
- workspaceMount := fullEnv.Map(workSpace)
- "-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)
- cmd.Append("-w", workdir)
- id, nErr := ImageID(image, logger, rt)
- // Check if the image does _not_ have a workdir set. If it doesn't,
- // set workdir to the convey workspace
- "{{.Config.WorkingDir}}",
- stdout, stderr, nErr := DockerOutput("checkWorkDir", cmdv, rt)
- logger.Errorf("%s", stderr)
- if strings.TrimSpace(stdout) == "" {
- cmd.Append("-w", workspaceMount)
- // 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()
- cmd.Append("-e", "UID="+user.Uid, "-e", "GID="+user.Gid)
- // set user if one was specified
- username := fullEnv.Map(r.User)
- cmd.Append("--user", username)
- // initialize some variables
- entryPoint := r.EntryPoint
- commandArg := fullEnv.Map(r.Command)
- // if we're using a script defined in the yaml, create it and override
- scriptFile, entryPoint, commandArg, err = r.writeScript(name, rt, fullEnv)
- cmd.Append("-v", scriptFile+":"+scriptFile)
- cmd.Append("--entrypoint", entryPoint)
- // add a label to the container
- normalize.Normalize(fmt.Sprintf("convey-%d-task=%s", os.Getpid(), name)),
- // run through any user supplied labels
- labels, err := fullEnv.MapSlice(r.Labels)
- for _, label := range labels {
- cmd.Append("--label", label)
- // add the ssh agent stuff
- if rt.State.EnableSSHAgent {
- authSock := os.Getenv("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() {
- // add the image to the command
- // append the command if we have one
- args, err := shellquote.Split(commandArg)
- logger.Infof("running container with id %s", runID)
- // Everything after this should be mostly dead code
- 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)
- return Docker(name, cmd.Command(), rt)
-// New creates a new run task.
-func (r *Run) New() tasks.Task {
-// Valid validates the run task.
-func (r *Run) Valid() error {
--- a/docker/run_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestRun(t *testing.T) {
- assert.Nil(t, r.Valid())
-func TestRunImageRequired(t *testing.T) {
- assert.EqualError(t, r.Valid(), errNoImage.Error())
--- a/docker/tag.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-// 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/>.
- 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"
-// Tag represents a docker tag command.
- Source string `yaml:"source"`
- Destination string `yaml:"destination"`
- Destinations yaml.StringOrSlice `yaml:"destinations"`
-// 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)
- for _, destination := range destinations {
- cmdv := []string{"tag", source, destination}
- if err := Docker(name, cmdv, rt); err != nil {
-// New creates a new docker tag task.
-func (t *Tag) New() tasks.Task {
-// Valid validates the tag task.
-func (t *Tag) Valid() error {
- if t.Destination != "" {
- t.Destinations = append([]string{t.Destination}, t.Destinations...)
- if len(t.Destinations) == 0 {
- return errNoDestinationTags
--- a/docker/tag_test.go Thu Aug 26 08:11:34 2021 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
- cYaml "keep.imfreedom.org/grim/convey/yaml"
-func TestTag(t *testing.T) {
- Destination: "destination",
- assert.Nil(t, p1.Valid())
- Destinations: cYaml.StringOrSlice{"dest1", "dest2", "dest3"},
- assert.Nil(t, p2.Valid())
-func TestTagSourceRequired(t *testing.T) {
- Destination: "destination",
- assert.EqualError(t, tag.Valid(), errNoSourceTag.Error())
-func TestTagDestinationRequired(t *testing.T) {
- assert.EqualError(t, p1.Valid(), errNoDestinationTags.Error())
- Destinations: cYaml.StringOrSlice{},
- assert.EqualError(t, p2.Valid(), errNoDestinationTags.Error())
-func TestTagValidNormalizesDestinations(t *testing.T) {
- Destinations: cYaml.StringOrSlice{"dest2", "dest3"},
- assert.Nil(t, p.Valid())
- assert.Equal(t, p.Destinations, cYaml.StringOrSlice{"dest1", "dest2", "dest3"})
--- 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 @@
-// 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/>.
- 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) {
- // split the image into two pieces on the first /
- parts := strings.SplitN(image, "/", 2)
- if strings.Contains(parts[0], ".") || strings.Contains(parts[0], ":") || parts[0] == "localhost" {
- parts = strings.SplitN(image, ":", 2)
- 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)
- logger.Warnf("%s", strings.TrimSpace(stderr))
-// 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)
- logger.Errorf("error: %s", stderr)
- 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-type parseImageData struct {
-func TestParseImage(t *testing.T) {
- data := []parseImageData{
- input: "convey/workspace",
- name: "convey/workspace",
- input: "localhost/convey/workspace",
- name: "convey/workspace",
- input: "localhost:5000/convey/workspace",
- registry: "localhost:5000",
- name: "convey/workspace",
- input: "registry.docker.io/python:3",
- registry: "registry.docker.io",
- input: "registry.docker.io/rw_grim/convey:latest",
- registry: "registry.docker.io",
- name: "rw_grim/convey",
- 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/globals/globals.go Tue Oct 05 01:14:12 2021 -0500
@@ -0,0 +1,6 @@
+ 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 Tue Oct 05 01:14:12 2021 -0500
@@ -3,6 +3,7 @@
bitbucket.org/rw_grim/govcs v0.0.0-20190727184842-ba0d24484add
github.com/alecthomas/kingpin v2.2.6+incompatible
+ github.com/alecthomas/kong v0.2.17 // indirect 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
@@ -19,7 +20,7 @@
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/objx v0.1.1 // indirect
- github.com/stretchr/testify v1.4.0
+ github.com/stretchr/testify v1.7.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
--- a/go.sum Thu Aug 26 08:11:34 2021 -0500
+++ b/go.sum Tue Oct 05 01:14:12 2021 -0500
@@ -2,6 +2,8 @@
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/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0= +github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ= 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=
@@ -46,6 +48,8 @@
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/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=
@@ -60,6 +64,7 @@
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=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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=
@@ -88,3 +93,5 @@
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= --- 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 @@
-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
-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`.
-----
-An apply task will apply kubernetes manifests to the cluster.
-| 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. |
-----
-A create task will create kubernetes manifests on the cluster.
-| 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. |
-----
-A delete task will delete kubernetes manifests from the cluster.
-| 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. |
----
-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. |
--- 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 @@
-// 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/>.
- 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`.
-// Execute runs `kubectl apply` with the given arguments.
-func (a *Apply) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
- Namespace: a.Namespace,
- return cmd.Execute(name, "apply", logger, env, rt)
-// New creates a new kubectl/apply task.
-func (a *Apply) New() tasks.Task {
-// Valid check if the apply task is valid
-func (a *Apply) Valid() error {
--- 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestApplyFilesRequired(t *testing.T) {
- 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 @@
-// 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/>.
- 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{
- // make sure the export task is valid
- return export.Execute(name, logger, env, rt)
-func templateEnvironmentFile(filename string, env *environment.Environment) error {
- raw, err := ioutil.ReadFile(filename)
- // 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)
- for _, file := range files {
- absFile := filepath.Join(directory, file.Name())
- err = templateEnvironmentFile(absFile, env)
-// templateEnvironment will copy files from the workspace into the scratch
-// directory and replace environment variables with those that convey knows
-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)
- // 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)
- err = templateEnvironmentDirectory(absFile, env)
- err = templateEnvironmentFile(absFile, env)
-// 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
- err := useContext(name, c.Context, logger, env, rt.State.PlanTimeout)
- // now build the apply command line
- cmd := exec.NewGenerator("kubectl", action)
- namespace := fullEnv.Map(c.Namespace)
- cmd.Append("-n", namespace)
- selectors, err := fullEnv.MapSlice(c.Selector)
- for _, selector := range selectors {
- cmd.Append("-l", selector)
- // create our scratch directory
- td, err := rt.State.Workspace.CreateTaskDirectory(name)
- // run our files through the template engine
- err = c.templateNone(name, td.Path(), cmd, logger, fullEnv, rt)
- err = c.templateEnvironment(name, td.Path(), cmd, logger, fullEnv, rt)
- // if there was an error with templating bail
- // 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 @@
-// 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/>.
- 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 @@
-// 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/>.
- 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`.
-// Execute runs `kubectl create` with the given arguments.
-func (c *Create) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
- Namespace: c.Namespace,
- return cmd.Execute(name, "create", logger, env, rt)
-// New creates a new kubectl/create task.
-func (c *Create) New() tasks.Task {
-// Valid check if the apply task is valid
-func (c *Create) Valid() error {
--- 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestCreateFilesRequired(t *testing.T) {
- 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 @@
-// 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/>.
- 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`.
-// Execute runs `kubectl delete` with the given arguments.
-func (d *Delete) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
- Namespace: d.Namespace,
- return cmd.Execute(name, "delete", logger, env, rt)
-// New creates a new kubectl/create task.
-func (d *Delete) New() tasks.Task {
-// Valid check if the apply task is valid
-func (d *Delete) Valid() error {
--- 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestDeleteFilesRequired(t *testing.T) {
- 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 @@
-// 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/>.
- 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 @@
-// 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
- "keep.imfreedom.org/grim/convey/tasks"
- // Tasks is a map of kubectl tasks.
- Tasks = map[string]tasks.Task{
--- 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 @@
-// 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/>.
- 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.
- Context string `yaml:"context"`
- Namespace string `yaml:"namespace"`
- Target string `yaml:"target"`
-func (r *Rollout) New() tasks.Task {
-func (r *Rollout) Valid() error {
- return errors.New("no target specified")
-// 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
- err := useContext(name, r.Context, logger, env, rt.State.PlanTimeout)
- // now build the command line
- cmd := exec.NewGenerator("kubectl", "rollout", "status")
- namespace := fullEnv.Map(r.Namespace)
- cmd.Append("-n", namespace)
- // 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestRolloutTargetRequired(t *testing.T) {
- 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 @@
-// 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.
--- 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
--- 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.
--- 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 @@
- name: registry.docker.io/python:3
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - 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 @@
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - 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 @@
- name: registry.docker.io/python:3
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - 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 @@
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - 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 @@
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - 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
- - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
- - docker push atlassian/my-app:$BITBUCKET_COMMIT
--- 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 @@
- - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
- - docker push atlassian/my-app:$BITBUCKET_COMMIT
--- 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 @@
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - 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 @@
-// 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/>.
- "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"
- defaultPlan = "default"
-// Loader is a loader.Loader that loads bitbucket-pipelines.yml files
-func addPipeline(name string, defImage image, pipelines []pipeline, cfg *config.Config) error {
- Stages: []stages.Stage{
- Tasks: []string{"import"},
- for idx, pipeline := range pipelines {
- 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,
- // 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{
- } 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
- // if we have a login task, add it to the tasks for this stage
- plan.Stages[idx].Tasks = append(plan.Stages[idx].Tasks, loginTask)
- parsedTasks, err := script.Parse(img.Name, "/bin/sh", pipeline.Steps.Script)
- 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
- plan.Stages[idx].Tasks = append(plan.Stages[idx].Tasks, logoutTask)
-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)
-// 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)
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- 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,
- cfg.Tasks["logout"] = &docker.Logout{
- // add the default pipelines
- if len(pipeline.Pipelines.Default) > 0 {
- err = addPipeline("default", defImage, pipeline.Pipelines.Default, cfg)
- err = addPipelines("bookmark", defImage, pipeline.Pipelines.Bookmarks, cfg)
- err = addPipelines("branch", defImage, pipeline.Pipelines.Branches, cfg)
- err = addPipelines("custom", defImage, pipeline.Pipelines.Custom, cfg)
- err = addPipelines("tag", defImage, pipeline.Pipelines.Tags, cfg)
-// 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 {
-// DefaultPlan returns the default plan name.
-func (l *Loader) DefaultPlan() string {
- vcs, err := govcs.Detect(".")
- if branch == "master" || branch == "default" {
- return "branch-" + branch
-// ResolvePlanName resolves the plan name if the one in the config contains a
-func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, rt *runtime.Runtime) string {
- // try to shortcut if we can
- if plan == defaultPlan {
- customPlan := fmt.Sprintf("custom-%s", plan)
- if _, found := cfg.Plans[customPlan]; found {
- // we couldn't find the plan, so just return it so convey can return
- // an error saying it wasn't found.
- // we're in auto discovery mode! woo?
- matchMapper := map[string][]string{
- // 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 {
--- 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 @@
-// 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/>.
- "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) {
- data, err := ioutil.ReadFile("data/simple.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0": &docker.Run{
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "default-0"},
- assert.Equal(t, cfg, expected)
-func TestLoaderComplexGlobalImage(t *testing.T) {
- data, err := ioutil.ReadFile("data/complex-global-image.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0": &docker.Run{
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "default-0"},
- assert.Equal(t, cfg, expected)
-func TestLoaderComplexGlobalImageWithLogin(t *testing.T) {
- data, err := ioutil.ReadFile("data/complex-global-image-with-login.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "login": &docker.Login{
- Server: "registry.docker.io",
- "default-0": &docker.Run{
- Image: "registry.docker.io/python:3",
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
- "logout": &docker.Logout{
- Server: "registry.docker.io",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "login", "default-0", "logout"},
- assert.Equal(t, cfg, expected)
-func TestLoaderComplexStepSimpleImage(t *testing.T) {
- data, err := ioutil.ReadFile("data/complex-step-simple-image.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0": &docker.Run{
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "default-0"},
- assert.Equal(t, cfg, expected)
-func TestLoaderComplexStepImage(t *testing.T) {
- data, err := ioutil.ReadFile("data/complex-step-image.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0": &docker.Run{
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "default-0"},
- assert.Equal(t, cfg, expected)
-func TestLoaderComplexStepImageWithLogin(t *testing.T) {
- data, err := ioutil.ReadFile("data/complex-step-image-with-login.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0-login": &docker.Login{
- Server: "registry.docker.io",
- "default-0": &docker.Run{
- Image: "registry.docker.io/python:3",
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
- "default-0-logout": &docker.Logout{
- Server: "registry.docker.io",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "default-0-login", "default-0", "default-0-logout"},
- assert.Equal(t, cfg, expected)
-func TestLoaderBranchNoImage(t *testing.T) {
- data, err := ioutil.ReadFile("data/branch-no-image.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
-func TestLoaderBranchImage(t *testing.T) {
- data, err := ioutil.ReadFile("data/branch-image.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "branch-develop-0": &docker.Run{
- Image: "library/alpine",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- Tasks: []string{"import", "branch-develop-0"},
- assert.Equal(t, cfg, expected)
-func TestLoaderDockerMixed(t *testing.T) {
- data, err := ioutil.ReadFile("data/docker-mixed.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0": &docker.Run{
- Image: "gliderlabs/alpine:edge",
- Script: []string{"hostname"},
- "default-1": &docker.Login{
- Username: "$DOCKER_USERNAME",
- Password: "$DOCKER_PASSWORD",
- "default-2": &docker.Run{
- Image: "gliderlabs/alpine:edge",
- Script: []string{"date +%s"},
- "default-3": &docker.Build{
- 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"},
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- assert.Equal(t, cfg, expected)
-func TestLoaderDockerSimple(t *testing.T) {
- data, err := ioutil.ReadFile("data/docker-simple.yml")
- cfg, err := l.Load("", "", data, []string{}, true)
- expected := &config.Config{
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- "default-0": &docker.Login{
- Username: "$DOCKER_USERNAME",
- Password: "$DOCKER_PASSWORD",
- "default-1": &docker.Build{
- Tag: "atlassian/my-app:$BITBUCKET_COMMIT",
- "default-2": &docker.Push{
- Image: "atlassian/my-app:$BITBUCKET_COMMIT",
- Plans: map[string]plans.Plan{
- Stages: []stages.Stage{
- 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 @@
-// 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/>.
- "github.com/go-yaml/yaml"
- 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
- err := unmarshal(&name)
- // if we got a yaml type error, try to unmarshal it as the struct
- if _, ok := err.(*yaml.TypeError); ok {
- if err = unmarshal(&raw); err != nil {
- Image image `yaml:"image"`
- Script []string `yaml:"script"`
- Services []string `yaml:"services"`
- Steps step `yaml:"step"`
- 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"`
- 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"`
- 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 {
- *b = bitbucketPipelines(raw)
- Depth int `yaml:"depth"`
-func (c *clone) UnmarshalYAML(unmarshal func(interface{}) error) error {
- raw := rawClone{Depth: 50}
- if err := unmarshal(&raw); err != 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 @@
-// 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/>.
- "github.com/go-yaml/yaml"
- "github.com/stretchr/testify/assert"
-func TestUnmarshalSimple(t *testing.T) {
- yamlData := `image: python:3
- - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
- - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
- - pip install -r dev-requirements.txt
- - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests`
- var actual bitbucketPipelines
- err := yaml.Unmarshal([]byte(yamlData), &actual)
- expected := bitbucketPipelines{
- "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
- "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
- "pip install -r dev-requirements.txt",
- "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
- - ./autogen.sh --enable-debug --enable-gtk-doc
- - make -s -j$(nproc) distcheck
- image: pidgin/release-builder:release-2.x.y
- - ./autogen.sh --enable-debug
- - make -s -j$(nproc) check
- var actual bitbucketPipelines
- err := yaml.Unmarshal([]byte(yamlData), &actual)
- expected := bitbucketPipelines{
- Name: "pidgin/builder-debian:stretch",
- "./autogen.sh --enable-debug --enable-gtk-doc",
- "make -s -j$(nproc) distcheck",
- Branches: map[string][]pipeline{
- Name: "pidgin/release-builder:release-2.x.y",
- "./autogen.sh --enable-debug",
- "make -s -j$(nproc) check",
- assert.Equal(t, actual, expected)
-func TestUnmarshalWithDocker(t *testing.T) {
- yamlData := `pipelines:
- - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
- - docker push atlassian/my-app:$BITBUCKET_COMMIT
- var actual bitbucketPipelines
- err := yaml.Unmarshal([]byte(yamlData), &actual)
- expected := bitbucketPipelines{
- "docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD",
- "docker build -t atlassian/my-app:$BITBUCKET_COMMIT .",
- "docker push atlassian/my-app:$BITBUCKET_COMMIT",
- assert.Equal(t, actual, expected)
-func TestUnmarshalComplexGlobalImage(t *testing.T) {
- yamlData := `pipelines:
- var actual bitbucketPipelines
- err := yaml.Unmarshal([]byte(yamlData), &actual)
- expected := bitbucketPipelines{
- assert.Equal(t, actual, expected)
-func TestUnmarshalComplexStepImage(t *testing.T) {
- yamlData := `pipelines:
- email: test@example.com
- var actual bitbucketPipelines
- err := yaml.Unmarshal([]byte(yamlData), &actual)
- expected := bitbucketPipelines{
- EMail: "test@example.com",
- 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 @@
-// 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/
--- 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 @@
-// 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/>.
- "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.
-func (l *Loader) loadOptions(options []string) error {
- for _, opt := range options {
- parts := strings.SplitN(opt, "=", 2)
- return fmt.Errorf("invalid option '%s'", opt)
- return fmt.Errorf("no image specified")
-// 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)
- err = yaml.Unmarshal(data, &cb)
- Tasks: map[string]tasks.Task{
- "import": &tasks.Import{
- Plans: map[string]plans.Plan{},
- // create our plan and add our tasks
- Stages: []stages.Stage{
- Tasks: []string{"import"},
- // TODO put the right image here
- err = l.addPhases(cb, cfg, l.image, &plan)
- l.createExportTask(cb, cfg, &plan)
- cfg.Plans["default"] = plan
-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
- parsedTasks, err := script.Parse(imageName, "/bin/sh", phase.Commands)
- // now add the tasks to the config and the stage
- for idx, task := range parsedTasks {
- name := fmt.Sprintf("%s-%d", phaseName, idx)
- stage.Tasks = append(stage.Tasks, name)
- // add the stage to the plan
- plan.Stages = append(plan.Stages, stage)
-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)
-func (l *Loader) createExportTask(cb CodeBuild, cfg *config.Config, plan *plans.Plan) {
- if len(cb.Artifacts.Files) == 0 {
- 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{
- 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 {
-// DefaultPlan returns the default plan to use when an AWS CodeBuild
-// configuration is loaded.
-func (l *Loader) DefaultPlan() string {
-// ResolvePlanName satisfies the LoaderInterface and returns the default value.
-func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, rt *runtime.Runtime) string {
--- 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 @@
-// 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/>.
-type environment struct {
- Variables map[string]string `yaml:"variables"`
- ParameterStore map[string]string `yaml:"parameter-store"`
- Commands []string `yaml:"commands"`
- 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
- 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 @@
-// 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/>.
- "github.com/go-yaml/yaml"
- "github.com/stretchr/testify/assert"
-func TestUnmarshalSimple(t *testing.T) {
- yamlData := `version: 0.2
- JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64"
- LOGIN_PASSWORD: "dockerLoginPassword"
- - apt-get install -y maven
- - echo Nothing to do in the pre_build phase...
- - echo Build started on $(date)
- - echo Build completed on $(date)
- - target/messageUtil-1.0.jar
- err := yaml.Unmarshal([]byte(yamlData), &actual)
- 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{
- "apt-get install -y maven",
- "echo Nothing to do in the pre_build phase...",
- "echo Build started on $(date)",
- "echo Build completed on $(date)",
- "target/messageUtil-1.0.jar",
- 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 @@
-// 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.
- "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"
- DefaultPlan string `yaml:"default-plan"`
- SSHIdentities cYaml.StringOrSlice `yaml:"ssh-identities"`
- 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"`
- Environment cYaml.StringOrSlice `yaml:"environment"`
- Options options `yaml:"options"`
-// Loader is a loader.Loader for loading convey.yml files.
- 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 l.depth >= MaxExtendsDepth {
- return nil, ErrMaxExtendsDepth
- extendsAbsName := filepath.Join(path, cfg.Extends)
- baseConfig, err := l.loadFile(extendsAbsName, options, disableDeprecated)
- // 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 &cConfig.Config{
- Tasks: map[string]tasks.Task{},
- Plans: map[string]plans.Plan{},
- MetaPlans: map[string]plans.MetaPlan{},
- SSHIdentities: []string{},
-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 {
- base, ok := baseConfig.Plans[name]
- return fmt.Errorf("cannot merge with unknown plan '%s'", name)
- base, err := mergePlan(base, plan, name)
- baseConfig.Plans[name] = base
- baseConfig.Plans[name] = plan
-// 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) {
- l.logger = logging.NewAdapter("config loader")
- err := yaml.Unmarshal(data, &cfg)
- if cfg.RequiredVersion != "" {
- verRange, nErr := semver.ParseRange(cfg.RequiredVersion)
- curVer, nErr := semver.Make(consts.Version)
- "convey version %s required, but %s is in use",
- baseConfig, err := l.loadBase(cfg, path, options, disableDeprecated)
- // turn the raw tasks into real tasks
- realTasks, err := loadTasks(path, cfg.Tasks, l.logger, disableDeprecated)
- err = l.loadPlans(baseConfig, cfg)
- // 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
-// 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)
- 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 {
-// DefaultPlan returns the default plan name.
-func (l *Loader) DefaultPlan() string {
- if l.defaultPlan != "" {
-// ResolvePlanName resolves a plan name if wildcards are supported.
-func (l *Loader) ResolvePlanName(plan string, cfg *cConfig.Config, rt *runtime.Runtime) string {
--- 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 @@
-// 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/>.
- "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) {
- assert.Equal(t, l.OverrideSuffix(), "-override")
-/* ResolvePlanName tests */
-func TestResolvePlanName(t *testing.T) {
- cfg := &realConfig.Config{}
- rt := runtime.New(&state.State{})
- for _, name := range tests {
- assert.Equal(t, l.ResolvePlanName(name, cfg, rt), name)
-/* required fields tests */
-func TestTasksRequired(t *testing.T) {
- _, err := l.Load(".", ".", []byte(data), []string{}, true)
- assert.EqualError(t, err, ErrNoTasks.Error())
-func TestPlansRequired(t *testing.T) {
- _, 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-func TestDefaultPlan(t *testing.T) {
- assert.Equal(t, l.DefaultPlan(), "default")
-func TestDefaultPlanSet(t *testing.T) {
- defaultPlan: "something else",
- assert.Equal(t, l.DefaultPlan(), "something else")
-func TestDefaultPlanFromConfig(t *testing.T) {
- default-plan: overridden-plan
- cfg, err := l.Load(".", ".", []byte(data), []string{}, true)
- assert.Equal(t, l.defaultPlan, "overridden-plan")
-func TestDefaultPlanFromOverride(t *testing.T) {
- cfg, err := l.Load(".", ".", []byte(data), []string{}, true)
- assert.Equal(t, l.defaultPlan, "")
- 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
-/* These test ensure that the convey yaml config properly loads environment
-func TestEnvironmentUnmarshalGlobalSlice(t *testing.T) {
- cfg, err := loader.Load(".", ".", []byte(data), []string{}, true)
- assert.ElementsMatch(t, cfg.Environment, []string{"foo=bar", "baz"})
-func TestEnvironmentUnmarshalGlobalString(t *testing.T) {
- data := `environment: foo=bar
- cfg, err := loader.Load(".", ".", []byte(data), []string{}, true)
- assert.Equal(t, cfg.Environment, []string{"foo=bar"})
-func TestEnvironmentUnmarshalGlobalOverrideSlice(t *testing.T) {
- cfg, err := loader.Load(".", ".", []byte(baseData), []string{}, true)
- assert.Empty(t, cfg.Environment)
- loader.LoadOverride(".", ".", []byte(overrideData), cfg, true)
- assert.ElementsMatch(t, cfg.Environment, []string{"foo=bar", "baz"})
-func TestEnvironmentUnmarshalGlobalOverrideString(t *testing.T) {
- cfg, err := loader.Load(".", ".", []byte(baseData), []string{}, true)
- assert.Empty(t, cfg.Environment)
- 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 @@
-// 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/>.
- // MaxExtendsDepth is the maximum depth that configs can extend each other.
- // ErrNoTasks is used to represent a config that doesn't have any tasks
- ErrNoTasks = errors.New("no tasks specified")
- // ErrNoPlans is used to represent a config that doesn't have any plans
- 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 @@
-// 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/>.
- "github.com/stretchr/testify/assert"
- cConfig "keep.imfreedom.org/grim/convey/config"
- "keep.imfreedom.org/grim/convey/docker"
-func TestExtendNoClash(t *testing.T) {
- - tasks: [foo, bar, baz]
- 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.Len(t, cfg.Plans, 2)
- assert.Contains(t, cfg.Plans, "plan1")
- assert.Contains(t, cfg.Plans, "plan2")
- []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) {
- - tasks: [foo, bar, baz]
- 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.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")
- cfg.Plans["plan1"].Stages[0].Tasks,
- []string{"foo", "baz"},
-func TestExtendExtendDefault(t *testing.T) {
- 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.Equal(t, loader.defaultPlan, "plan2")
-func TestExtendExtendTasksOnly(t *testing.T) {
- 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.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) {
- extendedData := `extends: base.yml`
- 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) {
- extendedData := `extends: base.yaml`
- 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) {
- 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) {
- fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
- 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 @@
-// 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/>.
- "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)",
- parent.Environment = environment.Merge(parent.Environment, child.Environment)
-func replaceStage(plan plans.Plan, stage stages.Stage) bool {
- for i, match := range plan.Stages {
- if match.Name == stage.Name {
--- 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 @@
-// 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/>.
- "github.com/go-yaml/yaml"
- log "github.com/sirupsen/logrus"
- cConfig "keep.imfreedom.org/grim/convey/config"
- "keep.imfreedom.org/grim/convey/tasks"
- 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.
- for _, item := range yamlTask {
- if key, ok := item.Key.(string); ok && key == "type" {
- if value, ok := item.Value.(string); ok {
- task, ok := cConfig.TasksMap[rawType]
- // if we didn't find the task, check deprecated and old naming formats
- if depTask, found := deprecatedTasks[rawType]; found {
- 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]
- return nil, fmt.Errorf(
- "task %q is deprecated, but can't find task %q that replaces it",
- return nil, fmt.Errorf("task type %q not found", rawType)
- realTask, err := tasks.CloneTask(yamlTask, task)
- return nil, fmt.Errorf("%s: %s", name, err.Error())
-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)
- 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
- return nil, fmt.Errorf("failed to find parent task %q for task %q", subtask.Base, name)
--- a/main.go Thu Aug 26 08:11:34 2021 -0500
+++ b/main.go Tue Oct 05 01:14:12 2021 -0500
@@ -18,237 +18,56 @@
- "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"
- 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)
- _ = 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 = 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()
+ fmt.Printf("additional information...\n")
- 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 {
- return &bitbucket.Loader{}
- return &codebuild.Loader{}
- return &convey.Loader{}
-// determineRunner will return the runner that handles the given command.
-func determineRunner(command string) runners.Runner {
- return &runners.Graphviz{}
- case "list environment":
- return &runners.ListEnvironment{}
- return &runners.ListMetaPlans{}
- return &runners.ListPlans{}
- return &runners.ListTasks{}
- return &runners.ShowConfig{}
- 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...)
- realPlans = append(realPlans, loader.ResolvePlanName(planName, cfg, rt))
+ Run runner.RunnerCmd `kong:"cmd,help='Run plans'"` + Version VersionCmd `kong:"cmd,help='Show the version and exit'"` -// 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
- for _, filename := range loader.Filenames() {
- if _, err := os.Stat(filename); os.IsNotExist(err) {
- // now make sure we found a config file
- err := fmt.Errorf("config file not found, looking for %s", strings.Join(loader.Filenames(), ","))
- // figure out the path to the config file
- cfgPath, err := filepath.Abs(filepath.Dir(*configFile))
- cfg, err := config.LoadFile(*configFile, loader, *options, *disableDeprecated)
- return cfg, cfgPath, nil
- command, err := app.Parse(args)
- logging.Setup(*color, *verbose)
- defer logging.Shutdown()
- // create a logger to use now that logging is set up
- logger := logging.NewAdapter("convey")
- loader := determineLoader()
- cfg, cfgPath, err := loadConfig(loader)
- logger.Errorf("Could not load config: %s", err)
+ app = kingpin.New("convey", "Convey is a container pipeline runner.").Version(consts.Version) - // 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)
- // if the user specified the shortcut, add * to the list of acceptable keys
- *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)
- logger.Errorf("Could not configure ssh agent: %s", err)
- runner := determineRunner(command)
- if len(*planNames) == 0 {
- *planNames = []string{loader.DefaultPlan()}
- // set the state's variables and validate it
- st.KeepWorkspace = *keep
- st.DisableDeprecated = *disableDeprecated
- st.ForceSequential = *forceSequential
- st.EnableSSHAgent = enableSSHAgent
- st.PlanTimeout = *planTimeout
- st.DockerConfig = *dockerConfig
- st.CPUShares = *cpuShares
- if err := st.Valid(); err != nil {
- logger.Errorf("Error validating convey state: %s", err)
- // finally create our runtime
- fullEnv := environment.New().Merge(defEnv).MergeSlice(*env)
- rt := runtime.NewWithEnvironment(st, fullEnv)
- // resolve the plan name with the load and options
- realPlans := resolvePlans(cfg, loader, rt)
- return runner.Run(cfg, realPlans, *env, rt)
+ configFile = app.Flag("config", "The config file name to use").Short('f').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() + planTimeout = app.Flag("timeout", "The maximum amount of time a plan can run. 0 to disable. Units must be specified.").Default("15m").Duration()
- if r := recover(); r != nil {
- fmt.Printf("panic: %s\n%s", r, debug.Stack())
+ kong.Description("Convey is a container pipeline runner."), + kong.ConfigureHelp(kong.HelpOptions{
+ err := ctx.Run(&cli.Globals) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/metaplans/metaplans.go Tue Oct 05 01:14:12 2021 -0500
@@ -0,0 +1,44 @@
+// 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/>. +// MetaPlan is a representation of a meta plan. + 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 + if err := unmarshal(&raw); err != nil { + if len(raw.Plans) == 0 { + return fmt.Errorf("no plans specified") --- 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 @@
-// 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/>.
-// MetaPlan is a representation of a meta plan.
- 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
- if err := unmarshal(&raw); err != nil {
- if len(raw.Plans) == 0 {
- return fmt.Errorf("no plans specified")
--- a/plans/plans.go Thu Aug 26 08:11:34 2021 -0500
+++ b/plans/plans.go Tue Oct 05 01:14:12 2021 -0500
@@ -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"
@@ -47,19 +46,6 @@
-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 {
// Execute runs the plan.
func (p *Plan) Execute(path string, tasks map[string]tasks.Task, env *environment.Environment, rt *runtime.Runtime) error {
if err := p.Valid(); err != nil {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/cmd.go Tue Oct 05 01:14:12 2021 -0500
@@ -0,0 +1,44 @@
+ "keep.imfreedom.org/grim/convey/config" + "keep.imfreedom.org/grim/convey/environment" + "keep.imfreedom.org/grim/convey/globals" + ConfigFile string `kong:"flag,help='The config file to load',placeholder='FILE',short='f',default='convey.yml'"` + Plans []string `kong:"arg,help='The names of the plans to run',default='default'"` +func (c *RunnerCmd) Run(g *globals.Globals) error { + cfg, err := config.LoadFile(c.ConfigFile) + return fmt.Errorf("config is invalid: %s", err) + err = cfg.HasPlans(c.Plans) + env := environment.New(cfg.Environment...) + for _, name := range c.Plans { + plan := cfg.Plans[name] + err := plan.Execute(name, cfg.Tasks, env, 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 @@
-// 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/>.
- "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.
-// 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 {
- logger.Errorf("%s", err)
- err = plan.Execute(planName, cfg.Tasks, environment.New(cfg.Environment...), rt)
- logger.Errorf("%s", err)
- logger.Fatalf("plan %s not found", planName)
--- 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 @@
-// 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/>.
- "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 = `
- node[style="filled,rounded" shape="rect"]
- node[shape="rect" fillcolor="plum"]
-{{range .MetaPlans}} {{.Normalized}}_start[label="{{.Name}} start"]
- {{.Normalized}}_finish[label="{{.Name}} finish"]
- # 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"]
- node[style="filled,rounded" shape="rect" fillcolor="powderblue"]
-{{range .Tasks}} {{.Normalized}}[label="{{.Name}}"]
- # add the stages for {{.Name}}
- edge[color="{{.Color}}"]{{$planNormalized := .Normalized }}
- {{$planNormalized}}_{{.Normalized}} [label="{{.Name}}" shape="octagon" fillcolor="{{if .Enabled}}lightpink1{{else}}gray{{end}}"{{if eq ".Run" "always"}} style="bold,filled,rounded"{{end}}]{{end}}
- {{.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"]
- edge[color="{{.Color}}" style="dashed,bold"]
- {{.Normalized}}_start -> {{range .Plans}}{{.Normalized}}_start
- {{.Normalized}}_finish -> {{end}} {{.Normalized}}_finish
-var normalizeRegex *regexp.Regexp
- // Graphviz is a Runner that will create a graphviz output for the config
- // that has been loaded.
- FallThroughs []graphvizStage
- graphvizMetaPlan struct {
-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 {
- Normalized: normalize("task", name),
- tasks = append(tasks, task)
-func (g *Graphviz) getMetaPlans(cfg *config.Config) []graphvizMetaPlan {
- metaPlans := []graphvizMetaPlan{}
- for name, cMetaPlan := range cfg.MetaPlans {
- metaPlan := graphvizMetaPlan{
- Normalized: normalize("metaplan", 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)
-func (g Graphviz) plan(name string, plan plans.Plan) graphvizPlan {
- gvizPlan := graphvizPlan{
- Normalized: normalize("plan", name),
- Color: color.X11(name),
- Stages: []graphvizStage{},
- for idx, stage := range plan.Stages {
- gvizStage := graphvizStage{
- Normalized: normalize("stage", stage.Name),
- Enabled: stage.Enabled,
- Concurrent: stage.Concurrent,
- 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)
-func (g *Graphviz) getPlans(cfg *config.Config) []graphvizPlan {
- plans := []graphvizPlan{}
- for name, plan := range cfg.Plans {
- plans = append(plans, g.plan(name, plan))
-// 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),
- tmpl, err := template.New("graphviz").Parse(graphvizTemplate)
- logger.Fatalf("error: %s", err)
- output := new(bytes.Buffer)
- err = tmpl.Execute(output, params)
- logger.Fatalf("error: %s", err)
--- 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 @@
-// 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/>.
- "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()
- for _, key := range stateEnv {
- fmt.Printf(" %s=%s\n", key, os.Getenv(key))
- fmt.Printf("Config:\n")
- cfgEnv := cfg.Environment
- for _, env := range cfgEnv {
- if strings.Contains(env, "=") {
- fmt.Printf(" %s\n", env)
- fmt.Printf(" %s=%s\n", env, os.Getenv(env))
--- 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 @@
-// 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/>.
- "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)
--- 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 @@
-// 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/>.
- "keep.imfreedom.org/grim/convey/config"
- "keep.imfreedom.org/grim/convey/runtime"
-// ListPlans is a Runner that outputs the plans in the loaded config.
-// 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)
--- 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 @@
-// 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/>.
- "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.
-// Run runs the ListTasks runner.
-func (l *ListTasks) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
- for taskName := range config.TasksMap {
- tasks = append(tasks, taskName)
- for _, taskName := range tasks {
- fmt.Printf(" %s\n", taskName)
--- 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 @@
-// 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.
- "keep.imfreedom.org/grim/convey/config"
- "keep.imfreedom.org/grim/convey/runtime"
-// Runner is an interface for defining something that convey can run.
- 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 @@
-// 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/>.
- "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 {
--- 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 @@
-// 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/>.
- "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
-func KeysAvailable(identites []string) (bool, error) {
- sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
- return keysAvailable(sshAgent, identites)
-func keysAvailable(rw io.ReadWriter, identities []string) (bool, error) {
- keys, err := agent.NewClient(rw).List()
- // this runs in O(m * n) but thankful the number of keys should be low.
- for _, identity := range identities {
- // if the user doesn't care which key and we have a key, return true
- // 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 {
- } else if strings.HasPrefix(uIdentity, "MD5:") {
- if uIdentity[4:] == fpMD5 {
- // no known prefix check sha256 then md5, then give up
- if uIdentity == fpSHA256[7:] || uIdentity == fpMD5 {
- err = fmt.Errorf("no usable ssh identities found")
-// ShouldEnable returns true if the ssh agent should be enabled.
-func ShouldEnable(identities []string) (bool, error) {
- if len(identities) <= 0 {
- haveKeys, err := KeysAvailable(identities)
- return false, fmt.Errorf("error talking to ssh-agent: %s", err)
- return false, fmt.Errorf("no keys available in ssh-agent")
--- 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 @@
-// 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/>.
- "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 := agent.ServeAgent(a, c2)
-func generateKey() *ecdsa.PrivateKey {
- priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
-func TestKeysAvailableEmpty(t *testing.T) {
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{}),
-func TestKeysAvailableWildcardNoKeys(t *testing.T) {
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{}),
-func TestKeysAvailableWildcardWithKeys(t *testing.T) {
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{
- {PrivateKey: generateKey()},
-func TestKeysAvailableUnknownFingerprint(t *testing.T) {
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{
- {PrivateKey: generateKey()},
- []string{"SHA256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"},
-func TestKeysAvailableFingerprintMD5NoPrefix(t *testing.T) {
- sshPub, err := ssh.NewPublicKey(pub)
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{
- []string{ssh.FingerprintLegacyMD5(sshPub)},
-func TestKeysAvailableFingerprintMD5Prefix(t *testing.T) {
- sshPub, err := ssh.NewPublicKey(pub)
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{
- []string{"MD5:" + ssh.FingerprintLegacyMD5(sshPub)},
-func TestKeysAvailableFingerprintSHA256Prefix(t *testing.T) {
- sshPub, err := ssh.NewPublicKey(pub)
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{
- []string{ssh.FingerprintSHA256(sshPub)},
-func TestKeysAvailableFingerprintSHA256NoPrefix(t *testing.T) {
- sshPub, err := ssh.NewPublicKey(pub)
- // FingerprintSHA256 always has the prefix so we strip it off for testing
- avail, err := keysAvailable(
- setupAgent([]agent.AddedKey{
- []string{ssh.FingerprintSHA256(sshPub)[7:]},
--- 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 @@
-// 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.
--- a/tasks/clone.go Thu Aug 26 08:11:34 2021 -0500
+++ b/tasks/clone.go Tue Oct 05 01:14:12 2021 -0500
@@ -23,7 +23,7 @@
// 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)