grim/convey

Bump the version for release
v0.11.1
2017-10-21, Gary Kramlich
00923ff4e245
Bump the version for release
/*
* Convey
* Copyright 2016-2017 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"
"strings"
"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"
"github.com/go-yaml/yaml"
)
type Loader struct{}
func addTask(name string, task tasks.Task, plan plans.Plan, cfg *config.Config) {
cfg.Tasks[name] = task
plan.Stages[0].Tasks = append(plan.Stages[0].Tasks, name)
}
func addScript(name string, image image, script []string, plan plans.Plan, cfg *config.Config) {
task := &docker.Run{
Shell: "/bin/sh",
Script: script,
Image: image.Name,
WorkDir: "/workspace",
}
addTask(name, task, plan, cfg)
}
func addPipeline(name string, defImage image, pipelines []pipeline, cfg *config.Config) error {
plan := plans.Plan{
Stages: []stages.Stage{
stages.Stage{
Name: "stage-0",
Enabled: true,
Tasks: []string{"import"},
Run: "on-success",
},
},
}
for idx, pipeline := range pipelines {
loginTask := ""
logoutTask := ""
image := defImage
if pipeline.Steps.Image.Name != "" {
image = pipeline.Steps.Image
// if the step has an image with a username, we need to add the tasks to login and logout
if image.Username != "" {
registry, _, _ := docker.ParseImage(image.Name)
// create and add the login task to the stage
loginTask = fmt.Sprintf("%s-%d-login", name, idx)
cfg.Tasks[loginTask] = &docker.Login{
Username: image.Username,
Password: image.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 image.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)
}
// now figure out how to parse the script in the config
currentScript := []string{}
last := -1
for scriptIdx, command := range pipeline.Steps.Script {
argv, err := shellquote.Split(command)
if err != nil {
return err
}
if len(argv) > 0 {
if strings.TrimSpace(strings.ToLower(argv[0])) == "docker" {
taskName := fmt.Sprintf("%s-step-%d-%d", name, idx, scriptIdx)
// if we have existing script commands add them now
if len(currentScript) > 0 {
taskName := fmt.Sprintf("%s-step-%d-%d", name, idx, scriptIdx-1)
addScript(taskName, image, currentScript, plan, cfg)
currentScript = []string{}
last = scriptIdx
}
// now figure out what docker command we're running
task, err := parseDockerCommand(argv)
if err != nil {
return err
}
addTask(taskName, task, plan, cfg)
last = scriptIdx
} else {
currentScript = append(currentScript, command)
}
}
}
// if we have existing script commands add them now
if len(currentScript) > 0 {
name := fmt.Sprintf("%s-step-%d-%d", name, idx, last+1)
addScript(name, image, currentScript, plan, cfg)
}
// 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 {
name := fmt.Sprintf("%s-%s", base, name)
err := addPipeline(name, defImage, branchPipeline, cfg)
if err != nil {
return err
}
}
}
return nil
}
func (l *Loader) Load(base, path string, data []byte) (*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": &docker.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
}
func (l *Loader) LoadOverride(base, path string, data []byte, cfg *config.Config) {
return
}
func (l *Loader) Filenames() []string {
return []string{"bitbucket-pipelines.yml"}
}
func (l *Loader) OverrideSuffix() string {
return "-override"
}
func (l *Loader) DefaultPlan() string {
return "default"
}
func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, st *state.State) string {
if plan != "" {
// try to shortcut if we can
if plan == "default" {
return "default"
}
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": []string{},
}
// figure out the we're looking for
if gitBranch := os.Getenv("GIT_BRANCH"); gitBranch != "" {
matchMapper["branch"] = append(matchMapper["branch"], gitBranch)
}
if hgBranch := os.Getenv("HG_BRANCH"); hgBranch != "" {
matchMapper["branch"] = append(matchMapper["branch"], hgBranch)
}
if hgBookmark := os.Getenv("HG_BOOKMARK"); hgBookmark != "" {
matchMapper["bookmark"] = append(matchMapper["bookmark"], hgBookmark)
}
for name, _ := range cfg.Plans {
for prefix, matchers := range matchMapper {
for _, matcher := range matchers {
calculatedName := fmt.Sprintf("%s-%s", prefix, matcher)
fmt.Printf("checking %s against %s\n", calculatedName, name)
if matched, _ := filepath.Match(name, calculatedName); matched {
return name
}
}
}
}
return "default"
}