Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more support for user-facing errors to state init #2901

Merged
merged 5 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/locale/locales/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,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:
Expand Down Expand Up @@ -2032,6 +2034,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:
Expand Down
29 changes: 20 additions & 9 deletions internal/runners/initialize/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()}
Expand Down Expand Up @@ -87,24 +97,27 @@ 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
if path == "" {
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)
Expand All @@ -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'.
Expand Down Expand Up @@ -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}

Expand Down Expand Up @@ -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 {
Expand Down
52 changes: 45 additions & 7 deletions internal/runners/initialize/rationalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
8 changes: 4 additions & 4 deletions pkg/platform/api/buildplanner/model/buildplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 22 additions & 0 deletions test/integration/init_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Loading