Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions model/task_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,34 @@ type RunTaskConfiguration struct {

type Container struct {
Image string `json:"image" validate:"required"`
Name string `json:"name,omitempty"`
Command string `json:"command,omitempty"`
Ports map[string]interface{} `json:"ports,omitempty"`
Volumes map[string]interface{} `json:"volumes,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
Input string `json:"stdin,omitempty"`
Arguments []string `json:"arguments,omitempty"`
Lifetime *ContainerLifetime `json:"lifetime,omitempty"`
}

type ContainerLifetime struct {
Cleanup string `json:"cleanup" validate:"required,oneof=always never eventually"`
After *Duration `json:"after" validate:"required_if=Cleanup eventually"`
}

type Script struct {
Language string `json:"language" validate:"required"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
InlineCode *string `json:"code,omitempty"`
External *ExternalResource `json:"source,omitempty"`
Language string `json:"language" validate:"required,oneof=javascript js python"` // "js" exists for legacy reasons, use "javascript" instead
Arguments *RunArguments `json:"arguments,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
InlineCode *string `json:"code,omitempty"`
External *ExternalResource `json:"source,omitempty"`
Input string `json:"stdin,omitempty"`
}

type Shell struct {
Command string `json:"command" validate:"required"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
Command string `json:"command" validate:"required"`
Arguments *RunArguments `json:"arguments,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
}

type RunWorkflow struct {
Expand Down Expand Up @@ -126,3 +136,46 @@ func (rtc *RunTaskConfiguration) MarshalJSON() ([]byte, error) {

return json.Marshal(temp)
}

type RunArguments struct {
Value any `json:"-"`
}

func (a *RunArguments) MarshalJSON() ([]byte, error) {
switch v := a.Value.(type) {
case map[string]interface{}, []string:
return json.Marshal(v)
default:
return nil, errors.New("unknown RunArguments type")
}
}

func (a *RunArguments) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
if err := json.Unmarshal(data, &m); err == nil {
a.Value = m
return nil
}

var s []string
if err := json.Unmarshal(data, &s); err == nil {
a.Value = s
return nil
}

return errors.New("data must be a valid array of strings or object map")
}

func (a *RunArguments) AsMap() map[string]interface{} {
if v, ok := a.Value.(map[string]interface{}); ok {
return v
}
return nil
}

func (a *RunArguments) AsSlice() []string {
if v, ok := a.Value.([]string); ok {
return v
}
return nil
}
144 changes: 135 additions & 9 deletions model/task_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,23 @@ func TestRunTask_MarshalJSON(t *testing.T) {
Await: boolPtr(true),
Container: &Container{
Image: "example-image",
Name: "example-name",
Command: "example-command",
Ports: map[string]interface{}{
"8080": "80",
},
Environment: map[string]string{
"ENV_VAR": "value",
},
Input: "example-input",
Arguments: []string{
"arg1",
"arg2",
},
Lifetime: &ContainerLifetime{
Cleanup: "eventually",
After: NewDurationExpr("20s"),
},
},
},
}
Expand All @@ -61,9 +71,16 @@ func TestRunTask_MarshalJSON(t *testing.T) {
"await": true,
"container": {
"image": "example-image",
"name": "example-name",
"command": "example-command",
"ports": {"8080": "80"},
"environment": {"ENV_VAR": "value"}
"environment": {"ENV_VAR": "value"},
"stdin": "example-input",
"arguments": ["arg1","arg2"],
"lifetime": {
"cleanup": "eventually",
"after": "20s"
}
}
}
}`, string(data))
Expand All @@ -81,9 +98,18 @@ func TestRunTask_UnmarshalJSON(t *testing.T) {
"await": true,
"container": {
"image": "example-image",
"name": "example-name",
"command": "example-command",
"ports": {"8080": "80"},
"environment": {"ENV_VAR": "value"}
"environment": {"ENV_VAR": "value"},
"stdin": "example-input",
"arguments": ["arg1","arg2"],
"lifetime": {
"cleanup": "eventually",
"after": {
"seconds": 20
}
}
}
}
}`
Expand All @@ -102,9 +128,14 @@ func TestRunTask_UnmarshalJSON(t *testing.T) {
assert.Equal(t, "example-command", runTask.Run.Container.Command)
assert.Equal(t, map[string]interface{}{"8080": "80"}, runTask.Run.Container.Ports)
assert.Equal(t, map[string]string{"ENV_VAR": "value"}, runTask.Run.Container.Environment)
assert.Equal(t, "example-name", runTask.Run.Container.Name)
assert.Equal(t, "example-input", runTask.Run.Container.Input)
assert.Equal(t, []string{"arg1", "arg2"}, runTask.Run.Container.Arguments)
assert.Equal(t, "eventually", runTask.Run.Container.Lifetime.Cleanup)
assert.Equal(t, &DurationInline{Seconds: 20}, runTask.Run.Container.Lifetime.After.AsInline())
}

func TestRunTaskScript_MarshalJSON(t *testing.T) {
func TestRunTaskScriptArgsMap_MarshalJSON(t *testing.T) {
runTask := RunTask{
TaskBase: TaskBase{
If: &RuntimeExpression{Value: "${condition}"},
Expand All @@ -120,13 +151,16 @@ func TestRunTaskScript_MarshalJSON(t *testing.T) {
Await: boolPtr(true),
Script: &Script{
Language: "python",
Arguments: map[string]interface{}{
"arg1": "value1",
Arguments: &RunArguments{
Value: map[string]interface{}{
"arg1": "value1",
},
},
Environment: map[string]string{
"ENV_VAR": "value",
},
InlineCode: stringPtr("print('Hello, World!')"),
Input: "example-input",
},
},
}
Expand All @@ -146,13 +180,14 @@ func TestRunTaskScript_MarshalJSON(t *testing.T) {
"language": "python",
"arguments": {"arg1": "value1"},
"environment": {"ENV_VAR": "value"},
"code": "print('Hello, World!')"
"code": "print('Hello, World!')",
"stdin": "example-input"
}
}
}`, string(data))
}

func TestRunTaskScript_UnmarshalJSON(t *testing.T) {
func TestRunTaskScriptArgsMap_UnmarshalJSON(t *testing.T) {
jsonData := `{
"if": "${condition}",
"input": { "from": {"key": "value"} },
Expand All @@ -166,7 +201,97 @@ func TestRunTaskScript_UnmarshalJSON(t *testing.T) {
"language": "python",
"arguments": {"arg1": "value1"},
"environment": {"ENV_VAR": "value"},
"code": "print('Hello, World!')"
"code": "print('Hello, World!')",
"stdin": "example-input"
}
}
}`

var runTask RunTask
err := json.Unmarshal([]byte(jsonData), &runTask)
assert.NoError(t, err)
assert.Equal(t, &RuntimeExpression{Value: "${condition}"}, runTask.If)
assert.Equal(t, &Input{From: &ObjectOrRuntimeExpr{Value: map[string]interface{}{"key": "value"}}}, runTask.Input)
assert.Equal(t, &Output{As: &ObjectOrRuntimeExpr{Value: map[string]interface{}{"result": "output"}}}, runTask.Output)
assert.Equal(t, &TimeoutOrReference{Timeout: &Timeout{After: NewDurationExpr("10s")}}, runTask.Timeout)
assert.Equal(t, &FlowDirective{Value: "continue"}, runTask.Then)
assert.Equal(t, map[string]interface{}{"meta": "data"}, runTask.Metadata)
assert.Equal(t, true, *runTask.Run.Await)
assert.Equal(t, "python", runTask.Run.Script.Language)
assert.Equal(t, map[string]interface{}{"arg1": "value1"}, runTask.Run.Script.Arguments.AsMap())
assert.Equal(t, map[string]string{"ENV_VAR": "value"}, runTask.Run.Script.Environment)
assert.Equal(t, "print('Hello, World!')", *runTask.Run.Script.InlineCode)
assert.Equal(t, "example-input", runTask.Run.Script.Input)
}

func TestRunTaskScriptArgArray_MarshalJSON(t *testing.T) {
runTask := RunTask{
TaskBase: TaskBase{
If: &RuntimeExpression{Value: "${condition}"},
Input: &Input{From: &ObjectOrRuntimeExpr{Value: map[string]interface{}{"key": "value"}}},
Output: &Output{As: &ObjectOrRuntimeExpr{Value: map[string]interface{}{"result": "output"}}},
Timeout: &TimeoutOrReference{Timeout: &Timeout{After: NewDurationExpr("10s")}},
Then: &FlowDirective{Value: "continue"},
Metadata: map[string]interface{}{
"meta": "data",
},
},
Run: RunTaskConfiguration{
Await: boolPtr(true),
Script: &Script{
Language: "python",
Arguments: &RunArguments{
Value: []string{
"arg1=value1",
},
},
Environment: map[string]string{
"ENV_VAR": "value",
},
InlineCode: stringPtr("print('Hello, World!')"),
Input: "example-input",
},
},
}

data, err := json.Marshal(runTask)
assert.NoError(t, err)
assert.JSONEq(t, `{
"if": "${condition}",
"input": { "from": {"key": "value"} },
"output": { "as": {"result": "output"} },
"timeout": { "after": "10s" },
"then": "continue",
"metadata": {"meta": "data"},
"run": {
"await": true,
"script": {
"language": "python",
"arguments": ["arg1=value1"],
"environment": {"ENV_VAR": "value"},
"code": "print('Hello, World!')",
"stdin": "example-input"
}
}
}`, string(data))
}

func TestRunTaskScriptArgsArray_UnmarshalJSON(t *testing.T) {
jsonData := `{
"if": "${condition}",
"input": { "from": {"key": "value"} },
"output": { "as": {"result": "output"} },
"timeout": { "after": "10s" },
"then": "continue",
"metadata": {"meta": "data"},
"run": {
"await": true,
"script": {
"language": "python",
"arguments": ["arg1=value1"],
"environment": {"ENV_VAR": "value"},
"code": "print('Hello, World!')",
"stdin": "example-input"
}
}
}`
Expand All @@ -182,9 +307,10 @@ func TestRunTaskScript_UnmarshalJSON(t *testing.T) {
assert.Equal(t, map[string]interface{}{"meta": "data"}, runTask.Metadata)
assert.Equal(t, true, *runTask.Run.Await)
assert.Equal(t, "python", runTask.Run.Script.Language)
assert.Equal(t, map[string]interface{}{"arg1": "value1"}, runTask.Run.Script.Arguments)
assert.Equal(t, []string{"arg1=value1"}, runTask.Run.Script.Arguments.AsSlice())
assert.Equal(t, map[string]string{"ENV_VAR": "value"}, runTask.Run.Script.Environment)
assert.Equal(t, "print('Hello, World!')", *runTask.Run.Script.InlineCode)
assert.Equal(t, "example-input", runTask.Run.Script.Input)
}

func boolPtr(b bool) *bool {
Expand Down
Loading