diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index 22088dd2b3..125530eae4 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -1220,6 +1220,8 @@ err_invalid_output_format: other: "Invalid output format specified: '[NOTICE]{{.V0}}[/RESET]'; Supported formats: [ACTIONABLE]{{.V1}}[/RESET]" err_invalid_language: other: "Invalid language specified: '[NOTICE]{{.V0}}[/RESET]'; Supported languages: [ACTIONABLE]{{.V1}}[/RESET]" +err_init_invalid_org: + other: The organization [ACTIONABLE]{{.V0}}[/RESET] either does not exist, or you do not have permissions to create a project in it. push_creating_project: other: "Creating project [NOTICE]{{.V1}}[/RESET] under [NOTICE]{{.V0}}[/RESET] on the ActiveState Platform" push_project_updated: @@ -2034,6 +2036,8 @@ pjfile_deprecation_entry: other: " - '[ACTIONABLE]{{.V0}}[/RESET]' located at byte [ACTIONABLE]{{.V1}}[/RESET]" err_init_authenticated: other: You need to be authenticated to initialize a project. Authenticate by running '[ACTIONABLE]state auth[/RESET]'. +err_init_project_exists: + other: A project file already exists for {{.V0}} at {{.V1}} err_country_blocked: other: This service is not available in your region. err_commit_id_invalid: diff --git a/internal/runners/initialize/init.go b/internal/runners/initialize/init.go index 9703958965..7c719a4ddf 100644 --- a/internal/runners/initialize/init.go +++ b/internal/runners/initialize/init.go @@ -20,6 +20,7 @@ import ( "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/runbits" "github.com/ActiveState/cli/internal/runbits/commitmediator" + "github.com/ActiveState/cli/internal/runbits/rationalize" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/setup" @@ -53,6 +54,15 @@ type primeable interface { primer.SvcModeler } +type errProjectExists struct { + error + path string +} + +var errNoOwner = errs.New("Could not find organization") + +var errNoLanguage = errs.New("No language specified") + // New returns a prepared ptr to Initialize instance. func New(prime primeable) *Initialize { return &Initialize{prime.Auth(), prime.Config(), prime.Output(), prime.Analytics(), prime.SvcModel()} @@ -87,11 +97,11 @@ func inferLanguage(config projectfile.ConfigGetter) (string, string, bool) { } func (r *Initialize) Run(params *RunParams) (rerr error) { - defer rationalizeError(&rerr) + defer rationalizeError(params.Namespace, &rerr) logging.Debug("Init: %s/%s %v", params.Namespace.Owner, params.Namespace.Project, params.Private) if !r.auth.Authenticated() { - return locale.NewInputError("err_init_authenticated") + return rationalize.ErrNotAuthenticated } path := params.Path @@ -99,12 +109,15 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { var err error path, err = osutils.Getwd() if err != nil { - return locale.WrapInputError(err, "err_init_sanitize_path", "Could not prepare path: {{.V0}}", err.Error()) + return errs.Wrap(err, "Unable to get current working directory") } } if fileutils.TargetExists(filepath.Join(path, constants.ConfigFileName)) { - return locale.NewInputError("err_projectfile_exists") + return &errProjectExists{ + error: errs.New("Project file already exists"), + path: path, + } } err := fileutils.MkdirUnlessExists(path) @@ -130,7 +143,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { } if languageName == "" { - return locale.NewInputError("err_init_no_language") + return errNoLanguage } // Require 'python', 'python@3', or 'python@2' instead of 'python3' or 'python2'. @@ -176,9 +189,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { } } if owner == "" { - return locale.NewInputError("err_invalid_org", - "The organization '[ACTIONABLE]{{.V0}}[/RESET]' either does not exist, or you do not have permissions to create a project in it.", - params.Namespace.Owner) + return errNoOwner } namespace := project.Namespaced{Owner: owner, Project: params.Namespace.Project} @@ -244,7 +255,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { Description: locale.T("commit_message_add_initial"), }) if err != nil { - return locale.WrapError(err, "err_init_commit", "Could not create initial commit") + return locale.WrapError(err, "err_init_commit", "Could not create project") } if err := commitmediator.Set(proj, commitID.String()); err != nil { diff --git a/internal/runners/initialize/rationalize.go b/internal/runners/initialize/rationalize.go index bf3c9fd011..35bda5c4a6 100644 --- a/internal/runners/initialize/rationalize.go +++ b/internal/runners/initialize/rationalize.go @@ -6,38 +6,76 @@ import ( "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/internal/runbits/rationalize" bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" "github.com/ActiveState/cli/pkg/platform/runtime/setup" + "github.com/ActiveState/cli/pkg/project" ) -func rationalizeError(err *error) { +func rationalizeError(namespace *project.Namespaced, rerr *error) { var pcErr *bpModel.ProjectCreatedError var errArtifactSetup *setup.ArtifactSetupErrors + var projectExistsErr *errProjectExists switch { - case err == nil: + case rerr == nil: return + // Not authenticated + case errors.Is(*rerr, rationalize.ErrNotAuthenticated): + *rerr = errs.WrapUserFacing(*rerr, + locale.T("err_init_authenticated"), + errs.SetInput(), + ) + + case errors.As(*rerr, &projectExistsErr): + *rerr = errs.WrapUserFacing(*rerr, + locale.Tr("err_init_project_exists", namespace.Project, projectExistsErr.path), + errs.SetInput(), + ) + + case errors.Is(*rerr, errNoLanguage): + *rerr = errs.WrapUserFacing(*rerr, + locale.T("err_init_no_language"), + errs.SetInput(), + ) + + case errors.Is(*rerr, errNoOwner): + *rerr = errs.WrapUserFacing(*rerr, + locale.Tr("err_init_invalid_org", namespace.Owner), + errs.SetInput(), + ) + // Error creating project. - case errors.As(*err, &pcErr): + case errors.As(*rerr, &pcErr): switch pcErr.Type { case bpModel.AlreadyExistsErrorType: - *err = errs.NewUserFacing(locale.Tl("err_create_project_exists", "That project already exists."), errs.SetInput()) + *rerr = errs.WrapUserFacing( + pcErr, + locale.Tl("err_create_project_exists", "The project {{.V0}} already exists under {{.V1}}", namespace.Project, namespace.Owner), + errs.SetInput(), + ) case bpModel.ForbiddenErrorType: - *err = errs.NewUserFacing( + *rerr = errs.NewUserFacing( locale.Tl("err_create_project_forbidden", "You do not have permission to create that project"), errs.SetInput(), errs.SetTips(locale.T("err_init_authenticated"))) + case bpModel.NotFoundErrorType: + *rerr = errs.WrapUserFacing( + pcErr, + locale.Tl("err_create_project_not_found", "Could not create project because the organization {{.V0}} was not found.", namespace.Owner), + errs.SetInput(), + errs.SetTips(locale.T("err_init_authenticated"))) } // If there was an artifact download error, say so, rather than reporting a generic "could not // update runtime" error. - case errors.As(*err, &errArtifactSetup): + case errors.As(*rerr, &errArtifactSetup): for _, serr := range errArtifactSetup.Errors() { if !errs.Matches(serr, &setup.ArtifactDownloadError{}) { continue } - *err = errs.WrapUserFacing(*err, + *rerr = errs.WrapUserFacing(*rerr, locale.Tl("err_init_download", "Your project could not be created because one or more artifacts failed to download."), errs.SetInput(), errs.SetTips(locale.Tr("err_user_network_solution", constants.ForumsURL)), diff --git a/pkg/platform/api/buildplanner/model/buildplan.go b/pkg/platform/api/buildplanner/model/buildplan.go index f06a6871b8..d1a7e64607 100644 --- a/pkg/platform/api/buildplanner/model/buildplan.go +++ b/pkg/platform/api/buildplanner/model/buildplan.go @@ -407,11 +407,11 @@ type ProjectCreatedError struct { func (p *ProjectCreatedError) Error() string { return p.Message } func ProcessProjectCreatedError(pcErr *projectCreated, fallbackMessage string) error { - if pcErr.Type != "" { - // These will be handled individually per type as user-facing errors in DX-2300. - return &ProjectCreatedError{pcErr.Type, pcErr.Message} + if pcErr.Error == nil { + return errs.New(fallbackMessage) } - return errs.New(fallbackMessage) + + return &ProjectCreatedError{pcErr.Type, pcErr.Message} } type BuildExpression struct { diff --git a/test/integration/init_int_test.go b/test/integration/init_int_test.go index eaca6a87ec..d4fb610ef4 100644 --- a/test/integration/init_int_test.go +++ b/test/integration/init_int_test.go @@ -141,6 +141,28 @@ func (suite *InitIntegrationTestSuite) TestInit_NotAuthenticated() { cp.Expect("You need to be authenticated to initialize a project.") } +func (suite *InitIntegrationTestSuite) TestInit_AlreadyExists() { + suite.OnlyRunForTags(tagsuite.Init) + ts := e2e.New(suite.T(), false) + defer ts.Close() + ts.LoginAsPersistentUser() + + cp := ts.Spawn("init", fmt.Sprintf("%s/test-project", e2e.PersistentUsername), "--language", "python@3") + cp.Expect("The project test-project already exists under cli-integration-tests") + cp.ExpectExitCode(1) +} + +func (suite *InitIntegrationTestSuite) TestInit_NoOrg() { + suite.OnlyRunForTags(tagsuite.Init) + ts := e2e.New(suite.T(), false) + defer ts.Close() + ts.LoginAsPersistentUser() + + cp := ts.Spawn("init", "random-org/test-project", "--language", "python@3") + cp.Expect("The organization random-org either does not exist, or you do not have permissions to create a project in it.") + cp.ExpectExitCode(1) +} + func TestInitIntegrationTestSuite(t *testing.T) { suite.Run(t, new(InitIntegrationTestSuite)) }