diff --git a/README.md b/README.md index b64adf2..c839a20 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,27 @@ The following is the parameter of Shell script stage. |:----------------:|:----------:|:--------------------------------------:| | file | false | shell script file run in the stage | +## Parallel stages + + You can set child stages and run these stages in parallel like this. + +```yaml +pipeline: + - name: parallel stages + parallel: + - name: parallel command 1 + type: command + command: parallel command 1 + - name: parallel command 2 + type: command + command: parallel command 2 + - name: parallel command 3 + type: command + command: parallel command 3 +``` + +`parallel command 1`, `parallel command 2` and `parallel command 3` are executed in parallel. + ## Cleanup pipeline Walter configuraiton can have one **cleanup** block; cleanup is another pipeline which needs to be executed after a pipeline has either failed or passed. diff --git a/config/parser.go b/config/parser.go index a17864c..ae3bab1 100644 --- a/config/parser.go +++ b/config/parser.go @@ -219,9 +219,16 @@ func mapStage(stageMap map[interface{}]interface{}, envs *EnvVariables) (stages. } } - if runAfters := stageMap["run_after"]; runAfters != nil { - for _, runAfter := range runAfters.([]interface{}) { - childStage, err := mapStage(runAfter.(map[interface{}]interface{}), envs) + parallelStages := stageMap["parallel"] + if parallelStages == nil { + if parallelStages = stageMap["run_after"]; parallelStages != nil { + log.Warn("`run_after' will be obsoleted in near future. Use `parallel' instead.") + } + } + + if parallelStages != nil { + for _, parallelStages := range parallelStages.([]interface{}) { + childStage, err := mapStage(parallelStages.(map[interface{}]interface{}), envs) if err != nil { return nil, err } diff --git a/config/parser_test.go b/config/parser_test.go index 8483187..e524799 100644 --- a/config/parser_test.go +++ b/config/parser_test.go @@ -67,6 +67,24 @@ func TestParseConfWithChildren(t *testing.T) { assert.Equal(t, 2, childStages.Len()) } +func TestParseConfWithParallel(t *testing.T) { + configData := ReadConfigBytes([]byte(`pipeline: + - name: parallel stages + parallel: + - name: parallel command 1 + type: command + command: echo "hello, world, parallel command 1" + - name: parallel command 2 + type: command + command: echo "hello, world, parallel command 2"`)) + result, err := Parse(configData) + assert.Equal(t, 1, result.Pipeline.Size()) + assert.Nil(t, err) + + childStages := result.Pipeline.Stages.Front().Value.(stages.Stage).GetChildStages() + assert.Equal(t, 2, childStages.Len()) +} + func TestParseConfDefaultStageTypeIsCommand(t *testing.T) { configData := ReadConfigBytes([]byte(`pipeline: - name: command_stage_1 @@ -148,6 +166,18 @@ func TestParseConfWithInvalidChildStage(t *testing.T) { assert.NotNil(t, err) } +func TestParseConfWithInvalidParallelStage(t *testing.T) { + configData := ReadConfigBytes([]byte(`pipeline: + - name: parallel stages + parallel: + - name: parallel command 1 + type: xxxxx +`)) + result, err := Parse(configData) + assert.Nil(t, result) + assert.NotNil(t, err) +} + func TestParseConfWithServiceBlock(t *testing.T) { configData := ReadConfigBytes([]byte(` service: