diff --git a/Makefile b/Makefile index 198f279ede..7b4e623569 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,9 @@ UI_DIST = ui/ui.tar.gz FILES = dist/FILES TARGET_DIR := dist/ -ALL_DIST = $(ENGINE_DIST) -ALL_DIST := $(ALL_DIST) $(WORKER_DIST) -ALL_DIST := $(ALL_DIST) $(CLI_DIST) +ALL_DIST = $(ENGINE_DIST) +ALL_DIST := $(ALL_DIST) $(WORKER_DIST) +ALL_DIST := $(ALL_DIST) $(CLI_DIST) ALL_DIST := $(ALL_DIST) $(UI_DIST) ALL_DIST := $(ALL_DIST) $(CONTRIB_DIST) ALL_TARGETS := $(foreach DIST,$(ALL_DIST),$(addprefix $(TARGET_DIR),$(notdir $(DIST)))) @@ -49,7 +49,7 @@ goinstall: build: $(info Building CDS Components for $(TARGET_OS) - $(TARGET_ARCH)) - $(MAKE) build_ui + $(MAKE) build_ui -j1 $(MAKE) build_engine -j4 $(MAKE) build_worker -j4 $(MAKE) build_cli -j4 @@ -85,7 +85,7 @@ dist: $(ALL_TARGETS) rm -f $(FILES) cd dist/ && for i in `ls -p | grep -v /|grep -v FILES`; do echo "$$i;`${SHA512} $$i|cut -d ' ' -f1`" >> FILES; done; -clean: +clean: @rm -rf target @rm -rf dist $(MAKE) clean -C engine @@ -95,7 +95,7 @@ clean: $(MAKE) clean -C contrib deb: dist target/cds-engine.deb - + $(TARGET_DIR)/config.toml.sample: $(TARGET_DIR)/cds-engine-linux-amd64 config new > $(TARGET_DIR)/config.toml.sample @@ -109,4 +109,4 @@ tar: target/cds-engine.tar.gz target/cds-engine.tar.gz: $(TARGET_DIR)/config.toml.sample $(TARGET_DIR)/tmpl-config mkdir -p target - tar -czvf target/cds-engine.tar.gz -C $(TARGET_DIR) . \ No newline at end of file + tar -czvf target/cds-engine.tar.gz -C $(TARGET_DIR) . diff --git a/engine/api/application/application_importer.go b/engine/api/application/application_importer.go index 8a5c21fe4c..ae2a80b1cc 100644 --- a/engine/api/application/application_importer.go +++ b/engine/api/application/application_importer.go @@ -2,7 +2,6 @@ package application import ( "context" - "fmt" "strings" "github.com/go-gorp/gorp" @@ -83,7 +82,7 @@ func Import(ctx context.Context, db gorp.SqlExecutor, proj sdk.Project, app *sdk for _, k := range app.Keys { k.ApplicationID = app.ID if err := InsertKey(db, &k); err != nil { - return sdk.WrapError(err, "Unable to insert key %s", k.Name) + return sdk.WrapError(err, "unable to insert key %s", k.Name) } if msgChan != nil { msgChan <- sdk.NewMessage(sdk.MsgAppKeyCreated, strings.ToUpper(k.Type.String()), k.Name, app.Name) @@ -92,13 +91,13 @@ func Import(ctx context.Context, db gorp.SqlExecutor, proj sdk.Project, app *sdk //Set deployment strategies if err := DeleteAllDeploymentStrategies(db, app.ID); err != nil { - return sdk.WrapError(err, "Unable to delete deployment strategies") + return sdk.WrapError(err, "unable to delete deployment strategies") } for pfName, pfConfig := range app.DeploymentStrategies { pf, has := proj.GetIntegration(pfName) if !has { - return sdk.WrapError(sdk.NewError(sdk.ErrNotFound, fmt.Errorf("integration %s not found", pfName)), "application.Import") + return sdk.NewErrorFrom(sdk.ErrNotFound, "integration %s not found", pfName) } if err := SetDeploymentStrategy(db, proj.ID, app.ID, pf.IntegrationModelID, pfName, pfConfig); err != nil { return sdk.WrapError(err, "unable to set deployment strategy %s", pfName) diff --git a/engine/api/application/application_parser.go b/engine/api/application/application_parser.go index 2cd2cee1f7..31c0122903 100644 --- a/engine/api/application/application_parser.go +++ b/engine/api/application/application_parser.go @@ -48,7 +48,7 @@ func ParseAndImport(ctx context.Context, db gorp.SqlExecutor, cache cache.Store, } if oldApp != nil && oldApp.FromRepository != "" && opts.FromRepository != oldApp.FromRepository { - return nil, msgList, sdk.WrapError(sdk.ErrApplicationAsCodeOverride, "unable to update as code application %s/%s.", oldApp.FromRepository, opts.FromRepository) + return nil, msgList, sdk.NewErrorFrom(sdk.ErrApplicationAsCodeOverride, "unable to update existing ascode application from %s", oldApp.FromRepository) } //Craft the application diff --git a/engine/api/application/dao_variable.go b/engine/api/application/dao_variable.go index b648426915..65b13dc9a5 100644 --- a/engine/api/application/dao_variable.go +++ b/engine/api/application/dao_variable.go @@ -2,7 +2,6 @@ package application import ( "context" - "fmt" "strings" "time" @@ -162,17 +161,16 @@ func InsertVariable(db gorp.SqlExecutor, appID int64, v *sdk.Variable, u sdk.Ide //Check variable name rx := sdk.NamePatternRegex if !rx.MatchString(v.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid variable name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "variable name should match pattern %s", sdk.NamePattern) } dbVar := newDBApplicationVariable(*v, appID) err := gorpmapping.InsertAndSign(context.Background(), db, &dbVar) if err != nil && strings.Contains(err.Error(), "application_variable_pkey") { return sdk.WithStack(sdk.ErrVariableExists) - } if err != nil { - return sdk.WrapError(err, "Cannot insert variable %s", v.Name) + return sdk.WrapError(err, "cannot insert variable %s", v.Name) } *v = dbVar.Variable() @@ -196,7 +194,7 @@ func InsertVariable(db gorp.SqlExecutor, appID int64, v *sdk.Variable, u sdk.Ide func UpdateVariable(db gorp.SqlExecutor, appID int64, variable *sdk.Variable, variableBefore *sdk.Variable, u sdk.Identifiable) error { rx := sdk.NamePatternRegex if !rx.MatchString(variable.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid variable name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "variable name should match pattern %s", sdk.NamePattern) } dbVar := newDBApplicationVariable(*variable, appID) diff --git a/engine/api/environment/dao_variable.go b/engine/api/environment/dao_variable.go index 878f975f54..9bc9bbf619 100644 --- a/engine/api/environment/dao_variable.go +++ b/engine/api/environment/dao_variable.go @@ -2,7 +2,6 @@ package environment import ( "context" - "fmt" "time" "github.com/go-gorp/gorp" @@ -108,16 +107,16 @@ func InsertVariable(db gorp.SqlExecutor, envID int64, v *sdk.Variable, u sdk.Ide //Check variable name rx := sdk.NamePatternRegex if !rx.MatchString(v.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid variable name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "variable name should match %s", sdk.NamePattern) } if sdk.NeedPlaceholder(v.Type) && v.Value == sdk.PasswordPlaceholder { - return fmt.Errorf("You try to insert a placeholder for new variable %s", v.Name) + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "you try to insert a placeholder for new variable %s", v.Name) } dbVar := newdbEnvironmentVariable(*v, envID) if err := gorpmapping.InsertAndSign(context.Background(), db, &dbVar); err != nil { - return sdk.WrapError(err, "Cannot insert variable %s", v.Name) + return sdk.WrapError(err, "cannot insert variable %s", v.Name) } *v = dbVar.Variable() @@ -132,7 +131,7 @@ func InsertVariable(db gorp.SqlExecutor, envID int64, v *sdk.Variable, u sdk.Ide } if err := insertAudit(db, ava); err != nil { - return sdk.WrapError(err, "Cannot insert audit for variable %d", v.ID) + return sdk.WrapError(err, "cannot insert audit for variable %d", v.ID) } return nil } @@ -141,7 +140,7 @@ func InsertVariable(db gorp.SqlExecutor, envID int64, v *sdk.Variable, u sdk.Ide func UpdateVariable(db gorp.SqlExecutor, envID int64, variable *sdk.Variable, variableBefore *sdk.Variable, u sdk.Identifiable) error { rx := sdk.NamePatternRegex if !rx.MatchString(variable.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid variable name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "variable name should match %s", sdk.NamePattern) } dbVar := newdbEnvironmentVariable(*variable, envID) @@ -167,7 +166,7 @@ func UpdateVariable(db gorp.SqlExecutor, envID int64, variable *sdk.Variable, va } if err := insertAudit(db, ava); err != nil { - return sdk.WrapError(err, "Cannot insert audit for variable %s", variable.Name) + return sdk.WrapError(err, "cannot insert audit for variable %s", variable.Name) } return nil diff --git a/engine/api/environment/environment.go b/engine/api/environment/environment.go index f2a1b9c040..e295d74aff 100644 --- a/engine/api/environment/environment.go +++ b/engine/api/environment/environment.go @@ -197,13 +197,13 @@ func CheckDefaultEnv(db gorp.SqlExecutor) error { if err1 := db.QueryRow(query, sdk.DefaultEnv.Name).Scan(&env.ID); err1 != nil { return err1 } else if env.ID != sdk.DefaultEnv.ID { - return fmt.Errorf("CheckDefaultEnv> default env created, but with wrong id. Please check db") + return sdk.WithStack(fmt.Errorf("default env created but with wrong id, please check db")) } return nil } return err } else if env.ID != sdk.DefaultEnv.ID || env.Name != sdk.DefaultEnv.Name { - return fmt.Errorf("CheckDefaultEnv> default env exists, but with wrong id or name. Please check db") + return sdk.WithStack(fmt.Errorf("default env exists but with wrong id or name, please check db")) } return nil } @@ -231,7 +231,7 @@ func InsertEnvironment(db gorp.SqlExecutor, env *sdk.Environment) error { rx := sdk.NamePatternRegex if !rx.MatchString(env.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid environment name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "environment name should match pattern %s", sdk.NamePattern) } err := db.QueryRow(query, env.Name, env.ProjectID, env.FromRepository).Scan(&env.ID, &env.Created, &env.LastModified) diff --git a/engine/api/environment/environment_exporter.go b/engine/api/environment/environment_exporter.go index 84dd44780d..d0b9ebf231 100644 --- a/engine/api/environment/environment_exporter.go +++ b/engine/api/environment/environment_exporter.go @@ -41,7 +41,7 @@ func ExportEnvironment(db gorp.SqlExecutor, env sdk.Environment, encryptFunc sdk for _, v := range env.Variables { switch v.Type { case sdk.KeyVariable: - return exportentities.Environment{}, sdk.WrapError(fmt.Errorf("Unsupported variable %s", v.Name), "environment.Export> Unable to export application") + return exportentities.Environment{}, sdk.NewErrorFrom(sdk.ErrWrongRequest, "unsupported variable %s", v.Name) case sdk.SecretVariable: content, err := encryptFunc(db, env.ProjectID, fmt.Sprintf("envID:%d:%s", env.ID, v.Name), v.Value) if err != nil { diff --git a/engine/api/environment/environment_parser.go b/engine/api/environment/environment_parser.go index 74dfd9be89..86a4150869 100644 --- a/engine/api/environment/environment_parser.go +++ b/engine/api/environment/environment_parser.go @@ -45,7 +45,7 @@ func ParseAndImport(db gorp.SqlExecutor, proj sdk.Project, eenv exportentities.E } if oldEnv != nil && oldEnv.FromRepository != "" && opts.FromRepository != oldEnv.FromRepository { - return nil, nil, sdk.WrapError(sdk.ErrEnvironmentAsCodeOverride, "unable to update as code environment %s/%s.", oldEnv.FromRepository, opts.FromRepository) + return nil, nil, sdk.NewErrorFrom(sdk.ErrEnvironmentAsCodeOverride, "unable to update existing ascode environment from %s", oldEnv.FromRepository) } env := new(sdk.Environment) diff --git a/engine/api/pipeline.go b/engine/api/pipeline.go index 0032963a06..14dd56c48b 100644 --- a/engine/api/pipeline.go +++ b/engine/api/pipeline.go @@ -31,9 +31,13 @@ func (api *API) updateAsCodePipelineHandler() service.Handler { branch := FormString(r, "branch") message := FormString(r, "message") + if branch == "" || message == "" { + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "missing branch or message data") + } + var p sdk.Pipeline if err := service.UnmarshalBody(r, &p); err != nil { - return sdk.WrapError(err, "Cannot read body") + return err } // check pipeline name pattern diff --git a/engine/api/pipeline/pipeline.go b/engine/api/pipeline/pipeline.go index 0800de3b00..8a0115c920 100644 --- a/engine/api/pipeline/pipeline.go +++ b/engine/api/pipeline/pipeline.go @@ -335,7 +335,7 @@ func UpdatePipeline(db gorp.SqlExecutor, p *sdk.Pipeline) error { p.LastModified = now.Unix() rx := sdk.NamePatternRegex if !rx.MatchString(p.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid pipeline name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "pipeline name should match %s", sdk.NamePattern) } //Update pipeline @@ -350,7 +350,7 @@ func InsertPipeline(db gorp.SqlExecutor, p *sdk.Pipeline) error { rx := sdk.NamePatternRegex if !rx.MatchString(p.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("invalid pipeline name, should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "pipeline name should match %s", sdk.NamePattern) } if p.ProjectID == 0 { diff --git a/engine/api/pipeline/pipeline_importer.go b/engine/api/pipeline/pipeline_importer.go index 4d75bda547..6928d51375 100644 --- a/engine/api/pipeline/pipeline_importer.go +++ b/engine/api/pipeline/pipeline_importer.go @@ -2,7 +2,6 @@ package pipeline import ( "context" - "fmt" "time" "github.com/go-gorp/gorp" @@ -58,7 +57,7 @@ func ImportUpdate(ctx context.Context, db gorp.SqlExecutor, proj sdk.Project, pi s := &pip.Stages[i] // stage name mandatory if there are many stages if len(pip.Stages) > 1 && !rx.MatchString(s.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid stage name '%s'. It should match %s", s.Name, sdk.NamePatternSpace)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "stage name '%s' should match pattern %s", s.Name, sdk.NamePatternSpace) } //Insert stage diff --git a/engine/api/pipeline/pipeline_parameter.go b/engine/api/pipeline/pipeline_parameter.go index b43462db7b..5c2a59754a 100644 --- a/engine/api/pipeline/pipeline_parameter.go +++ b/engine/api/pipeline/pipeline_parameter.go @@ -2,7 +2,6 @@ package pipeline import ( "context" - "fmt" "github.com/go-gorp/gorp" "github.com/lib/pq" @@ -58,12 +57,12 @@ func GetAllParametersInPipeline(ctx context.Context, db gorp.SqlExecutor, pipeli // InsertParameterInPipeline Insert a new parameter in the given pipeline func InsertParameterInPipeline(db gorp.SqlExecutor, pipelineID int64, param *sdk.Parameter) error { if param.Type == "" { - return sdk.NewError(sdk.ErrWrongRequest, fmt.Errorf("Invalid parameter, wrong type")) + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid parameter, wrong type") } rx := sdk.NamePatternRegex if !rx.MatchString(param.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid parameter name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "parameter name should match %s", sdk.NamePattern) } if string(param.Type) == string(sdk.SecretVariable) { @@ -83,22 +82,21 @@ func InsertParameterInPipeline(db gorp.SqlExecutor, pipelineID int64, param *sdk // UpdateParameterInPipeline Update a parameter in the given pipeline func UpdateParameterInPipeline(db gorp.SqlExecutor, pipelineID int64, oldParamName string, param sdk.Parameter) error { if param.Type == "" { - return sdk.NewError(sdk.ErrWrongRequest, fmt.Errorf("Invalid parameter, wrong type")) + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid parameter, wrong type") } rx := sdk.NamePatternRegex if !rx.MatchString(param.Name) { - return sdk.NewError(sdk.ErrInvalidName, fmt.Errorf("Invalid parameter name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrInvalidName, "parameter name should match pattern %s", sdk.NamePattern) } // update parameter query := `UPDATE pipeline_parameter SET value=$1, type=$2, description=$3, name=$4 WHERE pipeline_id=$5 AND name=$6` - _, err := db.Exec(query, param.Value, string(param.Type), param.Description, param.Name, pipelineID, oldParamName) - if err != nil { + if _, err := db.Exec(query, param.Value, string(param.Type), param.Description, param.Name, pipelineID, oldParamName); err != nil { if errPG, ok := err.(*pq.Error); ok && errPG.Code == gorpmapping.ViolateUniqueKeyPGCode { - return sdk.WithStack(sdk.ErrParameterExists) + return sdk.NewErrorWithStack(err, sdk.ErrParameterExists) } - return err + return sdk.WithStack(err) } return nil @@ -109,7 +107,7 @@ func DeleteParameterFromPipeline(db gorp.SqlExecutor, pipelineID int64, paramNam query := `DELETE FROM pipeline_parameter WHERE pipeline_id=$1 AND name=$2` _, err := db.Exec(query, pipelineID, paramName) if err != nil { - return err + return sdk.WithStack(err) } return nil @@ -119,5 +117,5 @@ func DeleteParameterFromPipeline(db gorp.SqlExecutor, pipelineID int64, paramNam func DeleteAllParameterFromPipeline(db gorp.SqlExecutor, pipelineID int64) error { query := `DELETE FROM pipeline_parameter WHERE pipeline_id=$1` _, err := db.Exec(query, pipelineID) - return sdk.WrapError(err, "Unable to delete all parameters") + return sdk.WrapError(err, "unable to delete all parameters") } diff --git a/engine/api/pipeline_test.go b/engine/api/pipeline_test.go index dec741d225..86298663d1 100644 --- a/engine/api/pipeline_test.go +++ b/engine/api/pipeline_test.go @@ -176,6 +176,10 @@ func TestUpdateAsCodePipelineHandler(t *testing.T) { "pipelineKey": pip.Name, }) req := assets.NewJWTAuthentifiedRequest(t, pass, "PUT", uri, pip) + q := req.URL.Query() + q.Set("branch", "master") + q.Set("message", "my message") + req.URL.RawQuery = q.Encode() // Do the request wr := httptest.NewRecorder() diff --git a/engine/api/repositoriesmanager/events.go b/engine/api/repositoriesmanager/events.go index 27f7627c71..f73ae2b313 100644 --- a/engine/api/repositoriesmanager/events.go +++ b/engine/api/repositoriesmanager/events.go @@ -50,9 +50,6 @@ func RetryEvent(e *sdk.Event, err error, store cache.Store) error { } func processEvent(ctx context.Context, db *gorp.DbMap, event sdk.Event, store cache.Store) error { - var c sdk.VCSAuthorizedClientService - var errC error - if event.EventType != fmt.Sprintf("%T", sdk.EventRunWorkflowNode{}) { return nil } @@ -60,26 +57,26 @@ func processEvent(ctx context.Context, db *gorp.DbMap, event sdk.Event, store ca var eventWNR sdk.EventRunWorkflowNode if err := json.Unmarshal(event.Payload, &eventWNR); err != nil { - return fmt.Errorf("cannot read payload: %v", err) + return sdk.WrapError(err, "cannot read payload") } if eventWNR.RepositoryManagerName == "" { return nil } vcsServer, err := LoadProjectVCSServerLinkByProjectKeyAndVCSServerName(ctx, db, event.ProjectKey, eventWNR.RepositoryManagerName) if err != nil { - return fmt.Errorf("repositoriesmanager>processEvent> AuthorizedClient (%s, %s) > err:%s", event.ProjectKey, eventWNR.RepositoryManagerName, err) + return sdk.WrapError(err, "AuthorizedClient (%s, %s)", event.ProjectKey, eventWNR.RepositoryManagerName) } - c, err = AuthorizedClient(ctx, db, store, event.ProjectKey, vcsServer) + c, err := AuthorizedClient(ctx, db, store, event.ProjectKey, vcsServer) if err != nil { - return fmt.Errorf("repositoriesmanager>processEvent> AuthorizedClient (%s, %s) > err:%s", event.ProjectKey, eventWNR.RepositoryManagerName, errC) + return sdk.WrapError(err, "AuthorizedClient (%s, %s)", event.ProjectKey, eventWNR.RepositoryManagerName) } if err := c.SetStatus(ctx, event); err != nil { - if err2 := RetryEvent(&event, err, store); err2 != nil { - log.Error(ctx, "repositoriesmanager>processEvent> err while retry event: %v", err2) + if err := RetryEvent(&event, err, store); err != nil { + log.Error(ctx, "repositoriesmanager>processEvent> err while retry event: %v", err) } - return fmt.Errorf("repositoriesmanager>processEvent> SetStatus > event.EventType:%s err:%s", event.EventType, err) + return sdk.WrapError(err, "event.EventType: %s", event.EventType) } return nil diff --git a/engine/api/repositoriesmanager/repositories_manager.go b/engine/api/repositoriesmanager/repositories_manager.go index 02b0a2954b..fe9f197ffb 100644 --- a/engine/api/repositoriesmanager/repositories_manager.go +++ b/engine/api/repositoriesmanager/repositories_manager.go @@ -230,15 +230,15 @@ func (c *vcsClient) doJSONRequest(ctx context.Context, method, path string, in i if code >= 400 { switch code { case http.StatusUnauthorized: - err = sdk.WrapError(sdk.ErrNoReposManagerClientAuth, "%s", err) + err = sdk.NewError(sdk.ErrNoReposManagerClientAuth, err) case http.StatusBadRequest: - err = sdk.WrapError(sdk.ErrWrongRequest, "%s", err) + err = sdk.NewError(sdk.ErrWrongRequest, err) case http.StatusNotFound: - err = sdk.WrapError(sdk.ErrNotFound, "%s", err) + err = sdk.NewError(sdk.ErrNotFound, err) case http.StatusForbidden: - err = sdk.WrapError(sdk.ErrForbidden, "%s", err) + err = sdk.NewError(sdk.ErrForbidden, err) default: - err = sdk.WrapError(sdk.ErrUnknownError, "%s", err) + err = sdk.NewError(sdk.ErrUnknownError, err) } } @@ -455,20 +455,16 @@ func (c *vcsClient) PullRequestCreate(ctx context.Context, fullname string, pr s func (c *vcsClient) CreateHook(ctx context.Context, fullname string, hook *sdk.VCSHook) error { path := fmt.Sprintf("/vcs/%s/repos/%s/hooks", c.name, fullname) - _, err := c.doJSONRequest(ctx, "POST", path, hook, hook) - if err != nil { - log.Error(ctx, "CreateHook> %v", err) - return sdk.NewErrorFrom(sdk.ErrUnknownError, "unable to create hook on repository %s from %s", fullname, c.name) + if _, err := c.doJSONRequest(ctx, "POST", path, hook, hook); err != nil { + return sdk.NewErrorFrom(err, "unable to create hook on repository %s from %s", fullname, c.name) } return nil } func (c *vcsClient) UpdateHook(ctx context.Context, fullname string, hook *sdk.VCSHook) error { path := fmt.Sprintf("/vcs/%s/repos/%s/hooks", c.name, fullname) - _, err := c.doJSONRequest(ctx, "PUT", path, hook, hook) - if err != nil { - log.Error(ctx, "UpdateHook> %v", err) - return sdk.NewErrorFrom(sdk.ErrUnknownError, "unable to update hook %s on repository %s from %s", hook.ID, fullname, c.name) + if _, err := c.doJSONRequest(ctx, "PUT", path, hook, hook); err != nil { + return sdk.NewErrorFrom(err, "unable to update hook %s on repository %s from %s", hook.ID, fullname, c.name) } return nil } diff --git a/engine/api/services/http.go b/engine/api/services/http.go index d59faa8501..489e2058e8 100644 --- a/engine/api/services/http.go +++ b/engine/api/services/http.go @@ -164,12 +164,12 @@ func _doJSONRequest(ctx context.Context, db gorp.SqlExecutor, srv *sdk.Service, mods = append(mods, cdsclient.SetHeader("Content-Type", "application/json")) res, headers, code, err := doRequest(ctx, db, srv, method, path, b, mods...) if err != nil { - return headers, code, sdk.ErrorWithFallback(err, sdk.ErrUnknownError, "Unable to perform request on service %s (%s)", srv.Name, srv.Type) + return headers, code, sdk.ErrorWithFallback(err, sdk.ErrUnknownError, "unable to perform request on service %s (%s)", srv.Name, srv.Type) } if out != nil { if err := json.Unmarshal(res, out); err != nil { - return headers, code, sdk.WrapError(err, "Unable to marshal output") + return headers, code, sdk.WrapError(err, "unable to unmarshal output") } } @@ -296,22 +296,22 @@ func doRequestFromURL(ctx context.Context, db gorp.SqlExecutor, method string, c // Sign the http request with API private RSA Key if err := HTTPSigner.Sign(req); err != nil { - return nil, nil, 0, sdk.WrapError(err, "services.DoRequest> Request signature failed") + return nil, nil, 0, sdk.WrapError(err, "request signature failed") } log.Debug("services.DoRequest> request %s %v (%s)", req.Method, req.URL, req.Header.Get("Authorization")) //Do the request - resp, errDo := HTTPClient.Do(req) - if errDo != nil { - return nil, nil, 0, sdk.WrapError(errDo, "services.DoRequest> Request failed") + resp, err := HTTPClient.Do(req) + if err != nil { + return nil, nil, 0, sdk.WrapError(err, "request failed") } defer resp.Body.Close() // Read the body - body, errBody := ioutil.ReadAll(resp.Body) - if errBody != nil { - return nil, resp.Header, resp.StatusCode, sdk.WrapError(errBody, "services.DoRequest> Unable to read body") + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, resp.Header, resp.StatusCode, sdk.WrapError(err, "unable to read body") } log.Debug("services.DoRequest> response code:%d", resp.StatusCode) @@ -326,6 +326,5 @@ func doRequestFromURL(ctx context.Context, db gorp.SqlExecutor, method string, c return nil, resp.Header, resp.StatusCode, cdserr } - return nil, resp.Header, resp.StatusCode, fmt.Errorf("Request Failed") - + return nil, resp.Header, resp.StatusCode, sdk.WithStack(fmt.Errorf("request failed")) } diff --git a/engine/api/templates.go b/engine/api/templates.go index a2d9f868d1..657363ab31 100644 --- a/engine/api/templates.go +++ b/engine/api/templates.go @@ -414,6 +414,10 @@ func (api *API) postTemplateApplyHandler() service.Handler { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "cannot find the root application of the workflow") } + if branch == "" || message == "" { + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "missing branch or message data") + } + ope, err := operation.PushOperationUpdate(ctx, api.mustDB(), api.Cache, *p, data, rootApp.VCSServer, rootApp.RepositoryFullname, branch, message, rootApp.RepositoryStrategy, consumer) if err != nil { return err @@ -645,6 +649,14 @@ func (api *API) postTemplateBulkHandler() service.Handler { continue } + if branch == "" || message == "" { + if errD := errorDefer(sdk.NewErrorFrom(sdk.ErrWrongRequest, "missing branch or message data")); errD != nil { + log.Error(ctx, "%v", errD) + return + } + continue + } + ope, err := operation.PushOperationUpdate(ctx, api.mustDB(), api.Cache, *p, data, rootApp.VCSServer, rootApp.RepositoryFullname, branch, message, rootApp.RepositoryStrategy, consumer) if err != nil { if errD := errorDefer(err); errD != nil { diff --git a/engine/api/workflow/dao.go b/engine/api/workflow/dao.go index 6dcd15c28e..1e9e878697 100644 --- a/engine/api/workflow/dao.go +++ b/engine/api/workflow/dao.go @@ -303,8 +303,8 @@ func Insert(ctx context.Context, db gorp.SqlExecutor, store cache.Store, proj sd w.LastModified = time.Now() if err := db.QueryRow(`INSERT INTO workflow ( name, description, icon, project_id, history_length, from_repository, purge_tags, workflow_data, metadata - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`, w.Name, w.Description, w.Icon, w.ProjectID, w.HistoryLength, w.FromRepository, w.PurgeTags, w.WorkflowData, w.Metadata).Scan(&w.ID); err != nil { return sdk.WrapError(err, "Unable to insert workflow %s/%s", w.ProjectKey, w.Name) @@ -755,7 +755,7 @@ func CompleteWorkflow(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workflow, func CheckValidity(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workflow) error { //Check project is not empty if w.ProjectKey == "" { - return sdk.NewError(sdk.ErrWorkflowInvalid, fmt.Errorf("Invalid project key")) + return sdk.NewErrorFrom(sdk.ErrWorkflowInvalid, "invalid project key") } if w.Icon != "" { @@ -770,24 +770,24 @@ func CheckValidity(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workflow) er //Check workflow name rx := sdk.NamePatternRegex if !rx.MatchString(w.Name) { - return sdk.NewError(sdk.ErrWorkflowInvalid, fmt.Errorf("Invalid workflow name. It should match %s", sdk.NamePattern)) + return sdk.NewErrorFrom(sdk.ErrWorkflowInvalid, "workflow name should match pattern %s", sdk.NamePattern) } //Check refs for _, j := range w.WorkflowData.Joins { if len(j.JoinContext) == 0 { - return sdk.NewError(sdk.ErrWorkflowInvalid, fmt.Errorf("Source node references is mandatory")) + return sdk.NewErrorFrom(sdk.ErrWorkflowInvalid, "source node references is mandatory") } } if w.WorkflowData.Node.Context != nil && w.WorkflowData.Node.Context.DefaultPayload != nil { defaultPayload, err := w.WorkflowData.Node.Context.DefaultPayloadToMap() if err != nil { - return sdk.WrapError(err, "cannot transform default payload to map") + return sdk.NewErrorFrom(err, "cannot transform default payload to map") } for payloadKey := range defaultPayload { if strings.HasPrefix(payloadKey, "cds.") { - return sdk.WrapError(sdk.ErrInvalidPayloadVariable, "cannot have key %s in default payload", payloadKey) + return sdk.NewErrorFrom(sdk.ErrInvalidPayloadVariable, "cannot have key %s in default payload", payloadKey) } } } diff --git a/engine/api/workflow/dao_node_job_run_info.go b/engine/api/workflow/dao_node_job_run_info.go index f30eaaa8f9..d50ba79b97 100644 --- a/engine/api/workflow/dao_node_job_run_info.go +++ b/engine/api/workflow/dao_node_job_run_info.go @@ -58,7 +58,7 @@ func insertNodeRunJobInfo(db gorp.SqlExecutor, info *sdk.WorkflowNodeJobRunInfo) if n, err := db.Exec(query, info.WorkflowNodeRunID, info.WorkflowNodeJobRunID, spawnJSON, time.Now()); err != nil { return sdk.WrapError(err, "err while inserting spawninfos into workflow_node_run_job_info") } else if n, _ := n.RowsAffected(); n == 0 { - return fmt.Errorf("insertNodeRunJobInfo> Unable to insert into workflow_node_run_job_info id = %d", info.WorkflowNodeJobRunID) + return sdk.WithStack(fmt.Errorf("unable to insert into workflow_node_run_job_info id = %d", info.WorkflowNodeJobRunID)) } log.Debug("insertNodeRunJobInfo> on node run: %d (job run:%d)", info.WorkflowNodeRunID, info.WorkflowNodeJobRunID) diff --git a/engine/api/workflow/execute_node_job_run.go b/engine/api/workflow/execute_node_job_run.go index b7063841d6..635802ca2b 100644 --- a/engine/api/workflow/execute_node_job_run.go +++ b/engine/api/workflow/execute_node_job_run.go @@ -145,8 +145,8 @@ func UpdateNodeJobRunStatus(ctx context.Context, db gorp.SqlExecutor, store cach switch status { case sdk.StatusBuilding: if currentStatus != sdk.StatusWaiting { - return nil, fmt.Errorf("workflow.UpdateNodeJobRunStatus> Cannot update status of WorkflowNodeJobRun %d to %s, expected current status %s, got %s", - job.ID, status, sdk.StatusWaiting, currentStatus) + return nil, sdk.WithStack(fmt.Errorf("cannot update status of WorkflowNodeJobRun %d to %s, expected current status %s, got %s", + job.ID, status, sdk.StatusWaiting, currentStatus)) } job.Start = time.Now() job.Status = status @@ -174,7 +174,7 @@ func UpdateNodeJobRunStatus(ctx context.Context, db gorp.SqlExecutor, store cach return nil, sdk.WrapError(err, "Cannot update WorkflowRun %d", wf.ID) } default: - return nil, fmt.Errorf("workflow.UpdateNodeJobRunStatus> Cannot update WorkflowNodeJobRun %d to status %v", job.ID, status) + return nil, sdk.WithStack(fmt.Errorf("cannot update WorkflowNodeJobRun %d to status %v", job.ID, status)) } //If the job has been set to building, set the stage to building diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index 848b77bf40..ff4cd15956 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -96,7 +96,7 @@ func syncTakeJobInNodeRun(ctx context.Context, db gorp.SqlExecutor, n *sdk.Workf // Save the node run in database if err := updateNodeRunStatusAndStage(db, n); err != nil { - return nil, sdk.WrapError(fmt.Errorf("Unable to update node id=%d at status %s. err:%s", n.ID, n.Status, err), "workflow.syncTakeJobInNodeRun> Unable to execute node") + return nil, sdk.WrapError(err, "unable to update node id=%d at status %s", n.ID, n.Status) } return report, nil } @@ -848,13 +848,13 @@ func stopWorkflowNodeOutGoingHook(ctx context.Context, dbFunc func() *gorp.DbMap srvs, err := services.LoadAllByType(ctx, db, services.TypeHooks) if err != nil { - return fmt.Errorf("unable to get hooks services: %v", err) + return sdk.WrapError(err, "unable to get hooks services") } if nodeRun.HookExecutionID != "" { path := fmt.Sprintf("/task/%s/execution/%d/stop", nodeRun.HookExecutionID, nodeRun.HookExecutionTimeStamp) if _, _, err := services.NewClient(db, srvs).DoJSONRequest(ctx, "POST", path, nil, nil); err != nil { - return fmt.Errorf("unable to stop task execution: %v", err) + return sdk.WrapError(err, "unable to stop task execution") } } @@ -1090,7 +1090,7 @@ func getVCSInfos(ctx context.Context, db gorp.SqlExecutor, store cache.Store, pr //If it's not a fork; reset this value to the application repository if !forkFound { - return nil, sdk.NewError(sdk.ErrNotFound, fmt.Errorf("repository %s not found", vcsInfos.Repository)) + return nil, sdk.NewErrorFrom(sdk.ErrNotFound, "repository %s not found", vcsInfos.Repository) } } diff --git a/engine/api/workflow/hook.go b/engine/api/workflow/hook.go index 7fbb03af3d..20be683fe5 100644 --- a/engine/api/workflow/hook.go +++ b/engine/api/workflow/hook.go @@ -96,7 +96,7 @@ func hookRegistration(ctx context.Context, db gorp.SqlExecutor, store cache.Stor //Perform the request on one off the hooks service if len(srvs) < 1 { - return sdk.WrapError(fmt.Errorf("no hooks service available, please try again"), "Unable to get services") + return sdk.WithStack(fmt.Errorf("no hooks service available, please try again")) } hookToUpdate := make(map[string]sdk.NodeHook) @@ -182,12 +182,12 @@ func hookRegistration(ctx context.Context, db gorp.SqlExecutor, store cache.Stor if h.HookModelName == sdk.RepositoryWebHookModelName && h.Config["vcsServer"].Value != "" { if !ok || v.Value == "" { if err := createVCSConfiguration(ctx, db, store, proj, h); err != nil { - return sdk.WrapError(err, "Cannot create vcs configuration") + return sdk.WithStack(err) } } if ok && v.Value != "" { if err := updateVCSConfiguration(ctx, db, store, proj, h); err != nil { - return sdk.WrapError(err, "Cannot update vcs configuration") + return sdk.WithStack(err) } } } @@ -279,23 +279,23 @@ func createVCSConfiguration(ctx context.Context, db gorp.SqlExecutor, store cach client, err := repositoriesmanager.AuthorizedClient(ctx, db, store, proj.Key, projectVCSServer) if err != nil { - return sdk.WrapError(err, "createVCSConfiguration> Cannot get vcs client") + return sdk.WrapError(err, "cannot get vcs client") } // We have to check the repository to know if webhooks are supported and how (events) - webHookInfo, errWH := repositoriesmanager.GetWebhooksInfos(ctx, client) - if errWH != nil { - return sdk.WrapError(errWH, "createVCSConfiguration> Cannot get vcs web hook info") + webHookInfo, err := repositoriesmanager.GetWebhooksInfos(ctx, client) + if err != nil { + return sdk.WrapError(err, "cannot get vcs web hook info") } if !webHookInfo.WebhooksSupported || webHookInfo.WebhooksDisabled { - return sdk.WrapError(sdk.ErrForbidden, "createVCSConfiguration> hook creation are forbidden") + return sdk.NewErrorFrom(sdk.ErrForbidden, "hook creation are forbidden") } // Check hook config to avoid sending wrong hooks to VCS if h.Config["repoFullName"].Value == "" { - return sdk.WrapError(sdk.ErrInvalidHookConfiguration, "wrong repoFullName value for hook") + return sdk.WrapError(sdk.ErrInvalidHookConfiguration, "missing repo fullname value for hook") } if !sdk.IsURL(h.Config["webHookURL"].Value) { - return sdk.WrapError(sdk.ErrInvalidHookConfiguration, "wrong webHookURL value (project: %s, repository: %s)", proj.Key, h.Config["repoFullName"].Value) + return sdk.WrapError(sdk.ErrInvalidHookConfiguration, "given webhook url value %s is not a url", h.Config["webHookURL"].Value) } // Prepare the hook that will be send to VCS diff --git a/engine/api/workflow/process_parameters.go b/engine/api/workflow/process_parameters.go index 7c5897c084..4e82cc1c2f 100644 --- a/engine/api/workflow/process_parameters.go +++ b/engine/api/workflow/process_parameters.go @@ -153,7 +153,7 @@ func getParentParameters(w *sdk.WorkflowRun, nodeRuns []*sdk.WorkflowNodeRun) ([ node := w.Workflow.WorkflowData.NodeByID(parentNodeRun.WorkflowNodeID) if node == nil { - return nil, sdk.WrapError(fmt.Errorf("Unable to find node %d in workflow", parentNodeRun.WorkflowNodeID), "getParentParameters>") + return nil, sdk.WithStack(fmt.Errorf("Unable to find node %d in workflow", parentNodeRun.WorkflowNodeID)) } nodeName = node.Name diff --git a/engine/api/workflow/repository.go b/engine/api/workflow/repository.go index aa9d3b808a..acd7177f19 100644 --- a/engine/api/workflow/repository.go +++ b/engine/api/workflow/repository.go @@ -81,7 +81,7 @@ func extractWorkflow(ctx context.Context, db *gorp.DbMap, store cache.Store, p * tr, err := ReadCDSFiles(ope.LoadFiles.Results) if err != nil { allMsgs = append(allMsgs, sdk.NewMessage(sdk.MsgWorkflowErrorBadCdsDir)) - return allMsgs, sdk.WrapError(err, "unable to read cds files") + return allMsgs, sdk.NewErrorWithStack(err, sdk.NewErrorFrom(sdk.ErrWorkflowInvalid, "unable to read cds files")) } ope.RepositoryStrategy.SSHKeyContent = sdk.PasswordPlaceholder ope.RepositoryStrategy.Password = sdk.PasswordPlaceholder @@ -155,10 +155,8 @@ func ReadCDSFiles(files map[string][]byte) (*tar.Reader, error) { if err := tw.WriteHeader(hdr); err != nil { return nil, sdk.WrapError(err, "cannot write header") } - if n, err := tw.Write(fcontent); err != nil { + if _, err := tw.Write(fcontent); err != nil { return nil, sdk.WrapError(err, "cannot write content") - } else if n == 0 { - return nil, fmt.Errorf("nothing to write") } } // Make sure to check the error on Close. @@ -191,7 +189,7 @@ func pollRepositoryOperation(c context.Context, db gorp.SqlExecutor, store cache opeTrusted := *ope opeTrusted.RepositoryStrategy.SSHKeyContent = sdk.PasswordPlaceholder opeTrusted.RepositoryStrategy.Password = sdk.PasswordPlaceholder - return nil, sdk.WrapError(fmt.Errorf("%s", ope.Error), "getImportAsCodeHandler> Operation in error. %+v", opeTrusted) + return nil, sdk.WrapError(fmt.Errorf("%s", ope.Error), "operation in error: %+v", opeTrusted) case sdk.OperationStatusDone: return ope, nil } @@ -237,7 +235,7 @@ func createOperationRequest(w sdk.Workflow, opts sdk.WorkflowRunPostHandlerOptio e.ExtraFields.Type = false m1, errm1 := e.ToStringMap(opts.Manual.Payload) if errm1 != nil { - return ope, sdk.WrapError(errm1, "CreateFromRepository> Unable to compute payload") + return ope, sdk.WrapError(errm1, "unable to compute payload") } tag = m1[tagGitTag] branch = m1[tagGitBranch] @@ -249,7 +247,7 @@ func createOperationRequest(w sdk.Workflow, opts sdk.WorkflowRunPostHandlerOptio // This should not append because the hook must set a default payload with git.branch if ope.Setup.Checkout.Branch == "" && ope.Setup.Checkout.Tag == "" { - return ope, sdk.WrapError(sdk.NewError(sdk.ErrWrongRequest, fmt.Errorf("branch or tag parameter are mandatories")), "createOperationRequest") + return ope, sdk.NewErrorFrom(sdk.ErrWrongRequest, "branch or tag parameter are mandatories") } return ope, nil diff --git a/engine/api/workflow/workflow_importer.go b/engine/api/workflow/workflow_importer.go index aadea92a90..3d4756796b 100644 --- a/engine/api/workflow/workflow_importer.go +++ b/engine/api/workflow/workflow_importer.go @@ -2,7 +2,6 @@ package workflow import ( "context" - "fmt" "github.com/go-gorp/gorp" "github.com/ovh/cds/engine/api/cache" @@ -45,7 +44,7 @@ func Import(ctx context.Context, db gorp.SqlExecutor, store cache.Store, proj sd } if !force { - return sdk.NewError(sdk.ErrConflict, fmt.Errorf("Workflow exists")) + return sdk.NewErrorFrom(sdk.ErrConflict, "workflow exists") } // Retrieve existing hook diff --git a/engine/hatchery/marathon/marathon_test.go b/engine/hatchery/marathon/marathon_test.go index d963824b6b..10e56fd519 100644 --- a/engine/hatchery/marathon/marathon_test.go +++ b/engine/hatchery/marathon/marathon_test.go @@ -4,16 +4,17 @@ import ( "bytes" "context" "encoding/json" - "github.com/gambol99/go-marathon" - "github.com/ovh/cds/sdk" - "github.com/ovh/cds/sdk/hatchery" - "github.com/stretchr/testify/assert" - "gopkg.in/h2non/gock.v1" "io/ioutil" "net/http" "strings" "testing" "time" + + "github.com/gambol99/go-marathon" + "github.com/ovh/cds/sdk" + "github.com/ovh/cds/sdk/hatchery" + "github.com/stretchr/testify/assert" + "gopkg.in/h2non/gock.v1" ) func TestWorkerStarted(t *testing.T) { @@ -320,7 +321,7 @@ func TestSpawnWorkerTimeout(t *testing.T) { }) t.Logf("%+v\n", err) assert.Error(t, err) - assert.Equal(t, "spawnMarathonDockerWorker> deployment for aaa timeout", err.Error()) + assert.Equal(t, "spawnMarathonDockerWorker> TestSpawnWorkerTimeout>SpawnWorker: internal server error (caused by: deployment for aaa timeout)", err.Error()) } func TestCanSpawn(t *testing.T) { diff --git a/engine/repositories/processor_checkout.go b/engine/repositories/processor_checkout.go index c620cc445d..af7bd036ac 100644 --- a/engine/repositories/processor_checkout.go +++ b/engine/repositories/processor_checkout.go @@ -38,8 +38,9 @@ func (s *Service) processCheckout(ctx context.Context, op *sdk.Operation) error // Check commit if op.Setup.Checkout.Commit == "" { - log.Debug("processCheckout> pulling branch %s", op.Setup.Checkout.Branch) - if err := gitRepo.Pull("origin", op.Setup.Checkout.Branch); err != nil { + // Reset HARD to the latest commit of the remote branch (don't use pull because there can be conflicts if the remote was forced) + log.Debug("processCheckout> resetting the branch %s from remote", op.Setup.Checkout.Branch) + if err := gitRepo.ResetHard("origin/" + op.Setup.Checkout.Branch); err != nil { return sdk.WithStack(err) } } else { diff --git a/engine/vcs/bitbucketserver/bitbucketserver.go b/engine/vcs/bitbucketserver/bitbucketserver.go index 3f6cfbf0e9..bf0b5b7490 100644 --- a/engine/vcs/bitbucketserver/bitbucketserver.go +++ b/engine/vcs/bitbucketserver/bitbucketserver.go @@ -60,7 +60,7 @@ func New(consumerKey string, privateKey []byte, URL, apiURL, uiURL, proxyURL, us func getRepo(fullname string) (string, string, error) { t := strings.Split(fullname, "/") if len(t) != 2 { - return "", "", fmt.Errorf("fullname %s must be /", fullname) + return "", "", sdk.WithStack(fmt.Errorf("fullname %s must be /", fullname)) } project := t[0] slug := t[1] diff --git a/engine/vcs/bitbucketserver/client_hook.go b/engine/vcs/bitbucketserver/client_hook.go index 947686efe0..9f62db9d76 100644 --- a/engine/vcs/bitbucketserver/client_hook.go +++ b/engine/vcs/bitbucketserver/client_hook.go @@ -18,7 +18,7 @@ func (b *bitbucketClient) getHooks(ctx context.Context, repo string) ([]WebHook, var resp GetWebHooksResponse getPath := fmt.Sprintf("/projects/%s/repos/%s/webhooks", project, slug) if err := b.do(ctx, "GET", "core", getPath, nil, nil, &resp, nil); err != nil { - return nil, sdk.WrapError(err, "Unable to get hook config") + return nil, sdk.WrapError(err, "unable to get hook config") } return resp.Values, nil @@ -34,7 +34,7 @@ func (b *bitbucketClient) getHookByID(ctx context.Context, repo string, webHookI getPath := fmt.Sprintf("/projects/%s/repos/%s/webhooks/%s", project, slug, webHookID) if err := b.do(ctx, "GET", "core", getPath, nil, nil, &resp, nil); err != nil { - return resp, sdk.WrapError(err, "Unable to get hook %s", webHookID) + return resp, sdk.WrapError(err, "unable to get hook %s", webHookID) } return resp, nil @@ -97,10 +97,10 @@ func (b *bitbucketClient) CreateHook(ctx context.Context, repo string, hook *sdk values, err := json.Marshal(&request) if err != nil { - return err + return sdk.WithStack(err) } if err := b.do(ctx, "POST", "core", url, nil, values, &request, nil); err != nil { - return sdk.WrapError(err, "Unable to get enable webhook") + return sdk.WrapError(err, "unable to get enable webhook") } hook.ID = fmt.Sprintf("%d", request.ID) return nil @@ -131,7 +131,7 @@ func (b *bitbucketClient) UpdateHook(ctx context.Context, repo string, hook *sdk return err } if err := b.do(ctx, "PUT", "core", url, nil, values, &bitbucketHook, nil); err != nil { - return sdk.WrapError(err, "Unable to update webhook") + return sdk.WrapError(err, "unable to update webhook") } return nil } @@ -145,7 +145,7 @@ func (b *bitbucketClient) DeleteHook(ctx context.Context, repo string, hook sdk. url := fmt.Sprintf("/projects/%s/repos/%s/webhooks/%s", project, slug, hook.ID) if err := b.do(ctx, "DELETE", "core", url, nil, nil, nil, nil); err != nil { if !sdk.ErrorIs(err, sdk.ErrNotFound) { - return sdk.WrapError(err, "Unable to get enable webhook") + return sdk.WrapError(err, "unable to get enable webhook") } } return nil diff --git a/engine/vcs/vcs_handlers.go b/engine/vcs/vcs_handlers.go index 9e929fd0cf..51e0057e18 100644 --- a/engine/vcs/vcs_handlers.go +++ b/engine/vcs/vcs_handlers.go @@ -1125,11 +1125,11 @@ func (s *Service) postHookHandler() service.Handler { body := sdk.VCSHook{} if err := service.UnmarshalBody(r, &body); err != nil { - return sdk.WrapError(err, "Unable to read body %s %s/%s", name, owner, repo) + return sdk.WrapError(err, "unable to read body %s %s/%s", name, owner, repo) } if err := client.CreateHook(ctx, fmt.Sprintf("%s/%s", owner, repo), &body); err != nil { - return sdk.WrapError(err, "CreateHook %s %s/%s", name, owner, repo) + return sdk.WrapError(err, "cannot create hook on %s for repository %s/%s", name, owner, repo) } return service.WriteJSON(w, body, http.StatusOK) } diff --git a/sdk/error.go b/sdk/error.go index 12d45b24b3..0cea2ebc3b 100644 --- a/sdk/error.go +++ b/sdk/error.go @@ -591,6 +591,21 @@ func (e Error) Error() string { return message } +func (e Error) printLight() string { + var message string + if e.Message != "" { + message = e.Message + } else if en, ok := errorsAmericanEnglish[e.ID]; ok { + message = en + } else { + message = errorsAmericanEnglish[ErrUnknownError.ID] + } + if e.From != "" { + message = fmt.Sprintf("%s: %s", message, e.From) + } + return message +} + func (e Error) Translate(al string) string { acceptedLanguages, _, err := language.ParseAcceptLanguage(al) if err != nil { @@ -691,11 +706,16 @@ func (s *stack) String() string { if strings.HasPrefix(name, "github.com/ovh/cds") { sp := strings.Split(name, "/") sp = strings.Split(sp[len(sp)-1], ".") + var name string // check if it's a struct or package func if strings.HasPrefix(sp[1], "(") { - names = append(names, sp[2]) + name = sp[2] } else { - names = append(names, sp[1]) + name = sp[1] + } + ignoredNames := StringSlice{"NewError", "NewErrorFrom", "WithStack", "WrapError", "Append"} + if !ignoredNames.Contains(name) { + names = append(names, name) } } } @@ -732,8 +752,17 @@ func NewError(httpError Error, err error) error { return e } + if e, ok := err.(*MultiError); ok { + var ss []string + for i := range *e { + ss = append(ss, ExtractHTTPError((*e)[i], "").printLight()) + } + httpError.From = strings.Join(ss, ", ") + } else { + httpError.From = err.Error() + } + // if it's a library error create a new error with stack - httpError.From = err.Error() return errorWithStack{ root: errors.WithStack(err), stack: callers(), @@ -795,6 +824,11 @@ func WithStack(err error) error { return nil } + // if it's a MultiError we want to preserve the from info from errors + if _, ok := err.(*MultiError); ok { + err = NewError(ErrUnknownError, err) + } + // if it's already a CDS error do not override the stack if e, ok := err.(errorWithStack); ok { return e @@ -831,6 +865,13 @@ func ExtractHTTPError(source error, al string) Error { // try to recognize http error from source switch e := source.(type) { + case *MultiError: + httpError = ErrUnknownError + var ss []string + for i := range *e { + ss = append(ss, ExtractHTTPError((*e)[i], al).printLight()) + } + httpError.Message = strings.Join(ss, ", ") case errorWithStack: httpError = e.httpError case Error: @@ -870,7 +911,7 @@ func DecodeError(data []byte) error { return nil } - if e.Message == "" { + if e.ID == 0 { return nil } @@ -910,14 +951,11 @@ func ErrorIsUnknown(err error) bool { type MultiError []error func (e *MultiError) Error() string { - var s string + var ss []string for i := range *e { - if i > 0 { - s += ", " - } - s += (*e)[i].Error() + ss = append(ss, (*e)[i].Error()) } - return s + return strings.Join(ss, ", ") } // Join joins errors from MultiError to another errors MultiError @@ -937,7 +975,7 @@ func (e *MultiError) Append(err error) { e.Append((*mError)[i]) } } else { - *e = append(*e, err) + *e = append(*e, WithStack(err)) } } diff --git a/sdk/error_test.go b/sdk/error_test.go index f6b943978f..97e8886df9 100644 --- a/sdk/error_test.go +++ b/sdk/error_test.go @@ -90,10 +90,12 @@ func TestWrapError(t *testing.T) { t.Logf("%+v\n", err) } -func oneForStackTest() error { return WrapError(twoForStackTest(), "one") } -func twoForStackTest() error { return WrapError(threeForStackTest(), "two") } -func threeForStackTest() error { return NewError(ErrActionLoop, WrapError(fourForStackTest(), "three")) } -func fourForStackTest() error { return WrapError(fiveForStackTest(), "four") } +func oneForStackTest() error { return WrapError(twoForStackTest(), "one") } +func twoForStackTest() error { return WrapError(threeForStackTest(), "two") } +func threeForStackTest() error { + return NewError(ErrActionLoop, WrapError(fourForStackTest(), "three")) +} +func fourForStackTest() error { return WrapError(fiveForStackTest(), "four") } func fiveForStackTest() error { return WrapError(fmt.Errorf("this is an error generated from vendor"), "five") } @@ -115,22 +117,49 @@ func TestCause(t *testing.T) { func TestNewAdvancedError(t *testing.T) { err := NewErrorFrom(ErrWrongRequest, "this is an error generated from vendor") - assert.Equal(t, "TestNewAdvancedError>NewErrorFrom: wrong request (from: this is an error generated from vendor)", err.Error()) + assert.Equal(t, "TestNewAdvancedError: wrong request (from: this is an error generated from vendor)", err.Error()) httpErr := ExtractHTTPError(err, "fr") assert.Equal(t, "la requête est incorrecte (from: this is an error generated from vendor)", httpErr.Error()) err = WrapError(err, "Something no visible for http error") - assert.Equal(t, "TestNewAdvancedError>NewErrorFrom: wrong request (from: this is an error generated from vendor) (caused by: Something no visible for http error: this is an error generated from vendor)", err.Error()) + assert.Equal(t, "TestNewAdvancedError: wrong request (from: this is an error generated from vendor) (caused by: Something no visible for http error: this is an error generated from vendor)", err.Error()) httpErr = ExtractHTTPError(err, "fr") assert.Equal(t, "la requête est incorrecte (from: this is an error generated from vendor)", httpErr.Error()) err = NewError(ErrAlreadyTaken, err) - assert.Equal(t, "TestNewAdvancedError>NewErrorFrom: This job is already taken by another worker (from: this is an error generated from vendor) (caused by: Something no visible for http error: this is an error generated from vendor)", err.Error()) + assert.Equal(t, "TestNewAdvancedError: This job is already taken by another worker (from: this is an error generated from vendor) (caused by: Something no visible for http error: this is an error generated from vendor)", err.Error()) httpErr = ExtractHTTPError(err, "fr") assert.Equal(t, "Ce job est déjà en cours de traitement par un autre worker (from: this is an error generated from vendor)", httpErr.Error()) err = NewErrorWithStack(err, NewErrorFrom(ErrNotFound, "can't found this")) - assert.Equal(t, "TestNewAdvancedError>NewErrorFrom: resource not found (from: can't found this) (caused by: Something no visible for http error: this is an error generated from vendor)", err.Error()) + assert.Equal(t, "TestNewAdvancedError: resource not found (from: can't found this) (caused by: Something no visible for http error: this is an error generated from vendor)", err.Error()) httpErr = ExtractHTTPError(err, "fr") assert.Equal(t, "la ressource n'existe pas (from: can't found this)", httpErr.Error()) } + +func TestMultiError(t *testing.T) { + errOne := fmt.Errorf("my first error") + errTwo := NewErrorFrom(ErrWrongRequest, "my second error") + errThree := WrapError(fmt.Errorf("my third error"), "hidden info") + errFourth := NewError(ErrAlreadyExist, fmt.Errorf("my fourth error")) + + mError := new(MultiError) + mError.Append(errOne) + mError.Append(errTwo) + mError.Append(errThree) + mError.Append(errFourth) + + assert.Equal(t, "TestMultiError: internal server error (caused by: my first error), TestMultiError: wrong request (from: my second error), TestMultiError: internal server error (caused by: hidden info: my third error), TestMultiError: already exists (from: my fourth error)", mError.Error()) + httpErr := ExtractHTTPError(mError, "fr") + assert.Equal(t, "erreur interne, la requête est incorrecte: my second error, erreur interne, conflit: my fourth error", httpErr.Error()) + + wrappedErr := NewError(ErrWrongRequest, mError) + assert.Equal(t, "TestMultiError: wrong request (from: internal server error, wrong request: my second error, internal server error, already exists: my fourth error) (caused by: TestMultiError: internal server error (caused by: my first error), TestMultiError: wrong request (from: my second error), TestMultiError: internal server error (caused by: hidden info: my third error), TestMultiError: already exists (from: my fourth error))", wrappedErr.Error()) + httpErr = ExtractHTTPError(wrappedErr, "fr") + assert.Equal(t, "la requête est incorrecte (from: internal server error, wrong request: my second error, internal server error, already exists: my fourth error)", httpErr.Error()) + + stackErr := WithStack(mError) + assert.Equal(t, "TestMultiError: internal server error (from: internal server error, wrong request: my second error, internal server error, already exists: my fourth error) (caused by: TestMultiError: internal server error (caused by: my first error), TestMultiError: wrong request (from: my second error), TestMultiError: internal server error (caused by: hidden info: my third error), TestMultiError: already exists (from: my fourth error))", stackErr.Error()) + httpErr = ExtractHTTPError(stackErr, "fr") + assert.Equal(t, "erreur interne (from: internal server error, wrong request: my second error, internal server error, already exists: my fourth error)", httpErr.Error()) +} diff --git a/sdk/exportentities/pipeline.go b/sdk/exportentities/pipeline.go index 85bd538e94..722e34966c 100644 --- a/sdk/exportentities/pipeline.go +++ b/sdk/exportentities/pipeline.go @@ -1,7 +1,6 @@ package exportentities import ( - "fmt" "sort" "github.com/ovh/cds/sdk" @@ -306,7 +305,7 @@ func (p PipelineV1) Pipeline() (pip *sdk.Pipeline, err error) { for s, opt := range p.StageOptions { if mapStages[s] == nil { - return nil, fmt.Errorf("Invalid stage option. Stage %s not found", s) + return nil, sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid stage option, stage %s not found", s) } if opt.Enabled != nil { mapStages[s].Enabled = *opt.Enabled diff --git a/sdk/exportentities/template.go b/sdk/exportentities/template.go index 105701eab1..aa609aa69c 100644 --- a/sdk/exportentities/template.go +++ b/sdk/exportentities/template.go @@ -193,12 +193,12 @@ func ReadTemplateFromTar(tr *tar.Reader) (sdk.WorkflowTemplate, error) { break } if err != nil { - return wt, sdk.NewError(sdk.ErrWrongRequest, sdk.WrapError(err, "Unable to read tar file")) + return wt, sdk.NewError(sdk.ErrWrongRequest, sdk.WrapError(err, "unable to read tar file")) } buff := new(bytes.Buffer) if _, err := io.Copy(buff, tr); err != nil { - return wt, sdk.NewError(sdk.ErrWrongRequest, sdk.WrapError(err, "Unable to read tar file")) + return wt, sdk.NewError(sdk.ErrWrongRequest, sdk.WrapError(err, "unable to read tar file")) } b := buff.Bytes() @@ -212,18 +212,18 @@ func ReadTemplateFromTar(tr *tar.Reader) (sdk.WorkflowTemplate, error) { case hdr.Name == "workflow.yml": // if a workflow was already found, it's a mistake if len(wkf) != 0 { - mError.Append(fmt.Errorf("Two workflow files found")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "two workflow files found")) break } wkf = b default: // if a template was already found, it's a mistake if templateFileName != "" { - mError.Append(fmt.Errorf("Two template files found: %s and %s", templateFileName, hdr.Name)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "two template files found: %s and %s", templateFileName, hdr.Name)) break } if err := yaml.Unmarshal(b, &tmpl); err != nil { - mError.Append(sdk.WrapError(err, "Unable to unmarshal template %s", hdr.Name)) + mError.Append(sdk.NewErrorFrom(err, "unable to unmarshal template %s", hdr.Name)) continue } templateFileName = hdr.Name diff --git a/sdk/exportentities/v1/workflow.go b/sdk/exportentities/v1/workflow.go index f1c8ccdadd..d530891220 100644 --- a/sdk/exportentities/v1/workflow.go +++ b/sdk/exportentities/v1/workflow.go @@ -101,40 +101,40 @@ func (w Workflow) CheckValidity() error { //Check valid application name rx := sdk.NamePatternRegex if !rx.MatchString(w.Name) { - mError.Append(fmt.Errorf("workflow name %s do not respect pattern %s", w.Name, sdk.NamePattern)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "workflow name %s do not respect pattern %s", w.Name, sdk.NamePattern)) } if len(w.Workflow) != 0 { if w.ApplicationName != "" { - mError.Append(fmt.Errorf("error: wrong usage: application %s not allowed here", w.ApplicationName)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "application %s not allowed here", w.ApplicationName)) } if w.EnvironmentName != "" { - mError.Append(fmt.Errorf("error: wrong usage: environment %s not allowed here", w.EnvironmentName)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "environment %s not allowed here", w.EnvironmentName)) } if w.ProjectIntegrationName != "" { - mError.Append(fmt.Errorf("error: wrong usage: integration %s not allowed here", w.ProjectIntegrationName)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "integration %s not allowed here", w.ProjectIntegrationName)) } if w.PipelineName != "" { - mError.Append(fmt.Errorf("error: wrong usage: pipeline %s not allowed here", w.PipelineName)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "pipeline %s not allowed here", w.PipelineName)) } if w.Conditions != nil { - mError.Append(fmt.Errorf("error: wrong usage: conditions not allowed here")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "conditions not allowed here")) } if len(w.When) != 0 { - mError.Append(fmt.Errorf("error: wrong usage: when not allowed here")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "when not allowed here")) } if len(w.PipelineHooks) != 0 { - mError.Append(fmt.Errorf("error: wrong usage: pipeline_hooks not allowed here")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "pipeline_hooks not allowed here")) } } else { if len(w.Hooks) > 0 { - mError.Append(fmt.Errorf("error: wrong usage: hooks not allowed here")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "hooks not allowed here")) } } for name := range w.Hooks { if _, ok := w.Workflow[name]; !ok { - mError.Append(fmt.Errorf("Error: wrong usage: invalid hook on %s", name)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid hook on %s", name)) } } @@ -151,7 +151,7 @@ func (w Workflow) CheckDependencies() error { mError := new(sdk.MultiError) for s, e := range w.Entries() { if err := e.checkDependencies(s, w); err != nil { - mError.Append(fmt.Errorf("Error: %s invalid: %v", s, err)) + mError.Append(err) } } @@ -170,7 +170,7 @@ nextDep: continue nextDep } } - mError.Append(fmt.Errorf("the pipeline %s depends on an unknown pipeline: %s", nodeName, d)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "the pipeline %s depends on an unknown pipeline: %s", nodeName, d)) } if mError.IsEmpty() { return nil @@ -191,10 +191,10 @@ func (w Workflow) GetWorkflow() (*sdk.Workflow, error) { wf.ProjectIntegrations = make(map[int64]sdk.ProjectIntegration) if err := w.CheckValidity(); err != nil { - return nil, sdk.WrapError(err, "Unable to check validity") + return nil, sdk.WrapError(err, "unable to check validity") } if err := w.CheckDependencies(); err != nil { - return nil, sdk.WrapError(err, "Unable to check dependencies") + return nil, sdk.WrapError(err, "unable to check dependencies") } wf.PurgeTags = w.PurgeTags if len(w.Metadata) > 0 { @@ -307,7 +307,7 @@ func (e *NodeEntry) getNode(name string) (*sdk.Node, error) { if len(e.Payload) > 0 { if len(e.DependsOn) > 0 { - return nil, sdk.WrapError(sdk.ErrInvalidNodeDefaultPayload, "Default payload cannot be set on another node than the first one (node : %s)", name) + return nil, sdk.NewErrorFrom(sdk.ErrInvalidNodeDefaultPayload, "default payload cannot be set on another node than the first one (node: %s)", name) } node.Context.DefaultPayload = e.Payload } @@ -330,7 +330,7 @@ func (e *NodeEntry) getNode(name string) (*sdk.Node, error) { Variable: "cds.manual", }) default: - return nil, fmt.Errorf("Unsupported when condition %s", w) + return nil, sdk.NewErrorFrom(sdk.ErrWrongRequest, "unsupported when condition %s", w) } } diff --git a/sdk/exportentities/v1/workflow_notification.go b/sdk/exportentities/v1/workflow_notification.go index 7c8f39fecb..9168f20e11 100644 --- a/sdk/exportentities/v1/workflow_notification.go +++ b/sdk/exportentities/v1/workflow_notification.go @@ -2,7 +2,6 @@ package v1 import ( "context" - "fmt" "sort" "strings" @@ -59,7 +58,7 @@ func craftNotificationEntry(ctx context.Context, w sdk.Workflow, notif sdk.Workf if entry.Settings.Template != nil { defaultTemplate, has := sdk.UserNotificationTemplateMap[entry.Type] if !has { - return nil, entry, fmt.Errorf("workflow notification %s not found", entry.Type) + return nil, entry, sdk.NewErrorFrom(sdk.ErrWrongRequest, "workflow notification %s not found", entry.Type) } if defaultTemplate.Subject == entry.Settings.Template.Subject { entry.Settings.Template.Subject = "" @@ -112,11 +111,11 @@ func CheckWorkflowNotificationsValidity(w Workflow) error { mError := new(sdk.MultiError) if len(w.Workflow) != 0 { if len(w.Notifications) != 0 { - mError.Append(fmt.Errorf("Error: wrong usage: notify not allowed here")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "notify not allowed here")) } } else { if len(w.MapNotifications) > 0 { - mError.Append(fmt.Errorf("Error: wrong usage: notifications not allowed here")) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "notifications not allowed here")) } } @@ -126,7 +125,7 @@ func CheckWorkflowNotificationsValidity(w Workflow) error { for _, s := range names { name := strings.TrimSpace(s) if _, ok := w.Workflow[name]; !ok { - mError.Append(fmt.Errorf("Error: wrong usage: invalid notification on %s (%s is missing)", nodeNames, name)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid notification on %s (%s is missing)", nodeNames, name)) } } } @@ -144,7 +143,7 @@ func ProcessNotificationValues(notif NotificationEntry) (sdk.WorkflowNotificatio defaultTemplate, has := sdk.UserNotificationTemplateMap[n.Type] //Check the type if !has { - return n, fmt.Errorf("Error: wrong usage: invalid notification type %s", n.Type) + return n, sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid notification type %s", n.Type) } //Default settings if notif.Settings == nil { diff --git a/sdk/exportentities/v2/workflow.go b/sdk/exportentities/v2/workflow.go index e78f721528..9d1b4f225b 100644 --- a/sdk/exportentities/v2/workflow.go +++ b/sdk/exportentities/v2/workflow.go @@ -101,7 +101,7 @@ func NewWorkflow(ctx context.Context, w sdk.Workflow, version string, opts ...Ex entry, err := craftNodeEntry(w, *n) if err != nil { - return exportedWorkflow, sdk.WrapError(err, "Unable to craft Node entry %s", n.Name) + return exportedWorkflow, sdk.WrapError(err, "unable to craft Node entry %s", n.Name) } exportedWorkflow.Workflow[n.Name] = entry @@ -112,7 +112,7 @@ func NewWorkflow(ctx context.Context, w sdk.Workflow, version string, opts ...Ex m := sdk.GetBuiltinHookModelByName(h.HookModelName) if m == nil { - return exportedWorkflow, sdk.WrapError(sdk.ErrNotFound, "unable to find hook model %s", h.HookModelName) + return exportedWorkflow, sdk.NewErrorFrom(sdk.ErrNotFound, "unable to find hook model %s", h.HookModelName) } pipHook := HookEntry{ Model: h.HookModelName, @@ -135,7 +135,7 @@ func NewWorkflow(ctx context.Context, w sdk.Workflow, version string, opts ...Ex for _, f := range opts { if err := f(w, &exportedWorkflow); err != nil { - return exportedWorkflow, sdk.WrapError(err, "Unable to run function") + return exportedWorkflow, sdk.WrapError(err, "unable to run function") } } @@ -239,7 +239,7 @@ func craftNodeEntry(w sdk.Workflow, n sdk.Node) (NodeEntry, error) { enc.Formatters = nil m, err := enc.ToMap(n.Context.DefaultPayload) if err != nil { - return entry, sdk.WrapError(err, "Unable to encode payload") + return entry, sdk.WrapError(err, "unable to encode payload") } entry.Payload = m } @@ -341,10 +341,10 @@ func (w Workflow) GetWorkflow() (*sdk.Workflow, error) { wf.ProjectIntegrations = make(map[int64]sdk.ProjectIntegration) if err := w.CheckValidity(); err != nil { - return nil, sdk.WrapError(err, "Unable to check validity") + return nil, sdk.WrapError(err, "unable to check validity") } if err := w.CheckDependencies(); err != nil { - return nil, sdk.WrapError(err, "Unable to check dependencies") + return nil, sdk.WrapError(err, "unable to check dependencies") } wf.PurgeTags = w.PurgeTags if len(w.Metadata) > 0 { @@ -368,7 +368,7 @@ func (w Workflow) GetWorkflow() (*sdk.Workflow, error) { entry.ID = fakeID ok, err := entry.processNode(name, wf) if err != nil { - return nil, sdk.WrapError(err, "Unable to process node") + return nil, sdk.WrapError(err, "unable to process node") } if ok { delete(w.Workflow, name) @@ -404,19 +404,18 @@ func (w Workflow) GetWorkflow() (*sdk.Workflow, error) { func (w Workflow) CheckValidity() error { mError := new(sdk.MultiError) - //Check valid application name rx := sdk.NamePatternRegex if !rx.MatchString(w.Name) { - mError.Append(fmt.Errorf("workflow name %s do not respect pattern %s", w.Name, sdk.NamePattern)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "workflow name %s do not respect pattern %s", w.Name, sdk.NamePattern)) } for name := range w.Hooks { if _, ok := w.Workflow[name]; !ok { - mError.Append(fmt.Errorf("error: wrong usage: invalid hook on %s", name)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid hook on %s", name)) } } - //Checks map notifications validity + // Checks map notifications validity mError.Append(CheckWorkflowNotificationsValidity(w)) if mError.IsEmpty() { @@ -429,7 +428,7 @@ func (w Workflow) CheckDependencies() error { mError := new(sdk.MultiError) for s, e := range w.Workflow { if err := e.checkDependencies(s, w); err != nil { - mError.Append(fmt.Errorf("Error: %s invalid: %v", s, err)) + mError.Append(err) } } @@ -448,7 +447,7 @@ nextDep: continue nextDep } } - mError.Append(fmt.Errorf("the pipeline %s depends on an unknown pipeline: %s", nodeName, d)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "the pipeline %s depends on an unknown pipeline: %s", nodeName, d)) } if mError.IsEmpty() { return nil @@ -631,7 +630,7 @@ func (e *NodeEntry) getNode(name string) (*sdk.Node, error) { if len(e.Payload) > 0 { if len(e.DependsOn) > 0 { - return nil, sdk.WrapError(sdk.ErrInvalidNodeDefaultPayload, "Default payload cannot be set on another node than the first one (node : %s)", name) + return nil, sdk.NewErrorFrom(sdk.ErrInvalidNodeDefaultPayload, "default payload cannot be set on another node than the first one (node: %s)", name) } node.Context.DefaultPayload = e.Payload } @@ -654,7 +653,7 @@ func (e *NodeEntry) getNode(name string) (*sdk.Node, error) { Variable: "cds.manual", }) default: - return nil, fmt.Errorf("Unsupported when condition %s", w) + return nil, sdk.NewErrorFrom(sdk.ErrWrongRequest, "unsupported when condition %s", w) } } diff --git a/sdk/exportentities/v2/workflow_notification.go b/sdk/exportentities/v2/workflow_notification.go index aed58c386b..79dc0fde2f 100644 --- a/sdk/exportentities/v2/workflow_notification.go +++ b/sdk/exportentities/v2/workflow_notification.go @@ -2,7 +2,6 @@ package v2 import ( "context" - "fmt" "sort" "github.com/ovh/cds/sdk" @@ -60,7 +59,7 @@ func craftNotificationEntry(ctx context.Context, w sdk.Workflow, notif sdk.Workf if entry.Settings.Template != nil { defaultTemplate, has := sdk.UserNotificationTemplateMap[entry.Type] if !has { - return entry, fmt.Errorf("workflow notification %s not found", entry.Type) + return entry, sdk.NewErrorFrom(sdk.ErrWrongRequest, "workflow notification %s not found", entry.Type) } if defaultTemplate.Subject == entry.Settings.Template.Subject { entry.Settings.Template.Subject = "" @@ -109,7 +108,7 @@ func CheckWorkflowNotificationsValidity(w Workflow) error { for _, notifEntry := range w.Notifications { for _, s := range notifEntry.Pipelines { if _, ok := w.Workflow[s]; !ok { - mError.Append(fmt.Errorf("error: wrong usage: invalid notification on %s (%s is missing)", notifEntry.Pipelines, s)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid notification on %s (%s is missing)", notifEntry.Pipelines, s)) } } } @@ -126,7 +125,7 @@ func ProcessNotificationValues(notif NotificationEntry) (sdk.WorkflowNotificatio defaultTemplate, has := sdk.UserNotificationTemplateMap[n.Type] //Check the type if !has { - return n, fmt.Errorf("Error: wrong usage: invalid notification type %s", n.Type) + return n, sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid notification type %s", n.Type) } //Default settings if notif.Settings == nil { diff --git a/sdk/exportentities/workflow.go b/sdk/exportentities/workflow.go index 995f77c850..8723c861a4 100644 --- a/sdk/exportentities/workflow.go +++ b/sdk/exportentities/workflow.go @@ -2,7 +2,6 @@ package exportentities import ( "context" - "fmt" "github.com/ovh/cds/sdk" v1 "github.com/ovh/cds/sdk/exportentities/v1" @@ -85,9 +84,9 @@ func ParseWorkflow(exportWorkflow Workflow) (*sdk.Workflow, error) { return workflowV1.GetWorkflow() } default: - return nil, sdk.WithStack(fmt.Errorf("exportentities workflow cannot be cast, unknown version %s", exportWorkflow.GetVersion())) + return nil, sdk.NewErrorFrom(sdk.ErrWrongRequest, "exportentities workflow cannot be cast, unknown version %s", exportWorkflow.GetVersion()) } - return nil, sdk.WithStack(fmt.Errorf("exportentities workflow cannot be cast %+v", exportWorkflow)) + return nil, sdk.WrapError(sdk.NewErrorFrom(sdk.ErrWrongRequest, "exportentities workflow cannot be cast"), "workflow: %+v", exportWorkflow) } func NewWorkflow(ctx context.Context, w sdk.Workflow, opts ...v2.ExportOptions) (Workflow, error) { diff --git a/sdk/exportentities/workflow_tar.go b/sdk/exportentities/workflow_tar.go index 318725cfaa..d9a4ed7fac 100644 --- a/sdk/exportentities/workflow_tar.go +++ b/sdk/exportentities/workflow_tar.go @@ -200,7 +200,7 @@ func UntarWorkflowComponents(ctx context.Context, tr *tar.Reader) (WorkflowCompo var app Application if err := Unmarshal(b, format, &app); err != nil { log.Error(ctx, "ExtractWorkflowFromTar> Unable to unmarshal application %s: %v", hdr.Name, err) - mError.Append(fmt.Errorf("unable to unmarshal application %s: %v", hdr.Name, err)) + mError.Append(sdk.NewErrorFrom(err, "unable to unmarshal application %s", hdr.Name)) continue } res.Applications = append(res.Applications, app) @@ -208,7 +208,7 @@ func UntarWorkflowComponents(ctx context.Context, tr *tar.Reader) (WorkflowCompo var pip PipelineV1 if err := Unmarshal(b, format, &pip); err != nil { log.Error(ctx, "ExtractWorkflowFromTar> Unable to unmarshal pipeline %s: %v", hdr.Name, err) - mError.Append(fmt.Errorf("unable to unmarshal pipeline %s: %v", hdr.Name, err)) + mError.Append(sdk.NewErrorFrom(err, "unable to unmarshal pipeline %s", hdr.Name)) continue } res.Pipelines = append(res.Pipelines, pip) @@ -216,18 +216,17 @@ func UntarWorkflowComponents(ctx context.Context, tr *tar.Reader) (WorkflowCompo var env Environment if err := Unmarshal(b, format, &env); err != nil { log.Error(ctx, "ExtractWorkflowFromTar> Unable to unmarshal environment %s: %v", hdr.Name, err) - mError.Append(fmt.Errorf("unable to unmarshal environment %s: %v", hdr.Name, err)) + mError.Append(sdk.NewErrorFrom(err, "unable to unmarshal environment %s", hdr.Name)) continue } res.Environments = append(res.Environments, env) default: if res.Workflow != nil { - log.Error(ctx, "only one workflow or template file should be given: %s and %s", - res.Workflow.GetName(), hdr.Name) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "only one workflow or template file should be given but %s and %s were found", res.Workflow.GetName(), hdr.Name)) + break } if res.Template.Name != "" { - mError.Append(fmt.Errorf("only one workflow or template file should be given: %s and %s", - res.Template.Name, hdr.Name)) + mError.Append(sdk.NewErrorFrom(sdk.ErrWrongRequest, "only one workflow or template file should be given but %s and %s were found", res.Template.Name, hdr.Name)) break } @@ -241,7 +240,7 @@ func UntarWorkflowComponents(ctx context.Context, tr *tar.Reader) (WorkflowCompo res.Workflow, err = UnmarshalWorkflow(b, format) if err != nil { log.Error(ctx, "Push> Unable to unmarshal workflow %s: %v", hdr.Name, err) - mError.Append(fmt.Errorf("unable to unmarshal workflow %s: %v", hdr.Name, err)) + mError.Append(sdk.NewErrorFrom(err, "unable to unmarshal workflow %s", hdr.Name)) continue } } diff --git a/sdk/workflow.go b/sdk/workflow.go index dbfb215594..bdf7e13fa2 100644 --- a/sdk/workflow.go +++ b/sdk/workflow.go @@ -344,7 +344,7 @@ func (w *Workflow) ValidateType() error { } } if len(namesInError) > 0 { - return WithStack(fmt.Errorf("wrong type for nodes %v", namesInError)) + return NewErrorFrom(ErrWrongRequest, "wrong type for nodes %v", namesInError) } return nil } diff --git a/sdk/workflow_node.go b/sdk/workflow_node.go index eb382edb5f..0fa011603c 100644 --- a/sdk/workflow_node.go +++ b/sdk/workflow_node.go @@ -124,7 +124,7 @@ func (c *NodeContext) HasDefaultPayload() bool { func (c *NodeContext) DefaultPayloadToMap() (map[string]string, error) { // DefaultPayloadToMap returns default payload to map if c == nil { - return nil, fmt.Errorf("Workflow node context is nil") + return nil, fmt.Errorf("workflow node context is nil") } if c.DefaultPayload == nil { return map[string]string{}, nil @@ -135,7 +135,6 @@ func (c *NodeContext) DefaultPayloadToMap() (map[string]string, error) { dumper.ExtraFields.Len = false dumper.ExtraFields.Type = false return dumper.ToStringMap(c.DefaultPayload) - } // NodeTrigger represents the link between 2 nodes diff --git a/tests/03_clictl_template_bulk.yml b/tests/03_clictl_template_bulk.yml index 6aaba59df5..4670540ab7 100644 --- a/tests/03_clictl_template_bulk.yml +++ b/tests/03_clictl_template_bulk.yml @@ -32,5 +32,5 @@ testcases: - result.code ShouldEqual 0 - result.systemoutjson.operations.operations0.status ShouldEqual 2 - result.systemoutjson.operations.operations1.status ShouldEqual 3 - - result.systemoutjson.operations.operations1.error ShouldEqual 'Unsupported when condition ok' + - result.systemoutjson.operations.operations1.error ShouldEqual 'unsupported when condition ok' - result.systemoutjson.operations.operations2.status ShouldEqual 2 diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index cec3d7a7d1..47aab773c5 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -15,7 +15,7 @@
- +
{{ 'common_loading_project' | translate }} @@ -34,4 +34,6 @@

{{ 'maintenance_title' | translate }}

+ +
diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 028f973e03..f6aa9c5f8a 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -52,7 +52,9 @@ export class AppComponent implements OnInit { _routerNavEndSubscription: Subscription; _sseSubscription: Subscription; displayResolver: boolean; - toasterConfig: any; + toasterConfigDefault: any; + toasterConfigErrorHTTP: any; + toasterConfigErrorHTTPLocked: any; lastPing: number; currentTheme: string; eventsRouteSubscription: Subscription; @@ -80,7 +82,9 @@ export class AppComponent implements OnInit { private _eventService: EventService ) { this.zone = new NgZone({ enableLongStackTrace: false }); - this.toasterConfig = this._toastService.getConfig(); + this.toasterConfigDefault = this._toastService.getConfigDefault(); + this.toasterConfigErrorHTTP = this._toastService.getConfigErrorHTTP(); + this.toasterConfigErrorHTTPLocked = this._toastService.getConfigErrorHTTPLocked(); _translate.addLangs(['en', 'fr']); _translate.setDefaultLang('en'); let browserLang = navigator.language.match(/fr/) ? 'fr' : 'en'; diff --git a/ui/src/app/service/authentication/error.interceptor.ts b/ui/src/app/service/authentication/error.interceptor.ts new file mode 100644 index 0000000000..b9aa804405 --- /dev/null +++ b/ui/src/app/service/authentication/error.interceptor.ts @@ -0,0 +1,44 @@ +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { NavigationExtras, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { ToastService } from 'app/shared/toast/ToastService'; +import { Observable, throwError as observableThrowError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +@Injectable() +export class ErrorInterceptor implements HttpInterceptor { + + constructor( + private _toast: ToastService, + private _translate: TranslateService) { + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req).pipe( + catchError(e => { + if (e instanceof HttpErrorResponse) { + if (e.status === 0) { + this._toast.error('API Unreachable', ''); + } else if (req.url.indexOf('auth/me') === -1) { // ignore error on auth/me used for auth pages + // error formatted from CDS API + if (e.error) { + if (e.error.message) { + this._toast.errorHTTP(e.status, e.error.message, e.error.from, e.error.request_id); + } else if (Array.isArray(e.error)) { + try { + let messages = e.error as Array; + this._toast.error(e.statusText, messages.join(', ')); + } catch (e) { + this._toast.error(e.statusText, this._translate.instant('common_error')); + } + } else { + this._toast.error(e.statusText, this._translate.instant('common_error')); + } + } + } + return observableThrowError(e); + } + })); + } +} diff --git a/ui/src/app/service/authentication/logout.interceptor.ts b/ui/src/app/service/authentication/logout.interceptor.ts index 44c9d57cd9..45bb9733f5 100644 --- a/ui/src/app/service/authentication/logout.interceptor.ts +++ b/ui/src/app/service/authentication/logout.interceptor.ts @@ -19,9 +19,7 @@ export class LogoutInterceptor implements HttpInterceptor { return next.handle(req).pipe( catchError(e => { if (e instanceof HttpErrorResponse) { - if (e.status === 0) { - this._toast.error('API Unreachable', ''); - } else if (req.url.indexOf('auth') === -1 && e.status === 401) { + if (req.url.indexOf('auth') === -1 && e.status === 401) { let navigationExtras: NavigationExtras = { queryParams: {} }; @@ -32,22 +30,6 @@ export class LogoutInterceptor implements HttpInterceptor { } this._router.navigate(['/auth/signin'], navigationExtras); - } else if (req.url.indexOf('auth/me') === -1) { // ignore error on auth/me used for auth pages - // error formatted from CDS API - if (e.error) { - if (e.error.message) { - this._toast.error(e.statusText, e.error.message); - } else if (Array.isArray(e.error)) { - try { - let messages = e.error as Array; - this._toast.error(e.statusText, messages.join(', ')); - } catch (e) { - this._toast.error(e.statusText, this._translate.instant('common_error')); - } - } else { - this._toast.error(e.statusText, this._translate.instant('common_error')); - } - } } return observableThrowError(e); } diff --git a/ui/src/app/service/services.module.ts b/ui/src/app/service/services.module.ts index 38d0697979..5c02748a23 100644 --- a/ui/src/app/service/services.module.ts +++ b/ui/src/app/service/services.module.ts @@ -16,6 +16,7 @@ import { ApplicationService } from './application/application.service'; import { ApplicationStore } from './application/application.store'; import { ApplicationWorkflowService } from './application/application.workflow.service'; import { AuthenticationService } from './authentication/authentication.service'; +import { ErrorInterceptor } from './authentication/error.interceptor'; import { LogoutInterceptor } from './authentication/logout.interceptor'; import { XSRFInterceptor } from './authentication/xsrf.interceptor'; import { BroadcastService } from './broadcast/broadcast.service'; @@ -139,6 +140,11 @@ export class ServicesModule { provide: HTTP_INTERCEPTORS, useClass: LogoutInterceptor, multi: true + }, + { + provide: HTTP_INTERCEPTORS, + useClass: ErrorInterceptor, + multi: true } ] }; diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 4ca1111d9f..84cd1c6318 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -69,6 +69,7 @@ import { StatusIconComponent } from './status/status.component'; import { DataTableComponent, SelectorPipe, SelectPipe } from './table/data-table.component'; import { PaginationComponent } from './table/pagination.component'; import { TabsComponent } from './tabs/tabs.component'; +import { ToastHTTPErrorComponent } from './toast/toast-http-error.component'; import { ToastService } from './toast/ToastService'; import { UsageApplicationsComponent } from './usage/applications/usage.applications.component'; import { UsageEnvironmentsComponent } from './usage/environments/usage.environments.component'; @@ -228,10 +229,12 @@ import { ZoneComponent } from './zone/zone.component'; WorkflowWNodeOutGoingHookComponent, WorkflowWNodePipelineComponent, ZoneComponent, - ZoneContentComponent + ZoneContentComponent, + ToastHTTPErrorComponent ], entryComponents: [ - NguiAutoCompleteComponent + NguiAutoCompleteComponent, + ToastHTTPErrorComponent ], providers: [ DurationService, @@ -336,7 +339,8 @@ import { ZoneComponent } from './zone/zone.component'; TabsComponent, MenuComponent, ScrollviewComponent, - AutoFocusInputComponent + AutoFocusInputComponent, + ToastHTTPErrorComponent ] }) export class SharedModule { diff --git a/ui/src/app/shared/toast/ToastService.ts b/ui/src/app/shared/toast/ToastService.ts index 7217d673a5..5bcfc4794a 100644 --- a/ui/src/app/shared/toast/ToastService.ts +++ b/ui/src/app/shared/toast/ToastService.ts @@ -1,26 +1,71 @@ -import {Injectable} from '@angular/core'; -import {ToasterConfig, ToasterService} from 'angular2-toaster/angular2-toaster'; +import { Injectable } from '@angular/core'; +import { BodyOutputType, ToasterConfig, ToasterService } from 'angular2-toaster/angular2-toaster'; +import { ToastHTTPErrorComponent, ToastHTTPErrorData } from './toast-http-error.component'; @Injectable() export class ToastService { - private toasterconfig: ToasterConfig = new ToasterConfig({mouseoverTimerStop: true}); + private configDefault: ToasterConfig = new ToasterConfig({ + mouseoverTimerStop: true, + toastContainerId: 1 + }); + private configErrorHTTP: ToasterConfig = new ToasterConfig({ + mouseoverTimerStop: true, + toastContainerId: 2 + }); + private configErrorHTTPLocked: ToasterConfig = new ToasterConfig({ + showCloseButton: true, + tapToDismiss: false, + timeout: 0, + toastContainerId: 3 + }); - constructor(private _toasterService: ToasterService) { + constructor(private _toasterService: ToasterService) { } + + getConfigDefault(): ToasterConfig { + return this.configDefault; + } + + getConfigErrorHTTP(): ToasterConfig { + return this.configErrorHTTP; } - getConfig(): ToasterConfig { - return this.toasterconfig; + getConfigErrorHTTPLocked(): ToasterConfig { + return this.configErrorHTTPLocked; } success(title: string, msg: string) { - this._toasterService.pop('success', title, msg); + this._toasterService.pop( + { type: 'success', title: title, body: msg, toastContainerId: 1 } + ); } info(title: string, msg: string) { - this._toasterService.pop('info', title, msg); + this._toasterService.pop( + { type: 'info', title: title, body: msg, toastContainerId: 1 } + ); } error(title: string, msg: string) { - this._toasterService.pop('error', title, msg); + this._toasterService.pop( + { type: 'error', title: title, body: msg, toastContainerId: 1 } + ); + } + + errorHTTP(status: number, message: string, from: string, request_id: string) { + this._toasterService.pop( + { + type: 'error', + title: message, + body: ToastHTTPErrorComponent, + bodyOutputType: BodyOutputType.Component, + toastContainerId: status < 500 ? 2 : 3, + data: { + status, + from, + request_id + } + } + ); } } + diff --git a/ui/src/app/shared/toast/toast-http-error.component.ts b/ui/src/app/shared/toast/toast-http-error.component.ts new file mode 100644 index 0000000000..e434db29d6 --- /dev/null +++ b/ui/src/app/shared/toast/toast-http-error.component.ts @@ -0,0 +1,17 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Toast } from 'angular2-toaster'; + +export class ToastHTTPErrorData { + status: number; + from: string; + request_id: string; +} + +@Component({ + selector: 'app-toast-http-error-component', + templateUrl: './toast-http-error.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ToastHTTPErrorComponent { + toast: Toast; +} diff --git a/ui/src/app/shared/toast/toast-http-error.html b/ui/src/app/shared/toast/toast-http-error.html new file mode 100644 index 0000000000..aa6652ee9b --- /dev/null +++ b/ui/src/app/shared/toast/toast-http-error.html @@ -0,0 +1,5 @@ +
+ {{toast.data.from}}
+ {{'toast_http_error_request_id' | translate}}: + {{toast.data.request_id}} +
diff --git a/ui/src/app/shared/vcs/vcs.strategy.html b/ui/src/app/shared/vcs/vcs.strategy.html index f81fac18bd..fd95572220 100644 --- a/ui/src/app/shared/vcs/vcs.strategy.html +++ b/ui/src/app/shared/vcs/vcs.strategy.html @@ -6,7 +6,8 @@
- +
@@ -17,13 +18,15 @@
-
+ [(value)]="strategy.ssh_key" (valueChange)="updatePublicKey($event)"> +
@@ -68,8 +71,7 @@
- -
{{ 'key_warning_add_repo_title' | translate }}
+ {{ 'key_warning_add_repo_title' | translate }}
@@ -80,6 +82,7 @@
{{ 'keys_add_title' | translate }}
- + +
diff --git a/ui/src/app/views/application/show/application.component.spec.ts b/ui/src/app/views/application/show/application.component.spec.ts index 7dfffa6611..e268b74046 100644 --- a/ui/src/app/views/application/show/application.component.spec.ts +++ b/ui/src/app/views/application/show/application.component.spec.ts @@ -11,6 +11,7 @@ import { AuthenticationService } from 'app/service/authentication/authentication import { UserService } from 'app/service/user/user.service'; import { VariableEvent } from 'app/shared/variable/variable.event.model'; import { AddApplicationVariable, DeleteApplicationVariable, UpdateApplicationVariable } from 'app/store/applications.action'; +import { ApplicationStateModel } from 'app/store/applications.state'; import { NgxsStoreModule } from 'app/store/store.module'; import { of } from 'rxjs'; import 'rxjs/add/observable/of'; @@ -38,7 +39,6 @@ import { SharedModule } from '../../../shared/shared.module'; import { ToastService } from '../../../shared/toast/ToastService'; import { ApplicationModule } from '../application.module'; import { ApplicationShowComponent } from './application.component'; -import { ApplicationStateModel } from 'app/store/applications.state'; describe('CDS: Application', () => { diff --git a/ui/src/app/views/pipeline/add/pipeline.add.component.spec.ts b/ui/src/app/views/pipeline/add/pipeline.add.component.spec.ts index 47790c3fe8..5f096320e0 100644 --- a/ui/src/app/views/pipeline/add/pipeline.add.component.spec.ts +++ b/ui/src/app/views/pipeline/add/pipeline.add.component.spec.ts @@ -5,8 +5,10 @@ import { ActivatedRoute, ActivatedRouteSnapshot, Router } from '@angular/router' import { RouterTestingModule } from '@angular/router/testing'; import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core'; import { Store } from '@ngxs/store'; +import { ApplicationService } from 'app/service/application/application.service'; import { AuthenticationService } from 'app/service/authentication/authentication.service'; import { MonitoringService } from 'app/service/monitoring/monitoring.service'; +import { RouterService } from 'app/service/router/router.service'; import { UserService } from 'app/service/user/user.service'; import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.service'; import { WorkflowService } from 'app/service/workflow/workflow.service'; @@ -27,8 +29,6 @@ import { SharedModule } from '../../../shared/shared.module'; import { ToastService } from '../../../shared/toast/ToastService'; import { PipelineModule } from '../pipeline.module'; import { PipelineAddComponent } from './pipeline.add.component'; -import { ApplicationService } from 'app/service/application/application.service'; -import { RouterService } from 'app/service/router/router.service'; describe('CDS: Pipeline Add Component', () => { diff --git a/ui/src/app/views/pipeline/show/admin/pipeline.admin.spec.ts b/ui/src/app/views/pipeline/show/admin/pipeline.admin.spec.ts index 80783a0e4b..d34c3a96cf 100644 --- a/ui/src/app/views/pipeline/show/admin/pipeline.admin.spec.ts +++ b/ui/src/app/views/pipeline/show/admin/pipeline.admin.spec.ts @@ -5,7 +5,9 @@ import { ActivatedRoute, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core'; import { Store } from '@ngxs/store'; +import { ApplicationService } from 'app/service/application/application.service'; import { AuthenticationService } from 'app/service/authentication/authentication.service'; +import { EnvironmentService } from 'app/service/environment/environment.service'; import { NavbarService } from 'app/service/navbar/navbar.service'; import { ProjectService } from 'app/service/project/project.service'; import { ProjectStore } from 'app/service/project/project.store'; @@ -13,6 +15,7 @@ import { MonitoringService, RouterService, UserService } from 'app/service/servi import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.service'; import { WorkflowService } from 'app/service/workflow/workflow.service'; import { NgxsStoreModule } from 'app/store/store.module'; +import { of } from 'rxjs'; import 'rxjs/add/observable/of'; import { Observable } from 'rxjs/Observable'; import { Pipeline } from '../../../../model/pipeline.model'; @@ -22,9 +25,6 @@ import { SharedModule } from '../../../../shared/shared.module'; import { ToastService } from '../../../../shared/toast/ToastService'; import { PipelineModule } from '../../pipeline.module'; import { PipelineAdminComponent } from './pipeline.admin.component'; -import { ApplicationService } from 'app/service/application/application.service'; -import { EnvironmentService } from 'app/service/environment/environment.service'; -import { of } from 'rxjs'; describe('CDS: Pipeline Admin Component', () => { diff --git a/ui/src/app/views/project/add/project.add.component.spec.ts b/ui/src/app/views/project/add/project.add.component.spec.ts index 8f22065d2a..42cef4291a 100644 --- a/ui/src/app/views/project/add/project.add.component.spec.ts +++ b/ui/src/app/views/project/add/project.add.component.spec.ts @@ -6,8 +6,10 @@ import { RouterTestingModule } from '@angular/router/testing'; import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core'; import { Store } from '@ngxs/store'; import { ToasterService } from 'angular2-toaster/angular2-toaster'; +import { ApplicationService } from 'app/service/application/application.service'; import { AuthenticationService } from 'app/service/authentication/authentication.service'; import { MonitoringService } from 'app/service/monitoring/monitoring.service'; +import { RouterService } from 'app/service/router/router.service'; import { UserService } from 'app/service/user/user.service'; import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.service'; import { WorkflowService } from 'app/service/workflow/workflow.service'; @@ -29,8 +31,6 @@ import { SharedModule } from '../../../shared/shared.module'; import { ToastService } from '../../../shared/toast/ToastService'; import { ProjectModule } from '../project.module'; import { ProjectAddComponent } from './project.add.component'; -import { ApplicationService } from 'app/service/application/application.service'; -import { RouterService } from 'app/service/router/router.service'; describe('CDS: Project Show Component', () => { diff --git a/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.spec.ts b/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.spec.ts index 93a99e1e6b..1c0173d7a8 100644 --- a/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.spec.ts +++ b/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.spec.ts @@ -7,6 +7,7 @@ import { Store } from '@ngxs/store'; import { ToasterService } from 'angular2-toaster/angular2-toaster'; import { Project } from 'app/model/project.model'; import { RepositoriesManager } from 'app/model/repositories.model'; +import { ApplicationService } from 'app/service/application/application.service'; import { AuthenticationService } from 'app/service/authentication/authentication.service'; import { EnvironmentService } from 'app/service/environment/environment.service'; import { MonitoringService } from 'app/service/monitoring/monitoring.service'; @@ -15,6 +16,7 @@ import { PipelineService } from 'app/service/pipeline/pipeline.service'; import { ProjectService } from 'app/service/project/project.service'; import { ProjectStore } from 'app/service/project/project.store'; import { RepoManagerService } from 'app/service/repomanager/project.repomanager.service'; +import { RouterService } from 'app/service/router/router.service'; import { UserService } from 'app/service/user/user.service'; import { VariableService } from 'app/service/variable/variable.service'; import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.service'; @@ -26,8 +28,6 @@ import { NgxsStoreModule } from 'app/store/store.module'; import { ProjectModule } from 'app/views/project/project.module'; import { of } from 'rxjs'; import { ProjectRepoManagerComponent } from './project.repomanager.list.component'; -import { ApplicationService } from 'app/service/application/application.service'; -import { RouterService } from 'app/service/router/router.service'; describe('CDS: Project RepoManager List Component', () => { diff --git a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts index 71650fd32b..d4b2730b72 100644 --- a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts +++ b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts @@ -1,8 +1,8 @@ -import {ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; -import {TranslateService} from '@ngx-translate/core'; -import {AuditWorkflowTemplate} from 'app/model/audit.model'; -import {Group} from 'app/model/group.model'; +import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { AuditWorkflowTemplate } from 'app/model/audit.model'; +import { Group } from 'app/model/group.model'; // tslint:disable-next-line: max-line-length import { InstanceStatus, @@ -11,21 +11,21 @@ import { WorkflowTemplateError, WorkflowTemplateInstance } from 'app/model/workflow-template.model'; -import {Workflow} from 'app/model/workflow.model'; -import {GroupService} from 'app/service/group/group.service'; -import {WorkflowTemplateService} from 'app/service/workflow-template/workflow-template.service'; -import {PathItem} from 'app/shared/breadcrumb/breadcrumb.component'; -import {AutoUnsubscribe} from 'app/shared/decorator/autoUnsubscribe'; -import {calculateWorkflowTemplateDiff} from 'app/shared/diff/diff'; -import {Item} from 'app/shared/diff/list/diff.list.component'; -import {Column, ColumnType} from 'app/shared/table/data-table.component'; -import {Tab} from 'app/shared/tabs/tabs.component'; -import {ToastService} from 'app/shared/toast/ToastService'; -import {WorkflowTemplateApplyModalComponent} from 'app/shared/workflow-template/apply-modal/workflow-template.apply-modal.component'; -import {WorkflowTemplateBulkModalComponent} from 'app/shared/workflow-template/bulk-modal/workflow-template.bulk-modal.component'; -import {Subscription} from 'rxjs'; -import {finalize} from 'rxjs/internal/operators/finalize'; -import {first} from 'rxjs/operators'; +import { Workflow } from 'app/model/workflow.model'; +import { GroupService } from 'app/service/group/group.service'; +import { WorkflowTemplateService } from 'app/service/workflow-template/workflow-template.service'; +import { PathItem } from 'app/shared/breadcrumb/breadcrumb.component'; +import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe'; +import { calculateWorkflowTemplateDiff } from 'app/shared/diff/diff'; +import { Item } from 'app/shared/diff/list/diff.list.component'; +import { Column, ColumnType } from 'app/shared/table/data-table.component'; +import { Tab } from 'app/shared/tabs/tabs.component'; +import { ToastService } from 'app/shared/toast/ToastService'; +import { WorkflowTemplateApplyModalComponent } from 'app/shared/workflow-template/apply-modal/workflow-template.apply-modal.component'; +import { WorkflowTemplateBulkModalComponent } from 'app/shared/workflow-template/bulk-modal/workflow-template.bulk-modal.component'; +import { Subscription } from 'rxjs'; +import { finalize } from 'rxjs/internal/operators/finalize'; +import { first } from 'rxjs/operators'; @Component({ selector: 'app-workflow-template-edit', @@ -153,10 +153,10 @@ export class WorkflowTemplateEditComponent implements OnInit { labels, value } : { - link: '/project/' + i.project.key + '/workflow/' + i.workflow.name, - labels, - value - }; + link: '/project/' + i.project.key + '/workflow/' + i.workflow.name, + labels, + value + }; } }, >{ type: ColumnType.LABEL, @@ -203,7 +203,7 @@ export class WorkflowTemplateEditComponent implements OnInit { this._cd.markForCheck(); })) .subscribe(wt => { - this.oldWorkflowTemplate = {...wt}; + this.oldWorkflowTemplate = { ...wt }; this.workflowTemplate = wt; if (this.workflowTemplate.editable) { @@ -258,7 +258,7 @@ export class WorkflowTemplateEditComponent implements OnInit { this._cd.markForCheck(); })) .subscribe(res => { - this.oldWorkflowTemplate = {...res}; + this.oldWorkflowTemplate = { ...res }; this.workflowTemplate = res; this.updatePath(); this.errors = []; diff --git a/ui/src/app/views/workflow/run/workflow.run.component.ts b/ui/src/app/views/workflow/run/workflow.run.component.ts index f4bdb57361..c9907ab6e5 100644 --- a/ui/src/app/views/workflow/run/workflow.run.component.ts +++ b/ui/src/app/views/workflow/run/workflow.run.component.ts @@ -75,7 +75,7 @@ export class WorkflowRunComponent implements OnInit { // Subscribe to workflow Run this.subWorkflowRun = this.workflowRun$.subscribe(wr => { - if (!wr || wr.status === 'Pending') { + if (!wr) { return; } diff --git a/ui/src/app/views/workflow/run/workflow.run.html b/ui/src/app/views/workflow/run/workflow.run.html index bb11337ec8..86399a1e73 100644 --- a/ui/src/app/views/workflow/run/workflow.run.html +++ b/ui/src/app/views/workflow/run/workflow.run.html @@ -3,8 +3,7 @@ [class.above]="workflowRunData['status'] === pipelineStatusEnum.PENDING"> - +
@@ -24,7 +23,7 @@

{{info.user_message}}. {{errorsMap[info.message.id].description}} + *ngIf="errorsMap[info.message.id] && errorsMap[info.message.id].description">{{errorsMap[info.message.id].description | translate}}

{{'common_find_help' | translate}}

- +
@@ -52,7 +50,8 @@
- {{warningsMap[info.message.id].title | translate}} + {{warningsMap[info.message.id].title | translate}} {{info.message.id}}
@@ -74,7 +73,7 @@ + [workflowData]="workflowRunData['workflow']"> diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index d4d9ad06fc..1268667737 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -634,6 +634,7 @@ "timeline_filter_project": "Mute events on: ", "timeline_filter_updated": "Filter updated", "timeline_filter_project_select": "Select a project", + "toast_http_error_request_id": "Request ID", "variable_added": "Variable added", "variable_deleted": "Variable deleted", "variable_no": "There is no variable", diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 6aeb61a9f2..5795e8991c 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -632,6 +632,7 @@ "timeline_loading": "Chargement du fil d'actualité...", "timeline_no": "Le fil d'actualité est vide", "timeline_title": "Fil d'actualité", + "toast_http_error_request_id": "Identifiant de la requête", "ui_updated": "L'interface vient d'être mise à jour. Merci de cliquer ici pour rafraîchir votre page.", "usage_application_list": "Applications utilisées", "usage_environment_list": "Environnements utilisés", diff --git a/ui/src/styles.scss b/ui/src/styles.scss index 11d09ca53d..64ed8b956a 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -109,6 +109,10 @@ body ::-webkit-scrollbar-thumb { margin: 0; } +.toast-close-button { + right: -3px; +} + // dropdown .ui.buttons { sm-dropdown>.ui.dropdown:last-child .menu { diff --git a/ui/src/theme.scss b/ui/src/theme.scss index 654021e4db..0e9174104e 100644 --- a/ui/src/theme.scss +++ b/ui/src/theme.scss @@ -538,6 +538,7 @@ a.ui.basic.grey.label:hover { } } +.ui.warning.message, .ui.orange.message { .night & { color: $darkTheme_light_orange;