--- a/config/loader.go Mon Jan 15 16:20:18 2018 -0600
+++ b/config/loader.go Mon Jan 15 22:15:08 2018 -0600
@@ -28,7 +28,7 @@
// Loader defines all the functions that a config loader needs to implement.
- Load(path, base string, data []byte, disableDeprecated bool) (*Config, error)
+ Load(path, base string, data []byte, options []string, disableDeprecated bool) (*Config, error) LoadOverride(path, base string, data []byte, config *Config, disableDeprecated bool)
@@ -63,7 +63,7 @@
// 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, disableDeprecated bool) (*Config, error) {
+func LoadFile(file string, loader Loader, options []string, disableDeprecated bool) (*Config, error) { // split the filename into our parts
path, base := filepath.Split(file)
@@ -72,7 +72,7 @@
return nil, fmt.Errorf("failed to read config file '%s'", file)
- cfg, err := loader.Load(path, base, data, disableDeprecated)
+ cfg, err := loader.Load(path, base, data, options, disableDeprecated) --- a/loaders/bitbucket/docker-parser.go Mon Jan 15 16:20:18 2018 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
- "github.com/alecthomas/kingpin"
- "bitbucket.org/rw_grim/convey/docker"
- "bitbucket.org/rw_grim/convey/tasks"
-// parseDockerCommand is a super stripped down kingpin parse that will parse
-// docker command line options and return a convey task or an error
-func parseDockerCommand(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()
- loginServer := login.Arg("", "").String()
- logout := app.Command("logout", "")
- logoutServer := logout.Arg("", "").String()
- build := app.Command("build", "")
- buildTag := build.Flag("tag", "").Short('t').String()
- buildContext := build.Arg("", "").String()
- push := app.Command("push", "")
- pushImage := push.Arg("", "").Required().String()
- pull := app.Command("pull", "")
- pullImage := pull.Arg("", "").Required().String()
- rmi := app.Command("rmi", "")
- rmi.Flag("--force", "").Short('f').Default("false").Bool()
- rmiImage := rmi.Arg("", "").Required().String()
- run := app.Command("run", "")
- runEntryPoint := run.Flag("entrypoint", "").String()
- runEnv := run.Flag("env", "").Short('e').Strings()
- runImage := run.Arg("image", "").Required().String()
- runWorkdir := run.Flag("workdir", "").Short('w').String()
- runCommand := run.Arg("", "").Strings()
- tag := app.Command("tag", "")
- tagSource := tag.Arg("src", "").Required().String()
- tagDestination := tag.Arg("dest", "").Required().String()
- cmd, err := app.Parse(argv[1:])
- Files: []string{*buildContext},
- Username: *loginUsername,
- Password: *loginPassword,
- Command: strings.Join(*runCommand, " "),
- EntryPoint: *runEntryPoint,
- Destination: *tagDestination,
- return nil, fmt.Errorf("unable to parse docker command line '%v'", argv)
--- a/loaders/bitbucket/loader.go Mon Jan 15 16:20:18 2018 -0600
+++ b/loaders/bitbucket/loader.go Mon Jan 15 22:15:08 2018 -0600
@@ -126,7 +126,7 @@
// now figure out what docker command we're running
- task, err := parseDockerCommand(argv)
+ task, err := docker.ParseCommand(argv) @@ -173,7 +173,7 @@
// Load loads the given filename and returns a config.Config for it.
-func (l *Loader) Load(base, path string, data []byte, disableDeprecated bool) (*config.Config, error) {
+func (l *Loader) Load(base, path string, data []byte, options []string, disableDeprecated bool) (*config.Config, error) { var pipeline bitbucketPipelines
err := yaml.Unmarshal(data, &pipeline)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/loaders/codebuild/codebuild.go Mon Jan 15 22:15:08 2018 -0600
@@ -0,0 +1,19 @@
+// 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/ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/loaders/codebuild/codebuild_test.go Mon Jan 15 22:15:08 2018 -0600
@@ -0,0 +1,37 @@
+// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + "github.com/aphistic/sweet" + junit "github.com/aphistic/sweet-junit" + . "github.com/onsi/gomega" +type codebuildSuite struct{} +func TestMain(m *testing.M) { + RegisterFailHandler(sweet.GomegaFail) + sweet.Run(m, func(s *sweet.S) { + s.RegisterPlugin(junit.NewPlugin()) + s.AddSuite(&codebuildSuite{}) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/loaders/codebuild/loader.go Mon Jan 15 22:15:08 2018 -0600
@@ -0,0 +1,210 @@
+// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + "github.com/go-yaml/yaml" + "github.com/kballard/go-shellquote" + "bitbucket.org/rw_grim/convey/config" + "bitbucket.org/rw_grim/convey/docker" + "bitbucket.org/rw_grim/convey/plans" + "bitbucket.org/rw_grim/convey/stages" + "bitbucket.org/rw_grim/convey/state" + "bitbucket.org/rw_grim/convey/tasks" +func (l *Loader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*config.Config, error) { + err := yaml.Unmarshal(data, &cb) + Tasks: map[string]tasks.Task{ + "import": &docker.Import{ + Plans: map[string]plans.Plan{}, + // create our plan and add our tasks + Stages: []stages.Stage{ + Tasks: []string{"import"}, + // TODO put the right image here + err = l.addPhases(cb, cfg, "alpine:edge", &plan) + l.createExportTask(cb, cfg, &plan) + cfg.Plans["default"] = plan +func (l *Loader) addPhase(cb CodeBuild, cfg *config.Config, phaseName, imageName string, plan *plans.Plan) error { + phase, ok := cb.Phases[phaseName] + // ignore phases that are not in the yaml + currentScript := []string{} + for idx, cmd := range phase.Commands { + argv, err := shellquote.Split(cmd) + if strings.TrimSpace(argv[0]) == "docker" { + taskName := fmt.Sprintf("%s-%d", phaseName, idx) + if len(currentScript) > 0 { + // create and add the task + taskName := fmt.Sprintf("%s-%d", phaseName, idx-1) + cfg.Tasks[taskName] = &docker.Run{ + stage.Tasks = append(stage.Tasks, taskName) + currentScript = []string{} + task, err := docker.ParseCommand(argv) + cfg.Tasks[taskName] = task + stage.Tasks = append(stage.Tasks, taskName) + currentScript = append(currentScript, cmd) + if len(currentScript) > 0 { + taskName := fmt.Sprintf("%s-%d", phaseName, last+1) + cfg.Tasks[taskName] = &docker.Run{ + stage.Tasks = append(stage.Tasks, taskName) + plan.Stages = append(plan.Stages, stage) +func (l *Loader) addPhases(cb CodeBuild, cfg *config.Config, imageName string, plan *plans.Plan) error { + phases := []string{"install", "pre_build", "build", "post_build"} + // load all of the phases scripts into a single slice + for _, phase := range phases { + err := l.addPhase(cb, cfg, phase, imageName, plan) +func (l *Loader) createExportTask(cb CodeBuild, cfg *config.Config, plan *plans.Plan) { + if len(cb.Artifacts.Files) == 0 { + export := &docker.Export{ + Files: cb.Artifacts.Files, + cfg.Tasks["artifacts"] = export + if cb.Artifacts.DiscardPaths == "yes" { + for idx, name := range cb.Artifacts.Files { + cb.Artifacts.Files[idx] = filepath.Base(name) + exportStage := stages.Stage{ + Tasks: []string{"artifacts"}, + plan.Stages = append(plan.Stages, exportStage) +func (l *Loader) LoadOverride(path, base string, data []byte, config *config.Config, disableDeprecated bool) { +func (l *Loader) Filenames() []string { + return []string{"buildspec.yml"} +func (l *Loader) OverrideSuffix() string { +func (l *Loader) DefaultPlan() string { +func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, st *state.State) string { --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/loaders/codebuild/types.go Mon Jan 15 22:15:08 2018 -0600
@@ -0,0 +1,39 @@
+// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +type environment struct { + Variables map[string]string `yaml:"variables"` + ParameterStore map[string]string `yaml:"parameter-store"` + Commands []string `yaml:"commands"` + Files []string `yaml:"files"` + DiscardPaths string `yaml:"discard-paths"` + BaseDirectory string `yaml:"base-directory"` + Version string `yaml:"version"` + Environment environment `yaml:"env"` + Phases map[string]phase `yaml:"phases"` + Artifacts artifacts `yaml:"artifacts"` --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/loaders/codebuild/unmarshal_test.go Mon Jan 15 22:15:08 2018 -0600
@@ -0,0 +1,100 @@
+// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com> +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + "github.com/aphistic/sweet" + "github.com/go-yaml/yaml" + . "github.com/onsi/gomega" +func (c *codebuildSuite) TestUnmarshalSimple(t sweet.T) { + yamlData := `version: 0.2 + JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64" + LOGIN_PASSWORD: "dockerLoginPassword" + - apt-get install -y maven + - echo Nothing to do in the pre_build phase... + - echo Build started on $(date) + - echo Build completed on $(date) + - target/messageUtil-1.0.jar + err := yaml.Unmarshal([]byte(yamlData), &actual) + Expect(err).To(BeNil()) + Environment: environment{ + Variables: map[string]string{ + "JAVA_HOME": "/usr/lib/jvm/java-8-openjdk-amd64", + ParameterStore: map[string]string{ + "LOGIN_PASSWORD": "dockerLoginPassword", + Phases: map[string]phase{ + "apt-get install -y maven", + "echo Nothing to do in the pre_build phase...", + "echo Build started on $(date)", + "echo Build completed on $(date)", + "target/messageUtil-1.0.jar", + Expect(actual).To(Equal(expected)) --- a/loaders/convey/convey.go Mon Jan 15 16:20:18 2018 -0600
+++ b/loaders/convey/convey.go Mon Jan 15 22:15:08 2018 -0600
@@ -63,7 +63,7 @@
fileLoader func(string, *Loader) (*cConfig.Config, error)
-func (l *Loader) loadBase(cfg config, path string, disableDeprecated bool) (*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
@@ -75,7 +75,7 @@
extendsAbsName := filepath.Join(path, cfg.Extends)
- baseConfig, err := l.loadFile(extendsAbsName, disableDeprecated)
+ baseConfig, err := l.loadFile(extendsAbsName, options, disableDeprecated) // We can safely ignore no plans and no tasks errors here
@@ -134,7 +134,7 @@
// Load loads the given filename and returns it as a config.Config.
-func (l *Loader) Load(path, base string, data []byte, disableDeprecated bool) (*cConfig.Config, error) {
+func (l *Loader) Load(path, base string, data []byte, options []string, disableDeprecated bool) (*cConfig.Config, error) { l.logger = logging.NewAdapter("config loader")
@@ -147,7 +147,7 @@
- baseConfig, err := l.loadBase(cfg, path, disableDeprecated)
+ baseConfig, err := l.loadBase(cfg, path, options, disableDeprecated) @@ -289,9 +289,9 @@
// Load loads the given filename and returns it as a config.Config.
-func (l *Loader) loadFile(path string, disableDeprecated bool) (*cConfig.Config, error) {
+func (l *Loader) loadFile(path string, options []string, disableDeprecated bool) (*cConfig.Config, error) { - return cConfig.LoadFile(path, l, disableDeprecated)
+ return cConfig.LoadFile(path, l, options, disableDeprecated) return l.fileLoader(path, l)
--- a/main.go Mon Jan 15 16:20:18 2018 -0600
+++ b/main.go Mon Jan 15 22:15:08 2018 -0600
@@ -29,6 +29,7 @@
"bitbucket.org/rw_grim/convey/config"
"bitbucket.org/rw_grim/convey/environment"
"bitbucket.org/rw_grim/convey/loaders/bitbucket"
+ "bitbucket.org/rw_grim/convey/loaders/codebuild" "bitbucket.org/rw_grim/convey/loaders/convey"
"bitbucket.org/rw_grim/convey/logging"
"bitbucket.org/rw_grim/convey/runners"
@@ -44,7 +45,7 @@
app = kingpin.New("convey", "Convey is a container pipeline runner.").Version(version)
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")
+ 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()
@@ -57,6 +58,7 @@
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() graphviz = app.Flag("graphviz", "Output a graphviz diagram of the config file").Short('g').Hidden().Default("False").Bool()
@@ -88,6 +90,8 @@
return &bitbucket.Loader{}
+ return &codebuild.Loader{} @@ -187,7 +191,7 @@
- cfg, err := config.LoadFile(*configFile, loader, *disableDeprecated)
+ cfg, err := config.LoadFile(*configFile, loader, *options, *disableDeprecated) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/codebuild/buildspec.yml Mon Jan 15 22:15:08 2018 -0600
@@ -0,0 +1,35 @@
+ JAVA_HOME: "/usr/lib/jvm/java-8-openjdk-amd64" + LOGIN_PASSWORD: "dockerLoginPassword" + - echo Entered the install phase... + - apt-get install -y maven + - echo Entered the pre_build phase... + - docker login –u User –p $LOGIN_PASSWORD + - echo Entered the build phase... + - echo Build started on `date` + - echo Entered the post_build phase... + - echo Build completed on `date` + - target/messageUtil-1.0.jar