goyaml

Using Go to create BitBucket Pipeline YAML with anchors and merging


I am trying to use the github.com/goccy/go-yaml Go YAML library to write BitBucket Pipeline files. The complicating fact is that I'm trying to use YAML's merge and anchor features to reuse common steps.

A condensed version of the output I'm looking for:

image: ubuntu:latest

definitions:
  steps:
    - step: &run-task
      name: Run a task
      oidc: true
      runs-on:
        - self.hosted
      script:
        - sbt test

pipelines:
  tags:
    '*.*.*':
      - step:
          <<: *run-task
          name: Release
          script:
            - sbt publish

Here is the current struct/logic:

package main

import (
    "fmt"

    "github.com/goccy/go-yaml"
)

type Pipeline struct {
    Image       string      `yaml:"image"`
    Definitions Definitions `yaml:"definitions"`
    Pipelines   Pipelines   `yaml:"pipelines"`
}

type Pipelines struct {
    Tags *Tag `yaml:"tags"`
}

type Definitions struct {
    Steps []*StepTemplate `yaml:"steps"`
}

type StepTemplate struct {
    *StepTemplate `yaml:"step,anchor=run-task"`
    Name          string   `yaml:"name,omitempty"`
    Oidc          bool     `yaml:"oidc,omitempty"`
    RunsOn        []string `yaml:"runs-on,omitempty"`
    Script        []string `yaml:"script,omitempty"`
}

type StepReal struct {
    *StepReal `yaml:"step,alias=run-task"`
    Name      string   `yaml:"name,omitempty"`
    Oidc      bool     `yaml:"oidc,omitempty"`
    RunsOn    []string `yaml:"runs-on,omitempty"`
    Script    []string `yaml:"script,omitempty"`
}

type Tag map[string][]*StepReal

func main() {
    defaultStep := &StepTemplate{
        Name:   "Run step",
        Oidc:   true,
        RunsOn: []string{"self.hosted", "default"},
        Script: []string{"sbt test"},
    }

    releaseStep := StepReal{
        //Step:   defaultStep,
        Name:   "Release",
        Script: []string{"sbt publish"},
    }

    tagSteps := &Tag{
        "*.*.*": []*StepReal{&releaseStep},
    }

    pipe := Pipeline{
        Image: "ubuntu:latest",
        Definitions: Definitions{
            Steps: []*StepTemplate{defaultStep},
        },
        Pipelines: Pipelines{
            Tags: tagSteps,
        },
    }

    bytes, _ := yaml.MarshalWithOptions(pipe, yaml.IndentSequence(true))

    fmt.Println(string(bytes))
}

Go Playground

I think I'm pretty close but I cannot figure out a couple of things:


Solution

  • It took me some time to figure out but maybe is this what you're looking for:

    package main
    
    import (
            "fmt"
    
            "github.com/goccy/go-yaml"
    )
    
    type Pipeline struct {
            Image       string      `yaml:"image"`
            Definitions Definitions `yaml:"definitions"`
            Pipelines   Pipelines   `yaml:"pipelines"`
    }
    
    type Pipelines struct {
            Tags *Tag `yaml:"tags"`
    }
    
    type Definitions struct {
            Steps []*StepTemplate `yaml:"steps"`
    }
    
    type Step struct {
            *Step  `yaml:",omitempty,inline,alias"`
            Name   string   `yaml:"name,omitempty"`
            Oidc   bool     `yaml:"oidc,omitempty"`
            RunsOn []string `yaml:"runs-on,omitempty"`
            Script []string `yaml:"script,omitempty"`
    }
    
    type StepTemplate struct {
            *Step `yaml:"step,anchor=run-task"`
    }
    
    type StepReal struct {
            *Step
    }
    
    type Tag map[string][]*StepReal
    
    func main() {
            defaultStep := &StepTemplate{
                    &Step{
                            Name:   "Run a task",
                            Oidc:   true,
                            RunsOn: []string{"self.hosted"},
                            Script: []string{"sbt test"},
                    },
            }
    
            releaseStep := StepReal{
                    &Step{
                            Step:   defaultStep.Step,
                            Name:   "Release",
                            Script: []string{"sbt publish"},
                    },
            }
    
            tagSteps := &Tag{
                    "*.*.*": []*StepReal{&releaseStep},
            }
    
            pipe := Pipeline{
                    Image: "ubuntu:latest",
                    Definitions: Definitions{
                            Steps: []*StepTemplate{defaultStep},
                    },
                    Pipelines: Pipelines{
                            Tags: tagSteps,
                    },
            }
    
            bytes, err := yaml.MarshalWithOptions(pipe, yaml.IndentSequence(true))
            if err != nil {
                    panic(err)
            }
    
            fmt.Println(string(bytes))
    }