Skip to content

Commit

Permalink
Support using YAML's anchors and aliases in runbooks
Browse files Browse the repository at this point in the history
  • Loading branch information
h6ah4i committed Dec 30, 2023
1 parent 5d3b0d9 commit acd345e
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 3 deletions.
15 changes: 15 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ func (r *httpRequest) encodeBody() (io.Reader, error) {
return strings.NewReader(v), nil
case []byte:
return bytes.NewBuffer(r.body.([]byte)), nil
case []any:
// NOTE: flattenYamlAliases converts !!binary base64 data into array
arr, ok := r.body.([]interface{})

Check failure on line 171 in http.go

View workflow job for this annotation

GitHub Actions / Test

[gostyle.ifacenames] All interface names with the -er suffix are required. (THIS IS NOT IN Effective Go): body
if !ok {
return nil, fmt.Errorf("invalid body: %v", r.body)
}
b := make([]byte, len(arr))
for i := range arr {
u64, ok := arr[i].(uint64)
if !ok {
return nil, fmt.Errorf("invalid body: %v", r.body)
}
b[i] = (uint8)(u64 & 0xff)
}
return bytes.NewBuffer(b), nil
}
return nil, fmt.Errorf("invalid body: %v", r.body)
case MediaTypeTextPlain:
Expand Down
40 changes: 37 additions & 3 deletions runbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/Songmu/axslogparser"
goyaml "github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/lexer"
"github.com/goccy/go-yaml/parser"
Expand Down Expand Up @@ -102,19 +103,52 @@ func parseRunbook(b []byte) (*runbook, error) {
if err != nil {
return nil, err
}
b = []byte(rep)
if err := yaml.Unmarshal(b, rb); err != nil {
if err := parseRunbookMapped(b, rb); err != nil {

flattened, err := flattenYamlAliases([]byte(rep))
if err != nil {
return nil, err
}

if err := yaml.Unmarshal(flattened, rb); err != nil {
if err := parseRunbookMapped(flattened, rb); err != nil {
return nil, err
}
}

if err := rb.validate(); err != nil {
return nil, err
}

return rb, nil
}

func flattenYamlAliases(in []byte) ([]byte, error) {
decOpts := []goyaml.DecodeOption{
goyaml.UseOrderedMap(),
}

encOpts := []goyaml.EncodeOption{
goyaml.Flow(false),
goyaml.UseSingleQuote(false),
goyaml.UseLiteralStyleIfMultiline(false),
goyaml.IndentSequence(true),
}

var tmp any

err := goyaml.UnmarshalWithOptions(in, &tmp, decOpts...)
if err != nil {
return nil, err
}

flattened, err := goyaml.MarshalWithOptions(tmp, encOpts...)
if err != nil {
return nil, err
}

return flattened, nil
}

func parseRunbookMapped(b []byte, rb *runbook) error {
m := &runbookMapped{}
if err := yaml.Unmarshal(b, m); err != nil {
Expand Down
30 changes: 30 additions & 0 deletions runbook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package runn

import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -262,3 +263,32 @@ func TestRunbookValidate(t *testing.T) {
})
}
}

func TestRunbookYamlAnchorAndAlias(t *testing.T) {
tests := []struct {
book string
wantErr bool
}{
{"testdata/book/yaml_anchor_alias.yml", false},
}
ctx := context.Background()
for _, tt := range tests {
tt := tt
t.Run(tt.book, func(t *testing.T) {
t.Parallel()
o, err := New(Book(tt.book))
if err != nil {
if !tt.wantErr {
t.Errorf("got %v", err)
}
return
}
if tt.wantErr {
t.Errorf("want err")
}
if err := o.Run(ctx); err != nil {
t.Error(err)
}
})
}
}
64 changes: 64 additions & 0 deletions testdata/book/yaml_anchor_alias.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
desc: YAML's anchor & alias check

my_aliases: # NOTE: this field name is not reserved by runn
my_string: &my_string_anchor "ABCDEFG"
my_array: &my_array_anchor
- 'A'
- 'B'
my_hash: &my_hash_anchor
a: 1
b: 2

vars:
string: &str_anchor "abcdefg"
string_alias: *str_anchor
array: &arr_anchor
- 1
- 2
array_alias: *arr_anchor
hash: &hash_anchor
foo: 1
bar: 2
hash_alias: *hash_anchor
hash_alias_with_merged:
<<: *hash_anchor
baz: 3
my_string: *my_string_anchor
my_array: *my_array_anchor
my_hash: *my_hash_anchor
my_hash_with_merged:
<<: *my_hash_anchor
c: 3

steps:
check_string_alias_in_vars:
test: |
vars.string_alias == "abcdefg"
check_array_alias_in_vars:
test: |
compare(vars.array_alias, [1, 2])
check_hash_alias_in_vars:
test: |
compare(vars.hash_alias, { foo: 1, bar: 2 })
check_hash_alias_with_merged_in_vars:
test: |
compare(vars.hash_alias_with_merged, { foo: 1, bar: 2, baz: 3 })
check_my_string:
test: |
compare(vars.my_string, "ABCDEFG")
check_my_array:
test: |
compare(vars.my_array, ["A", "B"])
check_my_hash:
test: |
compare(vars.my_hash, {a: 1, b: 2})
check_my_hash_with_merged:
test: |
compare(vars.my_hash_with_merged, {a: 1, b: 2, c: 3})

0 comments on commit acd345e

Please sign in to comment.