Skip to content

Commit f710115

Browse files
authored
feat(api): implement cancelled and stopped function (#7493)
1 parent 435a171 commit f710115

3 files changed

Lines changed: 251 additions & 4 deletions

File tree

engine/api/v2_workflow_run.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,22 @@ func (api *API) postStopJobHandler() ([]service.RbacChecker, service.Handler) {
237237
defer tx.Rollback() // nolint
238238

239239
for i := range runJobs {
240-
runJobs[i].Status = sdk.V2WorkflowRunJobStatusStopped
240+
rj := &runJobs[i]
241+
if rj.Status.IsTerminated() {
242+
continue
243+
}
244+
rj.Status = sdk.V2WorkflowRunJobStatusStopped
241245
now := time.Now()
242-
runJobs[i].Ended = &now
246+
rj.Ended = &now
247+
248+
for k, ss := range rj.StepsStatus {
249+
if !ss.Conclusion.IsTerminated() {
250+
ss.Conclusion = sdk.V2WorkflowRunJobStatusStopped
251+
ss.Ended = time.Now()
252+
rj.StepsStatus[k] = ss
253+
}
254+
}
255+
243256
if err := workflow_v2.UpdateJobRun(ctx, tx, &runJobs[i]); err != nil {
244257
return err
245258
}

sdk/action_parser_funcs.go

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
"success": success,
3737
"always": always,
3838
"cancelled": cancelled,
39+
"stopped": stopped,
3940
"failure": failure,
4041
"result": result,
4142
"toLower": newStringActionFunc("toLower", nilerr(strings.ToLower)),
@@ -544,11 +545,70 @@ func always(_ context.Context, _ *ActionParser, inputs ...interface{}) (interfac
544545
return true, nil
545546
}
546547

547-
func cancelled(_ context.Context, _ *ActionParser, inputs ...interface{}) (interface{}, error) {
548+
func stopped(_ context.Context, a *ActionParser, inputs ...interface{}) (interface{}, error) {
549+
if len(inputs) > 0 {
550+
return nil, NewErrorFrom(ErrInvalidData, "stopped function must not have arguments")
551+
}
552+
// Check scope
553+
if stepContext, has := a.contexts["steps"]; has && stepContext != nil {
554+
var steps StepsContext
555+
stepContextBts, _ := json.Marshal(stepContext)
556+
if err := json.Unmarshal(stepContextBts, &steps); err != nil {
557+
return nil, NewErrorFrom(ErrInvalidData, "unable to read step context")
558+
}
559+
for _, v := range steps {
560+
if v.Conclusion == V2WorkflowRunJobStatusStopped {
561+
return true, nil
562+
}
563+
}
564+
return false, nil
565+
} else if needsContext, has := a.contexts["needs"]; has && needsContext != nil {
566+
var needs NeedsContext
567+
needsCtxBts, _ := json.Marshal(needsContext)
568+
if err := json.Unmarshal(needsCtxBts, &needs); err != nil {
569+
return nil, NewErrorFrom(ErrInvalidData, "unable to read step context")
570+
}
571+
for _, v := range needs {
572+
if v.Result == V2WorkflowRunJobStatusStopped {
573+
return true, nil
574+
}
575+
}
576+
return false, nil
577+
}
578+
return nil, NewErrorFrom(ErrInvalidData, "missing needs context")
579+
}
580+
581+
func cancelled(_ context.Context, a *ActionParser, inputs ...interface{}) (interface{}, error) {
548582
if len(inputs) > 0 {
549583
return nil, NewErrorFrom(ErrInvalidData, "cancelled function must not have arguments")
550584
}
551-
return nil, NewErrorFrom(ErrNotImplemented, "cancelled is not implemented yet")
585+
// Check scope
586+
if stepContext, has := a.contexts["steps"]; has && stepContext != nil {
587+
var steps StepsContext
588+
stepContextBts, _ := json.Marshal(stepContext)
589+
if err := json.Unmarshal(stepContextBts, &steps); err != nil {
590+
return nil, NewErrorFrom(ErrInvalidData, "unable to read step context")
591+
}
592+
for _, v := range steps {
593+
if v.Conclusion == V2WorkflowRunJobStatusCancelled {
594+
return true, nil
595+
}
596+
}
597+
return false, nil
598+
} else if needsContext, has := a.contexts["needs"]; has && needsContext != nil {
599+
var needs NeedsContext
600+
needsCtxBts, _ := json.Marshal(needsContext)
601+
if err := json.Unmarshal(needsCtxBts, &needs); err != nil {
602+
return nil, NewErrorFrom(ErrInvalidData, "unable to read step context")
603+
}
604+
for _, v := range needs {
605+
if v.Result == V2WorkflowRunJobStatusCancelled {
606+
return true, nil
607+
}
608+
}
609+
return false, nil
610+
}
611+
return nil, NewErrorFrom(ErrInvalidData, "missing needs context")
552612
}
553613

554614
func failure(_ context.Context, a *ActionParser, inputs ...interface{}) (interface{}, error) {

sdk/action_parser_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,180 @@ func TestParserFailure(t *testing.T) {
889889
}
890890
}
891891

892+
func TestParserCancelled(t *testing.T) {
893+
log.Factory = log.NewTestingWrapper(t)
894+
log.UnregisterField(log.FieldCaller, log.FieldSourceFile, log.FieldSourceLine)
895+
tests := []struct {
896+
name string
897+
context map[string]interface{}
898+
input string
899+
result bool
900+
containError string
901+
}{
902+
{
903+
name: "Cancelled Job - true",
904+
input: "${{ cancelled() }}",
905+
result: true,
906+
context: map[string]interface{}{
907+
"needs": map[string]interface{}{
908+
"job1": map[string]string{
909+
"result": string(V2WorkflowRunJobStatusCancelled),
910+
},
911+
"job2": map[string]string{
912+
"result": "Success",
913+
},
914+
},
915+
},
916+
},
917+
{
918+
name: "Cancelled Job - false",
919+
input: "${{ cancelled() }}",
920+
result: false,
921+
context: map[string]interface{}{
922+
"needs": map[string]interface{}{
923+
"job1": map[string]string{
924+
"result": "Success",
925+
},
926+
"job2": map[string]string{
927+
"result": "Success",
928+
},
929+
},
930+
},
931+
},
932+
{
933+
name: "Cancelled Step - true",
934+
input: "${{ cancelled() }}",
935+
result: true,
936+
context: map[string]interface{}{
937+
"steps": map[string]interface{}{
938+
"step1": map[string]string{
939+
"conclusion": string(V2WorkflowRunJobStatusCancelled),
940+
},
941+
"step2": map[string]string{
942+
"conclusion": "Success",
943+
},
944+
},
945+
},
946+
},
947+
{
948+
name: "Cancelled Step - false",
949+
input: "${{ cancelled() }}",
950+
result: false,
951+
context: map[string]interface{}{
952+
"steps": map[string]interface{}{
953+
"step1": map[string]string{
954+
"conclusion": "Success",
955+
},
956+
"step2": map[string]string{
957+
"conclusion": "Success",
958+
},
959+
},
960+
},
961+
},
962+
}
963+
964+
for _, tt := range tests {
965+
t.Run(tt.name, func(t *testing.T) {
966+
ap := NewActionParser(tt.context, DefaultFuncs)
967+
result, err := ap.parse(context.TODO(), tt.input)
968+
if tt.containError != "" {
969+
require.Error(t, err)
970+
require.Contains(t, err.Error(), tt.containError)
971+
} else {
972+
require.NoError(t, err)
973+
require.Equal(t, tt.result, result)
974+
}
975+
})
976+
}
977+
}
978+
979+
func TestParserStopped(t *testing.T) {
980+
log.Factory = log.NewTestingWrapper(t)
981+
log.UnregisterField(log.FieldCaller, log.FieldSourceFile, log.FieldSourceLine)
982+
tests := []struct {
983+
name string
984+
context map[string]interface{}
985+
input string
986+
result bool
987+
containError string
988+
}{
989+
{
990+
name: "Stopped Job - true",
991+
input: "${{ stopped() }}",
992+
result: true,
993+
context: map[string]interface{}{
994+
"needs": map[string]interface{}{
995+
"job1": map[string]string{
996+
"result": string(V2WorkflowRunJobStatusStopped),
997+
},
998+
"job2": map[string]string{
999+
"result": "Success",
1000+
},
1001+
},
1002+
},
1003+
},
1004+
{
1005+
name: "Stopped Job - false",
1006+
input: "${{ stopped() }}",
1007+
result: false,
1008+
context: map[string]interface{}{
1009+
"needs": map[string]interface{}{
1010+
"job1": map[string]string{
1011+
"result": "Success",
1012+
},
1013+
"job2": map[string]string{
1014+
"result": "Success",
1015+
},
1016+
},
1017+
},
1018+
},
1019+
{
1020+
name: "Stopped Step - true",
1021+
input: "${{ stopped() }}",
1022+
result: true,
1023+
context: map[string]interface{}{
1024+
"steps": map[string]interface{}{
1025+
"step1": map[string]string{
1026+
"conclusion": string(V2WorkflowRunJobStatusStopped),
1027+
},
1028+
"step2": map[string]string{
1029+
"conclusion": "Success",
1030+
},
1031+
},
1032+
},
1033+
},
1034+
{
1035+
name: "Stopped Step - false",
1036+
input: "${{ stopped() }}",
1037+
result: false,
1038+
context: map[string]interface{}{
1039+
"steps": map[string]interface{}{
1040+
"step1": map[string]string{
1041+
"conclusion": "Success",
1042+
},
1043+
"step2": map[string]string{
1044+
"conclusion": "Success",
1045+
},
1046+
},
1047+
},
1048+
},
1049+
}
1050+
1051+
for _, tt := range tests {
1052+
t.Run(tt.name, func(t *testing.T) {
1053+
ap := NewActionParser(tt.context, DefaultFuncs)
1054+
result, err := ap.parse(context.TODO(), tt.input)
1055+
if tt.containError != "" {
1056+
require.Error(t, err)
1057+
require.Contains(t, err.Error(), tt.containError)
1058+
} else {
1059+
require.NoError(t, err)
1060+
require.Equal(t, tt.result, result)
1061+
}
1062+
})
1063+
}
1064+
}
1065+
8921066
func TestParserFuncToJSON(t *testing.T) {
8931067
log.Factory = log.NewTestingWrapper(t)
8941068
log.UnregisterField(log.FieldCaller, log.FieldSourceFile, log.FieldSourceLine)

0 commit comments

Comments
 (0)