1
0
mirror of https://github.com/drone/drone-cli.git synced 2024-11-23 01:11:57 +01:00

convert bitbucket pipelines

This commit is contained in:
Brad Rydzewski 2019-01-08 12:23:02 -08:00
parent cb8547c223
commit cb28d6e451
22 changed files with 343 additions and 73 deletions

6
Gopkg.lock generated

@ -110,12 +110,14 @@
"yaml/compiler/internal/rand",
"yaml/compiler/transform",
"yaml/converter",
"yaml/converter/internal",
"yaml/converter/bitbucket",
"yaml/converter/legacy",
"yaml/converter/legacy/internal",
"yaml/linter",
"yaml/pretty",
"yaml/signer"
]
revision = "90f0314ca0349f42f951a49ca20ac2b8074b8b48"
revision = "53f97a4f989bfcce0d26c1f582f44528cdcf4b50"
[[projects]]
name = "github.com/drone/envsubst"

@ -35,7 +35,7 @@ func convert(c *cli.Context) error {
return err
}
res, err := converter.ConvertBytes(raw)
res, err := converter.Convert(raw, converter.Metadata{Filename: path})
if err != nil {
return err
}

@ -7,7 +7,6 @@ import (
"log"
"net/url"
"os"
"strings"
"time"
"github.com/drone/envsubst"
@ -166,11 +165,12 @@ func exec(c *cli.Context) error {
// this code is temporarily in place to detect and convert
// the legacy yaml configuration file to the new format.
if converter.IsLegacy(dataS) {
dataS, err = converter.ConvertString(dataS)
if err != nil {
return err
}
dataS, err = converter.ConvertString(dataS, converter.Metadata{
Filename: file,
Ref: c.String("ref"),
})
if err != nil {
return err
}
manifest, err := yaml.ParseString(dataS)
@ -221,7 +221,7 @@ func exec(c *cli.Context) error {
Target: environ["DRONE_DEPLOY_TO"],
},
)
comp.TransformFunc = transform.Combine(
transforms := []func(*engine.Spec){
transform.Include(
c.StringSlice("include"),
),
@ -258,12 +258,24 @@ func exec(c *cli.Context) error {
c.String("secret-file"),
),
),
transform.WithVolumes(
toVolumes(
c.StringSlice("volume"),
),
transform.WithVolumeSlice(
c.StringSlice("volume"),
),
)
}
// the user has the option to disable the git clone
// if the pipeline is being executed on the local
// codebase.
if c.Bool("clone") == false {
// HACK(bradrydzewski) If the workspace defines a
// sub-path we append a host volume mount. Else we
// replace the empty dir workspace mount with a host
// volume mount.
switch pipeline.Workspace.Path {
case "", "/", "\\":
default:
}
}
comp.TransformFunc = transform.Combine(transforms...)
ir := comp.Compile(pipeline)
// the user has the option to disable the git clone
@ -305,21 +317,6 @@ func exec(c *cli.Context) error {
).Run(ctx)
}
// helper function converts a slice of colon-separated
// volumes to a map.
func toVolumes(items []string) map[string]string {
set := map[string]string{}
for _, item := range items {
parts := strings.Split(item, ":")
if len(parts) != 2 {
key := parts[0]
val := parts[1]
set[key] = val
}
}
return set
}
// helper function converts a slice of urls to a slice
// of docker registry credentials.
func toRegistry(items []string) []*engine.DockerAuth {

@ -28,10 +28,16 @@ func cloneImage(src *yaml.Pipeline) string {
// helper function configures the clone depth parameter,
// specific to the clone plugin.
//
// TODO(bradrydzewski) rename to setupCloneParams
func setupCloneDepth(src *yaml.Pipeline, dst *engine.Step) {
if depth := src.Clone.Depth; depth > 0 {
dst.Envs["PLUGIN_DEPTH"] = strconv.Itoa(depth)
}
if skipVerify := src.Clone.SkipVerify; skipVerify {
dst.Envs["GIT_SSL_NO_VERIFY"] = "true"
dst.Envs["PLUGIN_SKIP_VERIFY"] = "true"
}
}
// helper function configures the .git-clone credentials

@ -37,7 +37,15 @@ type Compiler struct {
// output prior to completion. This can be useful when
// you need to programatically modify the output,
// set defaults, etc.
TransformFunc func(spec *engine.Spec)
TransformFunc func(*engine.Spec)
// WorkspaceFunc can be used to customize the default
// workspace paths.
WorkspaceFunc func(*yaml.Pipeline) (base, path, full string)
// WorkspaceMountFunc can be used to override the default
// workspace volume mount.
WorkspaceMountFunc func(*engine.Spec, string)
}
// Compile returns an intermediate representation of the
@ -79,7 +87,7 @@ func (c *Compiler) Compile(from *yaml.Pipeline) *engine.Spec {
// create the default workspace path. If a container
// does not specify a working directory it defaults
// to the workspace path.
base, dir, workspace := createWorkspace(from)
base, dir, workspace := c.workspace(from)
// create the default workspace volume definition.
// the volume will be mounted to each container in
@ -283,3 +291,20 @@ func (c *Compiler) skip(container *yaml.Container) bool {
}
return false
}
// return the workspace paths. If the user-defined
// function is nil, default logic is used.
func (c *Compiler) workspace(pipeline *yaml.Pipeline) (base, path, full string) {
if c.WorkspaceFunc != nil {
return c.WorkspaceFunc(pipeline)
}
return createWorkspace(pipeline)
}
// func (c *Compiler) workspaceMount(step *engine.Step, base, path, full string) {
// if c.WorkspaceMountFunc != nil {
// c.WorkspaceMountFunc(step, path)
// return
// }
// setupWorkingDirMount(spec, base)
// }

@ -1,6 +1,8 @@
package transform
import (
"strings"
"github.com/drone/drone-runtime/engine"
"github.com/drone/drone-yaml/yaml/compiler/internal/rand"
)
@ -32,3 +34,20 @@ func WithVolumes(volumes map[string]string) func(*engine.Spec) {
}
}
}
// WithVolumeSlice is a transform function that adds a set
// of global volumes to the container that are defined in
// --volume=host:container format.
func WithVolumeSlice(volumes []string) func(*engine.Spec) {
to := map[string]string{}
for _, s := range volumes {
parts := strings.Split(s, ":")
if len(parts) != 2 {
continue
}
key := parts[0]
val := parts[1]
to[key] = val
}
return WithVolumes(to)
}

@ -0,0 +1,104 @@
package bitbucket
import (
"path"
"strings"
)
type (
// Config defines the pipeline configuration.
Config struct {
// Image specifies the Docker image with
// which we run your builds.
Image string
// Clone defines the depth of Git clones
// for all pipelines.
Clone struct {
Depth int
}
// Pipeline defines the pipeline configuration
// which includes a list of all steps for default,
// tag, and branch-specific execution.
Pipelines struct {
Default Stage
Tags map[string]Stage
Branches map[string]Stage
}
Definitions struct {
Services map[string]*Step
Caches map[string]string
}
}
// Stage contains a list of steps executed
// for a specific branch or tag.
Stage struct {
Name string
Steps []*Step
}
// Step defines a build execution unit.
Step struct {
// Name of the pipeline step.
Name string
// Image specifies the Docker image with
// which we run your builds.
Image string
// Script contains the list of bash commands
// that are executed in sequence.
Script []string
// Variables provides environment variables
// passed to the container at runtime.
Variables map[string]string
// Artifacts defines files that are to be
// snapshotted and shared with the subsequent
// step. This is not used, because Drone uses
// a shared volume to share artifacts.
Artifacts []string
}
)
// Pipeline returns the pipeline stage that best matches the branch
// and ref. If there is no matching pipeline specific to the branch
// or tag, the default pipeline is returned.
func (c *Config) Pipeline(ref string) Stage {
// match pipeline by tag name
tag := strings.TrimPrefix(ref, "refs/tags/")
for pattern, pipeline := range c.Pipelines.Tags {
if ok, _ := path.Match(pattern, tag); ok {
return pipeline
}
}
// match pipeline by branch name
branch := strings.TrimPrefix(ref, "refs/heads/")
for pattern, pipeline := range c.Pipelines.Branches {
if ok, _ := path.Match(pattern, branch); ok {
return pipeline
}
}
// use default
return c.Pipelines.Default
}
// UnmarshalYAML implements custom parsing for the stage section of the yaml
// to cleanup the structure a bit.
func (s *Stage) UnmarshalYAML(unmarshal func(interface{}) error) error {
in := []struct {
Step *Step
}{}
err := unmarshal(&in)
if err != nil {
return err
}
for _, step := range in {
s.Steps = append(s.Steps, step.Step)
}
return nil
}

@ -0,0 +1,85 @@
package bitbucket
import (
"bytes"
"fmt"
droneyaml "github.com/drone/drone-yaml/yaml"
"github.com/drone/drone-yaml/yaml/pretty"
"gopkg.in/yaml.v2"
)
// Convert converts the yaml configuration file from
// the legacy format to the 1.0+ format.
func Convert(b []byte, ref string) ([]byte, error) {
config := new(Config)
err := yaml.Unmarshal(b, config)
if err != nil {
return nil, err
}
// TODO (bradrydzewski) to correctly choose
// the pipeline we need to pass the branch
// and ref.
stage := config.Pipeline(ref)
pipeline := &droneyaml.Pipeline{}
pipeline.Name = "default"
pipeline.Kind = "pipeline"
pipeline.Workspace.Base = "/drone"
pipeline.Workspace.Path = "/src"
//
// clone
//
pipeline.Clone.Depth = config.Clone.Depth
//
// steps
//
for i, from := range stage.Steps {
to := toContainer(from)
// defaults to the global image if the
// step does not define an image.
if to.Image == "" {
to.Image = config.Image
}
if to.Name == "" {
to.Name = fmt.Sprintf("step_%d", i)
}
pipeline.Steps = append(pipeline.Steps, to)
}
//
// services
//
for name, from := range config.Definitions.Services {
to := toContainer(from)
to.Name = name
pipeline.Services = append(pipeline.Services, to)
}
//
// wrap the pipeline in the manifest
//
manifest := &droneyaml.Manifest{}
manifest.Resources = append(manifest.Resources, pipeline)
buf := new(bytes.Buffer)
pretty.Print(buf, manifest)
return buf.Bytes(), nil
}
func toContainer(from *Step) *droneyaml.Container {
return &droneyaml.Container{
Name: from.Name,
Image: from.Image,
Commands: from.Script,
}
}

@ -1,17 +1,50 @@
package converter
import (
"github.com/drone/drone-yaml/yaml/converter/internal"
"github.com/drone/drone-yaml/yaml/converter/bitbucket"
"github.com/drone/drone-yaml/yaml/converter/legacy"
)
// ConvertBytes converts the yaml configuration file from
// Metadata provides additional metadata used to
// convert the configuration file format.
type Metadata struct {
// Filename of the configuration file, helps
// determine the yaml configuration format.
Filename string
// Ref of the commit use to choose the correct
// pipeline if the configuration format defines
// multiple pipelines (like Bitbucket)
Ref string
}
// Convert converts the yaml configuration file from
// the legacy format to the 1.0+ format.
func ConvertBytes(d []byte) ([]byte, error) {
return yaml.ConvertBytes(d)
func Convert(d []byte, m Metadata) ([]byte, error) {
switch m.Filename {
case "bitbucket-pipelines.yml":
return bitbucket.Convert(d, m.Ref)
case "circle.yml", ".circleci/config.yml":
// TODO(bradrydzewski)
case ".gitlab-ci.yml":
// TODO(bradrydzewski)
case ".travis.yml":
// TODO(bradrydzewski)
}
// if the filename does not match any external
// systems we check to see if the configuration
// file is a legacy (pre 1.0) .drone.yml format.
if legacy.Match(d) {
return legacy.Convert(d)
}
// else return the unmodified configuration
// back to the caller.
return d, nil
}
// ConvertString converts the yaml configuration file from
// the legacy format to the 1.0+ format.
func ConvertString(s string) (string, error) {
return yaml.ConvertString(s)
func ConvertString(s string, m Metadata) (string, error) {
b, err := Convert([]byte(s), m)
return string(b), err
}

@ -1,21 +0,0 @@
package converter
import (
"regexp"
)
var re = regexp.MustCompile(`(?m)^pipeline:(\s+)?$`)
// IsLegacy returns true if the yaml configuration file is
// legacy and requires converstion.
func IsLegacy(s string) bool {
matches := re.FindAllString(s, -1)
return len(matches) != 0
}
// IsLegacyBytes returns true if the yaml configuration file
// is legacy and requires converstion.
func IsLegacyBytes(b []byte) bool {
matches := re.FindAll(b, -1)
return len(matches) != 0
}

@ -0,0 +1,9 @@
package legacy
import "github.com/drone/drone-yaml/yaml/converter/legacy/internal"
// Convert converts the yaml configuration file from
// the legacy format to the 1.0+ format.
func Convert(d []byte) ([]byte, error) {
return yaml.Convert(d)
}

@ -29,9 +29,9 @@ type Config struct {
}
}
// ConvertBytes converts the yaml configuration file from
// Convert converts the yaml configuration file from
// the legacy format to the 1.0+ format.
func ConvertBytes(d []byte) ([]byte, error) {
func Convert(d []byte) ([]byte, error) {
from := new(Config)
err := yaml.Unmarshal(d, from)
if err != nil {
@ -82,14 +82,6 @@ func ConvertBytes(d []byte) ([]byte, error) {
return buf.Bytes(), nil
}
// ConvertString converts the yaml configuration file from
// the legacy format to the 1.0+ format.
func ConvertString(s string) (string, error) {
dat := []byte(s)
out, err := ConvertBytes(dat)
return string(out), err
}
func toContainer(from *Container) *droneyaml.Container {
return &droneyaml.Container{
Name: from.Name,

@ -0,0 +1,14 @@
package legacy
import (
"regexp"
)
var re = regexp.MustCompile(`(?m)^pipeline:(\s+)?$`)
// Match returns true if the yaml configuration file
// is legacy and requires converstion.
func Match(b []byte) bool {
matches := re.FindAll(b, -1)
return len(matches) != 0
}

@ -58,7 +58,9 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
if err != nil {
return err
}
names := map[string]struct{}{}
names := map[string]struct{}{
"clone": struct{}{},
}
for _, container := range pipeline.Steps {
_, ok := names[container.Name]
if ok {

@ -29,8 +29,9 @@ func (p *Pipeline) GetKind() string { return p.Kind }
type (
// Clone configures the git clone.
Clone struct {
Disable bool `json:"disable,omitempty"`
Depth int `json:"depth,omitempty"`
Disable bool `json:"disable,omitempty"`
Depth int `json:"depth,omitempty"`
SkipVerify bool `json:"skip_verify,omitempty" yaml:"skip_verify"`
}
// Concurrency limits pipeline concurrency.

@ -85,6 +85,7 @@ func printClone(w writer, v yaml.Clone) {
w.IndentIncrease()
w.WriteTagValue("depth", v.Depth)
w.WriteTagValue("disable", v.Disable)
w.WriteTagValue("skip_verify", v.SkipVerify)
w.WriteByte('\n')
w.IndentDecrease()
}
@ -234,7 +235,8 @@ func isPlatformEmpty(v yaml.Platform) bool {
// object is empty.
func isCloneEmpty(v yaml.Clone) bool {
return v.Depth == 0 &&
v.Disable == false
v.Disable == false &&
v.SkipVerify == false
}
// helper function returns true if the concurrency