grim/convey

Properly handle export path's when we have an explicit destination
// 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/convey/config"
"bitbucket.org/rw_grim/convey/docker"
"bitbucket.org/rw_grim/convey/plans"
"bitbucket.org/rw_grim/convey/script"
"bitbucket.org/rw_grim/convey/stages"
"bitbucket.org/rw_grim/convey/state"
"bitbucket.org/rw_grim/convey/tasks"
"github.com/go-yaml/yaml"
)
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": &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
}
// 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 {
return defaultPlan
}
// ResolvePlanName resolves the plan name if the one in the config contains a
// wildcard.
func (l *Loader) ResolvePlanName(plan string, cfg *config.Config, st *state.State) 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
}