Skip to content

Commit 48c5d8e

Browse files
author
André R. de Miranda
authored
Programmatically build workflow (#190)
* Programmatically build workflow Signed-off-by: André R. de Miranda <[email protected]> * Add builder generated by builder-gen Signed-off-by: André R. de Miranda <[email protected]> * Bump version golang.org/x/net, and add file LICENSE Signed-off-by: André R. de Miranda <[email protected]> * Builder ApplyDefault Signed-off-by: André R. de Miranda <[email protected]> * Add builder suuport to map, exec validator, and improve tests Signed-off-by: André R. de Miranda <[email protected]> * Improve builder embedded, add method to remove element Signed-off-by: André R. de Miranda <[email protected]> * Improve return builder embedded Signed-off-by: André R. de Miranda <[email protected]> --------- Signed-off-by: André R. de Miranda <[email protected]>
1 parent d19b014 commit 48c5d8e

22 files changed

+3348
-20
lines changed

Makefile

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ lint:
1414

1515
.PHONY: test
1616
coverage="false"
17-
test: deepcopy
17+
test: deepcopy buildergen
1818
make lint
1919
@go test ./...
2020

21-
.PHONY: deepcopy
21+
.PHONY: deepcopy buildergen
2222
deepcopy: $(DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
2323
./hack/deepcopy-gen.sh deepcopy
2424

25+
buildergen: $(BUILDER_GEN) ## Download builder-gen locally if necessary.
26+
./hack/builder-gen.sh buildergen
27+
2528
.PHONY: kube-integration
2629
kube-integration: controller-gen
2730
$(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook paths="./..." output:crd:artifacts:config=config/crd/bases

builder/builder.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 The Serverless Workflow Specification Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package builder
16+
17+
import (
18+
"encoding/json"
19+
20+
"sigs.k8s.io/yaml"
21+
22+
"github.com/serverlessworkflow/sdk-go/v2/model"
23+
val "github.com/serverlessworkflow/sdk-go/v2/validator"
24+
)
25+
26+
func New() *model.WorkflowBuilder {
27+
return model.NewWorkflowBuilder()
28+
}
29+
30+
func Object(builder *model.WorkflowBuilder) (*model.Workflow, error) {
31+
workflow := builder.Build()
32+
ctx := model.NewValidatorContext(&workflow)
33+
if err := val.GetValidator().StructCtx(ctx, workflow); err != nil {
34+
return nil, err
35+
}
36+
return &workflow, nil
37+
}
38+
39+
func Json(builder *model.WorkflowBuilder) ([]byte, error) {
40+
workflow, err := Object(builder)
41+
if err != nil {
42+
return nil, err
43+
}
44+
return json.Marshal(workflow)
45+
}
46+
47+
func Yaml(builder *model.WorkflowBuilder) ([]byte, error) {
48+
data, err := Json(builder)
49+
if err != nil {
50+
return nil, err
51+
}
52+
return yaml.JSONToYAML(data)
53+
}

builder/builder_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2023 The Serverless Workflow Specification Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package builder
16+
17+
import (
18+
"testing"
19+
20+
"github.com/serverlessworkflow/sdk-go/v2/model"
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func prepareBuilder() *model.WorkflowBuilder {
25+
builder := New().Key("key test").ID("id test")
26+
27+
builder.AddFunctions().Name("function name").Operation("http://test")
28+
builder.AddFunctions().Name("function name2").Operation("http://test")
29+
30+
function3 := builder.AddFunctions().Name("function name2").Operation("http://test")
31+
builder.RemoveFunctions(function3)
32+
33+
state1 := builder.AddStates().
34+
Name("state").
35+
Type(model.StateTypeInject)
36+
state1.End().Terminate(true)
37+
38+
inject := state1.InjectState()
39+
inject.Data(map[string]model.Object{
40+
"test": model.FromMap(map[string]any{}),
41+
})
42+
43+
return builder
44+
}
45+
46+
func TestObject(t *testing.T) {
47+
workflow, err := Object(prepareBuilder())
48+
if assert.NoError(t, err) {
49+
assert.Equal(t, "key test", workflow.Key)
50+
assert.Equal(t, "id test", workflow.ID)
51+
assert.Equal(t, "0.8", workflow.SpecVersion)
52+
assert.Equal(t, "jq", workflow.ExpressionLang.String())
53+
assert.Equal(t, 2, len(workflow.Functions))
54+
55+
assert.Equal(t, "function name", workflow.Functions[0].Name)
56+
assert.Equal(t, "function name2", workflow.Functions[1].Name)
57+
}
58+
}
59+
60+
func TestJson(t *testing.T) {
61+
data, err := Json(prepareBuilder())
62+
if assert.NoError(t, err) {
63+
d := `{"id":"id test","key":"key test","version":"","specVersion":"0.8","expressionLang":"jq","states":[{"name":"state","type":"inject","end":{"terminate":true},"data":{"test":{}}}],"functions":[{"name":"function name","operation":"http://test","type":"rest"},{"name":"function name2","operation":"http://test","type":"rest"}]}`
64+
assert.Equal(t, d, string(data))
65+
}
66+
}
67+
68+
func TestYaml(t *testing.T) {
69+
data, err := Yaml(prepareBuilder())
70+
if assert.NoError(t, err) {
71+
d := `expressionLang: jq
72+
functions:
73+
- name: function name
74+
operation: http://test
75+
type: rest
76+
- name: function name2
77+
operation: http://test
78+
type: rest
79+
id: id test
80+
key: key test
81+
specVersion: "0.8"
82+
states:
83+
- data:
84+
test: {}
85+
end:
86+
terminate: true
87+
name: state
88+
type: inject
89+
version: ""
90+
`
91+
92+
assert.Equal(t, d, string(data))
93+
}
94+
}

go.mod

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ require (
2626
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2727
github.com/modern-go/reflect2 v1.0.2 // indirect
2828
github.com/pmezard/go-difflib v1.0.0 // indirect
29-
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
30-
golang.org/x/net v0.7.0 // indirect
31-
golang.org/x/sys v0.5.0 // indirect
32-
golang.org/x/text v0.7.0 // indirect
29+
golang.org/x/crypto v0.15.0 // indirect
30+
golang.org/x/net v0.18.0 // indirect
31+
golang.org/x/sys v0.14.0 // indirect
32+
golang.org/x/text v0.14.0 // indirect
3333
gopkg.in/inf.v0 v0.9.1 // indirect
3434
gopkg.in/yaml.v2 v2.4.0 // indirect
3535
k8s.io/klog/v2 v2.80.2-0.20221028030830-9ae4992afb54 // indirect

go.sum

+6-6
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
6969
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
7070
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
7171
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
72-
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
73-
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
72+
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
73+
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
7474
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
7575
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
7676
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -81,8 +81,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
8181
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
8282
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
8383
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
84-
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
85-
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
84+
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
85+
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
8686
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8787
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8888
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -96,8 +96,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
9696
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9797
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9898
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99-
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
100-
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99+
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
100+
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
101101
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
102102
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
103103
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=

hack/builder-gen.sh

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2022 The Serverless Workflow Specification Authors
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# retrieved from https://github.com/kubernetes/code-generator/blob/master/generate-internal-groups.sh
17+
# and adapted to only install and run the deepcopy-gen
18+
19+
set -o errexit
20+
set -o nounset
21+
set -o pipefail
22+
23+
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
24+
echo "Script root is $SCRIPT_ROOT"
25+
26+
GENS="$1"
27+
shift 1
28+
29+
(
30+
# To support running this script from anywhere, first cd into this directory,
31+
# and then install with forced module mode on and fully qualified name.
32+
# make sure your GOPATH env is properly set.
33+
# it will go under $GOPATH/bin
34+
cd "$(dirname "${0}")"
35+
GO111MODULE=on go install github.com/galgotech/builder-gen@latest
36+
)
37+
38+
function codegen::join() { local IFS="$1"; shift; echo "$*"; }
39+
40+
if [ "${GENS}" = "all" ] || grep -qw "buildergen" <<<"${GENS}"; then
41+
echo "Generating buildergen funcs"
42+
export GO111MODULE=on
43+
# for debug purposes, increase the log level by updating the -v flag to higher numbers, e.g. -v 4
44+
"${GOPATH}/bin/builder-gen" -v 1 \
45+
--input-dirs ./model -O zz_generated.buildergen \
46+
--go-header-file "${SCRIPT_ROOT}/hack/boilerplate.txt"
47+
"$@"
48+
fi

model/action.go

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package model
1717
import "github.com/serverlessworkflow/sdk-go/v2/util"
1818

1919
// Action specify invocations of services or other workflows during workflow execution.
20+
// +builder-gen:new-call=ApplyDefault
2021
type Action struct {
2122
// Defines Unique action identifier.
2223
// +optional
@@ -72,6 +73,7 @@ func (a *Action) ApplyDefault() {
7273
}
7374

7475
// FunctionRef defines the reference to a reusable function definition
76+
// +builder-gen:new-call=ApplyDefault
7577
type FunctionRef struct {
7678
// Name of the referenced function.
7779
// +kubebuilder:validation:Required

model/action_data_filter.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ import "github.com/serverlessworkflow/sdk-go/v2/util"
1818

1919
// ActionDataFilter used to filter action data results.
2020
// +optional
21-
// +optional
21+
// +builder-gen:new-call=ApplyDefault
2222
type ActionDataFilter struct {
2323
// Workflow expression that filters state data that can be used by the action.
2424
// +optional
25-
// +optional
2625
FromStateData string `json:"fromStateData,omitempty"`
2726
// If set to false, action data results are not added/merged to state data. In this case 'results'
2827
// and 'toStateData' should be ignored. Default is true.

model/event.go

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const (
3939
)
4040

4141
// Event used to define events and their correlations
42+
// +builder-gen:new-call=ApplyDefault
4243
type Event struct {
4344
Common `json:",inline"`
4445
// Unique event name.
@@ -56,6 +57,7 @@ type Event struct {
5657
Kind EventKind `json:"kind,omitempty" validate:"required,oneofkind"`
5758
// If `true`, only the Event payload is accessible to consuming Workflow states. If `false`, both event payload
5859
// and context attributes should be accessible. Defaults to true.
60+
// +kubebuilder:default=true
5961
// +optional
6062
DataOnly bool `json:"dataOnly,omitempty"`
6163
// Define event correlation rules for this event. Only used for consumed events.
@@ -88,6 +90,7 @@ type Correlation struct {
8890
}
8991

9092
// EventRef defining invocation of a function via event
93+
// +builder-gen:new-call=ApplyDefault
9194
type EventRef struct {
9295
// Reference to the unique name of a 'produced' event definition,
9396
// +kubebuilder:validation:Required

model/event_data_filter.go

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package model
1717
import "github.com/serverlessworkflow/sdk-go/v2/util"
1818

1919
// EventDataFilter used to filter consumed event payloads.
20+
// +builder-gen:new-call=ApplyDefault
2021
type EventDataFilter struct {
2122
// If set to false, event payload is not added/merged to state data. In this case 'data' and 'toStateData'
2223
// should be ignored. Default is true.

model/event_state.go

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
// EventState await one or more events and perform actions when they are received. If defined as the
2424
// workflow starting state, the event state definition controls when the workflow instances should be created.
25+
// +builder-gen:new-call=ApplyDefault
2526
type EventState struct {
2627
// TODO: EventState doesn't have usedForCompensation field.
2728

@@ -64,6 +65,7 @@ func (e *EventState) ApplyDefault() {
6465
}
6566

6667
// OnEvents define which actions are be performed for the one or more events.
68+
// +builder-gen:new-call=ApplyDefault
6769
type OnEvents struct {
6870
// References one or more unique event names in the defined workflow events.
6971
// +kubebuilder:validation:MinItems=1

model/foreach_state.go

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
)
4545

4646
// ForEachState used to execute actions for each element of a data set.
47+
// +builder-gen:new-call=ApplyDefault
4748
type ForEachState struct {
4849
// Workflow expression selecting an array element of the states' data.
4950
// +kubebuilder:validation:Required

model/function.go

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func (i FunctionType) String() string {
5959
}
6060

6161
// Function ...
62+
// +builder-gen:new-call=ApplyDefault
6263
type Function struct {
6364
Common `json:",inline"`
6465
// Unique function name

model/operation_state.go

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
)
2222

2323
// OperationState defines a set of actions to be performed in sequence or in parallel.
24+
// +builder-gen:new-call=ApplyDefault
2425
type OperationState struct {
2526
// Specifies whether actions are performed in sequence or in parallel, defaults to sequential.
2627
// +kubebuilder:validation:Enum=sequential;parallel

model/parallel_state.go

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
)
4646

4747
// ParallelState Consists of a number of states that are executed in parallel
48+
// +builder-gen:new-call=ApplyDefault
4849
type ParallelState struct {
4950
// List of branches for this parallel state.
5051
// +kubebuilder:validation:MinItems=1

model/retry.go

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
)
2323

2424
// Retry ...
25+
// +builder-gen:new-call=ApplyDefault
2526
type Retry struct {
2627
// Unique retry strategy name
2728
// +kubebuilder:validation:Required

model/states.go

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ func (b *BaseState) MarshalJSON() ([]byte, error) {
116116
return cus, err
117117
}
118118

119+
// +builder-gen:embedded-ignore-method=BaseState
119120
type State struct {
120121
BaseState `json:",inline"`
121122
// delayState Causes the workflow execution to delay for a specified duration.

0 commit comments

Comments
 (0)