Skip to content

Commit 90e1f3d

Browse files
authored
Feature/181 asynchronous return execution (#183)
1 parent 9c05d07 commit 90e1f3d

File tree

5 files changed

+78
-17
lines changed

5 files changed

+78
-17
lines changed

internal/decomposer/decomposer.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type ExecutionDetails struct {
3434
}
3535

3636
type IDecomposer interface {
37+
ExecuteAsync(playbook cacao.Playbook, detailsch chan ExecutionDetails)
3738
Execute(playbook cacao.Playbook) (*ExecutionDetails, error)
3839
}
3940

@@ -69,11 +70,35 @@ type Decomposer struct {
6970
}
7071

7172
// Execute a Playbook
72-
func (decomposer *Decomposer) Execute(playbook cacao.Playbook) (*ExecutionDetails, error) {
73+
func (decomposer *Decomposer) ExecuteAsync(playbook cacao.Playbook, detailsch chan ExecutionDetails) {
7374
executionId := decomposer.guid.New()
7475
log.Debugf("Starting execution %s for Playbook %s", executionId, playbook.ID)
7576

77+
details := ExecutionDetails{executionId, playbook.ID, playbook.PlaybookVariables}
78+
decomposer.details = details
79+
80+
if detailsch != nil {
81+
detailsch <- details
82+
}
83+
84+
_ = decomposer.execute(playbook)
85+
86+
}
87+
88+
func (decomposer *Decomposer) Execute(playbook cacao.Playbook) (*ExecutionDetails, error) {
89+
90+
executionId := decomposer.guid.New()
91+
log.Debugf("Starting execution %s for Playbook %s", executionId, playbook.ID)
7692
decomposer.details = ExecutionDetails{executionId, playbook.ID, playbook.PlaybookVariables}
93+
94+
err := decomposer.execute(playbook)
95+
96+
return &decomposer.details, err
97+
98+
}
99+
100+
func (decomposer *Decomposer) execute(playbook cacao.Playbook) error {
101+
77102
decomposer.playbook = playbook
78103

79104
stepId := playbook.WorkflowStart
@@ -88,7 +113,8 @@ func (decomposer *Decomposer) Execute(playbook cacao.Playbook) (*ExecutionDetail
88113
decomposer.details.Variables = outputVariables
89114
// Reporting workflow end
90115
decomposer.reporter.ReportWorkflowEnd(decomposer.details.ExecutionId, playbook, err)
91-
return &decomposer.details, err
116+
117+
return err
92118
}
93119

94120
// Execute a Workflow branch of a Playbook

routes/trigger/trigger_api.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"io"
55
"net/http"
66
"reflect"
7+
"time"
78

89
"soarca/internal/controller/decomposer_controller"
10+
"soarca/internal/decomposer"
911
"soarca/logger"
1012
"soarca/models/decoder"
1113
"soarca/routes/error"
@@ -26,12 +28,15 @@ func init() {
2628
}
2729

2830
type TriggerApi struct {
29-
controller decomposer_controller.IController
31+
controller decomposer_controller.IController
32+
Executionsch chan decomposer.ExecutionDetails
3033
}
3134

3235
func New(controller decomposer_controller.IController) *TriggerApi {
3336
instance := TriggerApi{}
3437
instance.controller = controller
38+
// Channel to get back execution details
39+
instance.Executionsch = make(chan decomposer.ExecutionDetails)
3540
return &instance
3641
}
3742

@@ -54,17 +59,31 @@ func (trigger *TriggerApi) Execute(context *gin.Context) {
5459
"POST /trigger/playbook", "")
5560
return
5661
}
57-
executionDetail, errDecomposer := decomposer.Execute(*playbook)
58-
if errDecomposer != nil {
59-
error.SendErrorResponse(context, http.StatusBadRequest,
60-
"Failed to decode playbook",
61-
"POST /trigger/playbook",
62-
executionDetail.ExecutionId.String())
63-
} else {
64-
msg := gin.H{
65-
"execution_id": executionDetail.ExecutionId.String(),
66-
"payload": executionDetail.PlaybookId,
62+
63+
go decomposer.ExecuteAsync(*playbook, trigger.Executionsch)
64+
65+
// Hard coding the timer to return execution id
66+
timer := time.NewTimer(time.Duration(3) * time.Second)
67+
for {
68+
select {
69+
case <-timer.C:
70+
msg := gin.H{
71+
"execution_id": nil,
72+
"payload": playbook.ID,
73+
}
74+
context.JSON(http.StatusRequestTimeout, msg)
75+
log.Error("async execution timed out for playbook ", playbook.ID)
76+
case exec_details := <-trigger.Executionsch:
77+
playbook_id := exec_details.PlaybookId
78+
exec_id := exec_details.ExecutionId
79+
if playbook_id == playbook.ID {
80+
msg := gin.H{
81+
"execution_id": exec_id,
82+
"payload": playbook_id,
83+
}
84+
context.JSON(http.StatusOK, msg)
85+
return
86+
}
6787
}
68-
context.JSON(http.StatusOK, msg)
6988
}
7089
}

test/unittest/executor/playbook_action/playbook_action_executor_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ func TestExecutePlaybook(t *testing.T) {
7979
Variables: cacao.NewVariables(returnedVariables)}
8080

8181
playbook2 := cacao.Playbook{ID: playbookId, PlaybookVariables: cacao.NewVariables(expectedVariables)}
82+
8283
mockDecomposer.On("Execute", playbook2).Return(&details, nil)
8384

8485
results, err := executerObject.Execute(metadata, step, cacao.NewVariables(addedVariables))
8586

87+
mockDecomposer.AssertExpectations(t)
8688
mock_reporter.AssertExpectations(t)
8789
assert.Equal(t, err, nil)
8890
assert.Equal(t, results, cacao.NewVariables(returnedVariables))

test/unittest/mocks/mock_decomposer/mock_decomposer.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ import (
44
"soarca/internal/decomposer"
55
"soarca/models/cacao"
66

7+
"github.com/google/uuid"
78
"github.com/stretchr/testify/mock"
89
)
910

1011
type Mock_Decomposer struct {
1112
mock.Mock
1213
}
1314

15+
func (mock *Mock_Decomposer) ExecuteAsync(playbook cacao.Playbook, detailsch chan decomposer.ExecutionDetails) {
16+
args := mock.Called(playbook, detailsch)
17+
if detailsch != nil {
18+
details := decomposer.ExecutionDetails{ExecutionId: args.Get(2).(uuid.UUID), PlaybookId: playbook.ID, Variables: cacao.NewVariables()}
19+
detailsch <- details
20+
}
21+
}
1422
func (mock *Mock_Decomposer) Execute(playbook cacao.Playbook) (*decomposer.ExecutionDetails, error) {
1523
args := mock.Called(playbook)
1624
return args.Get(0).(*decomposer.ExecutionDetails), args.Error(1)

test/unittest/routes/trigger_api/tigger_api_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import (
1717

1818
"github.com/gin-gonic/gin"
1919
"github.com/go-playground/assert/v2"
20+
"github.com/google/uuid"
2021
)
2122

22-
func TestExecutionOfPlaybook(t *testing.T) {
23+
func TestTriggerExecutionOfPlaybook(t *testing.T) {
2324
jsonFile, err := os.Open("../playbook.json")
2425
if err != nil {
2526
fmt.Println(err)
@@ -34,17 +35,22 @@ func TestExecutionOfPlaybook(t *testing.T) {
3435
mock_controller := new(mock_decomposer_controller.Mock_Controller)
3536
mock_controller.On("NewDecomposer").Return(mock_decomposer)
3637
playbook := cacao.Decode(byteValue)
37-
mock_decomposer.On("Execute", *playbook).Return(&decomposer.ExecutionDetails{}, nil)
3838

39-
recorder := httptest.NewRecorder()
4039
trigger_api := trigger.New(mock_controller)
40+
recorder := httptest.NewRecorder()
4141
trigger.Routes(app, trigger_api)
4242

43+
executionId, _ := uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
44+
mock_decomposer.On("ExecuteAsync", *playbook, trigger_api.Executionsch).Return(&decomposer.ExecutionDetails{}, nil, executionId)
45+
4346
request, err := http.NewRequest("POST", "/trigger/playbook", bytes.NewBuffer(byteValue))
4447
if err != nil {
4548
t.Fail()
4649
}
50+
51+
expected_return_string := `{"execution_id":"6ba7b810-9dad-11d1-80b4-00c04fd430c8","payload":"playbook--61a6c41e-6efc-4516-a242-dfbc5c89d562"}`
4752
app.ServeHTTP(recorder, request)
53+
assert.Equal(t, expected_return_string, recorder.Body.String())
4854
assert.Equal(t, 200, recorder.Code)
4955
mock_decomposer.AssertExpectations(t)
5056
}

0 commit comments

Comments
 (0)