296 lines
7.9 KiB
Go
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
|
|
}
|