grim/convey

Start of the refactor
redux
2021-10-05, Gary Kramlich
6ef456e4e8de
Parents 199d33e4d014
Children c0672a94a7d8
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
  • +0 -20
    aws/README.md
  • +0 -29
    aws/aws.go
  • +0 -21
    aws/data/ecr-test.yml
  • +0 -74
    aws/ecr-get-login.go
  • +0 -57
    aws/parser.go
  • +19 -6
    config/config.go
  • +12 -52
    config/loader.go
  • +0 -86
    config/override_test.go
  • +72 -0
    config/raw.go
  • +4 -22
    config/tasks.go
  • +1 -1
    consts/consts.go
  • +0 -8
    convey.sublime-project
  • +37 -37
    convey.yml
  • +0 -303
    docker/README.md
  • +0 -132
    docker/build.go
  • +0 -88
    docker/build_test.go
  • +0 -65
    docker/docker.go
  • +0 -142
    docker/environment.go
  • +0 -58
    docker/environment_test.go
  • +0 -35
    docker/errors.go
  • +0 -79
    docker/healthcheck.go
  • +0 -72
    docker/login.go
  • +0 -49
    docker/login_test.go
  • +0 -60
    docker/logout.go
  • +0 -37
    docker/logout_test.go
  • +0 -124
    docker/parser.go
  • +0 -41
    docker/parser_test.go
  • +0 -71
    docker/pull.go
  • +0 -57
    docker/pull_test.go
  • +0 -70
    docker/push.go
  • +0 -57
    docker/push_test.go
  • +0 -77
    docker/remove.go
  • +0 -57
    docker/remove_test.go
  • +0 -418
    docker/run.go
  • +0 -37
    docker/run_test.go
  • +0 -77
    docker/tag.go
  • +0 -73
    docker/tag_test.go
  • +0 -79
    docker/util.go
  • +0 -89
    docker/util_test.go
  • +6 -0
    globals/globals.go
  • +2 -1
    go.mod
  • +7 -0
    go.sum
  • +0 -99
    kubectl/README.md
  • +0 -55
    kubectl/apply.go
  • +0 -31
    kubectl/apply_test.go
  • +0 -191
    kubectl/command.go
  • +0 -35
    kubectl/context.go
  • +0 -55
    kubectl/create.go
  • +0 -31
    kubectl/create_test.go
  • +0 -55
    kubectl/delete.go
  • +0 -31
    kubectl/delete_test.go
  • +0 -25
    kubectl/errors.go
  • +0 -33
    kubectl/kubectl.go
  • +0 -74
    kubectl/rollout.go
  • +0 -31
    kubectl/rollout_test.go
  • +0 -18
    loaders/bitbucket/bitbucket.go
  • +0 -10
    loaders/bitbucket/data/branch-image.yml
  • +0 -9
    loaders/bitbucket/data/branch-no-image.yml
  • +0 -15
    loaders/bitbucket/data/complex-global-image-with-login.yml
  • +0 -12
    loaders/bitbucket/data/complex-global-image.yml
  • +0 -14
    loaders/bitbucket/data/complex-step-image-with-login.yml
  • +0 -12
    loaders/bitbucket/data/complex-step-image.yml
  • +0 -11
    loaders/bitbucket/data/complex-step-simple-image.yml
  • +0 -13
    loaders/bitbucket/data/docker-mixed.yml
  • +0 -9
    loaders/bitbucket/data/docker-simple.yml
  • +0 -11
    loaders/bitbucket/data/simple.yml
  • +0 -277
    loaders/bitbucket/loader.go
  • +0 -514
    loaders/bitbucket/loader_test.go
  • +0 -128
    loaders/bitbucket/types.go
  • +0 -249
    loaders/bitbucket/unmarshal_test.go
  • +0 -19
    loaders/codebuild/codebuild.go
  • +0 -210
    loaders/codebuild/loader.go
  • +0 -41
    loaders/codebuild/types.go
  • +0 -101
    loaders/codebuild/unmarshal_test.go
  • +0 -288
    loaders/convey/convey.go
  • +0 -77
    loaders/convey/convey_test.go
  • +0 -87
    loaders/convey/default_plan_test.go
  • +0 -94
    loaders/convey/environment_test.go
  • +0 -40
    loaders/convey/errors.go
  • +0 -301
    loaders/convey/extends_test.go
  • +0 -54
    loaders/convey/plans.go
  • +0 -109
    loaders/convey/tasks.go
  • +35 -216
    main.go
  • +44 -0
    metaplans/metaplans.go
  • +0 -44
    plans/metaplans.go
  • +0 -14
    plans/plans.go
  • +44 -0
    runner/cmd.go
  • +0 -56
    runners/convey.go
  • +0 -235
    runners/graphviz.go
  • +0 -53
    runners/listenvironment.go
  • +0 -36
    runners/listmetaplans.go
  • +0 -36
    runners/listplans.go
  • +0 -43
    runners/listtasks.go
  • +0 -28
    runners/runners.go
  • +0 -34
    runners/showconfig.go
  • +0 -105
    ssh/agent.go
  • +0 -188
    ssh/agent_test.go
  • +0 -18
    ssh/ssh.go
  • +1 -1
    tasks/clone.go
  • --- a/aws/README.md Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,20 +0,0 @@
    -# AWS
    -
    -The aws package provides tasks for interacting with Amazon Web Services.
    -
    -## aws/ecr-login
    -
    -The `aws/ecr-login` task will log into AWS Elastic Container Registry. It
    -depends on the AWS cli package being installed and configured to work.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| region | | | The AWS region to used. |
    -
    -### Example
    -
    - login:
    - type: aws/ecr-login
    -
    --- a/aws/aws.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,29 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package aws contains the tasks for interacting with AWS.
    -package aws
    -
    -import (
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - // Tasks is a map of intrinsic tasks.
    - Tasks = map[string]tasks.Task{
    - "ecr-login": &ECRGetLogin{},
    - }
    -)
    --- a/aws/data/ecr-test.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,21 +0,0 @@
    ----
    -environment:
    - - REGION=us-east-2
    - - ACCOUNT_ID=1234567890
    -tasks:
    - ecr-login:
    - type: aws/ecr-login
    - tag:
    - type: docker/tag
    - source: alpine:edge
    - destination: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/alpine:edge
    - push:
    - type: docker/push
    - image: ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/alpine:edge
    -plans:
    - default:
    - stages:
    - - tasks:
    - - ecr-login
    - - tag
    - - push
    --- a/aws/ecr-get-login.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,74 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package aws
    -
    -import (
    - "github.com/kballard/go-shellquote"
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ECRGetLogin represents an `aws ecr get-login` command.
    -type ECRGetLogin struct {
    - Region string `yaml:"region"`
    -}
    -
    -// Execute runs the `aws ecr get-login` command and calls docker login with the
    -// results.
    -func (ecr *ECRGetLogin) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - cmd := exec.NewGenerator("aws", "ecr", "get-login")
    - region := fullEnv.Map(ecr.Region)
    - if region != "" {
    - cmd.Append("--region", region)
    - }
    -
    - stdout, stderr, err := exec.RunOutput(name, cmd.Command(), rt.State.PlanTimeout)
    - if err != nil {
    - logger.Warnf("error: %s", stderr)
    -
    - return err
    - }
    -
    - argv, err := shellquote.Split(stdout)
    - if err != nil {
    - return err
    - }
    -
    - task, err := docker.ParseCommand(argv)
    - if err != nil {
    - return err
    - }
    -
    - return task.Execute(name, logger, env, rt)
    -}
    -
    -// New creates a aws/ecr-get-login task.
    -func (ecr *ECRGetLogin) New() tasks.Task {
    - return &ECRGetLogin{}
    -}
    -
    -// Valid checks that the build task is valid.
    -func (ecr *ECRGetLogin) Valid() error {
    - return nil
    -}
    --- a/aws/parser.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,57 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package aws
    -
    -import (
    - "fmt"
    -
    - "github.com/alecthomas/kingpin"
    -
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ParseCommand is a super stripped down kingpin parser that will parse aws
    -// command line options and return a convey task or an error
    -func ParseCommand(argv []string) (tasks.Task, error) {
    - app := kingpin.New("", "")
    -
    - ecr := app.Command("ecr", "")
    -
    - getLogin := ecr.Command("get-login", "")
    - region := getLogin.Flag("region", "").String()
    - getLogin.Flag("no-include-email", "").Bool()
    -
    - cmd, err := app.Parse(argv[1:])
    - if err != nil {
    - return nil, err
    - }
    -
    - var task tasks.Task
    -
    - switch cmd {
    - case "ecr get-login":
    - task = &ECRGetLogin{
    - Region: *region,
    - }
    - }
    -
    - if task != nil {
    - return task, nil
    - }
    -
    - return nil, fmt.Errorf("unable to parse aws command line '%v'", argv)
    -}
    --- a/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 @@
    import (
    "fmt"
    + "keep.imfreedom.org/grim/convey/metaplans"
    "keep.imfreedom.org/grim/convey/plans"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    // Config represents a full convey configuration.
    type Config struct {
    - Tasks map[string]tasks.Task
    - Plans map[string]plans.Plan
    - MetaPlans map[string]plans.MetaPlan
    - SSHIdentities []string
    - Environment []string
    + Tasks map[string]tasks.Task
    + Plans map[string]plans.Plan
    + MetaPlans map[string]metaplans.MetaPlan
    + Environment []string
    }
    // Valid returns an error if the command is valid or not.
    func (c *Config) Valid() error {
    for name, task := range c.Tasks {
    if err := task.Valid(); err != nil {
    - return fmt.Errorf("error: task %s : %s", name, err.Error())
    + return fmt.Errorf("error: task %q : %q", name, err.Error())
    }
    }
    return nil
    }
    +
    +// HasPlans checks if the given plans exist
    +func (c *Config) HasPlans(plans []string) error {
    + for _, name := range plans {
    + if _, found := c.Plans[name]; !found {
    + if _, found := c.MetaPlans[name]; !found {
    + return fmt.Errorf("no plan or metaplan named %q found", name)
    + }
    + }
    + }
    +
    + return nil
    +}
    --- a/config/loader.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/config/loader.go Tue Oct 05 01:14:12 2021 -0500
    @@ -17,67 +17,27 @@
    package config
    import (
    - "fmt"
    "io/ioutil"
    - "os"
    - "path/filepath"
    - "strings"
    - "keep.imfreedom.org/grim/convey/runtime"
    + "github.com/go-yaml/yaml"
    )
    -// Loader defines all the functions that a config loader needs to implement.
    -type Loader interface {
    - Load(path, base string, data []byte, options []string, disableDeprecated bool) (*Config, error)
    - LoadOverride(path, base string, data []byte, config *Config, disableDeprecated bool)
    - Filenames() []string
    - OverrideSuffix() string
    - DefaultPlan() string
    - ResolvePlanName(plan string, cfg *Config, rt *runtime.Runtime) string
    -}
    +func LoadString(data []byte) (*Config, error) {
    + var cfg rawConfig
    -func determineFilename(filename string, loader Loader) string {
    - ext := filepath.Ext(filename)
    - base := strings.TrimSuffix(filename, ext)
    + err := yaml.Unmarshal(data, &cfg)
    + if err != nil {
    + return nil, err
    + }
    - return base + loader.OverrideSuffix() + ext
    + return cfg.process()
    }
    -func loadOverride(path, base string, loader Loader, cfg *Config, disableDeprecated bool) {
    - overrideFilename := determineFilename(base, loader)
    -
    - absName := filepath.Join(path, overrideFilename)
    - if _, err := os.Stat(absName); os.IsNotExist(err) {
    - // no override so bail
    - return
    - }
    -
    - data, err := ioutil.ReadFile(absName)
    +func LoadFile(filename string) (*Config, error) {
    + contents, err := ioutil.ReadFile(filename)
    if err != nil {
    - fmt.Printf("error in override file '%s' : %s", absName, err)
    - return
    + return nil, 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)
    - if err != nil {
    - return nil, fmt.Errorf("failed to read config file '%s'", file)
    - }
    -
    - cfg, err := loader.Load(path, base, data, options, disableDeprecated)
    - if err != nil {
    - return cfg, err
    - }
    -
    - loadOverride(path, base, loader, cfg, disableDeprecated)
    -
    - return cfg, nil
    -}
    --- a/config/override_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,86 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package config
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -type testLoader struct{}
    -
    -func (l *testLoader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*Config, error) {
    - return nil, nil
    -}
    -
    -func (l *testLoader) LoadOverride(path, base string, data []byte, cfg *Config, disableDeprecated bool) {
    -}
    -
    -func (l *testLoader) OverrideSuffix() string {
    - return "-test-override"
    -}
    -
    -func (l *testLoader) Filenames() []string {
    - return []string{"test-loader.yml"}
    -}
    -
    -func (l *testLoader) DefaultPlan() string {
    - return "default"
    -}
    -
    -func (l *testLoader) ResolvePlanName(plan string, cfg *Config, rt *runtime.Runtime) string {
    - return plan
    -}
    -
    -func TestDetermineFilenameDefault(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(
    - t,
    - determineFilename("convey.yml", l),
    - "convey"+l.OverrideSuffix()+".yml",
    - )
    -}
    -
    -func TestDetermineFilenameEmpty(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(t, determineFilename("", l), l.OverrideSuffix())
    -}
    -
    -func TestDetermineFilenameCustom(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(
    - t,
    - determineFilename("foo.yml", l),
    - "foo"+l.OverrideSuffix()+".yml",
    - )
    -}
    -
    -func TestDetermineFilenameUnicodeSnowman(t *testing.T) {
    - l := &testLoader{}
    -
    - assert.Equal(
    - t,
    - determineFilename("☃.yml", l),
    - "☃"+l.OverrideSuffix()+".yml",
    - )
    -}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/config/raw.go Tue Oct 05 01:14:12 2021 -0500
    @@ -0,0 +1,72 @@
    +package config
    +
    +import (
    + "fmt"
    +
    + "github.com/go-yaml/yaml"
    +
    + "keep.imfreedom.org/grim/convey/metaplans"
    + "keep.imfreedom.org/grim/convey/plans"
    + "keep.imfreedom.org/grim/convey/tasks"
    +)
    +
    +type rawConfig struct {
    + Environment []string
    + Tasks map[string]yaml.MapSlice
    + Plans map[string]plans.Plan
    + MetaPlans map[string]metaplans.MetaPlan `yaml:"meta-plans"`
    +}
    +
    +func (r *rawConfig) processTasks() (map[string]tasks.Task, error) {
    + realTasks := map[string]tasks.Task{}
    +
    + for name, rawTask := range r.Tasks {
    + taskType := ""
    +
    + for _, item := range rawTask {
    + if item.Key == "type" {
    + rawType, ok := item.Value.(string)
    + if !ok {
    + return nil, fmt.Errorf("the type value for task %q is not a string", name)
    + }
    + taskType = rawType
    + }
    + }
    +
    + if taskType == "" {
    + return nil, fmt.Errorf("task %q is missing its type", name)
    + }
    +
    + // look for the real task in the taskmap
    + templateTask, found := tasksMap[taskType]
    + if !found {
    + return nil, fmt.Errorf("task %q has unknown type %q", name, taskType)
    + }
    +
    + realTask, err := tasks.Clone(rawTask, templateTask)
    + if err != nil {
    + return nil, err
    + }
    +
    + realTasks[name] = realTask
    + }
    +
    + return realTasks, nil
    +}
    +
    +func (r *rawConfig) process() (*Config, error) {
    + cfg := Config{
    + Environment: r.Environment,
    + MetaPlans: r.MetaPlans,
    + Plans: r.Plans,
    + }
    +
    + tasks, err := r.processTasks()
    + if err != nil {
    + return nil, err
    + }
    +
    + cfg.Tasks = tasks
    +
    + return &cfg, nil
    +}
    --- a/config/tasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/config/tasks.go Tue Oct 05 01:14:12 2021 -0500
    @@ -17,38 +17,20 @@
    package config
    import (
    - "keep.imfreedom.org/grim/convey/aws"
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/kubectl"
    "keep.imfreedom.org/grim/convey/tasks"
    )
    // TaskMap is a type alias for a map of task names to tasks.
    -type TaskMap map[string]tasks.Task
    +type taskMap map[string]tasks.Task
    var (
    - // TasksMap is a lookup table for tasks
    - TasksMap = map[string]tasks.Task{}
    + // tasksMap is a lookup table for tasks
    + tasksMap = map[string]tasks.Task{}
    )
    func init() {
    - // add the aws tasks
    - for taskName, task := range aws.Tasks {
    - TasksMap["aws/"+taskName] = task
    - }
    -
    - // add the docker tasks
    - 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 @@
    const (
    // Version is the current version of convey
    - Version = "0.14.0-dev"
    + Version = "0.15.0-dev"
    )
    --- a/convey.sublime-project Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,8 +0,0 @@
    -{
    - "folders":
    - [
    - {
    - "path": "."
    - }
    - ]
    -}
    --- a/convey.yml Thu Aug 26 08:11:34 2021 -0500
    +++ b/convey.yml Tue Oct 05 01:14:12 2021 -0500
    @@ -20,43 +20,43 @@
    type: convey/clean
    files:
    - convey-*
    - go-test:
    - type: docker/run
    - image: ${GO_IMAGE}
    - workdir: ${CONVEY_WORKSPACE}
    - command: go test ./...
    - build:
    - type: docker/run
    - image: ${GO_IMAGE}
    - environment:
    - - GOARCH=amd64
    - workdir: ${CONVEY_WORKSPACE}
    - command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}${SUFFIX}
    - build-linux:
    - type: convey/subtask
    - base: build
    - environment:
    - - CGO_ENABLED=0
    - - GOOS=linux
    - - SUFFIX=
    - build-windows:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=windows
    - - SUFFIX=.exe
    - build-darwin:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=darwin
    - - SUFFIX=
    - build-freebsd:
    - type: convey/subtask
    - base: build
    - environment:
    - - GOOS=freebsd
    - - SUFFIX=
    + # go-test:
    + # type: docker/run
    + # image: ${GO_IMAGE}
    + # workdir: ${CONVEY_WORKSPACE}
    + # command: go test ./...
    + # build:
    + # type: docker/run
    + # image: ${GO_IMAGE}
    + # environment:
    + # - GOARCH=amd64
    + # workdir: ${CONVEY_WORKSPACE}
    + # command: go build -o convey-${CONVEY_VERSION}-${GOOS}-${GOARCH}${SUFFIX}
    + # build-linux:
    + # type: convey/subtask
    + # base: build
    + # environment:
    + # - CGO_ENABLED=0
    + # - GOOS=linux
    + # - SUFFIX=
    + # build-windows:
    + # type: convey/subtask
    + # base: build
    + # environment:
    + # - GOOS=windows
    + # - SUFFIX=.exe
    + # build-darwin:
    + # type: convey/subtask
    + # base: build
    + # environment:
    + # - GOOS=darwin
    + # - SUFFIX=
    + # build-freebsd:
    + # type: convey/subtask
    + # base: build
    + # environment:
    + # - GOOS=freebsd
    + # - SUFFIX=
    plans:
    default:
    --- 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 @@
    -# Docker
    -
    -The docker package provides tasks for interacting with docker.
    -
    -## docker/build Task
    -
    -A build task will build a docker image.
    -
    -### Attributes
    -
    -| 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`.
    -
    -### Example
    -
    - build-image:
    - type: docker/build
    - dockerfile: Dockerfile
    - tag: myimage:latest
    -
    -----
    -
    -## 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.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ---------- | -------- | ------- | ----------- |
    -| from-file | | | A file that should be read. |
    -| from-files | | | A list of files that should be read. |
    -| prefix | | | A prefix to add to each variable read from a file |
    -
    -At least one file must be supplied by either the `from-file` or `from-files` attributes. If both are supplied,
    -`from-file` is inserted to the front of `from-files`. If the files being read are a result of environment variable
    -expansion, the order that the files are read are not guaranteed to be stable (or in the order supplied). Be cautious
    -of this if the environment files define overlapping variables.
    -
    -### Example
    -
    - inject-version:
    - type: docker/environment
    - from-file: version.txt
    - prefix: "APP_"
    -
    -----
    -
    -## docker/export Task
    -
    -An export task copies files from the volume to the host.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----- | -------- | ------- | ----------- |
    -| files | Yes | | A single filename or a list of files to copy from the workspace to the host. Files can be specified in one of two forms which can be mixed. The first is `source:destination` and the other is `filename` where filename will be used for both the host and the volume. |
    -
    -### Examples
    -
    - export:
    - type: docker/export
    - files: filename1
    -
    - export:
    - type: docker/export
    - files:
    - - logo.png
    - - binary:binary-linux-x86_64
    -
    -----
    -
    -## docker/import Task
    -
    -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.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----- | -------- | ------- | ----------- |
    -| files | Yes | | A single filename or a list of files to copy from the host to the workspace. Files can be specified in one of two forms which can be mixed. The first is `source:destination` and the other is `filename` where filename will be used for both the host and the volume. |
    -
    -### Examples
    -
    - import:
    - type: docker/import
    - files: filename1
    -
    - import:
    - type: docker/import
    - files:
    - - Dockerfile
    - - src:source
    -
    -----
    -
    -## docker/login Task
    -
    -A login task will run docker login to login to a registry.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| -------- | -------- | ------- | ----------- |
    -| username | Yes | | The username to login with. |
    -| password | Yes | | The password to login with. |
    -| server | | | The server to login to. If omitted docker hub is used. |
    -
    -### Example
    -
    - registry-login:
    - type: docker/login
    - username: superuser1
    - password: abc123
    -
    -----
    -
    -## docker/logout Task
    -
    -A logout task will log you out from a Docker registry.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| server | | | The server to logout from. If omitted docker hub is used. |
    -
    -### Example
    -
    - registry-logout:
    - type: docker/logout
    - server: registry.my.domain:5000
    -
    -----
    -
    -## docker/pull Task
    -
    -A pull task pulls an image.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| image | | | The name including the tag and registry of the image to pull. |
    -| images | | | A list of images as described above. |
    -
    -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`.
    -
    -### Example
    -
    - pull-alpine:
    - type: docker/pull
    - image: gliderlabs/alpine:edge
    -
    -----
    -
    -## docker/push Task
    -
    -A push task pushes an image.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| image | | | The name including the tag and registry of the image to push. |
    -| images | | | A list of images as described above. |
    -
    -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`.
    -
    -### Example
    -
    - push-image:
    - type: docker/push
    - images:
    - - registry.my.domain:5000/newimage:master-latest
    - - registry.my.domain:5000/newimage:master-deadbeef
    -
    -----
    -
    -## docker/remove Task
    -
    -A remove task removes an image.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------ | -------- | ------- | ----------- |
    -| image | | | The name including the tag and registry of the image to remove. |
    -| images | | | A list of images as described above. |
    -| quiet | | false | True if a missing image should not count as a task failure. |
    -
    -At least one image must be supplied by either the `image` or `images` attribute. If both are
    -supplied, `image` is inserted to the front of `images`.
    -
    -### Example
    -
    - remove-image:
    - type: docker/remove
    - images:
    - - registry.my.domain:5000/newimage:master-latest
    - - registry.my.domain:5000/newimage:master-deadbeef
    -
    -----
    -
    -## docker/run Task
    -
    -A run task runs an image.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------- | -------- | ---------- | ----------- |
    -| command | | | The command to run in the container. This is only necessary if the image does not provide a command by default. |
    -| detach | | false | Will run the container in the background allowing other tasks to run. The container will be cleaned up on exit. If the image has a HEALTHCHECK specified in the Dockerfile, convey will wait until it is healthy before proceeding. |
    -| entrypoint | | | The entrypoint to use for the container. This is only necessary if the image does not provide one by default. |
    -| environment | | | A list of environment variables to set. The should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. |
    -| healthcheck | | | See the HealthCheck attributes below. |
    -| hostname | | | Will set the --network-alias argument in the docker run command. This is generally only useful to connect to service containers. |
    -| image | Yes | | The image including the registry and tag to run. |
    -| labels | | | A list of labels to set on the image being built |
    -| script | | | A list of commands to run in the image. Overrides entrypoint and command. |
    -| shell | | | When the script attribute is present, it will be ran in `/bin/sh` by default. You can use this attribute to choose a different shell. |
    -| user | | | The user to run as when executing the script or command inside the container. |
    -| workdir | | | The working directory to use in the container. |
    -| workspace | | /workspace | The path inside the container to mount the workspace volume. |
    -
    -### HealthCheck Attributes
    -
    -All of these attributes map directory to the `--health` arguments to `docker run`.
    -
    -| Name | Description |
    -| -------- | ----------- |
    -| command | The command to run as the health check. |
    -| interval | The interval with which to check the health. |
    -| retries | The number of attempts before giving up. |
    -| start-period | How long to wait before starting to check the container's health. |
    -| timeout | How long to wait for a health check to return before aborting. |
    -
    -### Example
    -
    -A basic example where the image knows everything to do.
    -
    - build-golang:
    - type: docker/run
    - image: golang:onbuild
    -
    -A basic example using a standard image to do something else
    -
    - download-file:
    - type: docker/run
    - image: debian:jessie-slim
    - script:
    - - wget https://somedomain.tld/file1
    - - wget https://somedomain.tld/file1.sha256
    - - sha256sum -c file1.sha256
    -
    -A more compilicated example waiting for a detached nginx to start
    -
    - web-server:
    - type: docker/run
    - image: nginx:alpine
    - detach: true
    - command: wget http://localhost
    - interval: 1s
    -
    -----
    -
    -## docker/tag Task
    -
    -A tag task will tag an existing image.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ------------ | -------- | ---------- | ----------- |
    -| source | | | The source image, including registry and tag, to tag. |
    -| destination | | | The destination tag, including registry and tag, to tag. |
    -| destinations | | | The destination tags, including registry and tag, to tag. |
    -
    -### Example
    -
    - tag-development:
    - type: docker/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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "path/filepath"
    -
    - 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.
    -type Build struct {
    - 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)
    - if err != nil {
    - return err
    - }
    -
    - wsPath := rt.State.Workspace.Volume().Path()
    -
    - // import all of the declared files to our task directory
    - for _, filespec := range b.Files {
    - spec := fs.ParsePathSpec(filespec)
    - td.Import(filepath.Join(wsPath, spec.Source()), spec.Destination())
    - }
    -
    - dockerfile := fullEnv.Map(b.Dockerfile)
    -
    - tags, err := fullEnv.MapSlice(b.Tags)
    - if err != nil {
    - return err
    - }
    -
    - labels, err := fullEnv.MapSlice(b.Labels)
    - if err != nil {
    - return err
    - }
    -
    - arguments, err := fullEnv.MapSlice(b.Arguments)
    - if err != nil {
    - return err
    - }
    -
    - td.Import(dockerfile, filepath.Base(dockerfile))
    -
    - // create the basic command
    - cmd := exec.NewGenerator(
    - "build",
    - "-f", filepath.Join(td.Path(), filepath.Base(dockerfile)),
    - )
    -
    - // add any and all tags
    - for _, tag := range tags {
    - cmd.Append("-t", tag)
    - }
    -
    - // add any and all labels
    - for _, label := range labels {
    - cmd.Append("--label", label)
    - }
    -
    - // add any and all build arguments
    - for _, arg := range arguments {
    - cmd.Append("--build-arg", arg)
    - }
    -
    - // add the target if we have one
    - if b.Target != "" {
    - cmd.Append("--target", b.Target)
    - }
    -
    - // finally add the build context
    - cmd.Append(td.Path())
    -
    - return Docker(name, cmd.Command(), rt)
    -}
    -
    -// New creates a new docker build task.
    -func (b *Build) New() tasks.Task {
    - return &Build{}
    -}
    -
    -// Valid checks that the build task is valid.
    -func (b *Build) Valid() error {
    - if b.Dockerfile == "" {
    - return errNoDockerFile
    - }
    -
    - if b.Tag != "" {
    - b.Tags = append([]string{b.Tag}, b.Tags...)
    - }
    -
    - if len(b.Tags) == 0 {
    - return errNoTag
    - }
    -
    - return nil
    -}
    --- a/docker/build_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,88 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestBuildDockerfileRequired(t *testing.T) {
    - b := &Build{Tag: "foo"}
    - assert.EqualError(t, b.Valid(), errNoDockerFile.Error())
    -}
    -
    -func TestBuildTagRequired(t *testing.T) {
    - b := &Build{Dockerfile: "Dockerfile"}
    - assert.EqualError(t, b.Valid(), errNoTag.Error())
    -}
    -
    -func TestBuild(t *testing.T) {
    - b := &Build{
    - Dockerfile: "Dockerfile",
    - Tag: "tag",
    - }
    - assert.Nil(t, b.Valid())
    -}
    -
    -func TestBuildUnmarshalNormal(t *testing.T) {
    - data := `dockerfile: dockerfile
    -files:
    - - filename1
    -tag: tag
    -labels:
    - - label1
    -arguments:
    - - argument1
    -`
    -
    - b := Build{}
    - err := yaml.Unmarshal([]byte(data), &b)
    -
    - assert.Nil(t, err)
    - assert.Nil(t, b.Valid())
    -
    - assert.Equal(t, b.Dockerfile, "dockerfile")
    - assert.Equal(t, b.Files, cYaml.StringOrSlice{"filename1"})
    - assert.Equal(t, b.Tag, "tag")
    - assert.Equal(t, b.Labels, cYaml.StringOrSlice{"label1"})
    - assert.Equal(t, b.Arguments, cYaml.StringOrSlice{"argument1"})
    -}
    -
    -func TestBuildUnmarshalMissingDockerfile(t *testing.T) {
    - data := `tag: tag`
    -
    - b := Build{}
    - err := yaml.Unmarshal([]byte(data), &b)
    -
    - assert.Nil(t, err)
    - assert.EqualError(t, b.Valid(), errNoDockerFile.Error())
    -}
    -
    -func TestBuildUnmarshalMissingTag(t *testing.T) {
    - data := `dockerfile: dockerfile`
    -
    - b := Build{}
    - err := yaml.Unmarshal([]byte(data), &b)
    -
    - assert.Nil(t, err)
    - assert.EqualError(t, b.Valid(), errNoTag.Error())
    -}
    --- a/docker/docker.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,65 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package docker implements the docker task types.
    -package docker
    -
    -import (
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - // Tasks is a map of all docker tasks.
    - Tasks = map[string]tasks.Task{
    - "build": &Build{},
    - "login": &Login{},
    - "logout": &Logout{},
    - "pull": &Pull{},
    - "push": &Push{},
    - "remove": &Remove{},
    - "run": &Run{},
    - "tag": &Tag{},
    - "environment": &Environment{},
    - }
    -)
    -
    -func dockerCommand(cmdv []string, rt *runtime.Runtime) []string {
    - cmd := exec.NewGenerator()
    - cmd.Appendv(cmdv)
    -
    - // if we have a docker config prepend it first
    - if rt.State.DockerConfig != "" {
    - cmd.Prepend("--config", rt.State.DockerConfig)
    - }
    -
    - // finally prepend the actual docker command
    - cmd.Prepend("docker")
    -
    - return cmd.Command()
    -}
    -
    -// Docker runs a docker command.
    -func Docker(name string, cmdv []string, rt *runtime.Runtime) error {
    - return exec.Run(name, dockerCommand(cmdv, rt), rt.State.PlanTimeout)
    -}
    -
    -// DockerOutput runs a docker command but returns the output rather than
    -// outputting to the logger.
    -func DockerOutput(name string, cmdv []string, rt *runtime.Runtime) (string, string, error) {
    - return exec.RunOutput(name, dockerCommand(cmdv, rt), rt.State.PlanTimeout)
    -}
    --- a/docker/environment.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,142 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "fmt"
    - "io/ioutil"
    - "path/filepath"
    - "strings"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    - "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -// Environment contains options for loading environment variables from a file
    -// in the workspace.
    -type Environment struct {
    - File string `yaml:"from-file"`
    - Files yaml.StringOrSlice `yaml:"from-files"`
    - Prefix string `yaml:"prefix"`
    -}
    -
    -// Execute runs the environment task.
    -func (e *Environment) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := environment.New().Merge(env)
    - fullEnv.Merge(rt.Environment)
    -
    - prefix := fullEnv.Map(e.Prefix)
    -
    - files, err := fullEnv.MapSlice(e.Files)
    - if err != nil {
    - return err
    - }
    -
    - // create a directory in our state for the task
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return err
    - }
    -
    - for _, file := range files {
    - // Export the file into the temp directory and maintain the
    - // structure of the file (for ease of error messages, so we
    - // get the file a/b/c/env instead of env).
    -
    - e := tasks.Export{
    - Files: []string{file},
    - Path: td.Path(),
    - }
    -
    - err = e.Execute(name, logger, env, rt)
    - if err != nil {
    - return err
    - }
    -
    - // Process the entries of the file and apply them to the
    - // state's base environment immediately.
    - dest := filepath.Clean(filepath.Join(td.Path(), file))
    - entries, err := processFile(dest, file, prefix)
    - if err != nil {
    - return err
    - }
    -
    - env.MergeSlice(entries)
    - }
    -
    - return nil
    -}
    -
    -func processFile(path, name, prefix string) ([]string, error) {
    - data, err := ioutil.ReadFile(path)
    - if err != nil {
    - return nil, fmt.Errorf("failed to read environment file '%s'", name)
    - }
    -
    - entries := []string{}
    - for _, line := range strings.Split(string(data), "\n") {
    - // Allow blank lines
    - if len(strings.TrimSpace(line)) == 0 {
    - continue
    - }
    -
    - // Each non-empty line requires the form key=val. Split the
    - // key and the val, then uppercase the key and add the prefix.
    - // We don't care what form val takes, it will be treated as a
    - // string (does not need to be quoted).
    -
    - parts := strings.SplitN(line, "=", 2)
    - if len(parts) != 2 {
    - return nil, fmt.Errorf("malformed entry in environments file '%s'", line)
    - }
    -
    - var (
    - key = strings.TrimSpace(parts[0])
    - val = strings.TrimSpace(parts[1])
    - )
    -
    - if len(key) == 0 {
    - return nil, fmt.Errorf("malformed entry in environments file '%s'", line)
    - }
    -
    - entries = append(entries, fmt.Sprintf("%s%s=%s", prefix, strings.ToUpper(key), val))
    - }
    -
    - return entries, nil
    -}
    -
    -// New creates a new environment command.
    -func (e *Environment) New() tasks.Task {
    - return &Environment{}
    -}
    -
    -// Valid validate the environment command.
    -func (e *Environment) Valid() error {
    - if e.File != "" {
    - e.Files = append([]string{e.File}, e.Files...)
    - }
    -
    - if len(e.Files) == 0 {
    - return errNoFilesEnvironment
    - }
    -
    - return nil
    -}
    --- a/docker/environment_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,58 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestEnvironmentSimple(t *testing.T) {
    - i := &Environment{Files: cYaml.StringOrSlice{"foo"}}
    - assert.Nil(t, i.Valid())
    -}
    -
    -func TestEnvironmentFilesRequired(t *testing.T) {
    - i := &Environment{}
    - assert.EqualError(t, i.Valid(), errNoFilesEnvironment.Error())
    -}
    -
    -func TestEnvironmentUnmarshalString(t *testing.T) {
    - data := `from-files: filename`
    -
    - imp := Environment{}
    - err := yaml.Unmarshal([]byte(data), &imp)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, imp.Files, cYaml.StringOrSlice{"filename"})
    -}
    -
    -func TestEnvironmentUnmarshalStringSlice(t *testing.T) {
    - data := `from-files:
    - - filename1
    - - filename2`
    -
    - imp := Environment{}
    - err := yaml.Unmarshal([]byte(data), &imp)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, imp.Files, cYaml.StringOrSlice{"filename1", "filename2"})
    -}
    --- a/docker/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,35 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "errors"
    -)
    -
    -var (
    - errNoDockerFile = errors.New("no dockerfile specified")
    - errNoTag = errors.New("no tag specified")
    - errWildcardWithDestination = errors.New("file list contains a wildcard with a destination")
    - errNoFiles = errors.New("no files specified")
    - errNoServer = errors.New("no server specified")
    - errNoUsername = errors.New("no username specified")
    - errNoImage = errors.New("no image specified")
    - errNoImages = errors.New("no images specified")
    - errNoSourceTag = errors.New("no source tag specified")
    - errNoDestinationTags = errors.New("no destination tags specified")
    - errNoFilesEnvironment = errors.New("no environment files specified")
    -)
    --- a/docker/healthcheck.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,79 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    - "time"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// HealthCheck represents a docker health check.
    -type HealthCheck struct {
    - Command string `yaml:"command"`
    - Interval time.Duration `yaml:"interval"`
    - Retries int `yaml:"retries"`
    - StartPeriod time.Duration `yaml:"start-period"`
    - Timeout time.Duration `yaml:"timeout"`
    -}
    -
    -func containerHasHealthCheck(name, cid string, rt *runtime.Runtime, logger *log.Entry) (bool, error) {
    - cmdv := []string{
    - "inspect",
    - "-f",
    - "\"{{if .Config.Healthcheck}}true{{else}}false{{end}}\"",
    - cid,
    - }
    -
    - stdout, stderr, err := DockerOutput(name+"/healthcheck", cmdv, rt)
    - if err != nil {
    - logger.Error(stderr)
    -
    - return false, err
    - }
    -
    - if strings.TrimSpace(stdout) == "true" {
    - return true, nil
    - }
    -
    - return false, nil
    -}
    -
    -func containerIsHealthy(name, cid string, rt *runtime.Runtime, logger *log.Entry) (bool, error) {
    - cmdv := []string{
    - "inspect",
    - "-f",
    - "\"{{.State.Health.Status}}\"",
    - cid,
    - }
    -
    - stdout, stderr, err := DockerOutput(name+"/healthcheck", cmdv, rt)
    - if err != nil {
    - logger.Error(stderr)
    -
    - return false, err
    - }
    -
    - if strings.ToLower(strings.TrimSpace(stdout)) == "healthy" {
    - return true, err
    - }
    -
    - return false, nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - 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.
    -type Login struct {
    - 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(
    - "login",
    - "-u", username,
    - "-p", password,
    - )
    -
    - if server != "" {
    - cmd.Append(server)
    - }
    -
    - return Docker(name, cmd.Command(), rt)
    -}
    -
    -// New creates a new login task.
    -func (l *Login) New() tasks.Task {
    - return &Login{}
    -}
    -
    -// Valid validate the login task.
    -func (l *Login) Valid() error {
    - if l.Server == "" {
    - return errNoServer
    - }
    -
    - if l.Username == "" {
    - return errNoUsername
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestLogin(t *testing.T) {
    - l := &Login{
    - Server: "server",
    - Username: "username",
    - Password: "password",
    - }
    -
    - assert.Nil(t, l.Valid())
    -}
    -
    -func TestLoginServerRequired(t *testing.T) {
    - l := &Login{
    - Username: "username",
    - }
    -
    - assert.EqualError(t, l.Valid(), errNoServer.Error())
    -}
    -
    -func TestLoginUsernameRequired(t *testing.T) {
    - l := &Login{
    - Server: "server",
    - }
    -
    - 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - 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.
    -type Logout struct {
    - 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")
    -
    - if server != "" {
    - cmd.Append(server)
    - }
    -
    - return Docker(name, cmd.Command(), rt)
    -}
    -
    -// New creates a new logout task.
    -func (l *Logout) New() tasks.Task {
    - return &Logout{}
    -}
    -
    -// Valid validates the logout task.
    -func (l *Logout) Valid() error {
    - if l.Server == "" {
    - return errNoServer
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestLogout(t *testing.T) {
    - l := &Logout{
    - Server: "server",
    - }
    -
    - assert.Nil(t, l.Valid())
    -}
    -
    -func TestLogoutServerRequired(t *testing.T) {
    - l := &Logout{}
    -
    - 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "fmt"
    - "strings"
    -
    - "github.com/alecthomas/kingpin"
    -
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// ParseCommand is a super stripped down kingpin parser that will parse docker
    -// command line options and return a convey task or an error
    -func ParseCommand(argv []string) (tasks.Task, error) {
    - app := kingpin.New("", "")
    -
    - login := app.Command("login", "")
    - loginUsername := login.Flag("username", "").Short('u').Default("").String()
    - loginPassword := login.Flag("password", "").Short('p').Default("").String()
    - // `aws ecr get-login adds environment variables to the login command,
    - // this handles that, but we ignore them.
    - login.Flag("env", "").Short('e').Default("").Strings()
    - loginServer := login.Arg("", "").String()
    -
    - logout := app.Command("logout", "")
    - logoutServer := logout.Arg("", "").String()
    -
    - build := app.Command("build", "")
    - buildTag := build.Flag("tag", "").Short('t').String()
    - buildContext := build.Arg("", "").String()
    -
    - push := app.Command("push", "")
    - pushImage := push.Arg("", "").Required().String()
    -
    - pull := app.Command("pull", "")
    - pullImage := pull.Arg("", "").Required().String()
    -
    - rmi := app.Command("rmi", "")
    - rmi.Flag("--force", "").Short('f').Default("false").Bool()
    - rmiImage := rmi.Arg("", "").Required().String()
    -
    - run := app.Command("run", "")
    - runEntryPoint := run.Flag("entrypoint", "").String()
    - runEnv := run.Flag("env", "").Short('e').Strings()
    - runImage := run.Arg("image", "").Required().String()
    - runWorkdir := run.Flag("workdir", "").Short('w').String()
    - runCommand := run.Arg("", "").Strings()
    -
    - tag := app.Command("tag", "")
    - tagSource := tag.Arg("src", "").Required().String()
    - tagDestination := tag.Arg("dest", "").Required().String()
    -
    - cmd, err := app.Parse(argv[1:])
    - if err != nil {
    - return nil, err
    - }
    -
    - var task tasks.Task
    -
    - switch cmd {
    - case "build":
    - task = &Build{
    - Tag: *buildTag,
    - Files: []string{*buildContext},
    - }
    - case "login":
    - task = &Login{
    - Username: *loginUsername,
    - Password: *loginPassword,
    - Server: *loginServer,
    - }
    - case "logout":
    - task = &Logout{
    - Server: *logoutServer,
    - }
    - case "pull":
    - task = &Pull{
    - Image: *pullImage,
    - }
    - case "push":
    - task = &Push{
    - Image: *pushImage,
    - }
    - case "rmi":
    - task = &Remove{
    - Image: *rmiImage,
    - }
    - case "run":
    - task = &Run{
    - Command: strings.Join(*runCommand, " "),
    - Image: *runImage,
    - EntryPoint: *runEntryPoint,
    - Environment: *runEnv,
    - WorkDir: *runWorkdir,
    - }
    - case "tag":
    - task = &Tag{
    - Source: *tagSource,
    - Destination: *tagDestination,
    - }
    - }
    -
    - if task != nil {
    - return task, nil
    - }
    -
    - return nil, fmt.Errorf("unable to parse docker command line '%v'", argv)
    -}
    --- a/docker/parser_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,41 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestECRGetLogin(t *testing.T) {
    - argv := strings.Split("docker login -u AWS -p password -e none https://1234567890.dkr.ecr.us-east-2.amazonaws.com", " ")
    -
    - task, err := ParseCommand(argv)
    - assert.Nil(t, err)
    - assert.NotNil(t, task)
    -
    - // make sure we can type assert into a Login command
    - assert.IsType(t, task, &Login{})
    - login := task.(*Login)
    -
    - // now check the login command
    - assert.Equal(t, login.Username, "AWS")
    - assert.Equal(t, login.Password, "password")
    - assert.Equal(t, login.Server, "https://1234567890.dkr.ecr.us-east-2.amazonaws.com")
    -}
    --- a/docker/pull.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,71 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - 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
    -// registry.
    -type Pull struct {
    - 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)
    - if err != nil {
    - return err
    - }
    -
    - for _, image := range images {
    - cmdv := []string{"pull", image}
    -
    - if err := Docker(name, cmdv, rt); err != nil {
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// New creates a new pull task.
    -func (p *Pull) New() tasks.Task {
    - return &Pull{}
    -}
    -
    -// Valid validates the pull task.
    -func (p *Pull) Valid() error {
    - if p.Image != "" {
    - p.Images = append([]string{p.Image}, p.Images...)
    - }
    -
    - if len(p.Images) == 0 {
    - return errNoImages
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestPull(t *testing.T) {
    - p1 := &Pull{
    - Image: "image",
    - }
    - assert.Nil(t, p1.Valid())
    -
    - p2 := &Pull{
    - Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
    - }
    - assert.Nil(t, p2.Valid())
    -}
    -
    -func TestPullImageRequired(t *testing.T) {
    - p1 := &Pull{}
    - assert.EqualError(t, p1.Valid(), errNoImages.Error())
    -
    - p2 := &Pull{
    - Images: cYaml.StringOrSlice{},
    - }
    - assert.EqualError(t, p2.Valid(), errNoImages.Error())
    -}
    -
    -func TestPullValidNormalizesImages(t *testing.T) {
    - p := &Pull{
    - Image: "image1",
    - Images: cYaml.StringOrSlice{"image2", "image3"},
    - }
    -
    - assert.Nil(t, p.Valid())
    - assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - 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.
    -type Push struct {
    - 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)
    - if err != nil {
    - return err
    - }
    -
    - for _, image := range images {
    - cmdv := []string{"push", image}
    -
    - if err := Docker(name, cmdv, rt); err != nil {
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// New creates a new push task.
    -func (p *Push) New() tasks.Task {
    - return &Push{}
    -}
    -
    -// Valid validates the push task.
    -func (p *Push) Valid() error {
    - if p.Image != "" {
    - p.Images = append([]string{p.Image}, p.Images...)
    - }
    -
    - if len(p.Images) == 0 {
    - return errNoImages
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestPush(t *testing.T) {
    - p1 := &Push{
    - Image: "image",
    - }
    - assert.Nil(t, p1.Valid())
    -
    - p2 := &Push{
    - Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
    - }
    - assert.Nil(t, p2.Valid())
    -}
    -
    -func TestPushImageRequired(t *testing.T) {
    - p1 := &Push{}
    - assert.EqualError(t, p1.Valid(), errNoImages.Error())
    -
    - p2 := &Push{
    - Images: cYaml.StringOrSlice{},
    - }
    - assert.EqualError(t, p2.Valid(), errNoImages.Error())
    -}
    -
    -func TestPushValidNormalizesImages(t *testing.T) {
    - p := &Push{
    - Image: "image1",
    - Images: cYaml.StringOrSlice{"image2", "image3"},
    - }
    -
    - assert.Nil(t, p.Valid())
    - assert.Equal(t, p.Images, cYaml.StringOrSlice{"image1", "image2", "image3"})
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/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.
    -type Remove struct {
    - 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)
    - if err != nil {
    - return err
    - }
    -
    - for _, image := range images {
    - cmdv := []string{"rmi", image}
    -
    - if _, stderr, err := DockerOutput(name, cmdv, rt); err != nil {
    - if strings.Contains(stderr, "No such image") && r.Quiet {
    - continue
    - }
    -
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// New creates a new remove task.
    -func (r *Remove) New() tasks.Task {
    - return &Remove{}
    -}
    -
    -// Valid validates the remove task.
    -func (r *Remove) Valid() error {
    - if r.Image != "" {
    - r.Images = append([]string{r.Image}, r.Images...)
    - }
    -
    - if len(r.Images) == 0 {
    - return errNoImages
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestRemove(t *testing.T) {
    - p1 := &Remove{
    - Image: "image",
    - }
    - assert.Nil(t, p1.Valid())
    -
    - p2 := &Remove{
    - Images: cYaml.StringOrSlice{"image1", "image2", "image3"},
    - }
    - assert.Nil(t, p2.Valid())
    -}
    -
    -func TestRemoveImageRequired(t *testing.T) {
    - p1 := &Remove{}
    - assert.EqualError(t, p1.Valid(), errNoImages.Error())
    -
    - p2 := &Remove{
    - Images: cYaml.StringOrSlice{},
    - }
    - assert.EqualError(t, p2.Valid(), errNoImages.Error())
    -}
    -
    -func TestRemoveValidNormalizesImages(t *testing.T) {
    - p := &Remove{
    - Image: "image1",
    - 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "fmt"
    - "io/ioutil"
    - "os"
    - "os/user"
    - "path/filepath"
    - "strings"
    - "time"
    -
    - "github.com/kballard/go-shellquote"
    - "github.com/satori/go.uuid"
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/normalize"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    - "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -// Run represents a docker run task which will run a container.
    -type Run struct {
    - Command string `yaml:"command"`
    - Detach bool `yaml:"detach"`
    - EntryPoint string `yaml:"entrypoint"`
    - 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 {
    - type rawRun Run
    - raw := rawRun{Shell: "/bin/sh", HealthCheck: HealthCheck{}}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *r = Run(raw)
    -
    - return nil
    -}
    -
    -func (r *Run) ReadEnvironment() []string {
    - return []string(r.Environment)
    -}
    -
    -// writeScript will write a shell script to the given tempfile for the given commands
    -func (r *Run) writeScript(name string, rt *runtime.Runtime, fullEnv *environment.Environment) (string, string, string, error) {
    - // create our task directory if it doesn't exist already
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return "", "", "", err
    - }
    -
    - // create the temp file to write the script to
    - scriptFile := filepath.Join(td.Path(), "script")
    -
    - entryPoint := r.Shell
    - if entryPoint == "" {
    - entryPoint = "/bin/sh"
    - }
    -
    - // Scripts must retain order, so don't use st.MapSlice to
    - // expand things (which results in a non-deterministically
    - // ordered slice of the expanded input). It also doesn't
    - // make sense to expand things here anyway - use a loop in
    - // bash if you need that kind of control.
    - scripts, err := fullEnv.MapSlice(r.Script)
    - if err != nil {
    - if nErr := os.Remove(scriptFile); nErr != nil {
    - fmt.Printf("error removing file: %s\n", nErr)
    - }
    -
    - return "", "", "", err
    - }
    -
    - // set the run command argument to the script file
    - commandArg := scriptFile
    -
    - // write the script to the file
    - err = ioutil.WriteFile(scriptFile, []byte(strings.Join(scripts, "\n")), 0700)
    - if err != nil {
    - return "", "", "", err
    - }
    -
    - // return it all
    - return scriptFile, entryPoint, commandArg, nil
    -}
    -
    -// detach will run the container detached
    -func (r *Run) detach(name string, cmdv []string, rt *runtime.Runtime, logger *log.Entry) error {
    - stdout, stderr, err := DockerOutput(name, cmdv, rt)
    - if err != nil {
    - logger.Errorf("%s", stderr)
    -
    - return err
    - }
    -
    - cid := strings.TrimSpace(stdout)
    -
    - rt.Cleanup(func(logger *log.Entry) {
    - logger.Debugf("stopping container %s", cid)
    -
    - err = StopContainer(cid, logger, rt)
    - if err != nil {
    - logger.Warnf("failed to stop container %s: %s", cid, err.Error())
    - } else {
    - logger.Infof("stopped container %s", cid)
    - }
    - })
    -
    - logger.Infof("started detached container %s", cid)
    - logger.Infof("checking for healthcheck")
    -
    - // check if the container has a health check
    - hasHealth, err := containerHasHealthCheck(name, cid, rt, logger)
    - if err != nil {
    - return err
    - }
    -
    - if hasHealth {
    - healthChan := make(chan error)
    -
    - logger.Infof("waiting for container to go healthy")
    -
    - go func() {
    - duration := 5 * time.Second
    -
    - for {
    - // check the health
    - healthy, err := containerIsHealthy(name, cid, rt, logger)
    - if err != nil {
    - healthChan <- err
    - }
    -
    - if healthy {
    - healthChan <- nil
    - }
    -
    - logger.Infof("container still not healthy, waiting %v", duration)
    -
    - time.Sleep(duration)
    - }
    - }()
    -
    - err := <-healthChan
    - if err != nil {
    - return err
    - }
    -
    - logger.Infof("container is ready")
    - } else {
    - logger.Infof("no healthcheck found")
    - }
    -
    - return nil
    -}
    -
    -func (r *Run) buildCommandHealthCheck(cmd *exec.Generator) {
    - // add the healthcheck command if we got one
    - if r.HealthCheck.Command != "" {
    - cmd.Append("--health-cmd", r.HealthCheck.Command)
    - }
    -
    - // add the provided interval
    - if r.HealthCheck.Interval != time.Duration(0) {
    - cmd.Append("--health-interval", r.HealthCheck.Interval.String())
    - }
    -
    - // add the provided retries
    - if r.HealthCheck.Retries != 0 {
    - cmd.Append("--health-retries", fmt.Sprintf("%d", r.HealthCheck.Retries))
    - }
    -
    - // add the provided start period
    - if r.HealthCheck.StartPeriod != time.Duration(0) {
    - cmd.Append("--health-start-period", r.HealthCheck.StartPeriod.String())
    - }
    -
    - // add the provided timeout
    - if r.HealthCheck.Timeout != time.Duration(0) {
    - cmd.Append("--health-timeout", r.HealthCheck.Timeout.String())
    - }
    -}
    -
    -// Execute runs the run task.
    -func (r *Run) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().MergeSlice(r.Environment).Merge(rt.Environment)
    -
    - // create an id for this container
    - runID := uuid.NewV4().String()[0:6]
    -
    - // Map the image name so we can use it elsewhere
    - image := fullEnv.Map(r.Image)
    - if image == "" {
    - return errNoImage
    - }
    -
    - // build the command
    - cmd := exec.NewGenerator(
    - "run", "--rm",
    - "--name", runID,
    - )
    -
    - // add the hostname if specified
    - hostname := fullEnv.Map(r.Hostname)
    - if hostname != "" {
    - cmd.Append("--network-alias", hostname)
    - }
    -
    - // assign a default workspace location
    - workSpace := r.WorkSpace
    - if workSpace == "" {
    - workSpace = "/workspace"
    - }
    -
    - workspaceMount := fullEnv.Map(workSpace)
    -
    - cmd.Append(
    - "-v", rt.State.Workspace.Volume().Path()+":"+workspaceMount,
    - "-e", "CONVEY_WORKSPACE="+workspaceMount,
    - )
    -
    - // we do workDir after workspaceMount so we can put workspaceMount into an
    - // environment that workDir will be mapped against.
    - wsEnv := fullEnv.Copy()
    - wsEnv.Set("CONVEY_WORKSPACE", workspaceMount)
    -
    - workdir := wsEnv.Map(r.WorkDir)
    - if workdir != "" {
    - cmd.Append("-w", workdir)
    - } else {
    - id, nErr := ImageID(image, logger, rt)
    - if nErr != nil {
    - return nErr
    - }
    -
    - if id != "" {
    - // Check if the image does _not_ have a workdir set. If it doesn't,
    - // set workdir to the convey workspace
    - cmdv := []string{
    - "inspect",
    - "--format",
    - "{{.Config.WorkingDir}}",
    - image,
    - }
    - stdout, stderr, nErr := DockerOutput("checkWorkDir", cmdv, rt)
    - if nErr != nil {
    - logger.Errorf("%s", stderr)
    -
    - return nErr
    - }
    -
    - if strings.TrimSpace(stdout) == "" {
    - cmd.Append("-w", workspaceMount)
    - }
    - }
    - }
    -
    - // detach if necessary
    - if r.Detach {
    - cmd.Append("-d")
    - }
    -
    - // add cpushares if necessary
    - if rt.State.CPUShares != "" {
    - cmd.Append("--cpu-shares", rt.State.CPUShares)
    - }
    -
    - // add memory constraints if necessary
    - if rt.State.Memory != "" {
    - cmd.Append("--memory", rt.State.Memory)
    - }
    -
    - // grab the current user's UID and GID
    - user, err := user.Current()
    - if err != nil {
    - return err
    - }
    - cmd.Append("-e", "UID="+user.Uid, "-e", "GID="+user.Gid)
    -
    - // set user if one was specified
    - username := fullEnv.Map(r.User)
    - if username != "" {
    - cmd.Append("--user", username)
    - }
    -
    - // initialize some variables
    - var scriptFile string
    - entryPoint := r.EntryPoint
    - commandArg := fullEnv.Map(r.Command)
    -
    - // if we're using a script defined in the yaml, create it and override
    - // some variables
    - if len(r.Script) > 0 {
    - scriptFile, entryPoint, commandArg, err = r.writeScript(name, rt, fullEnv)
    - if err != nil {
    - return err
    - }
    -
    - if scriptFile != "" {
    - cmd.Append("-v", scriptFile+":"+scriptFile)
    - }
    -
    - }
    -
    - if entryPoint != "" {
    - cmd.Append("--entrypoint", entryPoint)
    - }
    -
    - // add a label to the container
    - cmd.Append(
    - "--label",
    - normalize.Normalize(fmt.Sprintf("convey-%d-task=%s", os.Getpid(), name)),
    - )
    -
    - // run through any user supplied labels
    - labels, err := fullEnv.MapSlice(r.Labels)
    - if err != nil {
    - return err
    - }
    - for _, label := range labels {
    - cmd.Append("--label", label)
    - }
    -
    - // add the ssh agent stuff
    - if rt.State.EnableSSHAgent {
    - authSock := os.Getenv("SSH_AUTH_SOCK")
    - cmd.Append(
    - "-e", "SSH_AUTH_SOCK",
    - "-v", authSock+":"+authSock,
    - )
    - }
    -
    - // add the health check stuff
    - r.buildCommandHealthCheck(cmd)
    -
    - // now add all non-empty environment variables
    - for _, env := range fullEnv.Prune().Items() {
    - cmd.Append("-e", env)
    - }
    -
    - // add the image to the command
    - cmd.Append(image)
    -
    - // append the command if we have one
    - if commandArg != "" {
    - args, err := shellquote.Split(commandArg)
    - if err != nil {
    - return err
    - }
    -
    - cmd.Append(args...)
    - }
    -
    - logger.Infof("running container with id %s", runID)
    -
    - //
    - // Everything after this should be mostly dead code
    - //
    -
    - if r.Detach {
    - return r.detach(name, cmd.Command(), rt, logger)
    - }
    -
    - // Mark running so we can detach this container form the network
    - // when we've got a signal to shutdown. An active network cannot
    - // be removed, so we need to track things to kill along with it.
    - // This is no longer a problem once the subprocess returns.
    -
    - rt.Containers.MarkRunning(runID)
    - defer rt.Containers.UnmarkRunning(runID)
    -
    - // run the command
    - return Docker(name, cmd.Command(), rt)
    -}
    -
    -// New creates a new run task.
    -func (r *Run) New() tasks.Task {
    - return &Run{}
    -}
    -
    -// Valid validates the run task.
    -func (r *Run) Valid() error {
    - if r.Image == "" {
    - return errNoImage
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestRun(t *testing.T) {
    - r := &Run{
    - Image: "image",
    - }
    -
    - assert.Nil(t, r.Valid())
    -}
    -
    -func TestRunImageRequired(t *testing.T) {
    - r := &Run{}
    -
    - 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - 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.
    -type Tag struct {
    - 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)
    - if err != nil {
    - return err
    - }
    -
    - for _, destination := range destinations {
    - cmdv := []string{"tag", source, destination}
    -
    - if err := Docker(name, cmdv, rt); err != nil {
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// New creates a new docker tag task.
    -func (t *Tag) New() tasks.Task {
    - return &Tag{}
    -}
    -
    -// Valid validates the tag task.
    -func (t *Tag) Valid() error {
    - if t.Source == "" {
    - return errNoSourceTag
    - }
    -
    - if t.Destination != "" {
    - t.Destinations = append([]string{t.Destination}, t.Destinations...)
    - }
    -
    - if len(t.Destinations) == 0 {
    - return errNoDestinationTags
    - }
    -
    - return nil
    -}
    --- 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -func TestTag(t *testing.T) {
    - p1 := &Tag{
    - Source: "source",
    - Destination: "destination",
    - }
    - assert.Nil(t, p1.Valid())
    -
    - p2 := &Tag{
    - Source: "source",
    - Destinations: cYaml.StringOrSlice{"dest1", "dest2", "dest3"},
    - }
    - assert.Nil(t, p2.Valid())
    -}
    -
    -func TestTagSourceRequired(t *testing.T) {
    - tag := &Tag{
    - Destination: "destination",
    - }
    -
    - assert.EqualError(t, tag.Valid(), errNoSourceTag.Error())
    -}
    -
    -func TestTagDestinationRequired(t *testing.T) {
    - p1 := &Tag{
    - Source: "source",
    - }
    -
    - assert.EqualError(t, p1.Valid(), errNoDestinationTags.Error())
    -
    - p2 := &Tag{
    - Source: "source",
    - Destinations: cYaml.StringOrSlice{},
    - }
    -
    - assert.EqualError(t, p2.Valid(), errNoDestinationTags.Error())
    -}
    -
    -func TestTagValidNormalizesDestinations(t *testing.T) {
    - p := &Tag{
    - Source: "source",
    - Destination: "dest1",
    - 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 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "strings"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ParseImage will convert an image like python:3 to it's registry, name, and tag
    -func ParseImage(image string) (string, string, string) {
    - registry := ""
    - tag := ""
    -
    - // split the image into two pieces on the first /
    - parts := strings.SplitN(image, "/", 2)
    - if len(parts) == 2 {
    - if strings.Contains(parts[0], ".") || strings.Contains(parts[0], ":") || parts[0] == "localhost" {
    - registry = parts[0]
    - image = parts[1]
    - }
    - }
    -
    - parts = strings.SplitN(image, ":", 2)
    -
    - name := parts[0]
    - if len(parts) > 1 {
    - tag = parts[1]
    - }
    -
    - return registry, name, tag
    -}
    -
    -// StopContainer will call 'docker stop' on the given container id
    -func StopContainer(cid string, logger *log.Entry, rt *runtime.Runtime) error {
    - cmdv := []string{"stop", cid}
    -
    - _, stderr, err := DockerOutput("stop container", cmdv, rt)
    - if err != nil {
    - return err
    - }
    -
    - if stderr != "" {
    - logger.Warnf("%s", strings.TrimSpace(stderr))
    - }
    -
    - return nil
    -}
    -
    -// ImageID will return the ID of the given image if it's available locally
    -func ImageID(tag string, logger *log.Entry, rt *runtime.Runtime) (string, error) {
    - cmdv := []string{"image", "ls", "-q", tag}
    -
    - stdout, stderr, err := DockerOutput("get image id", cmdv, rt)
    - if err != nil {
    - logger.Errorf("error: %s", stderr)
    -
    - return "", err
    - }
    -
    - return strings.TrimSpace(stdout), nil
    -}
    --- a/docker/util_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,89 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package docker
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -type parseImageData struct {
    - input string
    - registry string
    - name string
    - tag string
    -}
    -
    -func TestParseImage(t *testing.T) {
    - data := []parseImageData{
    - {
    - input: "",
    - registry: "",
    - name: "",
    - tag: "",
    - }, {
    - input: "python",
    - registry: "",
    - name: "python",
    - tag: "",
    - }, {
    - input: "python:3",
    - registry: "",
    - name: "python",
    - tag: "3",
    - }, {
    - input: "convey/workspace",
    - registry: "",
    - name: "convey/workspace",
    - tag: "",
    - }, {
    - input: "localhost/convey/workspace",
    - registry: "localhost",
    - name: "convey/workspace",
    - tag: "",
    - }, {
    - input: "localhost:5000/convey/workspace",
    - registry: "localhost:5000",
    - name: "convey/workspace",
    - tag: "",
    - }, {
    - input: "registry.docker.io/python:3",
    - registry: "registry.docker.io",
    - name: "python",
    - tag: "3",
    - }, {
    - input: "registry.docker.io/rw_grim/convey:latest",
    - registry: "registry.docker.io",
    - name: "rw_grim/convey",
    - tag: "latest",
    - }, {
    - input: "registry.my.tld:5000/something/fancy:commit:deadbeef",
    - registry: "registry.my.tld:5000",
    - name: "something/fancy",
    - tag: "commit:deadbeef",
    - },
    - }
    -
    - for _, test := range data {
    - registry, name, tag := ParseImage(test.input)
    -
    - assert.Equal(t, registry, test.registry)
    - assert.Equal(t, name, test.name)
    - assert.Equal(t, tag, test.tag)
    - }
    -}
    --- /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 @@
    +package globals
    +
    +type Globals struct {
    + Color bool `kong:"flag,help='Enable colorized output.',default='1'"`
    + Verbose bool `kong:"flag,help='Be more verbose',short='v',default='0'"`
    +}
    --- a/go.mod Thu Aug 26 08:11:34 2021 -0500
    +++ b/go.mod Tue Oct 05 01:14:12 2021 -0500
    @@ -3,6 +3,7 @@
    require (
    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 @@
    -# kubectl
    -
    -The kubectl package provides tasks for interacting with kubernetes via kubectl.
    -It supplies the create, apply, and delete commands. It provides a few
    -templating engines that can be used to template your manifests before they're
    -used.
    -
    -If `none` or no engine is specified then no templating is done. An engine of `environment` will do normal variable swapping with the environment variables that convey knows about in the format of `${VARNAME}` or `$VARNAME`.
    -
    -----
    -
    -## kubectl/apply Task
    -
    -An apply task will apply kubernetes manifests to the cluster.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------| -------- | ---------- | ----------- |
    -| context | | | The kubernetes context to use. |
    -| files | Yes | | The list of manifest files to apply. |
    -| namespace | | | The kubernetes namespace to use. |
    -| selector | | | The list of selectors to use. |
    -| engine | | | The template engine to use. |
    -
    -### Example
    -
    - apply:
    - type: kubectl/apply
    - files:
    - - manifest.yml
    -
    -----
    -
    -## kubectl/create Task
    -
    -A create task will create kubernetes manifests on the cluster.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------| -------- | ---------- | ----------- |
    -| context | | | The kubernetes context to use. |
    -| files | Yes | | The list of manifest files to apply. |
    -| namespace | | | The kubernetes namespace to use. |
    -| selector | | | The selector to use. |
    -| engine | | | The template engine to use. |
    -
    -### Example
    -
    - apply:
    - type: kubectl/create
    - files:
    - - manifest.yml
    -
    -----
    -
    -## kubectl/delete Task
    -
    -A delete task will delete kubernetes manifests from the cluster.
    -
    -### Attributes
    -
    -| Name | Required | Default | Description |
    -| ----------| -------- | ---------- | ----------- |
    -| context | | | The kubernetes context to use. |
    -| files | Yes | | The list of manifest files to apply. |
    -| namespace | | | The kubernetes namespace to use. |
    -| selector | | | The selector to use. |
    -| engine | | | The template engine to use. |
    -
    -### Example
    -
    - apply:
    - type: kubectl/delete
    - files:
    - - manifest.yml
    -
    ----
    -
    -## kubectl/rollout Task
    -
    -A rollout task will run `kubectl status` to wait for a rollout to complete.
    -
    -###
    -
    -| Name | Required | Default | Description |
    -| --------- | -------- | ------- | ----------- |
    -| context | | | The context to use. |
    -| namespace | | | The namespace to use. |
    -| target | Yes | | The rollout target to wait for. |
    -
    -### Example
    -
    - wait-for-deploy:
    - type: kubectl/rollout
    - namespace: prod
    - target: deployment/www
    -
    --- a/kubectl/apply.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Apply is a task for calling `kubectl apply`.
    -type Apply CRUDCommand
    -
    -// Execute runs `kubectl apply` with the given arguments.
    -func (a *Apply) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - cmd := &CRUDCommand{
    - Context: a.Context,
    - Namespace: a.Namespace,
    - Files: a.Files,
    - Selector: a.Selector,
    - Engine: a.Engine,
    - }
    -
    - return cmd.Execute(name, "apply", logger, env, rt)
    -}
    -
    -// New creates a new kubectl/apply task.
    -func (a *Apply) New() tasks.Task {
    - return &Apply{}
    -}
    -
    -// Valid check if the apply task is valid
    -func (a *Apply) Valid() error {
    - if len(a.Files) <= 0 {
    - return errNoFiles
    - }
    -
    - return nil
    -}
    --- a/kubectl/apply_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestApplyFilesRequired(t *testing.T) {
    - a := &Apply{}
    - assert.NotNil(t, a.Valid())
    -
    - a = &Apply{Files: []string{"foo.yml"}}
    - assert.Nil(t, a.Valid())
    -}
    --- a/kubectl/command.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,191 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "io/ioutil"
    - "os"
    - "path/filepath"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    - "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -// CRUDCommand represents a call to a `kubectl` command to manage deployments.
    -type CRUDCommand struct {
    - Context string `yaml:"context"`
    - Files yaml.StringOrSlice `yaml:"files"`
    - Namespace string `yaml:"namespace"`
    - Selector yaml.StringOrSlice `yaml:"selector"`
    - Engine string `yaml:"engine"`
    -}
    -
    -// templateNone just copies files from the workspace into the scratch directory.
    -func (c *CRUDCommand) templateNone(name, scratchDir string, cmd *exec.Generator, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - // update the files we're going to run by joining them with our scratch directory
    - for _, file := range c.Files {
    - realFile := env.Map(file)
    -
    - cmd.Append("-f", filepath.Join(scratchDir, realFile))
    - }
    -
    - // now create an export task to get our files out of the workspace
    - export := &tasks.Export{
    - Files: c.Files,
    - Path: scratchDir,
    - }
    -
    - // make sure the export task is valid
    - err := export.Valid()
    - if err != nil {
    - return err
    - }
    -
    - // run the export
    - return export.Execute(name, logger, env, rt)
    -}
    -
    -func templateEnvironmentFile(filename string, env *environment.Environment) error {
    - // read the file
    - raw, err := ioutil.ReadFile(filename)
    - if err != nil {
    - return err
    - }
    -
    - // replace the environment variables in it
    - data := env.Map(string(raw))
    -
    - // write the templated data back out
    - return ioutil.WriteFile(filename, []byte(data), 0700)
    -}
    -
    -func templateEnvironmentDirectory(directory string, env *environment.Environment) error {
    - files, err := ioutil.ReadDir(directory)
    - if err != nil {
    - return err
    - }
    -
    - for _, file := range files {
    - absFile := filepath.Join(directory, file.Name())
    - err = templateEnvironmentFile(absFile, env)
    - if err != nil {
    - return err
    - }
    - }
    -
    - return nil
    -}
    -
    -// templateEnvironment will copy files from the workspace into the scratch
    -// directory and replace environment variables with those that convey knows
    -// about.
    -func (c *CRUDCommand) templateEnvironment(name, scratchDir string, cmd *exec.Generator, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - // run the normal export which we will update in place.
    - err := c.templateNone(name, scratchDir, cmd, logger, env, rt)
    - if err != nil {
    - return err
    - }
    -
    - // now iterate the files and replace all their environment variables with those that we have
    - for _, file := range c.Files {
    - // resolve the filename if it's templated
    - resolvedFile := env.Map(file)
    -
    - // figure out the full path to it
    - absFile := filepath.Join(scratchDir, resolvedFile)
    -
    - // check if we're looking at a file or a directory
    - entry, err := os.Stat(absFile)
    - if err != nil {
    - return err
    - }
    -
    - if entry.IsDir() {
    - err = templateEnvironmentDirectory(absFile, env)
    - } else {
    - err = templateEnvironmentFile(absFile, env)
    - }
    - if err != nil {
    - return err
    - }
    -
    - }
    -
    - return nil
    -}
    -
    -// Execute runs the given `kubectl` command with the given arguments.
    -func (c *CRUDCommand) Execute(name, action string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - // if we have a context use it
    - if c.Context != "" {
    - err := useContext(name, c.Context, logger, env, rt.State.PlanTimeout)
    - if err != nil {
    - return err
    - }
    - }
    -
    - // now build the apply command line
    - cmd := exec.NewGenerator("kubectl", action)
    -
    - namespace := fullEnv.Map(c.Namespace)
    - if namespace != "" {
    - cmd.Append("-n", namespace)
    - }
    -
    - selectors, err := fullEnv.MapSlice(c.Selector)
    - if err != nil {
    - return err
    - }
    - for _, selector := range selectors {
    - if selector != "" {
    - cmd.Append("-l", selector)
    - }
    - }
    -
    - // create our scratch directory
    - td, err := rt.State.Workspace.CreateTaskDirectory(name)
    - if err != nil {
    - return err
    - }
    -
    - // run our files through the template engine
    - switch c.Engine {
    - case "":
    - fallthrough
    - case "none":
    - err = c.templateNone(name, td.Path(), cmd, logger, fullEnv, rt)
    - case "env":
    - fallthrough
    - case "environment":
    - err = c.templateEnvironment(name, td.Path(), cmd, logger, fullEnv, rt)
    - }
    -
    - // if there was an error with templating bail
    - if err != nil {
    - return err
    - }
    -
    - // finally run the command
    - return exec.Run(name, cmd.Command(), rt.State.PlanTimeout)
    -}
    --- a/kubectl/context.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,35 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "time"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    -)
    -
    -// useContext calls `kubectl config use-context` for the given context
    -func useContext(name, context string, logger *log.Entry, env *environment.Environment, timeout time.Duration) error {
    - realContext := env.Map(context)
    -
    - cmdv := []string{"kubectl", "config", "use-context", realContext}
    -
    - return exec.Run(name, cmdv, timeout)
    -}
    --- a/kubectl/create.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Create is a task for calling `kubectl create`.
    -type Create CRUDCommand
    -
    -// Execute runs `kubectl create` with the given arguments.
    -func (c *Create) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - cmd := &CRUDCommand{
    - Context: c.Context,
    - Namespace: c.Namespace,
    - Files: c.Files,
    - Selector: c.Selector,
    - Engine: c.Engine,
    - }
    -
    - return cmd.Execute(name, "create", logger, env, rt)
    -}
    -
    -// New creates a new kubectl/create task.
    -func (c *Create) New() tasks.Task {
    - return &Create{}
    -}
    -
    -// Valid check if the apply task is valid
    -func (c *Create) Valid() error {
    - if len(c.Files) <= 0 {
    - return errNoFiles
    - }
    -
    - return nil
    -}
    --- a/kubectl/create_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestCreateFilesRequired(t *testing.T) {
    - c := &Create{}
    - assert.NotNil(t, c.Valid())
    -
    - c = &Create{Files: []string{"foo.yml"}}
    - assert.Nil(t, c.Valid())
    -}
    --- a/kubectl/delete.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,55 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Delete is a task for calling `kubectl delete`.
    -type Delete CRUDCommand
    -
    -// Execute runs `kubectl delete` with the given arguments.
    -func (d *Delete) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - cmd := &CRUDCommand{
    - Context: d.Context,
    - Namespace: d.Namespace,
    - Files: d.Files,
    - Selector: d.Selector,
    - Engine: d.Engine,
    - }
    -
    - return cmd.Execute(name, "delete", logger, env, rt)
    -}
    -
    -// New creates a new kubectl/create task.
    -func (d *Delete) New() tasks.Task {
    - return &Delete{}
    -}
    -
    -// Valid check if the apply task is valid
    -func (d *Delete) Valid() error {
    - if len(d.Files) <= 0 {
    - return errNoFiles
    - }
    -
    - return nil
    -}
    --- a/kubectl/delete_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestDeleteFilesRequired(t *testing.T) {
    - d := &Delete{}
    - assert.NotNil(t, d.Valid())
    -
    - d = &Delete{Files: []string{"foo.yml"}}
    - assert.Nil(t, d.Valid())
    -}
    --- a/kubectl/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,25 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "errors"
    -)
    -
    -var (
    - errNoFiles = errors.New("no files specified")
    -)
    --- a/kubectl/kubectl.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,33 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package kubectl contains the tasks for interacting with kubernetes via
    -// kubectl.
    -package kubectl
    -
    -import (
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - // Tasks is a map of kubectl tasks.
    - Tasks = map[string]tasks.Task{
    - "apply": &Apply{},
    - "create": &Create{},
    - "delete": &Delete{},
    - "rollout": &Rollout{},
    - }
    -)
    --- a/kubectl/rollout.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,74 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "errors"
    -
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/exec"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// CRUDCommand represents a call to a `kubectl` command to manage deployments.
    -type Rollout struct {
    - Context string `yaml:"context"`
    - Namespace string `yaml:"namespace"`
    - Target string `yaml:"target"`
    -}
    -
    -func (r *Rollout) New() tasks.Task {
    - return &Rollout{}
    -}
    -
    -func (r *Rollout) Valid() error {
    - if r.Target == "" {
    - return errors.New("no target specified")
    - }
    -
    - return nil
    -}
    -
    -// Execute runs the given `kubectl` command with the given arguments.
    -func (r *Rollout) Execute(name string, logger *log.Entry, env *environment.Environment, rt *runtime.Runtime) error {
    - fullEnv := env.Copy().Merge(rt.Environment)
    -
    - // if we have a context use it
    - if r.Context != "" {
    - err := useContext(name, r.Context, logger, env, rt.State.PlanTimeout)
    - if err != nil {
    - return err
    - }
    - }
    -
    - // now build the command line
    - cmd := exec.NewGenerator("kubectl", "rollout", "status")
    -
    - namespace := fullEnv.Map(r.Namespace)
    - if namespace != "" {
    - cmd.Append("-n", namespace)
    - }
    -
    - // add the target
    - cmd.Append(r.Target)
    -
    - // finally run the command
    - return exec.Run(name, cmd.Command(), rt.State.PlanTimeout)
    -}
    --- a/kubectl/rollout_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,31 +0,0 @@
    -// Convey
    -// Copyright 2016-2019 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package kubectl
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestRolloutTargetRequired(t *testing.T) {
    - r := &Rollout{}
    - assert.NotNil(t, r.Valid())
    -
    - r = &Rollout{Target: "deployment/www"}
    - assert.Nil(t, r.Valid())
    -}
    --- a/loaders/bitbucket/bitbucket.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,18 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package bitbucket providers a config loader for bitbucket-pipelines.yml.
    -package bitbucket
    --- a/loaders/bitbucket/data/branch-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,10 +0,0 @@
    ----
    -# This is a simple bitbucket-pipelines.yml that uses the branch format to
    -# setup the build
    -pipelines:
    - branches:
    - develop:
    - - step:
    - image: library/alpine
    - script:
    - - true
    --- a/loaders/bitbucket/data/branch-no-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,9 +0,0 @@
    ----
    -# This is a simple bitbucket-pipelines.yml that does not specify an image and
    -# should cause a failure.
    -pipelines:
    - branches:
    - develop:
    - - step:
    - script:
    - - true
    --- a/loaders/bitbucket/data/complex-global-image-with-login.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,15 +0,0 @@
    -image:
    - name: registry.docker.io/python:3
    - username: foo
    - password: bar
    - email: email
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-global-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,12 +0,0 @@
    -image:
    - name: python:3
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-step-image-with-login.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,14 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - image:
    - name: registry.docker.io/python:3
    - username: user
    - password: pass
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-step-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,12 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - image:
    - name: python:3
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/complex-step-simple-image.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,11 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - image: python:3
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/data/docker-mixed.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,13 +0,0 @@
    -image: gliderlabs/alpine:edge
    -pipelines:
    - default:
    - - step:
    - script:
    - - hostname
    - - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
    - - date +%s
    - - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
    - - docker push atlassian/my-app:$BITBUCKET_COMMIT
    - - date +%s
    -options:
    - docker: true
    --- a/loaders/bitbucket/data/docker-simple.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,9 +0,0 @@
    -pipelines:
    - default:
    - - step:
    - script:
    - - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
    - - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
    - - docker push atlassian/my-app:$BITBUCKET_COMMIT
    -options:
    - docker: true
    --- a/loaders/bitbucket/data/simple.yml Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,11 +0,0 @@
    -image: python:3
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests
    --- a/loaders/bitbucket/loader.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,277 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "fmt"
    - "os"
    - "path/filepath"
    -
    - "bitbucket.org/rw_grim/govcs"
    - "github.com/go-yaml/yaml"
    - log "github.com/sirupsen/logrus"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/script"
    - "keep.imfreedom.org/grim/convey/stages"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -const (
    - defaultPlan = "default"
    -)
    -
    -// Loader is a loader.Loader that loads bitbucket-pipelines.yml files
    -type Loader struct{}
    -
    -func addPipeline(name string, defImage image, pipelines []pipeline, cfg *config.Config) error {
    - plan := plans.Plan{
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Tasks: []string{"import"},
    - Run: "on-success",
    - },
    - },
    - }
    -
    - for idx, pipeline := range pipelines {
    - loginTask := ""
    - logoutTask := ""
    -
    - img := defImage
    - if pipeline.Steps.Image.Name != "" {
    - img = pipeline.Steps.Image
    -
    - // if the step has an image with a username, we need to add the tasks to login and logout
    - if img.Username != "" {
    - registry, _, _ := docker.ParseImage(img.Name)
    -
    - // create and add the login task to the stage
    - loginTask = fmt.Sprintf("%s-%d-login", name, idx)
    - cfg.Tasks[loginTask] = &docker.Login{
    - Username: img.Username,
    - Password: img.Password,
    - Server: registry,
    - }
    -
    - // create the logout task, but store the name so we can add it after the other tasks
    - logoutTask = fmt.Sprintf("%s-%d-logout", name, idx)
    - cfg.Tasks[logoutTask] = &docker.Logout{
    - Server: registry,
    - }
    - }
    - } else if img.Username != "" {
    - // if we're using the global image and it has a username, we need to add the tasks to the stage
    - loginTask = "login"
    - logoutTask = "logout"
    - }
    -
    - // if we have a login task, add it to the tasks for this stage
    - if loginTask != "" {
    - plan.Stages[idx].Tasks = append(plan.Stages[idx].Tasks, loginTask)
    - }
    -
    - parsedTasks, err := script.Parse(img.Name, "/bin/sh", pipeline.Steps.Script)
    - if err != nil {
    - return err
    - }
    -
    - for idx, task := range parsedTasks {
    - taskName := fmt.Sprintf("%s-%d", name, idx)
    - cfg.Tasks[taskName] = task
    -
    - plan.Stages[0].Tasks = append(plan.Stages[0].Tasks, taskName)
    -
    - }
    -
    - // if we have a logout task, add it to the tasks for this stage
    - if logoutTask != "" {
    - plan.Stages[idx].Tasks = append(plan.Stages[idx].Tasks, logoutTask)
    - }
    -
    - }
    -
    - cfg.Plans[name] = plan
    -
    - return nil
    -}
    -
    -func addPipelines(base string, defImage image, pipelines map[string][]pipeline, cfg *config.Config) error {
    - for name, branchPipeline := range pipelines {
    - if len(branchPipeline) > 0 {
    - planName := fmt.Sprintf("%s-%s", base, name)
    - err := addPipeline(planName, defImage, branchPipeline, cfg)
    - if err != nil {
    - return err
    - }
    - }
    - }
    -
    - return nil
    -}
    -
    -// Load loads the given filename and returns a config.Config for it.
    -func (l *Loader) Load(base, path string, data []byte, options []string, disableDeprecated bool) (*config.Config, error) {
    - var pipeline bitbucketPipelines
    - err := yaml.Unmarshal(data, &pipeline)
    - if err != nil {
    - return nil, err
    - }
    -
    - cfg := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - },
    - Plans: map[string]plans.Plan{},
    - }
    -
    - // store the default image
    - defImage := pipeline.Image
    -
    - // if the default image has a username, add a login/logout task
    - if defImage.Username != "" {
    - registry, _, _ := docker.ParseImage(defImage.Name)
    - cfg.Tasks["login"] = &docker.Login{
    - Username: defImage.Username,
    - Password: defImage.Password,
    - Server: registry,
    - }
    -
    - cfg.Tasks["logout"] = &docker.Logout{
    - Server: registry,
    - }
    - }
    -
    - // add the default pipelines
    - if len(pipeline.Pipelines.Default) > 0 {
    - err = addPipeline("default", defImage, pipeline.Pipelines.Default, cfg)
    - if err != nil {
    - return nil, err
    - }
    - }
    -
    - err = addPipelines("bookmark", defImage, pipeline.Pipelines.Bookmarks, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = addPipelines("branch", defImage, pipeline.Pipelines.Branches, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = addPipelines("custom", defImage, pipeline.Pipelines.Custom, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = addPipelines("tag", defImage, pipeline.Pipelines.Tags, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - return cfg, nil
    -}
    -
    -// LoadOverride loads the given override file into the config.
    -func (l *Loader) LoadOverride(base, path string, data []byte, cfg *config.Config, disableDeprecated bool) {
    -}
    -
    -// Filenames returns the list for filenames that this loader supports.
    -func (l *Loader) Filenames() []string {
    - return []string{"bitbucket-pipelines.yml"}
    -}
    -
    -// OverrideSuffix returns the suffix for override files.
    -func (l *Loader) OverrideSuffix() string {
    - return "-override"
    -}
    -
    -// DefaultPlan returns the default plan name.
    -func (l *Loader) DefaultPlan() string {
    - vcs, err := govcs.Detect(".")
    - if err != nil {
    - log.Error(err)
    - return defaultPlan
    - }
    -
    - branch := vcs.Branch()
    -
    - if branch == "master" || branch == "default" {
    - return defaultPlan
    - }
    -
    - return "branch-" + branch
    -}
    -
    -// ResolvePlanName resolves the plan name if the one in the config contains a
    -// wildcard.
    -func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, rt *runtime.Runtime) string {
    - if plan != "" {
    - // try to shortcut if we can
    - if plan == defaultPlan {
    - return defaultPlan
    - }
    -
    - customPlan := fmt.Sprintf("custom-%s", plan)
    - if _, found := cfg.Plans[customPlan]; found {
    - return customPlan
    - }
    -
    - // we couldn't find the plan, so just return it so convey can return
    - // an error saying it wasn't found.
    - return plan
    - }
    -
    - // we're in auto discovery mode! woo?
    -
    - matchMapper := map[string][]string{
    - "branch": {},
    - }
    -
    - // figure out the we're looking for
    - if gitBranch := os.Getenv("GIT_BRANCH"); gitBranch != "" {
    - matchMapper["branch"] = append(matchMapper["branch"], gitBranch)
    - }
    - if hgBranch := os.Getenv("HG_BRANCH"); hgBranch != "" {
    - matchMapper["branch"] = append(matchMapper["branch"], hgBranch)
    - }
    - if hgBookmark := os.Getenv("HG_BOOKMARK"); hgBookmark != "" {
    - matchMapper["bookmark"] = append(matchMapper["bookmark"], hgBookmark)
    - }
    -
    - for name := range cfg.Plans {
    - for prefix, matchers := range matchMapper {
    - for _, matcher := range matchers {
    - calculatedName := fmt.Sprintf("%s-%s", prefix, matcher)
    - fmt.Printf("checking %s against %s\n", calculatedName, name)
    - if matched, _ := filepath.Match(name, calculatedName); matched {
    - return name
    - }
    - }
    - }
    - }
    -
    - return defaultPlan
    -}
    --- a/loaders/bitbucket/loader_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,514 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "io/ioutil"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/docker"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/stages"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -func TestLoaderSimple(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/simple.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexGlobalImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-global-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexGlobalImageWithLogin(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-global-image-with-login.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "login": &docker.Login{
    - Username: "foo",
    - Password: "bar",
    - Server: "registry.docker.io",
    - },
    - "default-0": &docker.Run{
    - Image: "registry.docker.io/python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - "logout": &docker.Logout{
    - Server: "registry.docker.io",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Run: "on-success",
    - Concurrent: false,
    - Environment: nil,
    - Tasks: []string{"import", "login", "default-0", "logout"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexStepSimpleImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-step-simple-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexStepImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-step-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderComplexStepImageWithLogin(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/complex-step-image-with-login.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0-login": &docker.Login{
    - Username: "user",
    - Password: "pass",
    - Server: "registry.docker.io",
    - },
    - "default-0": &docker.Run{
    - Image: "registry.docker.io/python:3",
    - WorkDir: "/workspace",
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - Shell: "/bin/sh",
    - },
    - "default-0-logout": &docker.Logout{
    - Server: "registry.docker.io",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "default-0-login", "default-0", "default-0-logout"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderBranchNoImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/branch-no-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - err = cfg.Valid()
    - assert.NotNil(t, err)
    -}
    -
    -func TestLoaderBranchImage(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/branch-image.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "branch-develop-0": &docker.Run{
    - Image: "library/alpine",
    - WorkDir: "/workspace",
    - Script: []string{
    - "true",
    - },
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "branch-develop": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{"import", "branch-develop-0"},
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderDockerMixed(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/docker-mixed.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Run{
    - Image: "gliderlabs/alpine:edge",
    - Script: []string{"hostname"},
    - WorkDir: "/workspace",
    - Shell: "/bin/sh",
    - },
    - "default-1": &docker.Login{
    - Username: "$DOCKER_USERNAME",
    - Password: "$DOCKER_PASSWORD",
    - },
    - "default-2": &docker.Run{
    - Image: "gliderlabs/alpine:edge",
    - Script: []string{"date +%s"},
    - WorkDir: "/workspace",
    - Shell: "/bin/sh",
    - },
    - "default-3": &docker.Build{
    - Files: []string{"."},
    - Tag: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - "default-4": &docker.Push{
    - Image: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - "default-5": &docker.Run{
    - Image: "gliderlabs/alpine:edge",
    - Script: []string{"date +%s"},
    - WorkDir: "/workspace",
    - Shell: "/bin/sh",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{
    - "import",
    - "default-0",
    - "default-1",
    - "default-2",
    - "default-3",
    - "default-4",
    - "default-5",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    -
    -func TestLoaderDockerSimple(t *testing.T) {
    - l := Loader{}
    -
    - data, err := ioutil.ReadFile("data/docker-simple.yml")
    - assert.Nil(t, err)
    -
    - cfg, err := l.Load("", "", data, []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - expected := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - "default-0": &docker.Login{
    - Username: "$DOCKER_USERNAME",
    - Password: "$DOCKER_PASSWORD",
    - },
    - "default-1": &docker.Build{
    - Files: []string{"."},
    - Tag: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - "default-2": &docker.Push{
    - Image: "atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - },
    - Plans: map[string]plans.Plan{
    - "default": {
    - Stages: []stages.Stage{
    - {
    - Name: "stage-0",
    - Enabled: true,
    - Concurrent: false,
    - Run: "on-success",
    - Environment: nil,
    - Tasks: []string{
    - "import",
    - "default-0",
    - "default-1",
    - "default-2",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, cfg, expected)
    -}
    --- a/loaders/bitbucket/types.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,128 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "github.com/go-yaml/yaml"
    -)
    -
    -type image struct {
    - Name string `yaml:"name"`
    - Username string `yaml:"username"`
    - Password string `yaml:"password"`
    - EMail string `yaml:"email"`
    -}
    -
    -func (i *image) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - // first check if this is the basic format where it's just a name
    - name := ""
    - err := unmarshal(&name)
    - if err == nil {
    - *i = image{
    - Name: name,
    - }
    -
    - return nil
    - }
    -
    - // if we got a yaml type error, try to unmarshal it as the struct
    - if _, ok := err.(*yaml.TypeError); ok {
    - type rawImage image
    - raw := rawImage{}
    -
    - if err = unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *i = image(raw)
    -
    - return nil
    - }
    -
    - return err
    -}
    -
    -type step struct {
    - Image image `yaml:"image"`
    - Script []string `yaml:"script"`
    - Services []string `yaml:"services"`
    -}
    -
    -type pipeline struct {
    - Steps step `yaml:"step"`
    -}
    -
    -type pipelines struct {
    - Bookmarks map[string][]pipeline `yaml:"bookmarks"`
    - Branches map[string][]pipeline `yaml:"branches"`
    - Custom map[string][]pipeline `yaml:"custom"`
    - Default []pipeline `yaml:"default"`
    - Tags map[string][]pipeline `yaml:"tags"`
    -}
    -
    -type service struct {
    - Image image `yaml:"image"`
    - Environment []string `yaml:"environment"`
    - Username string `yaml:"username"`
    - Password string `yaml:"password"`
    -}
    -
    -type definition struct {
    - Services map[string]service `yaml:"services"`
    -}
    -
    -type options struct {
    - Docker bool `yaml:"docker"`
    -}
    -
    -type bitbucketPipelines struct {
    - Image image `yaml:"image"`
    - Clone clone `yaml:"clone"`
    - Pipelines pipelines `yaml:"pipelines"`
    - Definitions definition `yaml:"definitions"`
    - Options options `yaml:"options"`
    -}
    -
    -func (b *bitbucketPipelines) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawBitbucketPipelines bitbucketPipelines
    - raw := rawBitbucketPipelines{Clone: clone{}}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *b = bitbucketPipelines(raw)
    -
    - return nil
    -}
    -
    -type clone struct {
    - Depth int `yaml:"depth"`
    -}
    -
    -func (c *clone) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawClone clone
    - raw := rawClone{Depth: 50}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - *c = clone(raw)
    -
    - return nil
    -}
    --- a/loaders/bitbucket/unmarshal_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,249 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package bitbucket
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestUnmarshalSimple(t *testing.T) {
    - yamlData := `image: python:3
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - find . -type f -iname "*.pyc" -exec rm -f {} \; || true
    - - find . -type d -iname __pycache__ -exec rm -rf {} \; || true
    - - pip install -r dev-requirements.txt
    - - flake8
    - - PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests`
    -
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Image: image{
    - Name: "python:3",
    - },
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "set -ex",
    - "find . -type f -iname \"*.pyc\" -exec rm -f {} \\; || true",
    - "find . -type d -iname __pycache__ -exec rm -rf {} \\; || true",
    - "pip install -r dev-requirements.txt",
    - "flake8",
    - "PYTHONPATH=$(pwd) py.test --color=auto --cov=pipelines --cov-report=term-missing tests",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalMultipleBranches(t *testing.T) {
    - yamlData := `image: pidgin/builder-debian:stretch
    -pipelines:
    - default:
    - - step:
    - script:
    - - set -ex
    - - ./autogen.sh --enable-debug --enable-gtk-doc
    - - make -s -j$(nproc)
    - - make -s -j$(nproc) distcheck
    - branches:
    - release-2.x.y:
    - - step:
    - image: pidgin/release-builder:release-2.x.y
    - script:
    - - set -ex
    - - ./autogen.sh --enable-debug
    - - make -s -j$(nproc)
    - - make -s -j$(nproc) check
    - - make distcheck`
    -
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Image: image{
    - Name: "pidgin/builder-debian:stretch",
    - },
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "set -ex",
    - "./autogen.sh --enable-debug --enable-gtk-doc",
    - "make -s -j$(nproc)",
    - "make -s -j$(nproc) distcheck",
    - },
    - },
    - },
    - },
    - Branches: map[string][]pipeline{
    - "release-2.x.y": {
    - {
    - Steps: step{
    - Image: image{
    - Name: "pidgin/release-builder:release-2.x.y",
    - },
    - Script: []string{
    - "set -ex",
    - "./autogen.sh --enable-debug",
    - "make -s -j$(nproc)",
    - "make -s -j$(nproc) check",
    - "make distcheck",
    - },
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalWithDocker(t *testing.T) {
    - yamlData := `pipelines:
    - default:
    - - step:
    - script:
    - - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
    - - docker build -t atlassian/my-app:$BITBUCKET_COMMIT .
    - - docker push atlassian/my-app:$BITBUCKET_COMMIT
    -options:
    - docker: true
    -`
    -
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD",
    - "docker build -t atlassian/my-app:$BITBUCKET_COMMIT .",
    - "docker push atlassian/my-app:$BITBUCKET_COMMIT",
    - },
    - },
    - },
    - },
    - },
    - Options: options{
    - Docker: true,
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalComplexGlobalImage(t *testing.T) {
    - yamlData := `pipelines:
    -image:
    - name: foobar
    -pipelines:
    - default:
    - - step:
    - script:
    - - true
    - `
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Image: image{
    - Name: "foobar",
    - },
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Script: []string{
    - "true",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    -
    -func TestUnmarshalComplexStepImage(t *testing.T) {
    - yamlData := `pipelines:
    -pipelines:
    - default:
    - - step:
    - image:
    - name: name
    - username: user
    - password: pass
    - email: test@example.com
    - script:
    - - true
    - `
    - var actual bitbucketPipelines
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := bitbucketPipelines{
    - Pipelines: pipelines{
    - Default: []pipeline{
    - {
    - Steps: step{
    - Image: image{
    - Name: "name",
    - Username: "user",
    - Password: "pass",
    - EMail: "test@example.com",
    - },
    - Script: []string{
    - "true",
    - },
    - },
    - },
    - },
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    --- a/loaders/codebuild/codebuild.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,19 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package codebuild contains the convey load for AWS CodeBuild
    -// https://aws.amazon.com/codebuild/
    -package codebuild
    --- a/loaders/codebuild/loader.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,210 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package codebuild
    -
    -import (
    - "fmt"
    - "path/filepath"
    - "strings"
    -
    - "github.com/go-yaml/yaml"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/script"
    - "keep.imfreedom.org/grim/convey/stages"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -// Loader defines an AWS CodeBuild configuration.
    -type Loader struct {
    - region string
    - image string
    - accountID string
    -}
    -
    -func (l *Loader) loadOptions(options []string) error {
    - for _, opt := range options {
    - parts := strings.SplitN(opt, "=", 2)
    - if len(parts) != 2 {
    - return fmt.Errorf("invalid option '%s'", opt)
    - }
    -
    - switch parts[0] {
    - case "region":
    - l.region = parts[1]
    - case "image":
    - l.image = parts[1]
    - case "account-id":
    - l.accountID = parts[1]
    - }
    - }
    -
    - if l.image == "" {
    - return fmt.Errorf("no image specified")
    - }
    -
    - return nil
    -}
    -
    -// Load loads an AWS CodeBuild buildspec.yml.
    -func (l *Loader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*config.Config, error) {
    - err := l.loadOptions(options)
    - if err != nil {
    - return nil, err
    - }
    -
    - var cb CodeBuild
    - err = yaml.Unmarshal(data, &cb)
    - if err != nil {
    - return nil, err
    - }
    -
    - cfg := &config.Config{
    - Tasks: map[string]tasks.Task{
    - "import": &tasks.Import{
    - Files: []string{"."},
    - },
    - },
    - Plans: map[string]plans.Plan{},
    - }
    -
    - // create our plan and add our tasks
    - plan := plans.Plan{
    - Stages: []stages.Stage{
    - {
    - Name: "import",
    - Enabled: true,
    - Run: "on-success",
    - Tasks: []string{"import"},
    - },
    - },
    - }
    -
    - // TODO put the right image here
    - err = l.addPhases(cb, cfg, l.image, &plan)
    - if err != nil {
    - return nil, err
    - }
    -
    - l.createExportTask(cb, cfg, &plan)
    -
    - cfg.Plans["default"] = plan
    -
    - return cfg, nil
    -}
    -
    -func (l *Loader) addPhase(cb CodeBuild, cfg *config.Config, phaseName, imageName string, plan *plans.Plan) error {
    - phase, ok := cb.Phases[phaseName]
    - // ignore phases that are not in the yaml
    - if !ok {
    - return nil
    - }
    -
    - parsedTasks, err := script.Parse(imageName, "/bin/sh", phase.Commands)
    - if err != nil {
    - return err
    - }
    -
    - // create our stage
    - stage := stages.Stage{
    - Name: phaseName,
    - Enabled: true,
    - Run: "on-success",
    - }
    -
    - // now add the tasks to the config and the stage
    - for idx, task := range parsedTasks {
    - name := fmt.Sprintf("%s-%d", phaseName, idx)
    - cfg.Tasks[name] = task
    -
    - stage.Tasks = append(stage.Tasks, name)
    - }
    -
    - // add the stage to the plan
    - plan.Stages = append(plan.Stages, stage)
    -
    - return nil
    -}
    -
    -func (l *Loader) addPhases(cb CodeBuild, cfg *config.Config, imageName string, plan *plans.Plan) error {
    - phases := []string{"install", "pre_build", "build", "post_build"}
    -
    - // load all of the phases scripts into a single slice
    - for _, phase := range phases {
    - err := l.addPhase(cb, cfg, phase, imageName, plan)
    - if err != nil {
    - return err
    - }
    -
    - }
    -
    - return nil
    -}
    -
    -func (l *Loader) createExportTask(cb CodeBuild, cfg *config.Config, plan *plans.Plan) {
    - if len(cb.Artifacts.Files) == 0 {
    - return
    - }
    -
    - export := &tasks.Export{
    - Files: cb.Artifacts.Files,
    - }
    -
    - cfg.Tasks["artifacts"] = export
    -
    - if cb.Artifacts.DiscardPaths == "yes" {
    - for idx, name := range cb.Artifacts.Files {
    - cb.Artifacts.Files[idx] = filepath.Base(name)
    - }
    - }
    -
    - exportStage := stages.Stage{
    - Name: "artifacts",
    - Enabled: true,
    - Run: "on-success",
    - Tasks: []string{"artifacts"},
    - }
    -
    - plan.Stages = append(plan.Stages, exportStage)
    -}
    -
    -// LoadOverride just satisfies the Loader interface an is not implemented.
    -func (l *Loader) LoadOverride(path, base string, data []byte, config *config.Config, disableDeprecated bool) {
    -}
    -
    -// Filenames returns the filenames defining AWS CodeBuild configurations.
    -func (l *Loader) Filenames() []string {
    - return []string{"buildspec.yml"}
    -}
    -
    -// OverrideSuffix just satisfies the Loader interface and is not used.
    -func (l *Loader) OverrideSuffix() string {
    - return ""
    -}
    -
    -// DefaultPlan returns the default plan to use when an AWS CodeBuild
    -// configuration is loaded.
    -func (l *Loader) DefaultPlan() string {
    - return "default"
    -}
    -
    -// ResolvePlanName satisfies the LoaderInterface and returns the default value.
    -func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, rt *runtime.Runtime) string {
    - return plan
    -}
    --- a/loaders/codebuild/types.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,41 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package codebuild
    -
    -type environment struct {
    - Variables map[string]string `yaml:"variables"`
    - ParameterStore map[string]string `yaml:"parameter-store"`
    -}
    -
    -type phase struct {
    - Commands []string `yaml:"commands"`
    -}
    -
    -type artifacts struct {
    - Files []string `yaml:"files"`
    - DiscardPaths string `yaml:"discard-paths"`
    - BaseDirectory string `yaml:"base-directory"`
    -}
    -
    -// CodeBuild defines the data structure for the top level AWS CodeBuild
    -// configuration.
    -type CodeBuild struct {
    - Version string `yaml:"version"`
    - Environment environment `yaml:"env"`
    - Phases map[string]phase `yaml:"phases"`
    - Artifacts artifacts `yaml:"artifacts"`
    -}
    --- a/loaders/codebuild/unmarshal_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,101 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package codebuild
    -
    -import (
    - "testing"
    -
    - "github.com/go-yaml/yaml"
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestUnmarshalSimple(t *testing.T) {
    - yamlData := `version: 0.2
    -env:
    - variables:
    - JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64"
    - parameter-store:
    - LOGIN_PASSWORD: "dockerLoginPassword"
    -phases:
    - install:
    - commands:
    - - apt-get update -y
    - - apt-get install -y maven
    - pre_build:
    - commands:
    - - echo Nothing to do in the pre_build phase...
    - build:
    - commands:
    - - echo Build started on $(date)
    - - mvn install
    - post_build:
    - commands:
    - - echo Build completed on $(date)
    -artifacts:
    - files:
    - - target/messageUtil-1.0.jar
    - discard-paths: yes`
    -
    - var actual CodeBuild
    - err := yaml.Unmarshal([]byte(yamlData), &actual)
    -
    - assert.Nil(t, err)
    -
    - expected := CodeBuild{
    - Version: "0.2",
    - Environment: environment{
    - Variables: map[string]string{
    - "JAVA_HOME": "/usr/lib/jvm/java-8-openjdk-amd64",
    - },
    - ParameterStore: map[string]string{
    - "LOGIN_PASSWORD": "dockerLoginPassword",
    - },
    - },
    - Phases: map[string]phase{
    - "install": {
    - Commands: []string{
    - "apt-get update -y",
    - "apt-get install -y maven",
    - },
    - },
    - "pre_build": {
    - Commands: []string{
    - "echo Nothing to do in the pre_build phase...",
    - },
    - },
    - "build": {
    - Commands: []string{
    - "echo Build started on $(date)",
    - "mvn install",
    - },
    - },
    - "post_build": {
    - Commands: []string{
    - "echo Build completed on $(date)",
    - },
    - },
    - },
    - Artifacts: artifacts{
    - Files: []string{
    - "target/messageUtil-1.0.jar",
    - },
    - DiscardPaths: "yes",
    - },
    - }
    -
    - assert.Equal(t, actual, expected)
    -}
    --- a/loaders/convey/convey.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,288 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package convey provides the config loader for convey configurations.
    -package convey
    -
    -import (
    - "fmt"
    - "path/filepath"
    -
    - "github.com/blang/semver"
    - "github.com/go-yaml/yaml"
    - log "github.com/sirupsen/logrus"
    -
    - cConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/consts"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/tasks"
    - cYaml "keep.imfreedom.org/grim/convey/yaml"
    -)
    -
    -type options struct {
    - DefaultPlan string `yaml:"default-plan"`
    - SSHIdentities cYaml.StringOrSlice `yaml:"ssh-identities"`
    -}
    -
    -type config struct {
    - RequiredVersion string `yaml:"required-version"`
    - Extends string `yaml:"extends"`
    - Tasks map[string]yaml.MapSlice `yaml:"tasks"`
    - Plans map[string]plans.Plan `yaml:"plans"`
    - MetaPlans map[string]plans.MetaPlan `yaml:"meta-plans"`
    - Environment cYaml.StringOrSlice `yaml:"environment"`
    - Options options `yaml:"options"`
    -}
    -
    -type override struct {
    - Environment cYaml.StringOrSlice `yaml:"environment"`
    - Options options `yaml:"options"`
    -}
    -
    -// Loader is a loader.Loader for loading convey.yml files.
    -type Loader struct {
    - logger *log.Entry
    - defaultPlan string
    - depth int
    - fileLoader func(string, *Loader) (*cConfig.Config, error)
    -}
    -
    -func (l *Loader) loadBase(cfg config, path string, options []string, disableDeprecated bool) (*cConfig.Config, error) {
    - // see if we're extending something - partially load a base
    - // config object if so that we'll modify; otherwise use an
    - // empty base config object that we'll set in a similar way
    - if cfg.Extends != "" {
    - if l.depth >= MaxExtendsDepth {
    - return nil, ErrMaxExtendsDepth
    - }
    -
    - extendsAbsName := filepath.Join(path, cfg.Extends)
    -
    - l.depth++
    - baseConfig, err := l.loadFile(extendsAbsName, options, disableDeprecated)
    - l.depth--
    -
    - // We can safely ignore no plans and no tasks errors here
    - // as we're ensured to also get a valid base config back.
    - // This is a bit of a strange idiom, but is still used in
    - // places like io.Reader (return non-zero n on error).
    - if err != nil && err != ErrNoPlans && err != ErrNoTasks {
    - return nil, err
    - }
    -
    - return baseConfig, nil
    - }
    -
    - return &cConfig.Config{
    - Tasks: map[string]tasks.Task{},
    - Plans: map[string]plans.Plan{},
    - MetaPlans: map[string]plans.MetaPlan{},
    - SSHIdentities: []string{},
    - }, nil
    -}
    -
    -func (l *Loader) loadPlans(baseConfig *cConfig.Config, cfg config) error {
    - // iterate through each plan and do any cleanup we need to
    - for _, plan := range cfg.Plans {
    - // set stage names for any that are missing them
    - for idx := range plan.Stages {
    - if plan.Stages[idx].Name == "" {
    - plan.Stages[idx].Name = fmt.Sprintf("stage-%d", idx)
    - }
    - }
    - }
    -
    - // If the plan has the merge attribute set, try to overwrite
    - // stages of a plan that is already declared with the same name.
    - // If no such plan exists, raise an error (you're trying to
    - // overwrite a stage of nothing, which is almost certainly an error.
    - for name, plan := range cfg.Plans {
    - if plan.Merge {
    - base, ok := baseConfig.Plans[name]
    - if !ok {
    - return fmt.Errorf("cannot merge with unknown plan '%s'", name)
    - }
    -
    - base, err := mergePlan(base, plan, name)
    - if err != nil {
    - return err
    - }
    -
    - baseConfig.Plans[name] = base
    - } else {
    - baseConfig.Plans[name] = plan
    - }
    - }
    -
    - return nil
    -}
    -
    -// Load loads the given filename and returns it as a config.Config.
    -func (l *Loader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*cConfig.Config, error) {
    - if l.logger == nil {
    - l.logger = logging.NewAdapter("config loader")
    - }
    -
    - // load the raw config
    - cfg := config{}
    - err := yaml.Unmarshal(data, &cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - if cfg.RequiredVersion != "" {
    - verRange, nErr := semver.ParseRange(cfg.RequiredVersion)
    - if nErr != nil {
    - return nil, nErr
    - }
    -
    - curVer, nErr := semver.Make(consts.Version)
    - if nErr != nil {
    - return nil, nErr
    - }
    -
    - if !verRange(curVer) {
    - nErr = fmt.Errorf(
    - "convey version %s required, but %s is in use",
    - cfg.RequiredVersion,
    - consts.Version,
    - )
    -
    - return nil, nErr
    - }
    - }
    -
    - // get our base config
    - baseConfig, err := l.loadBase(cfg, path, options, disableDeprecated)
    - if err != nil {
    - return nil, err
    - }
    -
    - // turn the raw tasks into real tasks
    - realTasks, err := loadTasks(path, cfg.Tasks, l.logger, disableDeprecated)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = l.loadPlans(baseConfig, cfg)
    - if err != nil {
    - return nil, err
    - }
    -
    - // store the default plan in the loader
    - l.defaultPlan = cfg.Options.DefaultPlan
    -
    - // tasks, plans, and metaplans are all name => * maps, and
    - // if we're extending something we want to inherit all of these
    - // EXCEPT for things that we are explicitly overriding by using
    - // the same name. Do a shallow merge here
    -
    - for name, task := range realTasks {
    - baseConfig.Tasks[name] = task
    - }
    -
    - for name, metaPlan := range cfg.MetaPlans {
    - baseConfig.MetaPlans[name] = metaPlan
    - }
    -
    - baseConfig.Environment = environment.Merge(baseConfig.Environment, cfg.Environment)
    -
    - // Check if the default plan is being overridden
    - if cfg.Options.DefaultPlan != "" {
    - l.defaultPlan = cfg.Options.DefaultPlan
    - }
    -
    - // don't clobber ssh-identities with an empty list
    - if len(cfg.Options.SSHIdentities) > 0 {
    - baseConfig.SSHIdentities = cfg.Options.SSHIdentities
    - }
    -
    - // Return the base config at this point, but maybe possibly return an
    - // error that we're missing something in order to proceed. This gets
    - // kind of weird: if we're calling Load recursively through LoadFile,
    - // as is the case with extending a config file, we're going to ignore
    - // these errors but need the actual config (we may define tasks and
    - // plans in an extending config).
    -
    - if len(baseConfig.Tasks) == 0 {
    - return baseConfig, ErrNoTasks
    - }
    -
    - if len(baseConfig.Plans) == 0 {
    - return baseConfig, ErrNoPlans
    - }
    -
    - return baseConfig, nil
    -}
    -
    -// Load loads the given filename and returns it as a config.Config.
    -func (l *Loader) loadFile(path string, options []string, disableDeprecated bool) (*cConfig.Config, error) {
    - if l.fileLoader == nil {
    - return cConfig.LoadFile(path, l, options, disableDeprecated)
    - }
    -
    - return l.fileLoader(path, l)
    -}
    -
    -// LoadOverride loads the given override file into the given config.Config.
    -func (l *Loader) LoadOverride(path, base string, data []byte, config *cConfig.Config, disableDeprecated bool) {
    - var overrideData override
    -
    - err := yaml.Unmarshal(data, &overrideData)
    - if err != nil {
    - return
    - }
    -
    - config.Environment = environment.Merge(config.Environment, overrideData.Environment)
    -
    - // Check if the default plan is being overridden
    - if overrideData.Options.DefaultPlan != "" {
    - l.defaultPlan = overrideData.Options.DefaultPlan
    - }
    -
    - // if there are ssh-identities in the override they need to replace the
    - // ones in the normal config file.
    - if len(overrideData.Options.SSHIdentities) > 0 {
    - config.SSHIdentities = overrideData.Options.SSHIdentities
    - }
    -}
    -
    -// Filenames returns a string slice of filenames that are valid for this loader.
    -func (l *Loader) Filenames() []string {
    - return []string{"convey.yml", "convey.yaml"}
    -}
    -
    -// OverrideSuffix returns the suffix for override filenames.
    -func (l *Loader) OverrideSuffix() string {
    - return "-override"
    -}
    -
    -// DefaultPlan returns the default plan name.
    -func (l *Loader) DefaultPlan() string {
    - if l.defaultPlan != "" {
    - return l.defaultPlan
    - }
    -
    - return "default"
    -}
    -
    -// ResolvePlanName resolves a plan name if wildcards are supported.
    -func (l *Loader) ResolvePlanName(plan string, cfg *cConfig.Config, rt *runtime.Runtime) string {
    - return plan
    -}
    --- a/loaders/convey/convey_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,77 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - realConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/state"
    -)
    -
    -/* OverrideSuffix tests */
    -func TestOverrideSuffix(t *testing.T) {
    - l := Loader{}
    -
    - assert.Equal(t, l.OverrideSuffix(), "-override")
    -}
    -
    -/* ResolvePlanName tests */
    -func TestResolvePlanName(t *testing.T) {
    - l := Loader{}
    - cfg := &realConfig.Config{}
    - rt := runtime.New(&state.State{})
    -
    - tests := []string{
    - "",
    - "foo",
    - "*",
    - }
    -
    - for _, name := range tests {
    - assert.Equal(t, l.ResolvePlanName(name, cfg, rt), name)
    - }
    -}
    -
    -/* required fields tests */
    -func TestTasksRequired(t *testing.T) {
    - data := `plans:
    - default:
    - name: foo
    -`
    -
    - l := Loader{}
    -
    - _, err := l.Load(".", ".", []byte(data), []string{}, true)
    - assert.EqualError(t, err, ErrNoTasks.Error())
    -}
    -
    -func TestPlansRequired(t *testing.T) {
    - data := `tasks:
    - foo:
    - type: docker/run
    - image: foobar
    -`
    -
    - l := Loader{}
    -
    - _, err := l.Load(".", ".", []byte(data), []string{}, true)
    - assert.EqualError(t, err, ErrNoPlans.Error())
    -}
    --- a/loaders/convey/default_plan_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,87 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -func TestDefaultPlan(t *testing.T) {
    - l := Loader{}
    -
    - assert.Equal(t, l.DefaultPlan(), "default")
    -}
    -
    -func TestDefaultPlanSet(t *testing.T) {
    - l := Loader{
    - defaultPlan: "something else",
    - }
    -
    - assert.Equal(t, l.DefaultPlan(), "something else")
    -}
    -
    -func TestDefaultPlanFromConfig(t *testing.T) {
    - data := `options:
    - default-plan: overridden-plan
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary
    -plans:
    - overridden-plan:
    - stages:
    - - tasks:
    - - foo
    -`
    -
    - l := Loader{}
    - cfg, err := l.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, l.defaultPlan, "overridden-plan")
    - assert.NotNil(t, cfg)
    -}
    -
    -func TestDefaultPlanFromOverride(t *testing.T) {
    - data := `tasks:
    - foo:
    - type: docker/run
    - image: imaginary
    -plans:
    - overridden-plan:
    - stages:
    - - tasks:
    - - foo
    -`
    -
    - l := Loader{}
    - cfg, err := l.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, l.defaultPlan, "")
    -
    - overrideData := `
    -options:
    - default-plan: abcxyz
    -`
    -
    - l.LoadOverride(".", ".", []byte(overrideData), cfg, true)
    -
    - assert.Equal(t, l.DefaultPlan(), "abcxyz")
    -}
    --- a/loaders/convey/environment_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,94 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -)
    -
    -var baseData = `tasks:
    - foo:
    - type: docker/run
    - image: notreal
    -plans:
    - default:
    - stages:
    - - tasks:
    - - foo
    -`
    -
    -/* These test ensure that the convey yaml config properly loads environment
    - * settings.
    - */
    -
    -func TestEnvironmentUnmarshalGlobalSlice(t *testing.T) {
    - data := `environment:
    - - foo=bar
    - - baz
    -` + baseData
    -
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.ElementsMatch(t, cfg.Environment, []string{"foo=bar", "baz"})
    -}
    -
    -func TestEnvironmentUnmarshalGlobalString(t *testing.T) {
    - data := `environment: foo=bar
    -` + baseData
    -
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, cfg.Environment, []string{"foo=bar"})
    -}
    -
    -func TestEnvironmentUnmarshalGlobalOverrideSlice(t *testing.T) {
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(baseData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Empty(t, cfg.Environment)
    -
    - overrideData := `
    -environment:
    - - foo=bar
    - - baz
    -`
    - loader.LoadOverride(".", ".", []byte(overrideData), cfg, true)
    -
    - assert.ElementsMatch(t, cfg.Environment, []string{"foo=bar", "baz"})
    -}
    -
    -func TestEnvironmentUnmarshalGlobalOverrideString(t *testing.T) {
    - loader := Loader{}
    - cfg, err := loader.Load(".", ".", []byte(baseData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Empty(t, cfg.Environment)
    -
    - overrideData := `
    -environment: foo=bar
    -`
    - loader.LoadOverride(".", ".", []byte(overrideData), cfg, true)
    -
    - assert.Equal(t, cfg.Environment, []string{"foo=bar"})
    -}
    --- a/loaders/convey/errors.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,40 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "errors"
    -)
    -
    -const (
    - // MaxExtendsDepth is the maximum depth that configs can extend each other.
    - MaxExtendsDepth = 50
    -)
    -
    -var (
    - // ErrNoTasks is used to represent a config that doesn't have any tasks
    - // defined.
    - ErrNoTasks = errors.New("no tasks specified")
    -
    - // ErrNoPlans is used to represent a config that doesn't have any plans
    - // defined.
    - ErrNoPlans = errors.New("no plans specified")
    -
    - // ErrMaxExtendsDepth is returned when a cycle has been found in configs
    - // that extend each other.
    - ErrMaxExtendsDepth = errors.New("exceeded allowed depth for extended configs")
    -)
    --- a/loaders/convey/extends_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,301 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "fmt"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - cConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/docker"
    -)
    -
    -func TestExtendNoClash(t *testing.T) {
    - baseData := `
    -environment:
    - - x=1
    - - y=2
    - - z=3
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    - bar:
    - type: docker/run
    - image: imaginary2
    - baz:
    - type: docker/run
    - image: imaginary3
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo, bar, baz]
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -environment:
    - - w=4
    -tasks:
    - bonk:
    - type: docker/run
    - image: imaginary4
    -plans:
    - plan2:
    - stages:
    - - tasks: [foo, bonk]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Len(t, cfg.Plans, 2)
    - assert.Contains(t, cfg.Plans, "plan1")
    - assert.Contains(t, cfg.Plans, "plan2")
    -
    - assert.ElementsMatch(
    - t,
    - cfg.Environment,
    - []string{"x=1", "y=2", "z=3", "w=4"},
    - )
    - assert.Len(t, cfg.Tasks, 4)
    - assert.Contains(t, cfg.Tasks, "foo")
    - assert.Contains(t, cfg.Tasks, "bar")
    - assert.Contains(t, cfg.Tasks, "baz")
    - assert.Contains(t, cfg.Tasks, "bonk")
    -}
    -
    -func TestExtendExtendsAndOverwrite(t *testing.T) {
    - baseData := `
    -environment:
    - - x=1
    - - y=2
    - - z=3
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    - bar:
    - type: docker/run
    - image: imaginary2
    - baz:
    - type: docker/run
    - image: imaginary3
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo, bar, baz]
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -environment:
    - - z=4
    -tasks:
    - baz:
    - type: docker/run
    - image: imaginary4
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo, baz]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.ElementsMatch(t, cfg.Environment, []string{"x=1", "y=2", "z=4"})
    -
    - assert.Len(t, cfg.Plans, 1)
    - assert.Contains(t, cfg.Plans, "plan1")
    -
    - assert.Len(t, cfg.Tasks, 3)
    - assert.Contains(t, cfg.Tasks, "foo")
    - assert.Contains(t, cfg.Tasks, "bar")
    - assert.Contains(t, cfg.Tasks, "baz")
    -
    - assert.Equal(t, cfg.Tasks["baz"].(*docker.Run).Image, "imaginary4")
    - assert.ElementsMatch(
    - t,
    - cfg.Plans["plan1"].Stages[0].Tasks,
    - []string{"foo", "baz"},
    - )
    -}
    -
    -func TestExtendExtendDefault(t *testing.T) {
    - baseData := `
    -options:
    - default-plan: plan1
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    -plans:
    - plan1:
    - stages:
    - - tasks: [foo]
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -options:
    - default-plan: plan2
    -tasks:
    - bar:
    - type: docker/run
    - image: imaginary1
    -plans:
    - plan2:
    - stages:
    - - tasks: [bar]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - _, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    - assert.Equal(t, loader.defaultPlan, "plan2")
    -}
    -
    -func TestExtendExtendTasksOnly(t *testing.T) {
    - baseData := `
    -options:
    - default-plan: plan1
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    -`
    -
    - extendedData := `
    -extends: base.yaml
    -plans:
    - plan:
    - stages:
    - - tasks: [foo]
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.Nil(t, err)
    -
    - assert.Len(t, cfg.Plans, 1)
    - assert.Contains(t, cfg.Plans, "plan")
    -
    - assert.Len(t, cfg.Tasks, 1)
    - assert.Contains(t, cfg.Tasks, "foo")
    -}
    -
    -func TestExtendExtendNoTasks(t *testing.T) {
    - baseData := ``
    - extendedData := `extends: base.yml`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - _, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.EqualError(t, err, ErrNoTasks.Error())
    -}
    -
    -func TestExtendExtendNoPlans(t *testing.T) {
    - baseData := `
    -options:
    - default-plan: plan1
    -tasks:
    - foo:
    - type: docker/run
    - image: imaginary1
    -`
    -
    - extendedData := `extends: base.yaml`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(baseData), []string{}, true)
    - },
    - }
    -
    - cfg, err := loader.Load(".", ".", []byte(extendedData), []string{}, true)
    -
    - assert.EqualError(t, err, ErrNoPlans.Error())
    - assert.Len(t, cfg.Tasks, 1)
    - assert.Contains(t, cfg.Tasks, "foo")
    -}
    -
    -func TestExtendLoop(t *testing.T) {
    - sameData := `
    -extends: convey.yaml
    -`
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - return c.Load(".", name, []byte(sameData), []string{}, true)
    - },
    - }
    -
    - _, err := loader.Load(".", ".", []byte(sameData), []string{}, true)
    -
    - assert.EqualError(t, err, ErrMaxExtendsDepth.Error())
    -}
    -
    -func TestExtendFilename(t *testing.T) {
    - data := `
    -extends: ../base.yaml
    -`
    -
    - calledWith := ""
    -
    - loader := &Loader{
    - fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
    - calledWith = name
    - return nil, fmt.Errorf("early out")
    - },
    - }
    -
    - loader.Load(".", ".", []byte(data), []string{}, true)
    -
    - assert.Equal(t, calledWith, "../base.yaml")
    -}
    --- a/loaders/convey/plans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,54 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "fmt"
    -
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/stages"
    -)
    -
    -// mergePlan modifies parent in-place so that any stage declared in child
    -// will overwrite the stage of the same name in parent. Return an error if
    -// a stage exists in child but not parent. Environments are also merged.
    -func mergePlan(parent, child plans.Plan, name string) (plans.Plan, error) {
    - for _, stage := range child.Stages {
    - if !replaceStage(parent, stage) {
    - return plans.Plan{}, fmt.Errorf(
    - "cannot overwrite stage '%s' in plan '%s' (no such stage in parent)",
    - stage.Name,
    - name,
    - )
    - }
    - }
    -
    - parent.Environment = environment.Merge(parent.Environment, child.Environment)
    - return parent, nil
    -}
    -
    -func replaceStage(plan plans.Plan, stage stages.Stage) bool {
    - for i, match := range plan.Stages {
    - if match.Name == stage.Name {
    - plan.Stages[i] = stage
    - return true
    - }
    - }
    -
    - return false
    -}
    --- a/loaders/convey/tasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,109 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package convey
    -
    -import (
    - "fmt"
    -
    - "github.com/go-yaml/yaml"
    - log "github.com/sirupsen/logrus"
    -
    - cConfig "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/tasks"
    -)
    -
    -var (
    - deprecatedTasks map[string]string = map[string]string{
    - "docker/import": "convey/import",
    - "docker/export": "convey/export",
    - }
    -)
    -
    -func loadTask(name string, yamlTask yaml.MapSlice, logger *log.Entry, disableDeprecated bool) (tasks.Task, error) {
    - // task is a yaml.MapSlice since we can't unmarshal it into the proper data
    - // structure right away, so we have to walk it and grab the attributes
    - // we're interested in.
    - rawType := ""
    -
    - for _, item := range yamlTask {
    - if key, ok := item.Key.(string); ok && key == "type" {
    - if value, ok := item.Value.(string); ok {
    - rawType = value
    - }
    - }
    - }
    -
    - task, ok := cConfig.TasksMap[rawType]
    - // if we didn't find the task, check deprecated and old naming formats
    - if !ok {
    - if depTask, found := deprecatedTasks[rawType]; found {
    - if disableDeprecated {
    - return nil, fmt.Errorf("tasks %q not found", rawType)
    - }
    - log.Warnf("task %q is deprecated and replaced with %q", rawType, depTask)
    - newTask, ok := cConfig.TasksMap[depTask]
    - if !ok {
    - return nil, fmt.Errorf(
    - "task %q is deprecated, but can't find task %q that replaces it",
    - rawType,
    - depTask,
    - )
    - }
    - task = newTask
    - } else {
    - return nil, fmt.Errorf("task type %q not found", rawType)
    - }
    - }
    -
    - realTask, err := tasks.CloneTask(yamlTask, task)
    - if err != nil {
    - return nil, err
    - }
    -
    - err = realTask.Valid()
    - if err != nil {
    - return nil, fmt.Errorf("%s: %s", name, err.Error())
    - }
    -
    - return realTask, nil
    -}
    -
    -func loadTasks(path string, raw map[string]yaml.MapSlice, logger *log.Entry, disableDeprecated bool) (map[string]tasks.Task, error) {
    - realTasks := map[string]tasks.Task{}
    -
    - for name, task := range raw {
    - realTask, err := loadTask(name, task, logger, disableDeprecated)
    - if err != nil {
    - return nil, err
    - }
    -
    - realTasks[name] = realTask
    - }
    -
    - // now resolve any subtasks
    - for name, task := range realTasks {
    - if subtask, ok := task.(*tasks.SubTask); ok {
    - if parent, found := realTasks[subtask.Base]; found {
    - subtask.Parent = parent
    - } else {
    - return nil, fmt.Errorf("failed to find parent task %q for task %q", subtask.Base, name)
    - }
    - }
    - }
    -
    - return realTasks, nil
    -}
    --- a/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 @@
    import (
    "fmt"
    - "os"
    - "path/filepath"
    - "runtime/debug"
    - "strings"
    - "github.com/alecthomas/kingpin"
    + "github.com/alecthomas/kong"
    - "keep.imfreedom.org/grim/convey/config"
    "keep.imfreedom.org/grim/convey/consts"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/loaders/bitbucket"
    - "keep.imfreedom.org/grim/convey/loaders/codebuild"
    - "keep.imfreedom.org/grim/convey/loaders/convey"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/runners"
    - "keep.imfreedom.org/grim/convey/runtime"
    - "keep.imfreedom.org/grim/convey/ssh"
    - "keep.imfreedom.org/grim/convey/state"
    + "keep.imfreedom.org/grim/convey/globals"
    + "keep.imfreedom.org/grim/convey/runner"
    )
    -var (
    - app = kingpin.New("convey", "Convey is a container pipeline runner.").Version(consts.Version)
    +type VersionCmd struct {}
    - color = app.Flag("color", "Enable colorized output").Default("true").Bool()
    - configLoader = app.Flag("config-loader", "Select the configuration loader").Short('l').Default("convey").Enum("convey", "bitbucket", "codebuild")
    - configFile = app.Flag("config", "The config file name to use").Short('f').String()
    - cpuShares = app.Flag("cpu-shares", "The amount of cpu shares to give to a run task").Short('c').String()
    - dockerConfig = app.Flag("docker-config", "Location of docker client config files").String()
    - env = app.Flag("env", "Set environment variables").Short('e').Strings()
    - forceSequential = app.Flag("force-sequential", "Don't run anything concurrently").Short('S').Default("False").Bool()
    - keep = app.Flag("keep", "Keep the workspace volume").Short('k').Hidden().Default("False").Bool()
    - memory = app.Flag("memory", "The amount of memory to give the run task").Short('m').String()
    - sshAgent = app.Flag("ssh-agent", "A shortcut for --ssh-identity=*").Default("false").Bool()
    - sshIdentities = app.Flag("ssh-identity", "Enable ssh-agent for the given identities").Strings()
    - planTimeout = app.Flag("timeout", "The maximum amount of time a plan can run. 0 to disable. Units must be specified.").Default("15m").Duration()
    - verbose = app.Flag("verbose", "Be more verbose").Short('v').Default("False").Bool()
    - disableDeprecated = app.Flag("disable-deprecated", "Allow the use of deprecated features").Default("False").Bool()
    - options = app.Flag("opt", "Options to pass to the config loader").Short('o').Strings()
    +func (c *VersionCmd) Run(g *globals.Globals) error {
    + fmt.Printf("convey %s\n", consts.Version)
    - // simple commands
    - _ = app.Command("config", "Show a dump of the config file")
    - _ = app.Command("environment", "List the environment variables that are available")
    - _ = app.Command("graphviz", "Output a graphviz diagram of the config file")
    -
    - // run command
    - run = app.Command("run", "Run a plan or metaplan").Default()
    - planNames = run.Arg("plan", "The plan or list of plans to run in specified order").Strings()
    + if(g.Verbose) {
    + fmt.Printf("additional information...\n")
    + }
    - // list commands
    - ls = app.Command("list", "List information").Alias("ls")
    - _ = ls.Command("environment", "List the environment variables that are available.").Alias("env")
    - _ = ls.Command("metaplans", "List the metaplans defined in the configuration file.")
    - _ = ls.Command("plans", "List the plans defined in the configuration file.")
    - _ = ls.Command("tasks", "List the tasks defined in the configuration file.")
    -)
    -
    -// determineLoader will return the config loader to use.
    -func determineLoader() config.Loader {
    - switch *configLoader {
    - case "bitbucket":
    - return &bitbucket.Loader{}
    - case "codebuild":
    - return &codebuild.Loader{}
    - default:
    - return &convey.Loader{}
    - }
    + return nil
    }
    -// determineRunner will return the runner that handles the given command.
    -func determineRunner(command string) runners.Runner {
    - switch command {
    - case "graphviz":
    - return &runners.Graphviz{}
    - case "list environment":
    - return &runners.ListEnvironment{}
    - case "list metaplans":
    - return &runners.ListMetaPlans{}
    - case "list plans":
    - return &runners.ListPlans{}
    - case "list tasks":
    - return &runners.ListTasks{}
    - case "config":
    - return &runners.ShowConfig{}
    - default:
    - return &runners.Convey{}
    - }
    -}
    +var cli struct {
    + globals.Globals
    -// resolvePlans will run through all of the plans in the config and resolve
    -// their names via the loader.
    -func resolvePlans(cfg *config.Config, loader config.Loader, rt *runtime.Runtime) []string {
    - realPlans := []string{}
    -
    - for _, planName := range *planNames {
    - if metaPlan, found := cfg.MetaPlans[planName]; found {
    - realPlans = append(realPlans, metaPlan.Plans...)
    - } else {
    - realPlans = append(realPlans, loader.ResolvePlanName(planName, cfg, rt))
    - }
    - }
    -
    - return realPlans
    + 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
    - if *configFile == "" {
    - for _, filename := range loader.Filenames() {
    - if _, err := os.Stat(filename); os.IsNotExist(err) {
    - continue
    - }
    -
    - *configFile = filename
    - break
    - }
    - }
    -
    - // now make sure we found a config file
    - if *configFile == "" {
    - err := fmt.Errorf("config file not found, looking for %s", strings.Join(loader.Filenames(), ","))
    - return nil, "", err
    - }
    -
    - // figure out the path to the config file
    - cfgPath, err := filepath.Abs(filepath.Dir(*configFile))
    - if err != nil {
    - return nil, "", err
    - }
    -
    - cfg, err := config.LoadFile(*configFile, loader, *options, *disableDeprecated)
    - if err != nil {
    - return nil, "", err
    - }
    -
    - return cfg, cfgPath, nil
    -}
    -
    -func gomain() int {
    - args := os.Args[1:]
    -
    - command, err := app.Parse(args)
    - if err != nil {
    - app.Usage(args)
    - return 1
    - }
    -
    - // setup logging
    - logging.Setup(*color, *verbose)
    - defer logging.Shutdown()
    -
    - // create a logger to use now that logging is set up
    - logger := logging.NewAdapter("convey")
    -
    - // now load the config
    - loader := determineLoader()
    -
    - cfg, cfgPath, err := loadConfig(loader)
    - if err != nil {
    - logger.Errorf("Could not load config: %s", err)
    - return 1
    - }
    +var (
    +/*
    + 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)
    - return 1
    - }
    -
    - // if the user specified the shortcut, add * to the list of acceptable keys
    - if *sshAgent {
    - *sshIdentities = append(*sshIdentities, "*")
    - }
    -
    - // now merge in the keys from the config
    - *sshIdentities = append(*sshIdentities, cfg.SSHIdentities...)
    -
    - // now check if we have any keys and make sure one of them is usable
    - enableSSHAgent, err := ssh.ShouldEnable(*sshIdentities)
    - if err != nil {
    - logger.Errorf("Could not configure ssh agent: %s", err)
    - return 1
    - }
    -
    - runner := determineRunner(command)
    -
    - if len(*planNames) == 0 {
    - *planNames = []string{loader.DefaultPlan()}
    - }
    -
    - // create our state
    - st := state.New()
    -
    - // set the state's variables and validate it
    - st.CfgPath = cfgPath
    - st.KeepWorkspace = *keep
    - st.DisableDeprecated = *disableDeprecated
    - st.ForceSequential = *forceSequential
    - st.EnableSSHAgent = enableSSHAgent
    - st.PlanTimeout = *planTimeout
    - st.DockerConfig = *dockerConfig
    - st.CPUShares = *cpuShares
    - st.Memory = *memory
    -
    - if err := st.Valid(); err != nil {
    - logger.Errorf("Error validating convey state: %s", err)
    -
    - return 1
    - }
    -
    - // finally create our runtime
    - fullEnv := environment.New().Merge(defEnv).MergeSlice(*env)
    - rt := runtime.NewWithEnvironment(st, fullEnv)
    - defer rt.Shutdown()
    -
    - // resolve the plan name with the load and options
    - realPlans := resolvePlans(cfg, loader, rt)
    -
    - return runner.Run(cfg, realPlans, *env, rt)
    -}
    + 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()
    +*/
    +)
    func main() {
    - exitCode := 0
    - defer func() {
    - if r := recover(); r != nil {
    - fmt.Printf("panic: %s\n%s", r, debug.Stack())
    - }
    - os.Exit(exitCode)
    - }()
    + ctx := kong.Parse(
    + &cli,
    + kong.Name("convey"),
    + kong.Description("Convey is a container pipeline runner."),
    + kong.UsageOnError(),
    + kong.ConfigureHelp(kong.HelpOptions{
    + Compact: true,
    + Summary: true,
    + }),
    + )
    - exitCode = gomain()
    + err := ctx.Run(&cli.Globals)
    + ctx.FatalIfErrorf(err)
    }
    --- /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 @@
    +// Convey
    +// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    +//
    +// This program is free software: you can redistribute it and/or modify
    +// it under the terms of the GNU General Public License as published by
    +// the Free Software Foundation, either version 3 of the License, or
    +// (at your option) any later version.
    +//
    +// This program is distributed in the hope that it will be useful,
    +// but WITHOUT ANY WARRANTY; without even the implied warranty of
    +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    +// GNU General Public License for more details.
    +//
    +// You should have received a copy of the GNU General Public License
    +// along with this program. If not, see <http://www.gnu.org/licenses/>.
    +
    +package metaplans
    +
    +import (
    + "fmt"
    +)
    +
    +// MetaPlan is a representation of a meta plan.
    +type MetaPlan struct {
    + Plans []string `yaml:"plans"`
    +}
    +
    +// UnmarshalYAML is a custom yaml unmarshaller for MetaPlan's.
    +func (m *MetaPlan) UnmarshalYAML(unmarshal func(interface{}) error) error {
    + type rawMetaPlan MetaPlan
    + raw := rawMetaPlan{}
    +
    + if err := unmarshal(&raw); err != nil {
    + return err
    + }
    +
    + if len(raw.Plans) == 0 {
    + return fmt.Errorf("no plans specified")
    + }
    +
    + *m = MetaPlan(raw)
    +
    + return nil
    +}
    --- a/plans/metaplans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,44 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package plans
    -
    -import (
    - "fmt"
    -)
    -
    -// MetaPlan is a representation of a meta plan.
    -type MetaPlan struct {
    - Plans []string `yaml:"plans"`
    -}
    -
    -// UnmarshalYAML is a custom yaml unmarshaller for MetaPlan's.
    -func (m *MetaPlan) UnmarshalYAML(unmarshal func(interface{}) error) error {
    - type rawMetaPlan MetaPlan
    - raw := rawMetaPlan{}
    -
    - if err := unmarshal(&raw); err != nil {
    - return err
    - }
    -
    - if len(raw.Plans) == 0 {
    - return fmt.Errorf("no plans specified")
    - }
    -
    - *m = MetaPlan(raw)
    -
    - return nil
    -}
    --- a/plans/plans.go Thu Aug 26 08:11:34 2021 -0500
    +++ b/plans/plans.go 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 @@
    logger *log.Entry
    }
    -func (p *Plan) haveRunTask(taskMap map[string]tasks.Task) bool {
    - for _, stage := range p.Stages {
    - for _, taskName := range stage.Tasks {
    - task := taskMap[taskName]
    - if _, isRun := task.(*docker.Run); isRun {
    - return true
    - }
    - }
    - }
    -
    - return false
    -}
    -
    // Execute runs the plan.
    func (p *Plan) Execute(path string, tasks map[string]tasks.Task, env *environment.Environment, rt *runtime.Runtime) error {
    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 @@
    +package runner
    +
    +import (
    + "fmt"
    +
    + "keep.imfreedom.org/grim/convey/config"
    + "keep.imfreedom.org/grim/convey/environment"
    + "keep.imfreedom.org/grim/convey/globals"
    +)
    +
    +type RunnerCmd struct {
    + 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)
    + if err != nil {
    + return err
    + }
    +
    + err = cfg.Valid()
    + if err != nil {
    + return fmt.Errorf("config is invalid: %s", err)
    + }
    +
    + err = cfg.HasPlans(c.Plans)
    + if err != nil {
    + return err
    + }
    +
    + env := environment.New(cfg.Environment...)
    +
    + for _, name := range c.Plans {
    + plan := cfg.Plans[name]
    +
    + err := plan.Execute(name, cfg.Tasks, env, nil)
    + if err != nil {
    + return err
    + }
    + }
    +
    + return nil
    +}
    --- a/runners/convey.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,56 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/environment"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// Convey is the normal Convey Runner.
    -type Convey struct{}
    -
    -// Run runs the Convey Runner.
    -func (c *Convey) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - logger := logging.NewAdapter("convey-runner")
    - /* now look for the plan */
    - for _, planName := range plans {
    - if plan, found := cfg.Plans[planName]; found {
    - err := plan.Valid()
    - if err != nil {
    - logger.Errorf("%s", err)
    -
    - return 1
    - }
    -
    - err = plan.Execute(planName, cfg.Tasks, environment.New(cfg.Environment...), rt)
    - if err != nil {
    - logger.Errorf("%s", err)
    -
    - return 1
    - }
    - } else {
    - logger.Fatalf("plan %s not found", planName)
    -
    - return 1
    - }
    - }
    -
    - return 0
    -}
    --- a/runners/graphviz.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,235 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "bytes"
    - "fmt"
    - "regexp"
    - "text/template"
    -
    - "keep.imfreedom.org/grim/convey/color"
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/logging"
    - "keep.imfreedom.org/grim/convey/plans"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -var graphvizTemplate = `
    -digraph {
    - graph[rankdir="LR"]
    -
    - node[style="filled,rounded" shape="rect"]
    -
    - # add the meta plans
    - node[shape="rect" fillcolor="plum"]
    -{{range .MetaPlans}} {{.Normalized}}_start[label="{{.Name}} start"]
    - {{.Normalized}}_finish[label="{{.Name}} finish"]
    -{{end}}
    -
    - # add a start and finish node for each plan
    - node[shape="component" fillcolor="palegreen"]
    -{{range .Plans}} {{.Normalized}}_start[label="{{.Name}} start"]
    - {{.Normalized}}_finish[label="{{.Name}} finish"]
    -{{end}}
    -
    - # add the tasks
    - node[style="filled,rounded" shape="rect" fillcolor="powderblue"]
    -{{range .Tasks}} {{.Normalized}}[label="{{.Name}}"]
    -{{end}}
    -
    -{{range .Plans}}
    -
    - # add the stages for {{.Name}}
    - edge[color="{{.Color}}"]{{$planNormalized := .Normalized }}
    - {{- range .Stages}}
    - {{$planNormalized}}_{{.Normalized}} [label="{{.Name}}" shape="octagon" fillcolor="{{if .Enabled}}lightpink1{{else}}gray{{end}}"{{if eq ".Run" "always"}} style="bold,filled,rounded"{{end}}]{{end}}
    -
    - # {{.Name}} happy path
    - {{.Normalized}}_start{{range .Stages}} -> {{$planNormalized}}_{{.Normalized}}{{if .Concurrent}} -> { {{range .Tasks}} {{.}} {{end}} }{{else}}{{range .Tasks}} -> {{.}}{{end}}{{end}}{{end}} -> {{.Normalized}}_finish
    - # {{.Name}} unhappy path
    - {{ range .Stages}}{{$stage := .}}{{range .FallThroughs}}{{$planNormalized}}_{{$stage.Normalized}} -> {{$planNormalized}}_{{.Normalized}} [style="dotted"]
    - {{end}}{{end -}}
    -{{- end}}
    -
    -{{range .MetaPlans}}
    - # metaplan {{.Name}}
    - edge[color="{{.Color}}" style="dashed,bold"]
    - {{.Normalized}}_start -> {{range .Plans}}{{.Normalized}}_start
    - {{.Normalized}}_finish -> {{end}} {{.Normalized}}_finish
    -{{end}}
    -}
    -`
    -
    -var normalizeRegex *regexp.Regexp
    -
    -type (
    - // Graphviz is a Runner that will create a graphviz output for the config
    - // that has been loaded.
    - Graphviz struct{}
    -
    - graphvizTask struct {
    - Normalized string
    - Name string
    - }
    -
    - graphvizStage struct {
    - Normalized string
    - Name string
    - Enabled bool
    - Concurrent bool
    - Run string
    - Tasks []string
    - FallThroughs []graphvizStage
    - }
    -
    - graphvizPlan struct {
    - Normalized string
    - Name string
    - Color string
    - Stages []graphvizStage
    - }
    -
    - graphvizMetaPlan struct {
    - Normalized string
    - Name string
    - Color string
    - Plans []graphvizPlan
    - }
    -)
    -
    -func normalize(prefix, str string) string {
    - if normalizeRegex == nil {
    - normalizeRegex = regexp.MustCompile("[^a-zA-Z0-9_]")
    - }
    -
    - return prefix + "_" + normalizeRegex.ReplaceAllString(str, "_")
    -}
    -
    -func (g *Graphviz) getTasks(cfg *config.Config) []graphvizTask {
    - tasks := []graphvizTask{}
    -
    - for name := range cfg.Tasks {
    - task := graphvizTask{
    - Normalized: normalize("task", name),
    - Name: name,
    - }
    - tasks = append(tasks, task)
    - }
    -
    - return tasks
    -}
    -
    -func (g *Graphviz) getMetaPlans(cfg *config.Config) []graphvizMetaPlan {
    - metaPlans := []graphvizMetaPlan{}
    -
    - for name, cMetaPlan := range cfg.MetaPlans {
    - metaPlan := graphvizMetaPlan{
    - Normalized: normalize("metaplan", name),
    - Name: name,
    - Color: color.X11(name),
    - }
    -
    - for _, name := range cMetaPlan.Plans {
    - if plan, found := cfg.Plans[name]; found {
    - metaPlan.Plans = append(metaPlan.Plans, g.plan(name, plan))
    - }
    - }
    -
    - metaPlans = append(metaPlans, metaPlan)
    - }
    -
    - return metaPlans
    -}
    -
    -func (g Graphviz) plan(name string, plan plans.Plan) graphvizPlan {
    - gvizPlan := graphvizPlan{
    - Normalized: normalize("plan", name),
    - Name: name,
    - Color: color.X11(name),
    - Stages: []graphvizStage{},
    - }
    -
    - for idx, stage := range plan.Stages {
    - gvizStage := graphvizStage{
    - Normalized: normalize("stage", stage.Name),
    - Name: stage.Name,
    - Enabled: stage.Enabled,
    - Concurrent: stage.Concurrent,
    - Run: stage.Run,
    - Tasks: []string{},
    - }
    -
    - for _, task := range stage.Tasks {
    - gvizStage.Tasks = append(gvizStage.Tasks, normalize("task", task))
    - }
    -
    - gvizPlan.Stages = append(gvizPlan.Stages, gvizStage)
    -
    - // if this always runs, if it's not the first add it to the previous stages' fallthroughs
    - if stage.Run == "always" && idx > 0 {
    - for i := 0; i < idx; i++ {
    - gvizPlan.Stages[i].FallThroughs = append(gvizPlan.Stages[idx-1].FallThroughs, gvizStage)
    - }
    - }
    - }
    -
    - return gvizPlan
    -}
    -
    -func (g *Graphviz) getPlans(cfg *config.Config) []graphvizPlan {
    - plans := []graphvizPlan{}
    -
    - for name, plan := range cfg.Plans {
    - plans = append(plans, g.plan(name, plan))
    - }
    -
    - return plans
    -}
    -
    -// Run runs the graphviz runner.
    -func (g *Graphviz) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - logger := logging.NewAdapter("graphviz")
    -
    - params := map[string]interface{}{
    - "Tasks": g.getTasks(cfg),
    - "Plans": g.getPlans(cfg),
    - "MetaPlans": g.getMetaPlans(cfg),
    - }
    -
    - // Load the template
    - tmpl, err := template.New("graphviz").Parse(graphvizTemplate)
    - if err != nil {
    - logger.Fatalf("error: %s", err)
    -
    - return 1
    - }
    -
    - // Run the template
    - output := new(bytes.Buffer)
    - err = tmpl.Execute(output, params)
    - if err != nil {
    - logger.Fatalf("error: %s", err)
    -
    - return 1
    - }
    -
    - // Output the template
    - fmt.Print(output)
    -
    - return 0
    -}
    --- a/runners/listenvironment.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,53 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    - "os"
    - "sort"
    - "strings"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListEnvironment is a runner that will output the environment.
    -type ListEnvironment struct{}
    -
    -// Run runs the ListEnvironment runner.
    -func (l *ListEnvironment) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - fmt.Printf("Builtins:\n")
    - stateEnv := rt.Environment.Items()
    - sort.Strings(stateEnv)
    - for _, key := range stateEnv {
    - fmt.Printf(" %s=%s\n", key, os.Getenv(key))
    - }
    -
    - fmt.Printf("Config:\n")
    - cfgEnv := cfg.Environment
    - sort.Strings(cfgEnv)
    - for _, env := range cfgEnv {
    - if strings.Contains(env, "=") {
    - fmt.Printf(" %s\n", env)
    - } else {
    - fmt.Printf(" %s=%s\n", env, os.Getenv(env))
    - }
    - }
    -
    - return 0
    -}
    --- a/runners/listmetaplans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,36 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListMetaPlans is a Runner that outputs the metaplans for the loaded config.
    -type ListMetaPlans struct{}
    -
    -// Run runs the ListMetaPlans runner.
    -func (l *ListMetaPlans) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - for metaPlanName, _ := range cfg.MetaPlans {
    - fmt.Printf("%s\n", metaPlanName)
    - }
    -
    - return 0
    -}
    --- a/runners/listplans.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,36 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListPlans is a Runner that outputs the plans in the loaded config.
    -type ListPlans struct{}
    -
    -// Run runs the ListPlans Runner.
    -func (l *ListPlans) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - for planName, _ := range cfg.Plans {
    - fmt.Printf("%s\n", planName)
    - }
    -
    - return 0
    -}
    --- a/runners/listtasks.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,43 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "fmt"
    - "sort"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ListTasks is a Runner that will output the tasks defined in a config.
    -type ListTasks struct{}
    -
    -// Run runs the ListTasks runner.
    -func (l *ListTasks) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - tasks := []string{}
    - for taskName := range config.TasksMap {
    - tasks = append(tasks, taskName)
    - }
    -
    - sort.Strings(tasks)
    - for _, taskName := range tasks {
    - fmt.Printf(" %s\n", taskName)
    - }
    -
    - return 0
    -}
    --- a/runners/runners.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,28 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package runners contains all of the runners for convey.
    -package runners
    -
    -import (
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// Runner is an interface for defining something that convey can run.
    -type Runner interface {
    - Run(cfg *config.Config, plan []string, env []string, rt *runtime.Runtime) int
    -}
    --- a/runners/showconfig.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,34 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package runners
    -
    -import (
    - "github.com/davecgh/go-spew/spew"
    -
    - "keep.imfreedom.org/grim/convey/config"
    - "keep.imfreedom.org/grim/convey/runtime"
    -)
    -
    -// ShowConfig is a Runner that will dump the config to stdout.
    -type ShowConfig struct{}
    -
    -// Run runs the ShowConfig runner.
    -func (sc *ShowConfig) Run(cfg *config.Config, plans []string, env []string, rt *runtime.Runtime) int {
    - spew.Dump(cfg)
    -
    - return 0
    -}
    --- a/ssh/agent.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,105 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package ssh
    -
    -import (
    - "fmt"
    - "io"
    - "net"
    - "os"
    - "strings"
    -
    - "golang.org/x/crypto/ssh"
    - "golang.org/x/crypto/ssh/agent"
    -)
    -
    -// KeysAvailable returns True if there is at least one key that the ssh agent
    -// knows about, otherwise false. err will be set if there is an error along
    -// the way.
    -func KeysAvailable(identites []string) (bool, error) {
    - sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
    - if err != nil {
    - return false, err
    - }
    -
    - return keysAvailable(sshAgent, identites)
    -}
    -
    -func keysAvailable(rw io.ReadWriter, identities []string) (bool, error) {
    - keys, err := agent.NewClient(rw).List()
    - if err != nil {
    - return false, err
    - }
    -
    - // this runs in O(m * n) but thankful the number of keys should be low.
    - for _, identity := range identities {
    - if identity == "*" {
    - // if the user doesn't care which key and we have a key, return true
    - if len(keys) > 0 {
    - return true, nil
    - }
    -
    - // if the user doesn't care which key but we have no keys, return an error
    - return false, fmt.Errorf("no ssh keys available")
    - }
    -
    - uIdentity := strings.ToUpper(identity)
    - for _, pubKey := range keys {
    - fpSHA256 := strings.ToUpper(ssh.FingerprintSHA256(pubKey))
    - fpMD5 := strings.ToUpper(ssh.FingerprintLegacyMD5(pubKey))
    -
    - if strings.HasPrefix(uIdentity, "SHA256:") {
    - if uIdentity == fpSHA256 {
    - return true, nil
    - }
    - } else if strings.HasPrefix(uIdentity, "MD5:") {
    - if uIdentity[4:] == fpMD5 {
    - return true, nil
    - }
    - } else {
    - // no known prefix check sha256 then md5, then give up
    - if uIdentity == fpSHA256[7:] || uIdentity == fpMD5 {
    - return true, nil
    - }
    - }
    - }
    - }
    -
    - if err == nil {
    - err = fmt.Errorf("no usable ssh identities found")
    - }
    -
    - return false, err
    -}
    -
    -// ShouldEnable returns true if the ssh agent should be enabled.
    -func ShouldEnable(identities []string) (bool, error) {
    - if len(identities) <= 0 {
    - return false, nil
    - }
    -
    - haveKeys, err := KeysAvailable(identities)
    - if err != nil {
    - return false, fmt.Errorf("error talking to ssh-agent: %s", err)
    - }
    -
    - if !haveKeys {
    - return false, fmt.Errorf("no keys available in ssh-agent")
    - }
    -
    - return true, nil
    -}
    --- a/ssh/agent_test.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,188 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -package ssh
    -
    -import (
    - "crypto/ecdsa"
    - "crypto/elliptic"
    - "crypto/rand"
    - "net"
    - "testing"
    -
    - "github.com/stretchr/testify/assert"
    -
    - "golang.org/x/crypto/ssh"
    - "golang.org/x/crypto/ssh/agent"
    -)
    -
    -func setupAgent(keys []agent.AddedKey) net.Conn {
    - a := agent.NewKeyring()
    -
    - for _, key := range keys {
    - err := a.Add(key)
    - if err != nil {
    - panic(err)
    - }
    - }
    -
    - c1, c2 := net.Pipe()
    -
    - go func() {
    - defer c2.Close()
    -
    - err := agent.ServeAgent(a, c2)
    - if err != nil {
    - panic(err)
    - }
    - }()
    -
    - return c1
    -}
    -
    -func generateKey() *ecdsa.PrivateKey {
    - priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
    - if err != nil {
    - panic(err)
    - }
    -
    - return priv
    -}
    -
    -func TestKeysAvailableEmpty(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{}),
    - []string{},
    - )
    -
    - assert.False(t, avail)
    - assert.NotNil(t, err)
    -}
    -
    -func TestKeysAvailableWildcardNoKeys(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{}),
    - []string{"*"},
    - )
    -
    - assert.False(t, avail)
    - assert.NotNil(t, err)
    -}
    -
    -func TestKeysAvailableWildcardWithKeys(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: generateKey()},
    - }),
    - []string{"*"},
    - )
    -
    - assert.True(t, avail)
    - assert.Nil(t, err)
    -}
    -
    -func TestKeysAvailableUnknownFingerprint(t *testing.T) {
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: generateKey()},
    - }),
    - []string{"SHA256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"},
    - )
    -
    - assert.False(t, avail)
    - assert.NotNil(t, err)
    -}
    -
    -func TestKeysAvailableFingerprintMD5NoPrefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{ssh.FingerprintLegacyMD5(sshPub)},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    -
    -func TestKeysAvailableFingerprintMD5Prefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{"MD5:" + ssh.FingerprintLegacyMD5(sshPub)},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    -
    -func TestKeysAvailableFingerprintSHA256Prefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{ssh.FingerprintSHA256(sshPub)},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    -
    -func TestKeysAvailableFingerprintSHA256NoPrefix(t *testing.T) {
    - key := generateKey()
    - pub := key.Public()
    -
    - sshPub, err := ssh.NewPublicKey(pub)
    - if err != nil {
    - panic(err)
    - }
    -
    - // FingerprintSHA256 always has the prefix so we strip it off for testing
    - avail, err := keysAvailable(
    - setupAgent([]agent.AddedKey{
    - {PrivateKey: key},
    - }),
    - []string{ssh.FingerprintSHA256(sshPub)[7:]},
    - )
    -
    - assert.Nil(t, err)
    - assert.True(t, avail)
    -}
    --- a/ssh/ssh.go Thu Aug 26 08:11:34 2021 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,18 +0,0 @@
    -// Convey
    -// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
    -//
    -// This program is free software: you can redistribute it and/or modify
    -// it under the terms of the GNU General Public License as published by
    -// the Free Software Foundation, either version 3 of the License, or
    -// (at your option) any later version.
    -//
    -// This program is distributed in the hope that it will be useful,
    -// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -// GNU General Public License for more details.
    -//
    -// You should have received a copy of the GNU General Public License
    -// along with this program. If not, see <http://www.gnu.org/licenses/>.
    -
    -// Package ssh contains helpers for handling ssh-agent sockets.
    -package ssh
    --- a/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)
    if err != nil {
    return nil, err