From e904976af3d8a3714cba0f7434774413aeeb86e8 Mon Sep 17 00:00:00 2001 From: "jan.hajek@zerops.io" Date: Wed, 31 Jan 2024 19:57:31 +0100 Subject: [PATCH] updates pt.2 --- Makefile | 11 + go.mod | 1 + go.sum | 5 + src/archiveClient/handler_findFilesByRules.go | 10 +- .../handler_findFilesByRules_test.go | 9 +- src/archiveClient/handler_tarFile.go | 7 +- src/cmd/bucketS3Delete.go | 15 +- src/cmd/bucketZeropsDelete.go | 15 +- src/cmd/login.go | 12 +- src/cmd/projectDelete.go | 19 +- src/cmd/projectImport.go | 37 +++- src/cmd/projectServiceImport.go | 4 +- src/cmd/projectStart.go | 3 +- src/cmd/projectStop.go | 3 +- src/cmd/serviceDelete.go | 19 +- src/cmd/serviceDeploy.go | 83 ++++--- src/cmd/servicePush.go | 88 +++++--- src/cmd/serviceStart.go | 3 +- src/cmd/serviceStop.go | 3 +- src/cmd/uxHelpers.go | 29 +-- src/cmdBuilder/cmdBuilderCreateRunFunc.go | 25 +-- src/cmdBuilder/cmdBuilderExecuteRootCmd.go | 12 - src/cmdBuilder/dependencyTreeProject.go | 3 +- src/cmdBuilder/dependencyTreeService.go | 9 +- src/entity/org.go | 13 ++ src/entity/repository/org.go | 39 ++++ src/entity/repository/project.go | 21 +- src/entity/repository/service.go | 26 +-- src/i18n/en.go | 18 +- src/i18n/i18n.go | 14 +- src/uxBlock/blocks.go | 32 ++- src/uxBlock/logs.go | 18 +- src/uxBlock/mocks/blocks.go | 207 ++++++++++++++++++ src/uxBlock/prompt.go | 10 +- src/uxBlock/select.go | 9 +- src/uxBlock/showcase/main.go | 12 +- src/uxBlock/spinner.go | 27 +-- src/uxBlock/table.go | 2 +- src/uxHelpers/org.go | 66 ++++++ src/uxHelpers/project.go | 6 +- src/uxHelpers/service.go | 8 +- src/uxHelpers/spinner.go | 66 +++--- src/yamlReader/readYaml.go | 2 +- src/zeropsRestApiClient/errors.go | 68 ------ 44 files changed, 680 insertions(+), 409 deletions(-) create mode 100644 Makefile create mode 100644 src/entity/org.go create mode 100644 src/entity/repository/org.go create mode 100644 src/uxBlock/mocks/blocks.go create mode 100644 src/uxHelpers/org.go delete mode 100644 src/zeropsRestApiClient/errors.go diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e469439f --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +## help show this help +.PHONY: help + +help: + @printf "possible values: test, lint" + +test: + go test -v ./cmd/... ./src/... + +lint: + gomodrun golangci-lint run ./cmd/... ./src/... --verbose \ No newline at end of file diff --git a/go.mod b/go.mod index 7af8ee56..895c7e70 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/golang/mock v1.4.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 1f98d9b1..a13aa168 100644 --- a/go.sum +++ b/go.sum @@ -86,6 +86,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -288,6 +289,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -452,6 +455,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/src/archiveClient/handler_findFilesByRules.go b/src/archiveClient/handler_findFilesByRules.go index 21f206e8..1aa1a0ef 100644 --- a/src/archiveClient/handler_findFilesByRules.go +++ b/src/archiveClient/handler_findFilesByRules.go @@ -2,21 +2,21 @@ package archiveClient import ( "errors" - "fmt" "os" "path/filepath" "strings" "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/uxBlock" ) -func (h *Handler) FindFilesByRules(workingDir string, sources []string) ([]File, error) { +func (h *Handler) FindFilesByRules(uxBlocks uxBlock.UxBlocks, workingDir string, sources []string) ([]File, error) { workingDir, err := filepath.Abs(workingDir) if err != nil { return nil, err } - fmt.Printf(i18n.T(i18n.ArchClientWorkingDirectory)+"\n", workingDir) + uxBlocks.PrintLine(i18n.T(i18n.ArchClientWorkingDirectory, workingDir)) // resulting function returns File from provided path // if file shouldn't be included in the result, File.ArchivePath will be empty @@ -56,9 +56,9 @@ func (h *Handler) FindFilesByRules(workingDir string, sources []string) ([]File, if fileInfo.IsDir() { source = strings.TrimSuffix(source, string(os.PathSeparator)) + string(os.PathSeparator) - fmt.Printf(i18n.T(i18n.ArchClientPackingDirectory)+"\n", source) + uxBlocks.PrintLine(i18n.T(i18n.ArchClientPackingDirectory, source)) } else { - fmt.Printf(i18n.T(i18n.ArchClientPackingFile)+"\n", source) + uxBlocks.PrintLine(i18n.T(i18n.ArchClientPackingFile, source)) } trimPath, err := filepath.Abs(filepath.Join(workingDir, parts[0])) diff --git a/src/archiveClient/handler_findFilesByRules_test.go b/src/archiveClient/handler_findFilesByRules_test.go index 68f434bc..d56d5bd0 100644 --- a/src/archiveClient/handler_findFilesByRules_test.go +++ b/src/archiveClient/handler_findFilesByRules_test.go @@ -3,7 +3,9 @@ package archiveClient import ( "testing" + "github.com/golang/mock/gomock" . "github.com/onsi/gomega" + "github.com/zeropsio/zcli/src/uxBlock/mocks" ) var testErrorResponseDataProvider = []struct { @@ -219,14 +221,19 @@ var testErrorResponseDataProvider = []struct { } func TestValidation(t *testing.T) { + ctrl := gomock.NewController(t) + uxBlocks := mocks.NewMockUxBlocks(ctrl) + uxBlocks.EXPECT().PrintLine(gomock.Any()).AnyTimes() + for _, test := range testErrorResponseDataProvider { test := test // scope lint t.Run(test.name+" in "+test.workingDir, func(t *testing.T) { + RegisterTestingT(t) archiver := New(Config{}) - files, err := archiver.FindFilesByRules(test.workingDir, test.input) + files, err := archiver.FindFilesByRules(uxBlocks, test.workingDir, test.input) Expect(err).ShouldNot(HaveOccurred()) output := func() (res []string) { diff --git a/src/archiveClient/handler_tarFile.go b/src/archiveClient/handler_tarFile.go index 5309791f..7bd9f187 100644 --- a/src/archiveClient/handler_tarFile.go +++ b/src/archiveClient/handler_tarFile.go @@ -2,10 +2,11 @@ package archiveClient import ( "archive/tar" - "fmt" "io" "os" "strings" + + "github.com/pkg/errors" ) func tarFile(archive *tar.Writer, file File, info os.FileInfo) error { @@ -48,11 +49,11 @@ func tarFile(archive *tar.Writer, file File, info os.FileInfo) error { return cpErr } if n != info.Size() { - return fmt.Errorf("wrote %d, want %d", n, info.Size()) + return errors.Errorf("wrote %d, want %d", n, info.Size()) } default: // let user know instead of silently ignoring unsupported files - return fmt.Errorf("unsupported file type: %s", header.Name) + return errors.Errorf("unsupported file type: %s", header.Name) } return nil diff --git a/src/cmd/bucketS3Delete.go b/src/cmd/bucketS3Delete.go index fd472e54..eb62da3b 100644 --- a/src/cmd/bucketS3Delete.go +++ b/src/cmd/bucketS3Delete.go @@ -22,6 +22,7 @@ func bucketS3DeleteCmd() *cmdBuilder.Cmd { Arg("bucketName"). StringFlag(accessKeyIdName, "", i18n.T(i18n.BucketS3AccessKeyId)). StringFlag(secretAccessKeyName, "", i18n.T(i18n.BucketS3SecretAccessKey)). + BoolFlag("confirm", false, i18n.T(i18n.ConfirmFlag)). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { uxBlocks := cmdData.UxBlocks @@ -36,15 +37,11 @@ func bucketS3DeleteCmd() *cmdBuilder.Cmd { bucketName := fmt.Sprintf("%s.%s", strings.ToLower(accessKeyId), cmdData.Args["bucketName"][0]) - confirm, err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.BucketDeleteConfirm, bucketName)) - if err != nil { - return err - } - - if !confirm { - // TODO - janhajek message - fmt.Println("you have to confirm it") - return nil + if !cmdData.Params.GetBool("confirm") { + err = YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.BucketDeleteConfirm, bucketName)) + if err != nil { + return err + } } uxBlocks.PrintLine(i18n.T(i18n.BucketDeleteDeletingDirect, bucketName)) diff --git a/src/cmd/bucketZeropsDelete.go b/src/cmd/bucketZeropsDelete.go index 560d92a8..50c7a6fc 100644 --- a/src/cmd/bucketZeropsDelete.go +++ b/src/cmd/bucketZeropsDelete.go @@ -19,6 +19,7 @@ func bucketZeropsDeleteCmd() *cmdBuilder.Cmd { Short(i18n.T(i18n.CmdBucketDelete)). ScopeLevel(cmdBuilder.Service). Arg("bucketName"). + BoolFlag("confirm", false, i18n.T(i18n.ConfirmFlag)). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { uxBlocks := cmdData.UxBlocks @@ -30,15 +31,11 @@ func bucketZeropsDeleteCmd() *cmdBuilder.Cmd { // TODO - janhajek duplicate bucketName := fmt.Sprintf("%s.%s", strings.ToLower(serviceId.Native()), cmdData.Args["bucketName"][0]) - confirm, err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.BucketDeleteConfirm, bucketName)) - if err != nil { - return err - } - - if !confirm { - // TODO - janhajek message - fmt.Println("you have to confirm it") - return nil + if !cmdData.Params.GetBool("confirm") { + err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.BucketDeleteConfirm, bucketName)) + if err != nil { + return err + } } uxBlocks.PrintLine(i18n.T(i18n.BucketDeleteDeletingZeropsApi, bucketName)) diff --git a/src/cmd/login.go b/src/cmd/login.go index d3a7c9dc..a6027278 100644 --- a/src/cmd/login.go +++ b/src/cmd/login.go @@ -8,13 +8,11 @@ import ( "github.com/zeropsio/zcli/src/cliStorage" "github.com/zeropsio/zcli/src/cmdBuilder" "github.com/zeropsio/zcli/src/constants" - "github.com/zeropsio/zcli/src/errorsx" "github.com/zeropsio/zcli/src/httpClient" "github.com/zeropsio/zcli/src/i18n" "github.com/zeropsio/zcli/src/region" "github.com/zeropsio/zcli/src/uxBlock" "github.com/zeropsio/zcli/src/zeropsRestApiClient" - "github.com/zeropsio/zerops-go/errorCode" ) func loginCmd() *cmdBuilder.Cmd { @@ -48,13 +46,7 @@ func loginCmd() *cmdBuilder.Cmd { output, err := response.Output() if err != nil { - return zeropsRestApiClient.CheckError( - err, - zeropsRestApiClient.CheckErrorCode( - errorCode.NotAuthorized, - errorsx.NewUserError(i18n.T(i18n.LoginIncorrectToken), err), - ), - ) + return err } _, err = cmdData.CliStorage.Update(func(data cliStorage.Data) cliStorage.Data { @@ -74,7 +66,7 @@ func loginCmd() *cmdBuilder.Cmd { func getLoginRegion( ctx context.Context, - uxBlocks *uxBlock.UxBlocks, + uxBlocks uxBlock.UxBlocks, regions []region.Data, selectedRegion string, ) (region.Data, error) { diff --git a/src/cmd/projectDelete.go b/src/cmd/projectDelete.go index 8c06e59b..8e41089d 100644 --- a/src/cmd/projectDelete.go +++ b/src/cmd/projectDelete.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "fmt" "github.com/zeropsio/zcli/src/cmdBuilder" "github.com/zeropsio/zcli/src/uxHelpers" @@ -17,16 +16,13 @@ func projectDeleteCmd() *cmdBuilder.Cmd { Short(i18n.T(i18n.CmdProjectDelete)). ScopeLevel(cmdBuilder.Project). Arg(cmdBuilder.ProjectArgName, cmdBuilder.OptionalArg()). + BoolFlag("confirm", false, i18n.T(i18n.ConfirmFlag)). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { - confirm, err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.ProjectDeleteConfirm, cmdData.Project.Name)) - if err != nil { - return err - } - - if !confirm { - // TODO - janhajek message - fmt.Println("you have to confirm it") - return nil + if !cmdData.Params.GetBool("confirm") { + err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.ProjectDeleteConfirm, cmdData.Project.Name)) + if err != nil { + return err + } } deleteProjectResponse, err := cmdData.RestApiClient.DeleteProject( @@ -49,9 +45,8 @@ func projectDeleteCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: processId, + F: uxHelpers.CheckZeropsProcess(processId, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.ProjectDeleting), ErrorMessageMessage: i18n.T(i18n.ProjectDeleting), SuccessMessage: i18n.T(i18n.ProjectDeleted), diff --git a/src/cmd/projectImport.go b/src/cmd/projectImport.go index 03def1e3..f5f59690 100644 --- a/src/cmd/projectImport.go +++ b/src/cmd/projectImport.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zcli/src/entity/repository" "github.com/zeropsio/zcli/src/i18n" "github.com/zeropsio/zcli/src/uxHelpers" "github.com/zeropsio/zcli/src/yamlReader" @@ -20,14 +21,35 @@ func projectImportCmd() *cmdBuilder.Cmd { Short(i18n.T(i18n.CmdProjectImport)). Long(i18n.T(i18n.CmdProjectImportLong)). Arg(projectImportArgName). + StringFlag("orgId", "", i18n.T(i18n.OrgIdFlag)). + StringFlag("workingDie", "./", i18n.T(i18n.BuildWorkingDir)). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { uxBlocks := cmdData.UxBlocks - // TODO - janhajek client via flag - // TODO - janhajek interactive selector of clients + orgId := uuid.ClientId(cmdData.Params.GetString("orgId")) + if orgId == "" { + orgs, err := repository.GetAllOrgs(ctx, cmdData.RestApiClient) + if err != nil { + return err + } + + if len(orgs) == 1 { + orgId = orgs[0].ID + } else { + selectedOrg, err := uxHelpers.PrintOrgSelector(ctx, uxBlocks, cmdData.RestApiClient) + if err != nil { + return err + } + + orgId = selectedOrg.ID + } + } - // TODO - janhajek config - yamlContent, err := yamlReader.ReadContent(uxBlocks, cmdData.Args[projectImportArgName][0], "./") + yamlContent, err := yamlReader.ReadContent( + uxBlocks, + cmdData.Args[projectImportArgName][0], + cmdData.Params.GetString("workingDir"), + ) if err != nil { return err } @@ -35,8 +57,7 @@ func projectImportCmd() *cmdBuilder.Cmd { importProjectResponse, err := cmdData.RestApiClient.PostProjectImport( ctx, body.ProjectImport{ - // TODO - janhajek client id - ClientId: uuid.ClientId(cmdData.Args[projectImportArgName][0]), + ClientId: orgId, Yaml: types.Text(yamlContent), }, ) @@ -53,7 +74,7 @@ func projectImportCmd() *cmdBuilder.Cmd { for _, service := range responseOutput.ServiceStacks { for _, process := range service.Processes { processes = append(processes, uxHelpers.Process{ - Id: process.Id, + F: uxHelpers.CheckZeropsProcess(process.Id, cmdData.RestApiClient), RunningMessage: service.Name.String() + ": " + process.ActionName.String(), ErrorMessageMessage: service.Name.String() + ": " + process.ActionName.String(), SuccessMessage: service.Name.String() + ": " + process.ActionName.String(), @@ -65,7 +86,7 @@ func projectImportCmd() *cmdBuilder.Cmd { uxBlocks.PrintLine(i18n.T(i18n.QueuedProcesses, len(processes))) uxBlocks.PrintLine(i18n.T(i18n.CoreServices)) - err = uxHelpers.ProcessCheckWithSpinner(ctx, cmdData.UxBlocks, cmdData.RestApiClient, processes) + err = uxHelpers.ProcessCheckWithSpinner(ctx, cmdData.UxBlocks, processes) if err != nil { return err } diff --git a/src/cmd/projectServiceImport.go b/src/cmd/projectServiceImport.go index cb1d7dc2..a916d25f 100644 --- a/src/cmd/projectServiceImport.go +++ b/src/cmd/projectServiceImport.go @@ -47,7 +47,7 @@ func projectServiceImportCmd() *cmdBuilder.Cmd { for _, service := range responseOutput.ServiceStacks { for _, process := range service.Processes { processes = append(processes, uxHelpers.Process{ - Id: process.Id, + F: uxHelpers.CheckZeropsProcess(process.Id, cmdData.RestApiClient), RunningMessage: service.Name.String() + ": " + process.ActionName.String(), ErrorMessageMessage: service.Name.String() + ": " + process.ActionName.String(), SuccessMessage: service.Name.String() + ": " + process.ActionName.String(), @@ -58,7 +58,7 @@ func projectServiceImportCmd() *cmdBuilder.Cmd { uxBlocks.PrintLine(i18n.T(i18n.ServiceCount, len(responseOutput.ServiceStacks))) uxBlocks.PrintLine(i18n.T(i18n.QueuedProcesses, len(processes))) - err = uxHelpers.ProcessCheckWithSpinner(ctx, cmdData.UxBlocks, cmdData.RestApiClient, processes) + err = uxHelpers.ProcessCheckWithSpinner(ctx, cmdData.UxBlocks, processes) if err != nil { return err } diff --git a/src/cmd/projectStart.go b/src/cmd/projectStart.go index 546fcdf5..1fb196dc 100644 --- a/src/cmd/projectStart.go +++ b/src/cmd/projectStart.go @@ -36,9 +36,8 @@ func projectStartCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: processId, + F: uxHelpers.CheckZeropsProcess(processId, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.ProjectStarting), ErrorMessageMessage: i18n.T(i18n.ProjectStarting), SuccessMessage: i18n.T(i18n.ProjectStarted), diff --git a/src/cmd/projectStop.go b/src/cmd/projectStop.go index 086de9ae..8aeb7ef2 100644 --- a/src/cmd/projectStop.go +++ b/src/cmd/projectStop.go @@ -37,9 +37,8 @@ func projectStopCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: processId, + F: uxHelpers.CheckZeropsProcess(processId, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.ProjectStopping), ErrorMessageMessage: i18n.T(i18n.ProjectStopping), SuccessMessage: i18n.T(i18n.ProjectStopped), diff --git a/src/cmd/serviceDelete.go b/src/cmd/serviceDelete.go index f1357892..a09e45b7 100644 --- a/src/cmd/serviceDelete.go +++ b/src/cmd/serviceDelete.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "fmt" "github.com/zeropsio/zcli/src/cmdBuilder" "github.com/zeropsio/zcli/src/i18n" @@ -16,16 +15,13 @@ func serviceDeleteCmd() *cmdBuilder.Cmd { Short(i18n.T(i18n.CmdServiceDelete)). ScopeLevel(cmdBuilder.Service). Arg(cmdBuilder.ServiceArgName, cmdBuilder.OptionalArg()). + BoolFlag("confirm", false, i18n.T(i18n.ConfirmFlag)). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { - confirm, err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.ServiceDeleteConfirm, cmdData.Service.Name)) - if err != nil { - return err - } - - if !confirm { - // TODO - janhajek message - fmt.Println("you have to confirm it") - return nil + if !cmdData.Params.GetBool("confirm") { + err := YesNoPromptDestructive(ctx, cmdData, i18n.T(i18n.ServiceDeleteConfirm, cmdData.Service.Name)) + if err != nil { + return err + } } deleteServiceResponse, err := cmdData.RestApiClient.DeleteServiceStack( @@ -48,9 +44,8 @@ func serviceDeleteCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: processId, + F: uxHelpers.CheckZeropsProcess(processId, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.ServiceDeleting), ErrorMessageMessage: i18n.T(i18n.ServiceDeleting), SuccessMessage: i18n.T(i18n.ServiceDeleted), diff --git a/src/cmd/serviceDeploy.go b/src/cmd/serviceDeploy.go index f1b7a0c7..1e470713 100644 --- a/src/cmd/serviceDeploy.go +++ b/src/cmd/serviceDeploy.go @@ -57,21 +57,33 @@ func serviceDeployCmd() *cmdBuilder.Cmd { uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployCreatingPackageStart)) - files, err := arch.FindFilesByRules(cmdData.Params.GetString("workingDir"), cmdData.Args["pathToFileOrDir"]) + files, err := arch.FindFilesByRules( + uxBlocks, + cmdData.Params.GetString("workingDir"), + cmdData.Args["pathToFileOrDir"], + ) if err != nil { return err } - reader, writer := io.Pipe() - defer reader.Close() + var reader io.Reader + pipeReader, writer := io.Pipe() + defer pipeReader.Close() + reader = pipeReader tarErrChan := make(chan error, 1) go arch.TarFiles(writer, files, tarErrChan) - r, err := savePackage(cmdData.Params.GetString("archiveFilePath"), reader) - if err != nil { - return err + if cmdData.Params.GetString("archiveFilePath") != "" { + packageFile, err := openPackageFile( + cmdData.Params.GetString("archiveFilePath"), + cmdData.Params.GetString("workingDir"), + ) + if err != nil { + return err + } + reader = io.TeeReader(reader, packageFile) } appVersion, err := createAppVersion( @@ -89,25 +101,37 @@ func serviceDeployCmd() *cmdBuilder.Cmd { HttpTimeout: time.Minute * 15, }) - // TODO - janhajek spinner? - uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployUploadingPackageStart)) - if err := packageUpload(httpClient, appVersion.UploadUrl.String(), r); err != nil { - // if an error occurred while packing the app, return that error - select { - case err := <-tarErrChan: - return err - default: - return err - } - } - - // wait for packing and saving to finish (should already be done after the package upload has finished) - if tarErr := <-tarErrChan; tarErr != nil { - return tarErr + err = uxHelpers.ProcessCheckWithSpinner( + ctx, + cmdData.UxBlocks, + []uxHelpers.Process{{ + F: func(ctx context.Context) error { + if err := packageUpload(httpClient, appVersion.UploadUrl.String(), reader); err != nil { + // if an error occurred while packing the app, return that error + select { + case err := <-tarErrChan: + return err + default: + return err + } + } + + // wait for packing and saving to finish (should already be done after the package upload has finished) + if tarErr := <-tarErrChan; tarErr != nil { + return tarErr + } + + return nil + }, + RunningMessage: i18n.T(i18n.BuildDeployUploadingPackageStart), + ErrorMessageMessage: i18n.T(i18n.BuildDeployUploadPackageFailed), + SuccessMessage: i18n.T(i18n.BuildDeployUploadingPackageDone), + }}, + ) + if err != nil { + return err } - uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployUploadingPackageDone)) - uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployDeployingStart)) deployResponse, err := cmdData.RestApiClient.PutAppVersionDeploy( @@ -131,14 +155,14 @@ func serviceDeployCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: deployProcess.Id, + F: uxHelpers.CheckZeropsProcess(deployProcess.Id, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.PushRunning), ErrorMessageMessage: i18n.T(i18n.PushRunning), SuccessMessage: i18n.T(i18n.PushFinished), }}, ) + if err != nil { return err } @@ -147,7 +171,7 @@ func serviceDeployCmd() *cmdBuilder.Cmd { }) } -func getValidConfigContent(uxBlocks *uxBlock.UxBlocks, workingDir string, zeropsYamlPath string) ([]byte, error) { +func getValidConfigContent(uxBlocks uxBlock.UxBlocks, workingDir string, zeropsYamlPath string) ([]byte, error) { workingDir, err := filepath.Abs(workingDir) if err != nil { return nil, err @@ -162,11 +186,9 @@ func getValidConfigContent(uxBlocks *uxBlock.UxBlocks, workingDir string, zerops zeropsYamlStat, err := os.Stat(zeropsYamlPath) if err != nil { if os.IsNotExist(err) { - if zeropsYamlPath != "" { - return nil, errors.New(i18n.T(i18n.BuildDeployZeropsYamlNotFound)) - } + return nil, errors.New(i18n.T(i18n.BuildDeployZeropsYamlNotFound, zeropsYamlPath)) } - return nil, nil + return nil, err } uxBlocks.PrintLine(i18n.T(i18n.BuildDeployZeropsYamlFound, zeropsYamlPath)) @@ -192,7 +214,6 @@ func validateZeropsYamlContent( service *entity.Service, yamlContent []byte, ) error { - resp, err := restApiClient.PostServiceStackZeropsYamlValidation(ctx, body.ZeropsYamlValidation{ Name: service.Name, ServiceStackTypeId: service.ServiceTypeId, diff --git a/src/cmd/servicePush.go b/src/cmd/servicePush.go index fef91438..26ef3803 100644 --- a/src/cmd/servicePush.go +++ b/src/cmd/servicePush.go @@ -56,16 +56,24 @@ func servicePushCmd() *cmdBuilder.Cmd { return err } - reader, writer := io.Pipe() - defer reader.Close() + var reader io.Reader + pipeReader, writer := io.Pipe() + defer pipeReader.Close() + reader = pipeReader tarErrChan := make(chan error, 1) go arch.TarFiles(writer, files, tarErrChan) - r, err := savePackage(cmdData.Params.GetString("archiveFilePath"), reader) - if err != nil { - return err + if cmdData.Params.GetString("archiveFilePath") != "" { + packageFile, err := openPackageFile( + cmdData.Params.GetString("archiveFilePath"), + cmdData.Params.GetString("workingDir"), + ) + if err != nil { + return err + } + reader = io.TeeReader(reader, packageFile) } appVersion, err := createAppVersion( @@ -79,29 +87,41 @@ func servicePushCmd() *cmdBuilder.Cmd { } // TODO - janhajek merge with sdk client - HttpClient := httpClient.New(ctx, httpClient.Config{ + httpClient := httpClient.New(ctx, httpClient.Config{ HttpTimeout: time.Minute * 15, }) - // TODO - janhajek spinner? - uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployUploadingPackageStart)) - if err := packageUpload(HttpClient, appVersion.UploadUrl.String(), r); err != nil { - // if an error occurred while packing the app, return that error - select { - case err := <-tarErrChan: - return err - default: - return err - } - } - - // wait for packing and saving to finish (should already be done after the package upload has finished) - if tarErr := <-tarErrChan; tarErr != nil { - return tarErr + err = uxHelpers.ProcessCheckWithSpinner( + ctx, + cmdData.UxBlocks, + []uxHelpers.Process{{ + F: func(ctx context.Context) error { + if err := packageUpload(httpClient, appVersion.UploadUrl.String(), reader); err != nil { + // if an error occurred while packing the app, return that error + select { + case err := <-tarErrChan: + return err + default: + return err + } + } + + // wait for packing and saving to finish (should already be done after the package upload has finished) + if tarErr := <-tarErrChan; tarErr != nil { + return tarErr + } + + return nil + }, + RunningMessage: i18n.T(i18n.BuildDeployUploadingPackageStart), + ErrorMessageMessage: i18n.T(i18n.BuildDeployUploadPackageFailed), + SuccessMessage: i18n.T(i18n.BuildDeployUploadingPackageDone), + }}, + ) + if err != nil { + return err } - uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployUploadingPackageDone)) - uxBlocks.PrintInfoLine(i18n.T(i18n.BuildDeployCreatingPackageDone)) if cmdData.Params.GetString("archiveFilePath") != "" { @@ -136,9 +156,8 @@ func servicePushCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: deployProcess.Id, + F: uxHelpers.CheckZeropsProcess(deployProcess.Id, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.PushRunning), ErrorMessageMessage: i18n.T(i18n.PushRunning), SuccessMessage: i18n.T(i18n.PushFinished), @@ -181,31 +200,34 @@ func createAppVersion( return appVersion, nil } -func savePackage(archiveFilePath string, reader io.Reader) (io.Reader, error) { - if archiveFilePath == "" { - return reader, nil +func openPackageFile(archiveFilePath string, workingDir string) (*os.File, error) { + workingDir, err := filepath.Abs(workingDir) + if err != nil { + return nil, err } + archiveFilePath = filepath.Join(workingDir, archiveFilePath) + filePath, err := filepath.Abs(archiveFilePath) if err != nil { - return reader, err + return nil, err } // check if the target file exists _, err = os.Stat(filePath) if err != nil && !os.IsNotExist(err) { - return reader, err + return nil, err } if err == nil { - return reader, errors.Errorf(i18n.T(i18n.ArchClientFileAlreadyExists), archiveFilePath) + return nil, errors.Errorf(i18n.T(i18n.ArchClientFileAlreadyExists), archiveFilePath) } file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0660) if err != nil { - return reader, err + return nil, err } - return io.TeeReader(reader, file), nil + return file, nil } func packageUpload(client *httpClient.Handler, uploadUrl string, reader io.Reader) error { diff --git a/src/cmd/serviceStart.go b/src/cmd/serviceStart.go index 0562e2f5..bbf79b9c 100644 --- a/src/cmd/serviceStart.go +++ b/src/cmd/serviceStart.go @@ -36,9 +36,8 @@ func serviceStartCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: processId, + F: uxHelpers.CheckZeropsProcess(processId, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.ServiceStarting), ErrorMessageMessage: i18n.T(i18n.ServiceStarting), SuccessMessage: i18n.T(i18n.ServiceStarted), diff --git a/src/cmd/serviceStop.go b/src/cmd/serviceStop.go index fd8cd272..f7d65e2e 100644 --- a/src/cmd/serviceStop.go +++ b/src/cmd/serviceStop.go @@ -37,9 +37,8 @@ func serviceStopCmd() *cmdBuilder.Cmd { err = uxHelpers.ProcessCheckWithSpinner( ctx, cmdData.UxBlocks, - cmdData.RestApiClient, []uxHelpers.Process{{ - Id: processId, + F: uxHelpers.CheckZeropsProcess(processId, cmdData.RestApiClient), RunningMessage: i18n.T(i18n.ServiceStopping), ErrorMessageMessage: i18n.T(i18n.ServiceStopping), SuccessMessage: i18n.T(i18n.ServiceStopped), diff --git a/src/cmd/uxHelpers.go b/src/cmd/uxHelpers.go index 4fb3429c..9b5e9888 100644 --- a/src/cmd/uxHelpers.go +++ b/src/cmd/uxHelpers.go @@ -3,35 +3,22 @@ package cmd import ( "context" + "github.com/pkg/errors" "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zcli/src/i18n" ) -func YesNoPromptDestructive(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData, message string) (bool, error) { - if cmdData.QuietMode == cmdBuilder.QuietModeConfirmNothing { - return true, nil - } - +func YesNoPromptDestructive(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData, message string) error { // TODO - janhajek translate - choices := []string{"no", "yes"} + choices := []string{"NO", "YES"} choice, err := cmdData.UxBlocks.Prompt(ctx, message, choices) if err != nil { - return false, err + return err } - return choice == 1, nil -} - -func YesNoPromptNonDestructive(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData, message string) (bool, error) { - if cmdData.QuietMode == cmdBuilder.QuietModeConfirmNothing || cmdData.QuietMode == cmdBuilder.QuietModeConfirmOnlyDestructive { - return true, nil - } - - // TODO - janhajek translate - choices := []string{"no", "yes"} - choice, err := cmdData.UxBlocks.Prompt(ctx, message, choices) - if err != nil { - return false, err + if choice == 0 { + return errors.New(i18n.T(i18n.DestructiveOperationConfirmationFailed)) } - return choice == 1, nil + return nil } diff --git a/src/cmdBuilder/cmdBuilderCreateRunFunc.go b/src/cmdBuilder/cmdBuilderCreateRunFunc.go index a161e3b3..bfa57258 100644 --- a/src/cmdBuilder/cmdBuilderCreateRunFunc.go +++ b/src/cmdBuilder/cmdBuilderCreateRunFunc.go @@ -57,8 +57,7 @@ func (r *CmdParamReader) GetBool(name string) bool { type GuestCmdData struct { CliStorage *cliStorage.Handler - UxBlocks *uxBlock.UxBlocks - QuietMode QuietMode + UxBlocks uxBlock.UxBlocks Args map[string][]string Params ParamsReader } @@ -106,11 +105,6 @@ func (b *CmdBuilder) createCmdRunFunc(cmd *Cmd, params *params.Handler) func(*co return err } - quietMode, err := getQuietMode(isTerminal) - if err != nil { - return err - } - cliStorage, err := createCliStorage() if err != nil { return err @@ -124,7 +118,6 @@ func (b *CmdBuilder) createCmdRunFunc(cmd *Cmd, params *params.Handler) func(*co guestCmdData := &GuestCmdData{ CliStorage: cliStorage, UxBlocks: uxBlocks, - QuietMode: quietMode, Args: argsMap, Params: newCmdParamReader(cobraCmd, params), } @@ -199,7 +192,7 @@ func convertArgs(cmd *Cmd, args []string) (map[string][]string, error) { return argsMap, nil } -func printError(err error, uxBlocks *uxBlock.UxBlocks) { +func printError(err error, uxBlocks uxBlock.UxBlocks) { uxBlocks.PrintDebugLine(fmt.Sprintf("error: %+v", err)) if userErr := errorsx.AsUserError(err); userErr != nil { @@ -224,20 +217,6 @@ func printError(err error, uxBlocks *uxBlock.UxBlocks) { uxBlocks.PrintErrorLine(err.Error()) } -func getQuietMode(isTerminal bool) (QuietMode, error) { - if !isTerminal { - return QuietModeConfirmNothing, nil - } - - switch QuietMode(QuietModeFlag) { - case QuietModeConfirmNothing, QuietModeConfirmAll, QuietModeConfirmOnlyDestructive: - return QuietMode(QuietModeFlag), nil - default: - // TODO - janhajek message - return 0, errors.New("unknown quiet mode") - } -} - func isTerminal() (bool, error) { switch TerminalMode(TerminalFlag) { case TerminalModeAuto: diff --git a/src/cmdBuilder/cmdBuilderExecuteRootCmd.go b/src/cmdBuilder/cmdBuilderExecuteRootCmd.go index 1db123f2..94718841 100644 --- a/src/cmdBuilder/cmdBuilderExecuteRootCmd.go +++ b/src/cmdBuilder/cmdBuilderExecuteRootCmd.go @@ -9,16 +9,6 @@ import ( "github.com/zeropsio/zcli/src/params" ) -type QuietMode int - -const ( - QuietModeConfirmAll QuietMode = iota - QuietModeConfirmOnlyDestructive - QuietModeConfirmNothing -) - -var QuietModeFlag int - type TerminalMode string const ( @@ -73,8 +63,6 @@ func createRootCommand() *cobra.Command { // TODO - janhajek add a dynamic help for subcommands rootCmd.Flags().BoolP("help", "h", false, i18n.T(i18n.DisplayHelp)+i18n.T(i18n.GroupHelp)) - - rootCmd.PersistentFlags().IntVar(&QuietModeFlag, "quiet", int(QuietModeConfirmAll), i18n.T(i18n.QuietModeFlag)) rootCmd.PersistentFlags().StringVar(&TerminalFlag, "terminal", "auto", i18n.T(i18n.TerminalFlag)) return rootCmd diff --git a/src/cmdBuilder/dependencyTreeProject.go b/src/cmdBuilder/dependencyTreeProject.go index defaecdf..43f6eab2 100644 --- a/src/cmdBuilder/dependencyTreeProject.go +++ b/src/cmdBuilder/dependencyTreeProject.go @@ -18,8 +18,7 @@ type project struct { const ProjectArgName = "projectId" func (p *project) AddCommandFlags(cmd *Cmd) { - // TODO - janhajek translation - cmd.StringFlag(ProjectArgName, "", "Project id") + cmd.StringFlag(ProjectArgName, "", i18n.T(i18n.ProjectIdFlag)) } func (p *project) LoadSelectedScope(ctx context.Context, cmd *Cmd, cmdData *LoggedUserCmdData) error { diff --git a/src/cmdBuilder/dependencyTreeService.go b/src/cmdBuilder/dependencyTreeService.go index 1d5c82ba..7d5083d0 100644 --- a/src/cmdBuilder/dependencyTreeService.go +++ b/src/cmdBuilder/dependencyTreeService.go @@ -15,14 +15,13 @@ type service struct { } const ServiceArgName = "serviceIdOrName" -const ServiceFlagName = "serviceId" +const serviceFlagName = "serviceId" func (s *service) AddCommandFlags(cmd *Cmd) { - // TODO - janhajek translation - cmd.StringFlag(ServiceFlagName, "", "Service id") + cmd.StringFlag(serviceFlagName, "", i18n.T(i18n.ServiceIdFlag)) } -func (s *service) LoadSelectedScope(ctx context.Context, cmd *Cmd, cmdData *LoggedUserCmdData) error { +func (s *service) LoadSelectedScope(ctx context.Context, _ *Cmd, cmdData *LoggedUserCmdData) error { infoText := i18n.SelectedService var service *entity.Service var err error @@ -35,7 +34,7 @@ func (s *service) LoadSelectedScope(ctx context.Context, cmd *Cmd, cmdData *Logg } // service id is passed as a flag - if serviceId := cmdData.Params.GetString(ServiceFlagName); serviceId != "" { + if serviceId := cmdData.Params.GetString(serviceFlagName); serviceId != "" { service, err = repository.GetServiceById( ctx, cmdData.RestApiClient, diff --git a/src/entity/org.go b/src/entity/org.go new file mode 100644 index 00000000..279362b7 --- /dev/null +++ b/src/entity/org.go @@ -0,0 +1,13 @@ +package entity + +import ( + "github.com/zeropsio/zerops-go/types" + "github.com/zeropsio/zerops-go/types/enum" + "github.com/zeropsio/zerops-go/types/uuid" +) + +type Org struct { + ID uuid.ClientId + Role enum.ClientUserLightRoleCodeEnum + Name types.String +} diff --git a/src/entity/repository/org.go b/src/entity/repository/org.go new file mode 100644 index 00000000..5954c9fb --- /dev/null +++ b/src/entity/repository/org.go @@ -0,0 +1,39 @@ +package repository + +import ( + "context" + + "github.com/zeropsio/zcli/src/entity" + "github.com/zeropsio/zcli/src/zeropsRestApiClient" + "github.com/zeropsio/zerops-go/dto/output" +) + +func GetAllOrgs( + ctx context.Context, + restApiClient *zeropsRestApiClient.Handler, +) ([]entity.Org, error) { + response, err := restApiClient.GetUserInfo(ctx) + if err != nil { + return nil, err + } + + resOutput, err := response.Output() + if err != nil { + return nil, err + } + + orgs := make([]entity.Org, 0, len(resOutput.ClientUserList)) + for _, client := range resOutput.ClientUserList { + orgs = append(orgs, orgFromEsSearch(client)) + } + + return orgs, nil +} + +func orgFromEsSearch(esClientUser output.ClientUserExtra) entity.Org { + return entity.Org{ + ID: esClientUser.ClientId, + Name: esClientUser.Client.AccountName, + Role: esClientUser.RoleCode, + } +} diff --git a/src/entity/repository/project.go b/src/entity/repository/project.go index 0d2b4ff4..a20563cd 100644 --- a/src/entity/repository/project.go +++ b/src/entity/repository/project.go @@ -4,12 +4,9 @@ import ( "context" "github.com/zeropsio/zcli/src/entity" - "github.com/zeropsio/zcli/src/errorsx" - "github.com/zeropsio/zcli/src/i18n" "github.com/zeropsio/zcli/src/zeropsRestApiClient" "github.com/zeropsio/zerops-go/dto/input/path" "github.com/zeropsio/zerops-go/dto/output" - "github.com/zeropsio/zerops-go/errorCode" "github.com/zeropsio/zerops-go/types/uuid" ) @@ -25,17 +22,7 @@ func GetProjectById( projectOutput, err := projectResponse.Output() if err != nil { - return nil, zeropsRestApiClient.CheckError( - err, - zeropsRestApiClient.CheckInvalidUserInput( - "id", - errorsx.NewUserError(i18n.T(i18n.ProjectIdInvalidFormat), err), - ), - zeropsRestApiClient.CheckErrorCode( - errorCode.ProjectNotFound, - errorsx.NewUserError(i18n.T(i18n.ProjectNotFound, projectId), err), - ), - ) + return nil, err } project := projectFromApiOutput(projectOutput) @@ -51,14 +38,14 @@ func GetAllProjects( return nil, err } - i, err := info.Output() + output, err := info.Output() if err != nil { return nil, err } var projects []entity.Project - for _, b := range i.ClientUserList { - response, err := restApiClient.GetProjectsByClient(ctx, b.ClientId) + for _, clientUser := range output.ClientUserList { + response, err := restApiClient.GetProjectsByClient(ctx, clientUser.ClientId) if err != nil { return nil, err } diff --git a/src/entity/repository/service.go b/src/entity/repository/service.go index 158b001f..fb61ecd7 100644 --- a/src/entity/repository/service.go +++ b/src/entity/repository/service.go @@ -5,11 +5,9 @@ import ( "github.com/zeropsio/zcli/src/entity" "github.com/zeropsio/zcli/src/errorsx" - "github.com/zeropsio/zcli/src/i18n" "github.com/zeropsio/zcli/src/zeropsRestApiClient" "github.com/zeropsio/zerops-go/dto/input/path" "github.com/zeropsio/zerops-go/dto/output" - "github.com/zeropsio/zerops-go/errorCode" "github.com/zeropsio/zerops-go/types" "github.com/zeropsio/zerops-go/types/uuid" ) @@ -45,17 +43,7 @@ func GetServiceById( serviceOutput, err := serviceResponse.Output() if err != nil { - return nil, zeropsRestApiClient.CheckError( - err, - zeropsRestApiClient.CheckInvalidUserInput( - "id", - errorsx.NewUserError(i18n.T(i18n.ServiceIdInvalidFormat), err), - ), - zeropsRestApiClient.CheckErrorCode( - errorCode.ServiceStackNotFound, - errorsx.NewUserError(i18n.T(i18n.ServiceNotFound, serviceId), err), - ), - ) + return nil, err } service := serviceFromApiOutput(serviceOutput) @@ -78,17 +66,7 @@ func GetServiceByName( serviceOutput, err := serviceResponse.Output() if err != nil { - return nil, zeropsRestApiClient.CheckError( - err, - zeropsRestApiClient.CheckInvalidUserInput( - "id", - errorsx.NewUserError(i18n.T(i18n.ServiceIdInvalidFormat), err), - ), - zeropsRestApiClient.CheckErrorCode( - errorCode.ServiceStackNotFound, - errorsx.NewUserError(i18n.T(i18n.ServiceNotFound, serviceName), err), - ), - ) + return nil, err } service := serviceFromApiOutput(serviceOutput) diff --git a/src/i18n/en.go b/src/i18n/en.go index 1765756f..3b96d7c2 100644 --- a/src/i18n/en.go +++ b/src/i18n/en.go @@ -76,7 +76,7 @@ var en = map[string]string{ BuildArchiveFilePath: "If set, zCLI creates a tar.gz archive with the application code in the required path relative\nto the working directory. By default, no archive is created.", ZeropsYamlLocation: "Sets a custom path to the zerops.yml file relative to the working directory. By default zCLI\nlooks for zerops.yml in the working directory.", UploadGitFolder: "If set, zCLI the .git folder is also uploaded. By default, the .git folder is ignored.", - ClientId: "If you have access to more than one client, you must specify the client ID for which the\nproject is to be created.", + OrgIdFlag: "If you have access to more than one organization, you must specify the org ID for which the\nproject is to be created.", LogLimitFlag: "How many of the most recent log messages will be returned. Allowed interval is <1;1000>.\nDefault value = 100.", LogMinSeverityFlag: "Returns log messages with requested or higher severity. Set either severity number in the interval\n<0;7> or one of following severity codes:\nEMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG.", LogMsgTypeFlag: "Select either APPLICATION or WEBSERVER log messages to be returned. Default value = APPLICATION.", @@ -87,6 +87,9 @@ var en = map[string]string{ QuietModeFlag: "In terminal mode some operations need to be confirmed. 0 = Everything needs to be confirmed, 1 = Non-destructive operations need to be confirmed, 2 = Quiet mode, nothing needs to be confirmed. Default value is 0.", TerminalFlag: "If enabled provides a rich UI to communicate with a user. Possible values: auto, enabled, disabled. Default value is auto.", LogFilePathFlag: "Path to a log file. Default value: %s.", + ConfirmFlag: "If set, zCLI will not ask for confirmation of destructive operations.", + ServiceIdFlag: "If you have access to more than one service, you must specify the service ID for which the\ncommand is to be executed.", + ProjectIdFlag: "If you have access to more than one project, you must specify the project ID for which the\ncommand is to be executed.", // prompt PromptEnterZeropsServiceName: "Enter hostname of zerops service", @@ -196,7 +199,7 @@ var en = map[string]string{ BuildDeployZeropsYamlEmpty: "config file zerops.yml is empty", BuildDeployZeropsYamlTooLarge: "max. size of zerops.yml is 10 KB", BuildDeployZeropsYamlFound: "File zerops.yml found. Path: %s.", - BuildDeployZeropsYamlNotFound: "File zerops.yml not found", + BuildDeployZeropsYamlNotFound: "File zerops.yml not found. Expected path: %s.", // S3 BucketGenericXAmzAcl: "Defines one of predefined grants, known as canned ACLs.\nValid values are: private, public-read, public-read-write, authenticated-read.", @@ -241,17 +244,20 @@ more info: https://docs.zerops.io/documentation/cli/authorization.html`, ServiceSelectorListEmpty: "Project doesn't have any services yet. Create a new service using `zcli service import` command", ServiceSelectorPrompt: "Please, select a service", ServiceSelectorOutOfRangeError: "We couldn't find a service with the index you entered. Please, try again or contact our support team.", + OrgSelectorListEmpty: "You don't belong to any organization yet. Please, contact our support team.", + OrgSelectorPrompt: "Please, select an org", + OrgSelectorOutOfRangeError: "We couldn't find an org with the index you entered. Please, try again or contact our support team.", + SelectorAllowedOnlyInTerminal: "Interactive selection can be used only in terminal mode. Use command flags to specify missing parameters.", + PromptAllowedOnlyInTerminal: "Interactive prompt can be used only in terminal mode. Use --confirm=true flag to confirm it", // Global SelectedProject: "Selected project: %s", SelectedService: "Selected service: %s", ScopedProject: "Scoped project: %s", ScopedProjectNotFound: "Scoped project wasn't found, Select a different project using `zcli scope project` command.", - ScopedServiceNotFound: "Scoped service wasn't found, Select a different service using `zcli scope service` command.", - - ProjectIdInvalidFormat: "Invalid format of project ID. ID must have 22 characters.", - ProjectNotFound: "Project [%s] wasn't found", ServiceIdInvalidFormat: "Invalid format of service ID. ID must have 22 characters.", ServiceNotFound: "Service [%s] wasn't found", + + DestructiveOperationConfirmationFailed: "You have to confirm a destructive operation.", } diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go index 3a9a75ad..a5cc24b3 100644 --- a/src/i18n/i18n.go +++ b/src/i18n/i18n.go @@ -89,7 +89,7 @@ const ( BuildArchiveFilePath = "BuildArchiveFilePath" ZeropsYamlLocation = "ZeropsYamlLocation" UploadGitFolder = "UploadGitFolder" - ClientId = "ClientId" + OrgIdFlag = "OrgIdFlag" LogLimitFlag = "LogLimitFlag" LogMinSeverityFlag = "LogMinSeverityFlag" LogMsgTypeFlag = "LogMsgTypeFlag" @@ -100,6 +100,9 @@ const ( QuietModeFlag = "QuietModeFlag" TerminalFlag = "TerminalFlag" LogFilePathFlag = "LogFilePathFlag" + ConfirmFlag = "ConfirmFlag" + ServiceIdFlag = "ServiceIdFlag" + ProjectIdFlag = "ProjectIdFlag" // prompt PromptEnterZeropsServiceName = "PromptEnterZeropsServiceName" @@ -252,8 +255,13 @@ const ( ServiceSelectorListEmpty = "ServiceSelectorListEmpty" ServiceSelectorPrompt = "ServiceSelectorPrompt" ServiceSelectorOutOfRangeError = "ServiceSelectorOutOfRangeError" + OrgSelectorListEmpty = "OrgSelectorListEmpty" + OrgSelectorPrompt = "OrgSelectorPrompt" + OrgSelectorOutOfRangeError = "OrgSelectorOutOfRangeError" + SelectorAllowedOnlyInTerminal = "SelectorAllowedOnlyInTerminal" + PromptAllowedOnlyInTerminal = "PromptAllowedOnlyInTerminal" - // General + // Global SelectedProject = "SelectedProject" SelectedService = "SelectedService" ScopedProject = "ScopedProject" @@ -265,4 +273,6 @@ const ( ServiceIdInvalidFormat = "ServiceIdInvalidFormat" ServiceNotFound = "ServiceNotFound" + + DestructiveOperationConfirmationFailed = "DestructiveOperationConfirmationFailed" ) diff --git a/src/uxBlock/blocks.go b/src/uxBlock/blocks.go index 9f9fb7a1..b8f9111e 100644 --- a/src/uxBlock/blocks.go +++ b/src/uxBlock/blocks.go @@ -7,12 +7,36 @@ import ( "github.com/zeropsio/zcli/src/logger" ) -type UxBlocks struct { +//go:generate go run --mod=mod github.com/golang/mock/mockgen -source=$GOFILE -destination=$PWD/mocks/$GOFILE -package=mocks + +type UxBlocks interface { + PrintLine(values ...interface{}) + PrintSuccessLine(values ...string) + PrintInfoLine(values ...string) + PrintWarningLine(values ...string) + PrintErrorLine(values ...string) + PrintDebugLine(args ...interface{}) + Table(body *TableBody, auxOptions ...TableOption) + Select(ctx context.Context, tableBody *TableBody, auxOptions ...SelectOption) ([]int, error) + Prompt( + ctx context.Context, + message string, + choices []string, + auxOptions ...PromptOption, + ) (int, error) + RunSpinners(ctx context.Context, spinners []*Spinner, auxOptions ...SpinnerOption) func() +} + +type uxBlocks struct { isTerminal bool outputLogger logger.Logger debugFileLogger logger.Logger - // TODO - janhajek comment + // ctxCancel is used to cancel the context of the command. + // Bubbles package that we use to render graphic components steals the signal handler. + // In case that I want to cancel the context of a running component, e.g. spinner, the main context is not canceled. + // Therefore, we need to pass the cancel function to the uxBlocks. + // If the graphic component is canceled, we cancel the main context. ctxCancel context.CancelFunc } @@ -21,13 +45,13 @@ func NewBlock( debugFileLogger logger.Logger, isTerminal bool, ctxCancel context.CancelFunc, -) *UxBlocks { +) *uxBlocks { // safety check if ctxCancel == nil { ctxCancel = func() {} } - return &UxBlocks{ + return &uxBlocks{ outputLogger: outputLogger, debugFileLogger: debugFileLogger, isTerminal: isTerminal, diff --git a/src/uxBlock/logs.go b/src/uxBlock/logs.go index f9b5e1f6..3663192b 100644 --- a/src/uxBlock/logs.go +++ b/src/uxBlock/logs.go @@ -1,40 +1,40 @@ package uxBlock -func (b *UxBlocks) PrintLine(values ...interface{}) { +func (b *uxBlocks) PrintLine(values ...interface{}) { b.info(values...) } -func (b *UxBlocks) PrintSuccessLine(values ...string) { +func (b *uxBlocks) PrintSuccessLine(values ...string) { b.info(SuccessIcon, successColor.SetString(values...)) } -func (b *UxBlocks) PrintInfoLine(values ...string) { +func (b *uxBlocks) PrintInfoLine(values ...string) { b.info(InfoIcon, infoColor.SetString(values...)) } -func (b *UxBlocks) PrintWarningLine(values ...string) { +func (b *uxBlocks) PrintWarningLine(values ...string) { b.warning(WarningIcon, warningColor.SetString(values...)) } -func (b *UxBlocks) PrintErrorLine(values ...string) { +func (b *uxBlocks) PrintErrorLine(values ...string) { b.error(ErrorIcon, errorColor.SetString(values...)) } -func (b *UxBlocks) PrintDebugLine(args ...interface{}) { +func (b *uxBlocks) PrintDebugLine(args ...interface{}) { b.debugFileLogger.Debug(NewLine(args...).DisableStyle()) } -func (b *UxBlocks) info(args ...interface{}) { +func (b *uxBlocks) info(args ...interface{}) { b.outputLogger.Info(NewLine(args...)) b.debugFileLogger.Info(NewLine(args...).DisableStyle()) } -func (b *UxBlocks) warning(args ...interface{}) { +func (b *uxBlocks) warning(args ...interface{}) { b.outputLogger.Warning(NewLine(args...)) b.debugFileLogger.Warning(NewLine(args...).DisableStyle()) } -func (b *UxBlocks) error(args ...interface{}) { +func (b *uxBlocks) error(args ...interface{}) { b.outputLogger.Error(NewLine(args...)) b.debugFileLogger.Error(NewLine(args...).DisableStyle()) } diff --git a/src/uxBlock/mocks/blocks.go b/src/uxBlock/mocks/blocks.go new file mode 100644 index 00000000..a35e232a --- /dev/null +++ b/src/uxBlock/mocks/blocks.go @@ -0,0 +1,207 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: blocks.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + uxBlock "github.com/zeropsio/zcli/src/uxBlock" + reflect "reflect" +) + +// MockUxBlocks is a mock of UxBlocks interface +type MockUxBlocks struct { + ctrl *gomock.Controller + recorder *MockUxBlocksMockRecorder +} + +// MockUxBlocksMockRecorder is the mock recorder for MockUxBlocks +type MockUxBlocksMockRecorder struct { + mock *MockUxBlocks +} + +// NewMockUxBlocks creates a new mock instance +func NewMockUxBlocks(ctrl *gomock.Controller) *MockUxBlocks { + mock := &MockUxBlocks{ctrl: ctrl} + mock.recorder = &MockUxBlocksMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockUxBlocks) EXPECT() *MockUxBlocksMockRecorder { + return m.recorder +} + +// PrintLine mocks base method +func (m *MockUxBlocks) PrintLine(values ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range values { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "PrintLine", varargs...) +} + +// PrintLine indicates an expected call of PrintLine +func (mr *MockUxBlocksMockRecorder) PrintLine(values ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrintLine", reflect.TypeOf((*MockUxBlocks)(nil).PrintLine), values...) +} + +// PrintSuccessLine mocks base method +func (m *MockUxBlocks) PrintSuccessLine(values ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range values { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "PrintSuccessLine", varargs...) +} + +// PrintSuccessLine indicates an expected call of PrintSuccessLine +func (mr *MockUxBlocksMockRecorder) PrintSuccessLine(values ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrintSuccessLine", reflect.TypeOf((*MockUxBlocks)(nil).PrintSuccessLine), values...) +} + +// PrintInfoLine mocks base method +func (m *MockUxBlocks) PrintInfoLine(values ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range values { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "PrintInfoLine", varargs...) +} + +// PrintInfoLine indicates an expected call of PrintInfoLine +func (mr *MockUxBlocksMockRecorder) PrintInfoLine(values ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrintInfoLine", reflect.TypeOf((*MockUxBlocks)(nil).PrintInfoLine), values...) +} + +// PrintWarningLine mocks base method +func (m *MockUxBlocks) PrintWarningLine(values ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range values { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "PrintWarningLine", varargs...) +} + +// PrintWarningLine indicates an expected call of PrintWarningLine +func (mr *MockUxBlocksMockRecorder) PrintWarningLine(values ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrintWarningLine", reflect.TypeOf((*MockUxBlocks)(nil).PrintWarningLine), values...) +} + +// PrintErrorLine mocks base method +func (m *MockUxBlocks) PrintErrorLine(values ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range values { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "PrintErrorLine", varargs...) +} + +// PrintErrorLine indicates an expected call of PrintErrorLine +func (mr *MockUxBlocksMockRecorder) PrintErrorLine(values ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrintErrorLine", reflect.TypeOf((*MockUxBlocks)(nil).PrintErrorLine), values...) +} + +// PrintDebugLine mocks base method +func (m *MockUxBlocks) PrintDebugLine(args ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "PrintDebugLine", varargs...) +} + +// PrintDebugLine indicates an expected call of PrintDebugLine +func (mr *MockUxBlocksMockRecorder) PrintDebugLine(args ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrintDebugLine", reflect.TypeOf((*MockUxBlocks)(nil).PrintDebugLine), args...) +} + +// Table mocks base method +func (m *MockUxBlocks) Table(body *uxBlock.TableBody, auxOptions ...uxBlock.TableOption) { + m.ctrl.T.Helper() + varargs := []interface{}{body} + for _, a := range auxOptions { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Table", varargs...) +} + +// Table indicates an expected call of Table +func (mr *MockUxBlocksMockRecorder) Table(body interface{}, auxOptions ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{body}, auxOptions...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Table", reflect.TypeOf((*MockUxBlocks)(nil).Table), varargs...) +} + +// Select mocks base method +func (m *MockUxBlocks) Select(ctx context.Context, tableBody *uxBlock.TableBody, auxOptions ...uxBlock.SelectOption) ([]int, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, tableBody} + for _, a := range auxOptions { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Select", varargs...) + ret0, _ := ret[0].([]int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Select indicates an expected call of Select +func (mr *MockUxBlocksMockRecorder) Select(ctx, tableBody interface{}, auxOptions ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, tableBody}, auxOptions...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockUxBlocks)(nil).Select), varargs...) +} + +// Prompt mocks base method +func (m *MockUxBlocks) Prompt(ctx context.Context, message string, choices []string, auxOptions ...uxBlock.PromptOption) (int, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, message, choices} + for _, a := range auxOptions { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Prompt", varargs...) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Prompt indicates an expected call of Prompt +func (mr *MockUxBlocksMockRecorder) Prompt(ctx, message, choices interface{}, auxOptions ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, message, choices}, auxOptions...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prompt", reflect.TypeOf((*MockUxBlocks)(nil).Prompt), varargs...) +} + +// RunSpinners mocks base method +func (m *MockUxBlocks) RunSpinners(ctx context.Context, spinners []*uxBlock.Spinner, auxOptions ...uxBlock.SpinnerOption) func() { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, spinners} + for _, a := range auxOptions { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RunSpinners", varargs...) + ret0, _ := ret[0].(func()) + return ret0 +} + +// RunSpinners indicates an expected call of RunSpinners +func (mr *MockUxBlocksMockRecorder) RunSpinners(ctx, spinners interface{}, auxOptions ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, spinners}, auxOptions...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunSpinners", reflect.TypeOf((*MockUxBlocks)(nil).RunSpinners), varargs...) +} diff --git a/src/uxBlock/prompt.go b/src/uxBlock/prompt.go index e88b05ad..c0b8fe7e 100644 --- a/src/uxBlock/prompt.go +++ b/src/uxBlock/prompt.go @@ -6,15 +6,15 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/zeropsio/zcli/src/i18n" ) type promptConfig struct { - label string } type PromptOption = func(cfg *promptConfig) -func (b *UxBlocks) Prompt( +func (b *uxBlocks) Prompt( ctx context.Context, message string, choices []string, @@ -25,9 +25,9 @@ func (b *UxBlocks) Prompt( opt(&cfg) } - // TODO - janhajek fix message if !b.isTerminal { - return 0, errors.New(cfg.label + ", you can choose only in terminal") + b.PrintLine(message) + return 0, errors.New(i18n.T(i18n.PromptAllowedOnlyInTerminal)) } model := &promptModel{ @@ -52,7 +52,7 @@ func (b *UxBlocks) Prompt( type promptModel struct { cfg promptConfig - uxBlocks *UxBlocks + uxBlocks *uxBlocks message string choices []string cursor int diff --git a/src/uxBlock/select.go b/src/uxBlock/select.go index e4408696..4818fd9d 100644 --- a/src/uxBlock/select.go +++ b/src/uxBlock/select.go @@ -9,6 +9,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/table" + "github.com/zeropsio/zcli/src/i18n" ) type selectConfig struct { @@ -37,15 +38,15 @@ func SelectTableHeader(header *TableRow) SelectOption { type SelectOption = func(cfg *selectConfig) -func (b *UxBlocks) Select(ctx context.Context, tableBody *TableBody, auxOptions ...SelectOption) ([]int, error) { +func (b *uxBlocks) Select(ctx context.Context, tableBody *TableBody, auxOptions ...SelectOption) ([]int, error) { cfg := selectConfig{} for _, opt := range auxOptions { opt(&cfg) } - // TODO - janhajek fix message if !b.isTerminal { - return nil, errors.New(cfg.label + ", you can choose only in terminal") + b.PrintLine(cfg.label) + return nil, errors.New(i18n.T(i18n.SelectorAllowedOnlyInTerminal)) } model := &selectModel{ @@ -76,7 +77,7 @@ func (b *UxBlocks) Select(ctx context.Context, tableBody *TableBody, auxOptions type selectModel struct { cfg selectConfig - uxBlocks *UxBlocks + uxBlocks *uxBlocks tableBody *TableBody cursor int selected map[int]struct{} diff --git a/src/uxBlock/showcase/main.go b/src/uxBlock/showcase/main.go index d7afcd49..ffcdc233 100644 --- a/src/uxBlock/showcase/main.go +++ b/src/uxBlock/showcase/main.go @@ -30,7 +30,7 @@ func main() { } } -func do(ctx context.Context, blocks *UxBlocks) error { +func do(ctx context.Context, blocks UxBlocks) error { prompts(ctx, blocks) spinners(ctx, blocks) texts(ctx, blocks) @@ -39,7 +39,7 @@ func do(ctx context.Context, blocks *UxBlocks) error { return nil } -func spinners(ctx context.Context, blocks *UxBlocks) { +func spinners(ctx context.Context, blocks UxBlocks) { { fmt.Println("========= spinners block =========") @@ -79,7 +79,7 @@ func spinners(ctx context.Context, blocks *UxBlocks) { } } -func prompts(ctx context.Context, blocks *UxBlocks) { +func prompts(ctx context.Context, blocks UxBlocks) { fmt.Println("========= prompt block =========") choices := []string{"yes", "no", "maybe"} choice, err := blocks.Prompt(ctx, "Question?", choices) @@ -92,7 +92,7 @@ func prompts(ctx context.Context, blocks *UxBlocks) { fmt.Println("========= prompt block end =========") } -func texts(ctx context.Context, blocks *UxBlocks) { +func texts(ctx context.Context, blocks UxBlocks) { fmt.Println("========= texts block =========") blocks.PrintInfoLine("info line") blocks.PrintWarningLine("warning line") @@ -101,7 +101,7 @@ func texts(ctx context.Context, blocks *UxBlocks) { fmt.Println("========= texts block end =========") } -func tables(ctx context.Context, blocks *UxBlocks) { +func tables(ctx context.Context, blocks UxBlocks) { fmt.Println("========= table selection block =========") tableData := [][]string{ @@ -145,7 +145,7 @@ func regSignals(contextCancel func()) { }() } -func createBlocks(contextCancelFunc func()) (*UxBlocks, error) { +func createBlocks(contextCancelFunc func()) (UxBlocks, error) { isTerminal := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) outputLogger := logger.NewOutputLogger(logger.OutputConfig{ diff --git a/src/uxBlock/spinner.go b/src/uxBlock/spinner.go index 63cee2e2..92e7e590 100644 --- a/src/uxBlock/spinner.go +++ b/src/uxBlock/spinner.go @@ -8,22 +8,19 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -func (b *UxBlocks) RunSpinners(ctx context.Context, spinners []*Spinner, auxOptions ...SpinnerOption) func() { +func (b *uxBlocks) RunSpinners(ctx context.Context, spinners []*Spinner, auxOptions ...SpinnerOption) func() { cfg := spinnerConfig{} for _, opt := range auxOptions { opt(&cfg) } - //if !b.isTerminal { - // return func(success bool) { - // // TODO - janhajek - // //if success { - // // b.info(cfg.successMessage) - // //} else { - // // b.Error(cfg.failureMessage) - // //} - // } - //} + if !b.isTerminal { + return func() { + for _, spinner := range spinners { + b.PrintLine(spinner.text) + } + } + } model := &spinnerModel{ cfg: cfg, @@ -42,12 +39,6 @@ func (b *UxBlocks) RunSpinners(ctx context.Context, spinners []*Spinner, auxOpti return func() { p.Send(spinnerEndCmd{}) p.Wait() - // TODO - janhajek - //if success { - // b.info(cfg.successMessage) - //} else { - // b.Error(cfg.failureMessage) - //} } } @@ -57,7 +48,7 @@ type spinnerEndCmd struct { type spinnerModel struct { cfg spinnerConfig spinners []*Spinner - uxBlocks *UxBlocks + uxBlocks *uxBlocks quiting bool canceled bool diff --git a/src/uxBlock/table.go b/src/uxBlock/table.go index a7102013..636da285 100644 --- a/src/uxBlock/table.go +++ b/src/uxBlock/table.go @@ -82,7 +82,7 @@ func WithTableHeader(header *TableRow) TableOption { type TableOption = func(cfg *tableConfig) -func (b *UxBlocks) Table(body *TableBody, auxOptions ...TableOption) { +func (b *uxBlocks) Table(body *TableBody, auxOptions ...TableOption) { cfg := tableConfig{} for _, opt := range auxOptions { opt(&cfg) diff --git a/src/uxHelpers/org.go b/src/uxHelpers/org.go new file mode 100644 index 00000000..a958263d --- /dev/null +++ b/src/uxHelpers/org.go @@ -0,0 +1,66 @@ +package uxHelpers + +import ( + "context" + + "github.com/pkg/errors" + "github.com/zeropsio/zcli/src/entity" + "github.com/zeropsio/zcli/src/entity/repository" + "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/uxBlock" + "github.com/zeropsio/zcli/src/zeropsRestApiClient" +) + +func PrintOrgSelector( + ctx context.Context, + uxBlocks uxBlock.UxBlocks, + restApiClient *zeropsRestApiClient.Handler, +) (*entity.Org, error) { + orgs, err := repository.GetAllOrgs(ctx, restApiClient) + if err != nil { + return nil, err + } + + if len(orgs) == 0 { + uxBlocks.PrintWarningLine(i18n.T(i18n.OrgSelectorListEmpty)) + return nil, nil + } + + header, tableBody := createOrgTableRows(orgs) + + orgIndex, err := uxBlocks.Select( + ctx, + tableBody, + uxBlock.SelectLabel(i18n.T(i18n.OrgSelectorPrompt)), + uxBlock.SelectTableHeader(header), + ) + if err != nil { + return nil, err + } + + if len(orgIndex) == 0 { + return nil, errors.New(i18n.T(i18n.OrgSelectorOutOfRangeError)) + } + + if orgIndex[0] > len(orgs)-1 { + return nil, errors.New(i18n.T(i18n.OrgSelectorOutOfRangeError)) + } + + return &orgs[orgIndex[0]], nil +} + +func createOrgTableRows(projects []entity.Org) (*uxBlock.TableRow, *uxBlock.TableBody) { + // TODO - janhajek translation + header := (&uxBlock.TableRow{}).AddStringCells("ID", "Name", "Role") + + tableBody := &uxBlock.TableBody{} + for _, project := range projects { + tableBody.AddStringsRow( + string(project.ID), + project.Name.String(), + project.Role.Native(), + ) + } + + return header, tableBody +} diff --git a/src/uxHelpers/project.go b/src/uxHelpers/project.go index 1b7e4a22..52ac8278 100644 --- a/src/uxHelpers/project.go +++ b/src/uxHelpers/project.go @@ -13,7 +13,7 @@ import ( func PrintProjectSelector( ctx context.Context, - uxBlocks *uxBlock.UxBlocks, + uxBlocks uxBlock.UxBlocks, restApiClient *zeropsRestApiClient.Handler, ) (*entity.Project, error) { projects, err := repository.GetAllProjects(ctx, restApiClient) @@ -51,7 +51,7 @@ func PrintProjectSelector( func PrintProjectList( ctx context.Context, - uxBlocks *uxBlock.UxBlocks, + uxBlocks uxBlock.UxBlocks, restApiClient *zeropsRestApiClient.Handler) error { projects, err := repository.GetAllProjects(ctx, restApiClient) if err != nil { @@ -67,7 +67,7 @@ func PrintProjectList( func createProjectTableRows(projects []entity.Project) (*uxBlock.TableRow, *uxBlock.TableBody) { // TODO - janhajek translation - header := (&uxBlock.TableRow{}).AddStringCells("ID", "Name", "Description", "Client ID", "Status") + header := (&uxBlock.TableRow{}).AddStringCells("ID", "Name", "Description", "Org ID", "Status") tableBody := &uxBlock.TableBody{} for _, project := range projects { diff --git a/src/uxHelpers/service.go b/src/uxHelpers/service.go index 764bc0c5..b49f3872 100644 --- a/src/uxHelpers/service.go +++ b/src/uxHelpers/service.go @@ -13,7 +13,7 @@ import ( func PrintServiceSelector( ctx context.Context, - uxBlocks *uxBlock.UxBlocks, + uxBlocks uxBlock.UxBlocks, restApiClient *zeropsRestApiClient.Handler, project entity.Project, ) (*entity.Service, error) { @@ -40,11 +40,11 @@ func PrintServiceSelector( } if len(serviceIndex) == 0 { - return nil, errors.New(i18n.T(i18n.ProjectSelectorOutOfRangeError)) + return nil, errors.New(i18n.T(i18n.ServiceSelectorOutOfRangeError)) } if serviceIndex[0] > len(services)-1 { - return nil, errors.New(i18n.T(i18n.ProjectSelectorOutOfRangeError)) + return nil, errors.New(i18n.T(i18n.ServiceSelectorOutOfRangeError)) } return &services[serviceIndex[0]], nil @@ -52,7 +52,7 @@ func PrintServiceSelector( func PrintServiceList( ctx context.Context, - uxBlocks *uxBlock.UxBlocks, + uxBlocks uxBlock.UxBlocks, restApiClient *zeropsRestApiClient.Handler, project entity.Project, ) error { diff --git a/src/uxHelpers/spinner.go b/src/uxHelpers/spinner.go index ef41297a..517f3bd5 100644 --- a/src/uxHelpers/spinner.go +++ b/src/uxHelpers/spinner.go @@ -16,8 +16,7 @@ import ( func ProcessCheckWithSpinner( ctx context.Context, - uxBlocks *uxBlock.UxBlocks, - restApiClient *zeropsRestApiClient.Handler, + uxBlocks uxBlock.UxBlocks, processList []Process, ) error { spinners := make([]*uxBlock.Spinner, 0, len(processList)) @@ -38,7 +37,7 @@ func ProcessCheckWithSpinner( defer wg.Done() process := processList[i] - err := checkProcess(ctx, process.Id, restApiClient) + err := process.F(ctx) if err != nil { spinners[i].Finish(uxBlock.NewLine(uxBlock.ErrorIcon, uxBlock.ErrorText(process.ErrorMessageMessage)).String()) stopFunc() @@ -59,40 +58,45 @@ func ProcessCheckWithSpinner( } type Process struct { - Id uuid.ProcessId + F func(ctx context.Context) error RunningMessage string ErrorMessageMessage string SuccessMessage string } -func checkProcess(ctx context.Context, processId uuid.ProcessId, restApiClient *zeropsRestApiClient.Handler) error { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return nil - case <-ticker.C: - getProcessResponse, err := restApiClient.GetProcess(ctx, path.ProcessId{Id: processId}) - if err != nil { - return err - } - - processOutput, err := getProcessResponse.Output() - if err != nil { - return err - } - - processStatus := processOutput.Status - - if processStatus == enum.ProcessStatusEnumFinished { +func CheckZeropsProcess( + processId uuid.ProcessId, + restApiClient *zeropsRestApiClient.Handler, +) func(ctx context.Context) error { + return func(ctx context.Context) error { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): return nil - } - - if !(processStatus == enum.ProcessStatusEnumRunning || - processStatus == enum.ProcessStatusEnumPending) { - return errors.Errorf(i18n.T(i18n.ProcessInvalidState), processId) + case <-ticker.C: + getProcessResponse, err := restApiClient.GetProcess(ctx, path.ProcessId{Id: processId}) + if err != nil { + return err + } + + processOutput, err := getProcessResponse.Output() + if err != nil { + return err + } + + processStatus := processOutput.Status + + if processStatus == enum.ProcessStatusEnumFinished { + return nil + } + + if !(processStatus == enum.ProcessStatusEnumRunning || + processStatus == enum.ProcessStatusEnumPending) { + return errors.Errorf(i18n.T(i18n.ProcessInvalidState), processId) + } } } } diff --git a/src/yamlReader/readYaml.go b/src/yamlReader/readYaml.go index aac56c9e..8c2312f9 100644 --- a/src/yamlReader/readYaml.go +++ b/src/yamlReader/readYaml.go @@ -10,7 +10,7 @@ import ( "github.com/zeropsio/zcli/src/uxBlock" ) -func ReadContent(uxBlocks *uxBlock.UxBlocks, importYamlPath string, workingDir string) ([]byte, error) { +func ReadContent(uxBlocks uxBlock.UxBlocks, importYamlPath string, workingDir string) ([]byte, error) { if !filepath.IsAbs(importYamlPath) { workingDir, err := filepath.Abs(workingDir) if err != nil { diff --git a/src/zeropsRestApiClient/errors.go b/src/zeropsRestApiClient/errors.go deleted file mode 100644 index 873af9da..00000000 --- a/src/zeropsRestApiClient/errors.go +++ /dev/null @@ -1,68 +0,0 @@ -package zeropsRestApiClient - -import ( - "github.com/pkg/errors" - "github.com/zeropsio/zerops-go/apiError" - "github.com/zeropsio/zerops-go/errorCode" -) - -type checkErrorConfig struct { - checks []func(error) error -} - -func CheckInvalidUserInput(paramName string, returnedErr error) CheckErrorOption { - return func(cfg *checkErrorConfig) { - cfg.checks = append(cfg.checks, func(err error) error { - var apiErr apiError.Error - if errors.As(err, &apiErr) { - if apiErr.GetErrorCode() == string(errorCode.InvalidUserInput) { - if typedMeta, ok := apiErr.GetMeta().([]interface{}); ok { - if len(typedMeta) > 0 { - if typed, ok := typedMeta[0].(map[string]interface{}); ok { - if param, exists := typed["parameter"]; exists { - if value, ok := param.(string); ok && value == paramName { - return returnedErr - } - } - } - } - } - } - } - - return nil - }) - } -} - -func CheckErrorCode(errorCode errorCode.ErrorCode, returnedErr error) CheckErrorOption { - return func(cfg *checkErrorConfig) { - cfg.checks = append(cfg.checks, func(err error) error { - var apiErr apiError.Error - if errors.As(err, &apiErr) { - if apiErr.GetErrorCode() == string(errorCode) { - return returnedErr - } - } - - return nil - }) - } -} - -type CheckErrorOption = func(cfg *checkErrorConfig) - -func CheckError(err error, auxOptions ...CheckErrorOption) error { - cfg := checkErrorConfig{} - for _, opt := range auxOptions { - opt(&cfg) - } - - for _, check := range cfg.checks { - if err := check(err); err != nil { - return err - } - } - - return err -}