Skip to content

Commit

Permalink
required addons (#269)
Browse files Browse the repository at this point in the history
* dep update

* Adding meta field to step model.

* Using string to interface{} map for meta tag

* missing packages

* Convert to map[string]interface{}

* Dep update

* remove test case

* Converting StepModel receivers to pointer types.

* Dep update

* Renaming function

* fixes

* dep update
  • Loading branch information
lpusok authored and godrei committed Apr 17, 2019
1 parent 9b6ace5 commit 8cd3398
Show file tree
Hide file tree
Showing 36 changed files with 5,119 additions and 1,755 deletions.
20 changes: 17 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions _tests/integration/step_info_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package integration

import (
"testing"

"strings"
"testing"

"github.com/bitrise-io/go-utils/command"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -158,7 +157,7 @@ inputs:
is_required: true
is_expand: true`

const localTestStepDefinitionJSON = `{"library":"path","id":"./test-step","info":{},"step":{"title":"STEP TEMPLATE","summary":"A short summary of the step. Don't make it too long ;)","description":"This is a Step template.\nContains everything what's required for a valid Stepman managed step.\n\nA Step's description (and generally any description property)\ncan be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text.\n\nTo create your own Step:\n\n1. Create a new repository on GitHub\n2. Copy the files from this folder into your repository\n3. That's all, you can use it on your own machine\n4. Once you're happy with it you can share it with others.","website":"https://github.com/...","source_code_url":"https://github.com/...","support_url":"https://github.com/.../issues","host_os_tags":["osx-10.10"],"project_type_tags":["ios","android","xamarin"],"type_tags":["script"],"deps":{"brew":[{"name":"git"},{"name":"wget"}],"apt_get":[{"name":"git"},{"name":"wget"}]},"is_requires_admin_user":true,"is_always_run":false,"is_skippable":false,"run_if":"","timeout":0,"inputs":[{"example_step_input":"Default Value - you can leave this empty if you want to","opts":{"is_expand":true,"skip_if_empty":false,"title":"Example Step Input","description":"Description of this input.\n\nCan be Markdown formatted text.\n","summary":"Summary. No more than 2-3 sentences.","category":"","is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false,"unset":false}}],"outputs":[{"EXAMPLE_STEP_OUTPUT":null,"opts":{"is_expand":true,"skip_if_empty":false,"title":"Example Step Output","description":"Description of this output.\n\nCan be Markdown formatted text.\n","summary":"Summary. No more than 2-3 sentences.","category":"","is_required":false,"is_dont_change_value":false,"is_template":false,"is_sensitive":false,"unset":false}}]},"definition_pth":"test-step/step.yml"}`
const localTestStepDefinitionJSON = `{"library":"path","id":"./test-step","info":{},"step":{"title":"STEP TEMPLATE","summary":"A short summary of the step. Don't make it too long ;)","description":"This is a Step template.\nContains everything what's required for a valid Stepman managed step.\n\nA Step's description (and generally any description property)\ncan be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text.\n\nTo create your own Step:\n\n1. Create a new repository on GitHub\n2. Copy the files from this folder into your repository\n3. That's all, you can use it on your own machine\n4. Once you're happy with it you can share it with others.","website":"https://github.com/...","source_code_url":"https://github.com/...","support_url":"https://github.com/.../issues","host_os_tags":["osx-10.10"],"project_type_tags":["ios","android","xamarin"],"type_tags":["script"],"deps":{"brew":[{"name":"git"},{"name":"wget"}],"apt_get":[{"name":"git"},{"name":"wget"}]},"is_requires_admin_user":true,"is_always_run":false,"is_skippable":false,"run_if":"","timeout":0,"meta":{"bitrise.io.addons.optional":[{"addon_id":"addons-testing"}],"bitrise.io.addons.required":[{"addon_id":"addons-testing","addon_options":{"required":true,"title":"Testing Addon"},"addon_params":"--token TOKEN"},{"addon_id":"addons-ship","addon_options":{"required":true,"title":"Ship Addon"},"addon_params":"--token TOKEN"}]},"inputs":[{"example_step_input":"Default Value - you can leave this empty if you want to","opts":{"is_expand":true,"skip_if_empty":false,"title":"Example Step Input","description":"Description of this input.\n\nCan be Markdown formatted text.\n","summary":"Summary. No more than 2-3 sentences.","category":"","is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false,"unset":false}}],"outputs":[{"EXAMPLE_STEP_OUTPUT":null,"opts":{"is_expand":true,"skip_if_empty":false,"title":"Example Step Output","description":"Description of this output.\n\nCan be Markdown formatted text.\n","summary":"Summary. No more than 2-3 sentences.","category":"","is_required":false,"is_dont_change_value":false,"is_template":false,"is_sensitive":false,"unset":false}}]},"definition_pth":"test-step/step.yml"}`

const localTestStepDefinition = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` path
` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` ./test-step
Expand Down Expand Up @@ -195,6 +194,19 @@ type_tags:
is_requires_admin_user: true
is_always_run: false
is_skippable: false
meta:
bitrise.io.addons.required:
- addon_id: "addons-testing"
addon_params: "--token TOKEN"
addon_options:
required: true
title: "Testing Addon"
- addon_id: "addons-ship"
addon_params: "--token TOKEN"
addon_options:
required: true
title: "Ship Addon"
bitrise.io.addons.optional: [{"addon_id":"addons-testing"}]
deps:
brew:
- name: git
Expand Down
13 changes: 13 additions & 0 deletions _tests/integration/test-step/step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ type_tags:
is_requires_admin_user: true
is_always_run: false
is_skippable: false
meta:
bitrise.io.addons.required:
- addon_id: "addons-testing"
addon_params: "--token TOKEN"
addon_options:
required: true
title: "Testing Addon"
- addon_id: "addons-ship"
addon_params: "--token TOKEN"
addon_options:
required: true
title: "Ship Addon"
bitrise.io.addons.optional: [{"addon_id":"addons-testing"}]
deps:
brew:
- name: git
Expand Down
5 changes: 3 additions & 2 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ type StepModel struct {
// steps will run which are marked with IsAlwaysRun.
IsSkippable *bool `json:"is_skippable,omitempty" yaml:"is_skippable,omitempty"`
// RunIf : only run the step if the template example evaluates to true
RunIf *string `json:"run_if,omitempty" yaml:"run_if,omitempty"`
Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty"`
RunIf *string `json:"run_if,omitempty" yaml:"run_if,omitempty"`
Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty"`
Meta map[string]interface{} `json:"meta,omitempty" yaml:"meta,omitempty"`
//
Inputs []envmanModels.EnvironmentItemModel `json:"inputs,omitempty" yaml:"inputs,omitempty"`
Outputs []envmanModels.EnvironmentItemModel `json:"outputs,omitempty" yaml:"outputs,omitempty"`
Expand Down
44 changes: 26 additions & 18 deletions models/models_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,6 @@ func (stepInfo StepInfoModel) CreateFromJSON(jsonStr string) (StepInfoModel, err
return info, nil
}

// Normalize ...
func (step StepModel) Normalize() error {
for _, input := range step.Inputs {
if err := input.Normalize(); err != nil {
return err
}
}
for _, output := range step.Outputs {
if err := output.Normalize(); err != nil {
return err
}
}
return nil
}

// ValidateSource ...
func (source StepSourceModel) validateSource() error {
if source.Git == "" {
Expand All @@ -103,8 +88,31 @@ func (source StepSourceModel) validateSource() error {
return nil
}

// Normalize ...
func (step *StepModel) Normalize() error {
for _, input := range step.Inputs {
if err := input.Normalize(); err != nil {
return err
}
}

for _, output := range step.Outputs {
if err := output.Normalize(); err != nil {
return err
}
}

normalizedMeta, err := JSONMarshallable(step.Meta)
if err != nil {
return err
}
step.Meta = normalizedMeta

return nil
}

// ValidateInputAndOutputEnvs ...
func (step StepModel) ValidateInputAndOutputEnvs(checkRequiredFields bool) error {
func (step *StepModel) ValidateInputAndOutputEnvs(checkRequiredFields bool) error {
validateEnvs := func(envs []envmanModels.EnvironmentItemModel) error {
for _, env := range envs {
key, _, err := env.GetKeyValuePair()
Expand Down Expand Up @@ -148,7 +156,7 @@ func (step StepModel) ValidateInputAndOutputEnvs(checkRequiredFields bool) error
}

// AuditBeforeShare ...
func (step StepModel) AuditBeforeShare() error {
func (step *StepModel) AuditBeforeShare() error {
if step.Title == nil || *step.Title == "" {
return errors.New("Invalid step: missing or empty required 'title' property")
}
Expand All @@ -167,7 +175,7 @@ func (step StepModel) AuditBeforeShare() error {
}

// Audit ...
func (step StepModel) Audit() error {
func (step *StepModel) Audit() error {
if err := step.AuditBeforeShare(); err != nil {
return err
}
Expand Down
64 changes: 64 additions & 0 deletions models/parse_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package models

import (
"fmt"
)

// JSONMarshallable replaces map[interface{}]interface{} with map[string]string recursively
// map[interface{}]interface{} is usually returned by parser go-yaml/v2
func JSONMarshallable(source map[string]interface{}) (map[string]interface{}, error) {
target, err := recursiveJSONMarshallable(source)
if err != nil {
return nil, err
}
castedTarget, ok := target.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("could not cast to map[string]interface{}")
}
return castedTarget, nil
}

func recursiveJSONMarshallable(source interface{}) (interface{}, error) {
if array, ok := source.([]interface{}); ok {
var convertedArray []interface{}
for _, element := range array {
convertedValue, err := recursiveJSONMarshallable(element)
if err != nil {
return nil, err
}
convertedArray = append(convertedArray, convertedValue)
}
return convertedArray, nil
}

if interfaceToInterfaceMap, ok := source.(map[interface{}]interface{}); ok {
target := map[string]interface{}{}
for key, value := range interfaceToInterfaceMap {
strKey, ok := key.(string)
if !ok {
return nil, fmt.Errorf("failed to convert map key from type interface{} to string")
}

convertedValue, err := recursiveJSONMarshallable(value)
if err != nil {
return nil, err
}
target[strKey] = convertedValue
}
return target, nil
}

if stringToInterfaceMap, ok := source.(map[string]interface{}); ok {
target := map[string]interface{}{}
for key, value := range stringToInterfaceMap {
convertedValue, err := recursiveJSONMarshallable(value)
if err != nil {
return nil, err
}
target[key] = convertedValue
}
return target, nil
}

return source, nil
}
108 changes: 108 additions & 0 deletions models/parse_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package models

import (
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
)

func Test_castRecursiveToMapStringInterface(t *testing.T) {
tests := []struct {
name string
source interface{}
want interface{}
wantErr bool
}{
{
name: "1 level",
source: map[interface{}]interface{}{"aa": "bb"},
want: map[string]interface{}{"aa": "bb"},
wantErr: false,
},
{
name: "1 level map[string]interface at top",
source: map[string]interface{}{"aa": "bb"},
want: map[string]interface{}{"aa": "bb"},
wantErr: false,
},
{
name: "2 levels",
source: map[interface{}]interface{}{"aa": map[interface{}]interface{}{"aa": "bb"}, "b": "c"},
want: map[string]interface{}{"aa": map[string]interface{}{"aa": "bb"}, "b": "c"},
wantErr: false,
},
{
name: "2 levels map[string]interface at top",
source: map[string]interface{}{"aa": map[interface{}]interface{}{"aa": "bb"}, "b": "c"},
want: map[string]interface{}{"aa": map[string]interface{}{"aa": "bb"}, "b": "c"},
wantErr: false,
},
{
name: "Decoded from yml",
source: map[interface{}]interface{}{
"bitrise.io.addons.optional.2": []interface{}{
map[interface{}]interface{}{
"addon_id": "addons-testing",
},
},
"bitrise.io.addons.required": []interface{}{
map[interface{}]interface{}{
"addon_id": "addons-testing",
"addon_options": map[interface{}]interface{}{
"required": true,
"title": "Testing Addon",
},
"addon_params": "--token TOKEN",
},
map[interface{}]interface{}{
"addon_id": "addons-ship",
"addon_options": map[interface{}]interface{}{
"required": true,
"title": "Ship Addon",
},
"addon_params": "--token TOKEN",
},
},
},
want: map[string]interface{}{
"bitrise.io.addons.optional.2": []interface{}{
map[string]interface{}{
"addon_id": "addons-testing",
},
},
"bitrise.io.addons.required": []interface{}{
map[string]interface{}{
"addon_id": "addons-testing",
"addon_options": map[string]interface{}{
"required": true,
"title": "Testing Addon",
},
"addon_params": "--token TOKEN",
},
map[string]interface{}{
"addon_id": "addons-ship",
"addon_options": map[string]interface{}{
"required": true,
"title": "Ship Addon",
},
"addon_params": "--token TOKEN",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := recursiveJSONMarshallable(tt.source)
if (err != nil) != tt.wantErr {
t.Errorf("castRecursiveToMapStringInterface() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("castRecursiveToMapStringInterface() = %v, want %v, \n Diff %v", got, tt.want, cmp.Diff(got, tt.want))
}
})
}
}
4 changes: 4 additions & 0 deletions stepman/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ func ParseStepDefinition(pth string, validate bool) (models.StepModel, error) {
return models.StepModel{}, err
}

return parseStepModel(bytes, validate)
}

func parseStepModel(bytes []byte, validate bool) (models.StepModel, error) {
var stepModel models.StepModel
if err := yaml.Unmarshal(bytes, &stepModel); err != nil {
return models.StepModel{}, err
Expand Down
Loading

0 comments on commit 8cd3398

Please sign in to comment.