diff --git a/yaml/converter/legacy/internal/config.go b/yaml/converter/legacy/internal/config.go index ee82218..cf5676b 100644 --- a/yaml/converter/legacy/internal/config.go +++ b/yaml/converter/legacy/internal/config.go @@ -6,6 +6,7 @@ import ( "strings" droneyaml "github.com/drone/drone-yaml/yaml" + "github.com/drone/drone-yaml/yaml/converter/legacy/matrix" "github.com/drone/drone-yaml/yaml/pretty" "gopkg.in/yaml.v2" @@ -21,6 +22,7 @@ type Config struct { Pipeline Containers Services Containers Branches Constraint + Matrix interface{} Secrets map[string]struct { Driver string DriverOpts map[string]string `yaml:"driver_opts"` @@ -34,11 +36,14 @@ type Config struct { func Convert(d []byte) ([]byte, error) { from := new(Config) err := yaml.Unmarshal(d, from) + if err != nil { return nil, err } - pipeline := &droneyaml.Pipeline{} + manifest := &droneyaml.Manifest{} + + pipeline := droneyaml.Pipeline{} pipeline.Name = "default" pipeline.Kind = "pipeline" @@ -65,20 +70,57 @@ func Convert(d []byte) ([]byte, error) { toContainer(container), ) } + pipeline.Volumes = toVolumes(from) pipeline.Trigger.Branch.Include = from.Branches.Include pipeline.Trigger.Branch.Exclude = from.Branches.Exclude - manifest := &droneyaml.Manifest{} - manifest.Resources = append(manifest.Resources, pipeline) + if from.Matrix != nil { + axes, err := matrix.Parse(d) + + if err != nil { + return nil, err + } + + for index, environ := range axes { + current := pipeline + current.Name = fmt.Sprintf("matrix-%d", index+1) + + marshaled, err := yaml.Marshal(¤t) + + if err != nil { + return nil, err + } + + transformed := string(marshaled) + + for key, value := range environ { + if strings.Contains(value, "\n") { + value = fmt.Sprintf("%q", value) + } + + transformed = strings.Replace(transformed, fmt.Sprintf("${%s}", key), value, -1) + } + + if err := yaml.Unmarshal([]byte(transformed), ¤t); err != nil { + return nil, err + } + + manifest.Resources = append(manifest.Resources, ¤t) + } + } else { + manifest.Resources = append(manifest.Resources, &pipeline) + } secrets := toSecrets(from) + if secrets != nil { manifest.Resources = append(manifest.Resources, secrets) } buf := new(bytes.Buffer) pretty.Print(buf, manifest) + return buf.Bytes(), nil } diff --git a/yaml/converter/legacy/internal/config_test.go b/yaml/converter/legacy/internal/config_test.go index 66f74fb..8ee736b 100644 --- a/yaml/converter/legacy/internal/config_test.go +++ b/yaml/converter/legacy/internal/config_test.go @@ -26,6 +26,14 @@ func TestConvert(t *testing.T) { before: "testdata/vault_3.yml", after: "testdata/vault_3.yml.golden", }, + { + before: "testdata/matrix_1.yml", + after: "testdata/matrix_1.yml.golden", + }, + { + before: "testdata/matrix_2.yml", + after: "testdata/matrix_2.yml.golden", + }, } for _, test := range tests { diff --git a/yaml/converter/legacy/matrix/matrix.go b/yaml/converter/legacy/matrix/matrix.go new file mode 100644 index 0000000..76a0f6b --- /dev/null +++ b/yaml/converter/legacy/matrix/matrix.go @@ -0,0 +1,117 @@ +package matrix + +import ( + "strings" + + "gopkg.in/yaml.v2" +) + +const ( + limitTags = 10 + limitAxis = 25 +) + +// Matrix represents the build matrix. +type Matrix map[string][]string + +// Axis represents a single permutation of entries from the build matrix. +type Axis map[string]string + +// String returns a string representation of an Axis as a comma-separated list +// of environment variables. +func (a Axis) String() string { + var envs []string + for k, v := range a { + envs = append(envs, k+"="+v) + } + return strings.Join(envs, " ") +} + +// Parse parses the Yaml matrix definition. +func Parse(data []byte) ([]Axis, error) { + + axis, err := parseList(data) + if err == nil && len(axis) != 0 { + return axis, nil + } + + matrix, err := parse(data) + if err != nil { + return nil, err + } + + // if not a matrix build return an array with just the single axis. + if len(matrix) == 0 { + return nil, nil + } + + return calc(matrix), nil +} + +// ParseString parses the Yaml string matrix definition. +func ParseString(data string) ([]Axis, error) { + return Parse([]byte(data)) +} + +func calc(matrix Matrix) []Axis { + // calculate number of permutations and extract the list of tags + // (ie go_version, redis_version, etc) + var perm int + var tags []string + for k, v := range matrix { + perm *= len(v) + if perm == 0 { + perm = len(v) + } + tags = append(tags, k) + } + + // structure to hold the transformed result set + axisList := []Axis{} + + // for each axis calculate the uniqe set of values that should be used. + for p := 0; p < perm; p++ { + axis := map[string]string{} + decr := perm + for i, tag := range tags { + elems := matrix[tag] + decr = decr / len(elems) + elem := p / decr % len(elems) + axis[tag] = elems[elem] + + // enforce a maximum number of tags in the build matrix. + if i > limitTags { + break + } + } + + // append to the list of axis. + axisList = append(axisList, axis) + + // enforce a maximum number of axis that should be calculated. + if p > limitAxis { + break + } + } + + return axisList +} + +func parse(raw []byte) (Matrix, error) { + data := struct { + Matrix map[string][]string + }{} + err := yaml.Unmarshal(raw, &data) + return data.Matrix, err +} + +func parseList(raw []byte) ([]Axis, error) { + data := struct { + Matrix struct { + Include []Axis + } + }{} + + err := yaml.Unmarshal(raw, &data) + return data.Matrix.Include, err +}