Skip to content

Commit

Permalink
feat(api,ui): allow to send notification conditionally based on workf…
Browse files Browse the repository at this point in the history
…low variables

* feat(api,ui): allow to send notification conditionally based on workflow variables

close #4494

Signed-off-by: Benjamin Coenen <benjamin.coenen@corp.ovh.com>
  • Loading branch information
bnjjj authored and yesnault committed Aug 5, 2019
1 parent 6e505f6 commit 8e6f08a
Show file tree
Hide file tree
Showing 21 changed files with 383 additions and 205 deletions.
1 change: 1 addition & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func (api *API) InitRouter() {
r.Handle("/project/{key}/workflows/{permWorkflowName}/label", r.POST(api.postWorkflowLabelHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/label/{labelID}", r.DELETE(api.deleteWorkflowLabelHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/rollback/{auditID}", r.POST(api.postWorkflowRollbackHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/notifications/conditions", r.GET(api.getWorkflowNotificationsConditionsHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/groups", r.POST(api.postWorkflowGroupHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/groups/{groupName}", r.PUT(api.putWorkflowGroupHandler), r.DELETE(api.deleteWorkflowGroupHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/hooks/{uuid}", r.GET(api.getWorkflowHookHandler))
Expand Down
29 changes: 26 additions & 3 deletions engine/api/notification/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ovh/cds/sdk"
"github.com/ovh/cds/sdk/interpolate"
"github.com/ovh/cds/sdk/log"
"github.com/ovh/cds/sdk/luascript"
)

var (
Expand Down Expand Up @@ -141,20 +142,42 @@ func ShouldSendUserWorkflowNotification(notif sdk.WorkflowNotification, nodeRun

switch nodeRun.Status {
case sdk.StatusSuccess.String():
if check(notif.Settings.OnSuccess) {
if check(notif.Settings.OnSuccess) && checkConditions(notif.Settings.Conditions, nodeRun.BuildParameters) {
return true
}
case sdk.StatusFail.String():
if check(notif.Settings.OnFailure) {
if check(notif.Settings.OnFailure) && checkConditions(notif.Settings.Conditions, nodeRun.BuildParameters) {
return true
}
case sdk.StatusWaiting.String():
return notif.Settings.OnStart != nil && *notif.Settings.OnStart
return notif.Settings.OnStart != nil && *notif.Settings.OnStart && checkConditions(notif.Settings.Conditions, nodeRun.BuildParameters)
}

return false
}

func checkConditions(conditions sdk.WorkflowNodeConditions, params []sdk.Parameter) bool {
var conditionsOK bool
var errc error
if conditions.LuaScript == "" {
conditionsOK, errc = sdk.WorkflowCheckConditions(conditions.PlainConditions, params)
} else {
luacheck, err := luascript.NewCheck()
if err != nil {
log.Error("notification check condition error: %s", err)
return false
}
luacheck.SetVariables(sdk.ParametersToMap(params))
errc = luacheck.Perform(conditions.LuaScript)
conditionsOK = luacheck.Result
}
if errc != nil {
log.Error("notification check condition error on execution: %s", errc)
return false
}
return conditionsOK
}

func getWorkflowEvent(notif *sdk.UserNotificationSettings, params map[string]string) (sdk.EventNotif, error) {
subject, err := interpolate.Do(notif.Template.Subject, params)
if err != nil {
Expand Down
36 changes: 3 additions & 33 deletions engine/api/stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,18 +309,8 @@ func (api *API) getStageConditionsHandler() service.Handler {
Operators map[string]string `json:"operators"`
ConditionNames []string `json:"names"`
}{
Operators: sdk.WorkflowConditionsOperators,
ConditionNames: []string{
"git.hash",
"git.hash.short",
"git.branch",
"git.tag",
"git.author",
"git.repository",
"git.url",
"git.http_url",
"git.server",
},
Operators: sdk.WorkflowConditionsOperators,
ConditionNames: append(sdk.BasicGitVariableNames, "git.tag"),
}

// Check if pipeline exist
Expand All @@ -339,27 +329,7 @@ func (api *API) getStageConditionsHandler() service.Handler {
}

// add cds variable
data.ConditionNames = append(data.ConditionNames,
"cds.version",
"cds.application",
"cds.environment",
"cds.job",
"cds.manual",
"cds.pipeline",
"cds.project",
"cds.run",
"cds.run.number",
"cds.run.subnumber",
"cds.stage",
"cds.triggered_by.email",
"cds.triggered_by.fullname",
"cds.triggered_by.username",
"cds.ui.pipeline.run",
"cds.worker",
"cds.workflow",
"cds.workspace",
"payload",
)
data.ConditionNames = append(data.ConditionNames, sdk.BasicVariableNames...)

return service.WriteJSON(w, data, http.StatusOK)
}
Expand Down
39 changes: 5 additions & 34 deletions engine/api/suggest.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,44 +107,15 @@ func (api *API) getVariablesHandler() service.Handler {
}

// add cds variable
cdsVar := []string{
"{{.cds.version}}",
"{{.cds.application}}",
"{{.cds.environment}}",
"{{.cds.job}}",
"{{.cds.manual}}",
"{{.cds.node}}",
"{{.cds.node.id}}",
"{{.cds.pipeline}}",
"{{.cds.project}}",
"{{.cds.run}}",
"{{.cds.run.number}}",
"{{.cds.run.subnumber}}",
"{{.cds.stage}}",
"{{.cds.triggered_by.email}}",
"{{.cds.triggered_by.fullname}}",
"{{.cds.triggered_by.username}}",
"{{.cds.ui.pipeline.run}}",
"{{.cds.worker}}",
"{{.cds.workflow}}",
"{{.cds.workspace}}",
"{{.payload}}",
for i := range sdk.BasicVariableNames {
allVariables = append(allVariables, "{{."+sdk.BasicVariableNames[i]+"}}")
}
allVariables = append(allVariables, cdsVar...)

// add git variable
gitVar := []string{
"{{.git.hash}}",
"{{.git.hash.short}}",
"{{.git.branch}}",
"{{.git.tag}}",
"{{.git.author}}",
"{{.git.repository}}",
"{{.git.url}}",
"{{.git.http_url}}",
"{{.git.server}}",
for i := range sdk.BasicGitVariableNames {
allVariables = append(allVariables, "{{."+sdk.BasicGitVariableNames[i]+"}}")
}
allVariables = append(allVariables, gitVar...)
allVariables = append(allVariables, "{{.git.tag}}")

// Check permission on application
return service.WriteJSON(w, allVariables, http.StatusOK)
Expand Down
54 changes: 54 additions & 0 deletions engine/api/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -618,3 +619,56 @@ func (api *API) getWorkflowHookHandler() service.Handler {
return service.WriteJSON(w, task, http.StatusOK)
}
}

func (api *API) getWorkflowNotificationsConditionsHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
key := vars["key"]
name := vars["permWorkflowName"]

data := struct {
Operators map[string]string `json:"operators"`
ConditionNames []string `json:"names"`
}{
Operators: sdk.WorkflowConditionsOperators,
}

wr, errr := workflow.LoadLastRun(api.mustDB(), key, name, workflow.LoadRunOptions{})
if errr != nil {
if !sdk.ErrorIs(errr, sdk.ErrWorkflowNotFound) {
return sdk.WrapError(errr, "getWorkflowTriggerConditionHandler> Unable to load last run workflow")
}
}

params := []sdk.Parameter{}
var refNode *sdk.Node
if wr != nil {
refNode = &wr.Workflow.WorkflowData.Node
var errp error
params, errp = workflow.NodeBuildParametersFromRun(*wr, refNode.ID)
if errp != nil {
return sdk.WrapError(errp, "getWorkflowTriggerConditionHandler> Unable to load build parameters from workflow run")
}

if len(params) == 0 {
refNode = nil
}
} else {
data.ConditionNames = append(data.ConditionNames, sdk.BasicVariableNames...)
}

if sdk.ParameterFind(&params, "git.repository") == nil {
data.ConditionNames = append(data.ConditionNames, sdk.BasicGitVariableNames...)
}
if sdk.ParameterFind(&params, "git.tag") == nil {
data.ConditionNames = append(data.ConditionNames, "git.tag")
}

for _, p := range params {
data.ConditionNames = append(data.ConditionNames, p.Name)
}

sort.Strings(data.ConditionNames)
return service.WriteJSON(w, data, http.StatusOK)
}
}
129 changes: 129 additions & 0 deletions engine/api/workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"gopkg.in/yaml.v2"

"github.com/ovh/cds/engine/api/application"
"github.com/ovh/cds/engine/api/bootstrap"
"github.com/ovh/cds/engine/api/group"
"github.com/ovh/cds/engine/api/integration"
"github.com/ovh/cds/engine/api/pipeline"
Expand Down Expand Up @@ -48,6 +49,134 @@ func Test_getWorkflowsHandler(t *testing.T) {
assert.Equal(t, 200, w.Code)
}

func Test_getWorkflowNotificationsConditionsHandler(t *testing.T) {
// Init database
api, db, router, end := newTestAPI(t, bootstrap.InitiliazeDB)
defer end()
u, pass := assets.InsertAdminUser(db)
key := sdk.RandomString(10)
proj := assets.InsertTestProject(t, db, api.Cache, key, key, u)

//First pipeline
pip := sdk.Pipeline{
ProjectID: proj.ID,
ProjectKey: proj.Key,
Name: "pip1",
}
test.NoError(t, pipeline.InsertPipeline(db, api.Cache, proj, &pip, u))

s := sdk.NewStage("stage 1")
s.Enabled = true
s.PipelineID = pip.ID
test.NoError(t, pipeline.InsertStage(db, s))
j := &sdk.Job{
Enabled: true,
Action: sdk.Action{
Enabled: true,
},
}
test.NoError(t, pipeline.InsertJob(db, j, s.ID, &pip))
s.Jobs = append(s.Jobs, *j)

pip.Stages = append(pip.Stages, *s)

//Second pipeline
pip2 := sdk.Pipeline{
ProjectID: proj.ID,
ProjectKey: proj.Key,
Name: "pip2",
}
test.NoError(t, pipeline.InsertPipeline(db, api.Cache, proj, &pip2, u))
s = sdk.NewStage("stage 1")
s.Enabled = true
s.PipelineID = pip2.ID
test.NoError(t, pipeline.InsertStage(db, s))
j = &sdk.Job{
Enabled: true,
Action: sdk.Action{
Enabled: true,
},
}
test.NoError(t, pipeline.InsertJob(db, j, s.ID, &pip2))
s.Jobs = append(s.Jobs, *j)

w := sdk.Workflow{
Name: "test_1",
ProjectID: proj.ID,
ProjectKey: proj.Key,
WorkflowData: &sdk.WorkflowData{
Node: sdk.Node{
Name: "root",
Type: sdk.NodeTypePipeline,
Context: &sdk.NodeContext{
PipelineID: pip.ID,
},
Triggers: []sdk.NodeTrigger{
{
ChildNode: sdk.Node{
Name: "child",
Type: sdk.NodeTypePipeline,
Context: &sdk.NodeContext{
PipelineID: pip.ID,
},
},
},
},
},
},
}

proj2, errP := project.Load(api.mustDB(), api.Cache, proj.Key, u, project.LoadOptions.WithPipelines, project.LoadOptions.WithGroups, project.LoadOptions.WithIntegrations)
test.NoError(t, errP)

test.NoError(t, workflow.Insert(db, api.Cache, &w, proj2, u))
w1, err := workflow.Load(context.TODO(), db, api.Cache, proj, "test_1", u, workflow.LoadOptions{})
test.NoError(t, err)

wrCreate, err := workflow.CreateRun(db, w1, nil, u)
assert.NoError(t, err)
wrCreate.Workflow = *w1
_, errMR := workflow.StartWorkflowRun(context.TODO(), db, api.Cache, proj, wrCreate, &sdk.WorkflowRunPostHandlerOption{
Manual: &sdk.WorkflowNodeRunManual{
User: *u,
},
}, u, nil)
if errMR != nil {
test.NoError(t, errMR)
}
//Prepare request
vars := map[string]string{
"key": proj.Key,
"permWorkflowName": w1.Name,
}
uri := router.GetRoute("GET", api.getWorkflowNotificationsConditionsHandler, vars)
test.NotEmpty(t, uri)
req := assets.NewAuthentifiedRequest(t, u, pass, "GET", uri, vars)

//Do the request
writer := httptest.NewRecorder()
router.Mux.ServeHTTP(writer, req)
assert.Equal(t, 200, writer.Code)

data := struct {
Operators map[string]string `json:"operators"`
ConditionNames []string `json:"names"`
}{}
test.NoError(t, json.Unmarshal(writer.Body.Bytes(), &data))

found := false
for _, conditionName := range data.ConditionNames {
if conditionName == "cds.ui.pipeline.run" {
found = true
break
}
}

if !found {
t.Errorf("cannot find cds.ui.pipeline.run variable in response : %+v", data)
}
}

func Test_getWorkflowHandler(t *testing.T) {
// Init database
api, db, router, end := newTestAPI(t)
Expand Down
7 changes: 1 addition & 6 deletions engine/api/workflow_trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ func (api *API) getWorkflowTriggerConditionHandler() service.Handler {
}

if sdk.ParameterFind(&params, "git.repository") == nil {
data.ConditionNames = append(data.ConditionNames, "git.repository")
data.ConditionNames = append(data.ConditionNames, "git.branch")
data.ConditionNames = append(data.ConditionNames, "git.message")
data.ConditionNames = append(data.ConditionNames, "git.author")
data.ConditionNames = append(data.ConditionNames, "git.hash")
data.ConditionNames = append(data.ConditionNames, "git.hash.short")
data.ConditionNames = append(data.ConditionNames, sdk.BasicGitVariableNames...)
}
if sdk.ParameterFind(&params, "git.tag") == nil {
data.ConditionNames = append(data.ConditionNames, "git.tag")
Expand Down
1 change: 1 addition & 0 deletions sdk/notif.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type UserNotificationSettings struct {
SendToAuthor *bool `json:"send_to_author,omitempty" yaml:"send_to_author,omitempty"` // default is true, nil is true
Recipients []string `json:"recipients,omitempty" yaml:"recipients,omitempty"`
Template *UserNotificationTemplate `json:"template,omitempty" yaml:"template,omitempty"`
Conditions WorkflowNodeConditions `json:"conditions,omitempty" yaml:"conditions,omitempty"`
}

// UserNotificationTemplate is the notification content
Expand Down
Loading

0 comments on commit 8e6f08a

Please sign in to comment.