1
0
Fork 0
woodpecker-pipeline-transform/drone/drone.go
2022-07-30 03:48:03 +03:00

296 lines
7.9 KiB
Go

// Copyright 2022 Lauris BH. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package drone
import (
"bytes"
"errors"
"strings"
transform "codeberg.org/lafriks/woodpecker-pipeline-transform"
"codeberg.org/lafriks/woodpecker-pipeline-transform/core"
"github.com/goccy/go-yaml"
)
type UnsupportedEventError struct {
Event string
}
func (e UnsupportedEventError) Error() string {
return "unsupported event: " + e.Event
}
func New() *DronePipeline {
return &DronePipeline{}
}
type DronePipeline struct{}
func (d DronePipeline) ConvertImage(image string) string {
return image
}
func (d DronePipeline) ConvertEvents(events core.Strings) (core.Strings, error) {
ev := make([]string, 0, len(events))
for _, event := range events {
switch event {
case "push":
ev = append(ev, "push")
case "pull_request":
ev = append(ev, "pull_request")
case "tag":
ev = append(ev, "tag")
case "promote":
ev = append(ev, "deployment")
// rollback, cron, custom
default:
return nil, UnsupportedEventError{Event: event}
}
}
return ev, nil
}
func (d DronePipeline) ConvertConditions(when *When) (*transform.When, error) {
if when == nil {
return nil, nil
}
if when.Repositories != nil &&
(len(when.Repositories.Include) > 0 ||
len(when.Repositories.Exclude) > 0 ||
len(when.Repositories.Conditions) > 0) {
return nil, errors.New("unsupported condition: repo")
}
if when.Cron != nil &&
(len(when.Cron.Include) > 0 ||
len(when.Cron.Exclude) > 0 ||
len(when.Cron.Conditions) > 0) {
return nil, errors.New("unsupported condition: cron")
}
// Branch conditions
branch := &transform.Conditions{}
if when.Branch != nil {
branch.Conditions = when.Branch.Conditions
branch.Include = when.Branch.Include
branch.Exclude = when.Branch.Exclude
}
ev, err := d.ConvertEvents(when.Event)
if err != nil {
return nil, err
}
// Refs (branches and tags)
var tags string
if when.Refs != nil {
for _, ref := range when.Refs.Conditions {
if strings.HasPrefix(ref, "refs/tags/") {
tags += strings.TrimPrefix(ref, "refs/tags/") + ","
} else if strings.HasPrefix(ref, "refs/heads/") {
branch.Conditions = append(branch.Conditions, strings.TrimPrefix(ref, "refs/heads/"))
}
}
for _, ref := range when.Refs.Include {
if strings.HasPrefix(ref, "refs/tags/") {
tags += strings.TrimPrefix(ref, "refs/tags/") + ","
} else if strings.HasPrefix(ref, "refs/heads/") {
branch.Include = append(branch.Include, strings.TrimPrefix(ref, "refs/heads/"))
}
}
for _, ref := range when.Refs.Exclude {
if strings.HasPrefix(ref, "refs/tags/") {
if tags != "" {
return nil, errors.New("tags include and exclude are mutually exclusive in Woodpecker CI")
}
tags += strings.TrimPrefix(ref, "refs/tags/") + "|"
} else if strings.HasPrefix(ref, "refs/heads/") {
branch.Exclude = append(branch.Exclude, strings.TrimPrefix(ref, "refs/heads/"))
}
}
}
if tags != "" {
if strings.Count(tags, ",") == 1 {
tags = strings.TrimSuffix(tags, ",")
} else if strings.HasSuffix(tags, ",") {
tags = "{" + strings.TrimSuffix(tags, ",") + "}"
} else if strings.HasSuffix(tags, "|") {
tags = "!(" + strings.TrimSuffix(tags, "|") + ")"
}
}
// Check if branch is empty condition
if branch.IsEmpty() {
branch = nil
}
// Instance condition
var instance string
if when.Instance != nil {
if len(when.Instance.Conditions)+len(when.Instance.Include) > 1 {
return nil, errors.New("unsupported condition: only single instance is supported")
}
if len(when.Instance.Exclude) > 0 {
return nil, errors.New("unsupported condition: instance exclude condition is not supported")
}
if len(when.Instance.Conditions) == 1 {
instance = when.Instance.Conditions[0]
} else if len(when.Instance.Include) == 1 {
instance = when.Instance.Include[0]
}
}
// Target condition
var target string
if when.Target != nil {
if len(when.Target.Conditions)+len(when.Target.Include) > 1 {
return nil, errors.New("unsupported condition: only single target is supported")
}
if len(when.Target.Exclude) > 0 {
return nil, errors.New("unsupported condition: target exclude condition is not supported")
}
if len(when.Target.Conditions) == 1 {
target = when.Target.Conditions[0]
} else if len(when.Target.Include) == 1 {
target = when.Target.Include[0]
}
}
r := &transform.When{
Branch: branch,
Event: ev,
Tag: tags,
Instance: instance,
Status: when.Status,
Environment: target,
}
// If there is no actual conditions, skip the when
if r.IsEmpty() {
r = nil
}
return r, nil
}
func (d DronePipeline) Convert(pipeline *Pipeline) (*transform.Pipeline, error) {
if pipeline.Kind != "pipeline" {
return nil, transform.UnsupportedError
}
if pipeline.Type != "docker" {
return nil, transform.UnsupportedError
}
if pipeline.Platform != nil && len(pipeline.Platform.Version) > 0 {
return nil, errors.New("unsupported platform property: version")
}
// Pipeline basics
p := &transform.Pipeline{
Name: pipeline.Name,
Steps: make(transform.Steps, 0, len(pipeline.Steps)),
Labels: pipeline.Node,
DependsOn: pipeline.DependsOn,
}
// Platform
if pipeline.Platform != nil && len(pipeline.Platform.OS) > 0 && len(pipeline.Platform.Arch) > 0 {
p.Platform = pipeline.Platform.OS + "/" + pipeline.Platform.Arch
}
// Services
for _, service := range pipeline.Services {
if service.Privileged {
return nil, errors.New("unsupported service property: privileged")
}
if len(service.WorkingDir) > 0 {
return nil, errors.New("unsupported service property: working_dir")
}
env := make([]string, 0, len(service.Environment))
for k, v := range service.Environment {
if v.Secret != "" {
continue
}
env = append(env, k+"="+v.Value)
}
secrets := make(transform.Secrets, 0)
for k, v := range service.Environment {
if v.Secret == "" {
continue
}
secrets = append(secrets, transform.Secret{
Target: k,
Source: v.Secret,
})
}
p.Services = append(p.Services, &transform.Service{
Name: service.Name,
Image: service.Image,
Pull: service.Pull == "always",
Environment: env,
Secrets: secrets,
Commands: append(service.Entrypoint, service.Commands...),
})
}
// Steps
for _, step := range pipeline.Steps {
if len(step.DependsOn) > 0 {
return nil, errors.New("unsupported step property: depends_on")
}
if len(step.Failure) > 0 {
return nil, errors.New("unsupported step property: failure")
}
env := make([]string, 0, len(step.Environment))
for k, v := range step.Environment {
if v.Secret != "" {
continue
}
env = append(env, k+"="+v.Value)
}
secrets := make(transform.Secrets, 0)
for k, v := range step.Environment {
if v.Secret == "" {
continue
}
secrets = append(secrets, transform.Secret{
Target: k,
Source: v.Secret,
})
}
when, err := d.ConvertConditions(step.When)
if err != nil {
return nil, err
}
p.Steps = append(p.Steps, &transform.Step{
Name: step.Name,
Image: d.ConvertImage(step.Image),
Pull: step.Pull == "always",
Environment: env,
Secrets: secrets,
Settings: step.Settings,
Detach: step.Detach,
Privileged: step.Privileged,
Commands: step.Commands,
When: when,
})
}
return p, nil
}
func (d DronePipeline) Transform(sources []*transform.Source) ([]*transform.Pipeline, error) {
p := make([]*transform.Pipeline, 0, len(sources))
for _, source := range sources {
dec := yaml.NewDecoder(bytes.NewReader(source.Content))
var err error
for err == nil {
pipeline := &Pipeline{}
if err = dec.Decode(pipeline); err != nil {
if err.Error() != "EOF" {
return nil, err
}
break
}
r, err := d.Convert(pipeline)
if err != nil {
return nil, err
}
p = append(p, r)
}
}
return p, nil
}