From e26c885ce6dcba71e9258a3351d60a7419cfd33b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 18 Jul 2024 16:00:19 -0500 Subject: [PATCH 01/38] Refactored the user details unit test to remove a lot of junk testing. #851 --- README.md | 51 +- plugin/commands/user/details_test.go | 581 +++++------------- .../SoftLayer_Account/getHardware.json | 12 +- .../SoftLayer_Event_Log/getAllObjects.json | 38 +- .../SoftLayer_User_Customer/getObject.json | 2 +- 5 files changed, 230 insertions(+), 454 deletions(-) diff --git a/README.md b/README.md index 01b576f2..eab03df8 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,8 @@ Check testhelpers/fake_softlayer_session.go for all the fields that get recorded -### Fake Managers +### Test Fakes + CLI calls to manager functions need an entry in `plugin\testhelpers\fake_manager.go` Managers have a fake/test interface that is autogenerate with a program called [couterfieter](https://github.com/maxbrunsfeld/counterfeiter) @@ -177,24 +178,37 @@ each manager and defined interface should have this line in it to be automatical If you want to use the real manager but fixture API data, just initialize the manager like this in the CLI test -(filenames here is optional of course) +This example is from `plugin\commands\account\invoice-detail_test.go` ```go -BeforeEach(func() { - - filenames := []string{"getDatacenters_1",} - fakeSLSession = testhelpers.NewFakeSoftlayerSession(filenames) - OrderManager = managers.NewOrderManager(fakeSLSession) - fakeUI = terminal.NewFakeUI() - cmd = order.NewPlaceCommand(fakeUI, OrderManager, nil) - cliCommand = cli.Command{ - Name: metadata.OrderPlaceMetaData().Name, - Description: metadata.OrderPlaceMetaData().Description, - Usage: metadata.OrderPlaceMetaData().Usage, - Flags: metadata.OrderPlaceMetaData().Flags, - Action: cmd.Run, - } -}) +var _ = Describe(" Tests", func() { + var ( + fakeUI *terminal.FakeUI + cliCommand *account.InvoiceDetailCommand + fakeSession *session.Session + slCommand *metadata.SoftlayerCommand + fakeHandler *testhelpers.FakeTransportHandler + ) + BeforeEach(func() { + // Fake UI to capture output of comamnds + fakeUI = terminal.NewFakeUI() + // Fake session to handle loading data from testfixtures + fakeSession = testhelpers.NewFakeSoftlayerSession(nil) + // Fake handler to control error generation + fakeHandler = testhelpers.GetSessionHandler(fakeSession) + // Real parent command, with fake UI and Fake Session being passed in + slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) + // Real actual command + cliCommand = account.NewInvoiceDetailCommand(slCommand) + // Need to set output flag since its set manually in the parent command normally. + cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") + }) + AfterEach(func() { + // Clear API call logs and any errors that might have been set after every test + fakeHandler.ClearApiCallLogs() + fakeHandler.ClearErrors() + }) ``` +`plugin\commands\user\details_test.go` is also a good example test file for CLI commands. ### `[no tests to run]` New commands needs a `command_test.go` file in the CLI directory. @@ -224,8 +238,7 @@ fakeHandler = testhelpers.GetSessionHandler(fakeSession) // Then in a BeforeEach for the specific test... BeforeEach(func() { - fakeHandler.AddApiError("SoftLayer_User_Customer", "getObject", - 500, "Internal Server Error") + fakeHandler.AddApiError("SoftLayer_User_Customer", "getObject", 500, "Internal Server Error") }) ``` diff --git a/plugin/commands/user/details_test.go b/plugin/commands/user/details_test.go index 82666af7..2a5fbb5e 100644 --- a/plugin/commands/user/details_test.go +++ b/plugin/commands/user/details_test.go @@ -2,14 +2,11 @@ package user_test import ( "errors" - "time" - "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/session" - "github.com/softlayer/softlayer-go/sl" "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/user" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" @@ -17,460 +14,182 @@ import ( ) var testUser datatypes.User_Customer -var _ = Describe("Detail", func() { +var _ = Describe("sl user detail", func() { var ( fakeUI *terminal.FakeUI - fakeUserManager *testhelpers.FakeUserManager cliCommand *user.DetailsCommand fakeSession *session.Session slCommand *metadata.SoftlayerCommand + fakeHandler *testhelpers.FakeTransportHandler + fakeUserManager *testhelpers.FakeUserManager ) BeforeEach(func() { fakeUI = terminal.NewFakeUI() + fakeSession = testhelpers.NewFakeSoftlayerSession(nil) + fakeHandler = testhelpers.GetSessionHandler(fakeSession) fakeUserManager = new(testhelpers.FakeUserManager) - fakeSession = testhelpers.NewFakeSoftlayerSession([]string{}) slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) cliCommand = user.NewDetailsCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") - cliCommand.UserManager = fakeUserManager - - created, _ := time.Parse(time.RFC3339, "2017-11-08T00:00:00Z") - - testUser = datatypes.User_Customer{ - Id: sl.Int(5555), - Username: sl.String("ATestUser"), - ApiAuthenticationKeys: []datatypes.User_Customer_ApiAuthentication{datatypes.User_Customer_ApiAuthentication{ - AuthenticationKey: sl.String("StringKeyAuthentication"), - }}, - FirstName: sl.String("Name"), - LastName: sl.String("LastName"), - Email: sl.String("user@email.com"), - OpenIdConnectUserName: sl.String("123456"), - Address1: sl.String("addres with number N 123"), - CompanyName: sl.String("NameCompany"), - CreateDate: sl.Time(created), - OfficePhone: sl.String("123456789"), - PptpVpnAllowedFlag: sl.Bool(true), - SslVpnAllowedFlag: sl.Bool(true), - Parent: &datatypes.User_Customer{ - Username: sl.String("ParentName"), - }, - UserStatus: &datatypes.User_Customer_Status{ - Name: sl.String("ACTIVE"), - }, - SuccessfulLogins: []datatypes.User_Customer_Access_Authentication{ - datatypes.User_Customer_Access_Authentication{ - CreateDate: sl.Time(created), - IpAddress: sl.String("1.1.1.1"), - }, - }, - UnsuccessfulLogins: []datatypes.User_Customer_Access_Authentication{ - datatypes.User_Customer_Access_Authentication{ - CreateDate: sl.Time(created), - IpAddress: sl.String("2.2.2.2"), - }, - }, - DedicatedHosts: []datatypes.Virtual_DedicatedHost{ - datatypes.Virtual_DedicatedHost{ - Id: sl.Int(123456), - Name: sl.String("dedicatedHostName"), - CpuCount: sl.Int(50), - MemoryCapacity: sl.Int(1000), - DiskCapacity: sl.Int(2000), - CreateDate: sl.Time(created), - }, - datatypes.Virtual_DedicatedHost{ - Id: sl.Int(1234567), - Name: sl.String("dedicatedHostName2"), - CpuCount: sl.Int(60), - MemoryCapacity: sl.Int(1100), - DiskCapacity: sl.Int(2100), - CreateDate: sl.Time(created), - }}, - Hardware: []datatypes.Hardware{ - datatypes.Hardware{ - Id: sl.Int(12345678), - FullyQualifiedDomainName: sl.String("domain.test.com"), - PrimaryIpAddress: sl.String("10.10.10.10"), - PrimaryBackendIpAddress: sl.String("11.11.11.11"), - ProvisionDate: sl.Time(created), - }, - datatypes.Hardware{ - Id: sl.Int(123456789), - FullyQualifiedDomainName: sl.String("domain2.test.com"), - PrimaryIpAddress: sl.String("20.20.20.20"), - PrimaryBackendIpAddress: sl.String("21.21.21.21"), - ProvisionDate: sl.Time(created), - }}, - VirtualGuests: []datatypes.Virtual_Guest{ - datatypes.Virtual_Guest{ - Id: sl.Int(654321), - FullyQualifiedDomainName: sl.String("virtualtest.domain.com"), - PrimaryIpAddress: sl.String("30.30.30.30"), - PrimaryBackendIpAddress: sl.String("31.31.31.31"), - ProvisionDate: sl.Time(created), - }, - datatypes.Virtual_Guest{ - Id: sl.Int(7654321), - FullyQualifiedDomainName: sl.String("virtualtest2.domain.com"), - PrimaryIpAddress: sl.String("40.40.40.40"), - PrimaryBackendIpAddress: sl.String("41.41.41.41"), - ProvisionDate: sl.Time(created), - }}, - } - - testPermissions := []datatypes.User_Customer_CustomerPermission_Permission{ - datatypes.User_Customer_CustomerPermission_Permission{ - KeyName: sl.String("KEY_PERMISSION_1"), - Name: sl.String("Permission 1"), - }, - datatypes.User_Customer_CustomerPermission_Permission{ - KeyName: sl.String("KEY_PERMISSION_2"), - Name: sl.String("Permission 2"), - }, - } - - testLogins := []datatypes.User_Customer_Access_Authentication{ - datatypes.User_Customer_Access_Authentication{ - CreateDate: sl.Time(created), - IpAddress: sl.String("50.50.50.50"), - SuccessFlag: sl.Bool(true), - }, - datatypes.User_Customer_Access_Authentication{ - CreateDate: sl.Time(created), - IpAddress: sl.String("60.60.60.60"), - SuccessFlag: sl.Bool(false), - }, - } - - testEvents := []datatypes.Event_Log{ - datatypes.Event_Log{ - EventCreateDate: sl.Time(created), - EventName: sl.String("Login Successful"), - IpAddress: sl.String("70.70.70.70"), - Label: sl.String("test@test.com"), - Username: sl.String("test_test.com"), - }, - datatypes.Event_Log{ - EventCreateDate: sl.Time(created), - EventName: sl.String("IAM Token validation successful"), - IpAddress: sl.String("80.80.80.80"), - Label: sl.String("test2@test.com"), - Username: sl.String("test2_test.com"), - }, - } - - fakeUserManager.GetUserReturns(testUser, nil) - fakeUserManager.GetUserPermissionsReturns(testPermissions, nil) - fakeUserManager.GetLoginsReturns(testLogins, nil) - fakeUserManager.GetEventsReturns(testEvents, nil) }) - - Describe("user detail", func() { - Context("user detail with not enough parameters", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) - }) - }) - - Context("user detail with letters like parameter", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abcd") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: User ID should be a number.")) - }) - }) - - Context("user detail error", func() { - It("return error", func() { - fakeUserManager.GetUserReturns(datatypes.User_Customer{}, errors.New("Internal server error")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show user detail.")) - }) + AfterEach(func() { + // Clear API call logs and any errors that might have been set after every test + fakeHandler.ClearApiCallLogs() + fakeHandler.ClearErrors() + }) + Describe("Usage Errors", func() { + It("return error", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - - Context("user detail error with permissions", func() { - It("return error", func() { - fakeUserManager.GetUserPermissionsReturns([]datatypes.User_Customer_CustomerPermission_Permission{}, errors.New("Internal server error")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--permissions") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show user permissions.")) - }) - }) - - Context("user detail error with logins", func() { - It("return error", func() { - fakeUserManager.GetLoginsReturns([]datatypes.User_Customer_Access_Authentication{}, errors.New("Internal server error")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--logins") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show login history.")) - }) + It("return error", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "abcd") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: User ID should be a number.")) }) + }) - Context("user detail error with events", func() { - It("return error", func() { - fakeUserManager.GetEventsReturns([]datatypes.Event_Log{}, errors.New("Internal server error")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--events") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show event log.")) - }) - }) - Context("user detail error with events", func() { - It("return error", func() { - fakeUserManager.GetEventsReturns([]datatypes.Event_Log{}, errors.New("Internal server error")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--events") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show event log.")) - }) - }) - Context("Error getting hardware", func() { - It("return error", func() { - fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) - fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD HARDWARE")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--hardware") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show hardware.")) - }) - }) - Context("Error getting virtual", func() { - It("return error", func() { - fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) - fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD VIRTUAL")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--virtual") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show virual server.")) - }) - }) - Context("user detail with correct id", func() { - It("return a user", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - }) - }) + Describe("API Errors", func() { - Context("user detail with correct id and apikey", func() { - It("return a user with apikey", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--keys") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("APIKEY StringKeyAuthentication")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - }) + It("SoftLayer_User_Customer::getObject Exception", func() { + fakeHandler.AddApiError("SoftLayer_User_Customer", "getObject", 500, "Internal Server Error") + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show user detail.")) }) - - Context("user detail with correct id and permissions", func() { - It("return a user with permissions", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--permissions") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - Expect(fakeUI.Outputs()).To(ContainSubstring("keyName name")) - Expect(fakeUI.Outputs()).To(ContainSubstring("KEY_PERMISSION_1 Permission 1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("KEY_PERMISSION_2 Permission 2")) - - }) + It("SoftLayer_User_Customer::getPermissions Exception", func() { + fakeHandler.AddApiError("SoftLayer_User_Customer", "getPermissions", 500, "Internal Server Error") + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--permissions") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show user permissions.")) }) - - Context("user detail with correct id and hardware", func() { - It("return a user with hardware", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--hardware") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID Name Cpus Memory Disk Created Dedicated Access")) - Expect(fakeUI.Outputs()).To(ContainSubstring("123456 dedicatedHostName 50 1000 2000 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("1234567 dedicatedHostName2 60 1100 2100 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("12345678 domain.test.com 10.10.10.10 11.11.11.11 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("123456789 domain2.test.com 20.20.20.20 21.21.21.21 2017-11-08T00:00:00Z")) - }) + It("SoftLayer_User_Customer::getLoginAttempts Exception", func() { + fakeHandler.AddApiError("SoftLayer_User_Customer", "getLoginAttempts", 500, "Internal Server Error") + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--logins") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show login history.")) }) - - Context("user detail with correct id and virtual", func() { - It("return a user with virtual", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--virtual") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - Expect(fakeUI.Outputs()).To(ContainSubstring("654321 virtualtest.domain.com 30.30.30.30 31.31.31.31 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("7654321 virtualtest2.domain.com 40.40.40.40 41.41.41.41 2017-11-08T00:00:00Z")) - }) + It("SoftLayer_Event_Log::getAllObjects Exception", func() { + fakeHandler.AddApiError("SoftLayer_Event_Log", "getAllObjects", 500, "Internal Server Error") + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--events") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show event log.")) }) + }) - Context("user detail with correct id and logins", func() { - It("return a user with logins", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--logins") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Date IP Address Successful Login?")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z 50.50.50.50 true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z 60.60.60.60 false")) - }) + Describe("Tests needing a fake UserManager", func() { + var testUser datatypes.User_Customer + BeforeEach(func() { + testUser = datatypes.User_Customer{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_User_Customer", "getObject", nil, nil, &testUser, + ) + Expect(txError).NotTo(HaveOccurred()) + fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) + cliCommand.UserManager = fakeUserManager + }) + It("SoftLayer_User_Customer::getObject Second Exception Hardware", func() { + fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD HARDWARE")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--hardware") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show hardware.")) + }) + It("SoftLayer_User_Customer::getObject Second Exception Virtual", func() { + fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD VIRTUAL")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--virtual") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show virual server.")) }) + }) - Context("user detail with correct id and events", func() { - It("return a user with events", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--events") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ID 5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Username ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Email user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("OpenID 123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Address addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Company NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Created 2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Phone Number 123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Parent User ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Status ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("PPTP VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("SSL VPN true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2017-11-08T00:00:00Z From: 1.1.1.1")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Last Failed Login 2017-11-08T00:00:00Z From: 2.2.2.2")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Date Type IP Address Label Username")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z Login Successful 70.70.70.70 test@test.com test_test.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z IAM Token validation successful 80.80.80.80 test2@test.com test2_test.com")) - }) + Describe("Happy Path Tests", func() { + It("return a user", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("XXX.ASD@ibm.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Last Login 2018-11-09T00:40:58+08:00 From: 169.60.96.34")) + Expect(fakeUI.Outputs()).NotTo(ContainSubstring("StringKeyAuthentication")) + Expect(fakeUI.Outputs()).NotTo(ContainSubstring("KEY_PERMISSION_2")) + }) + It("return a user with apikey", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--keys") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) + Expect(fakeUI.Outputs()).To(ContainSubstring("ID 345234")) + Expect(fakeUI.Outputs()).To(ContainSubstring("StringKeyAuthentication")) + }) + It("return a user with permissions", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--permissions") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("APIKEY Yes")) + Expect(fakeUI.Outputs()).To(ContainSubstring("ACCESS_ALL_HARDWARE")) + Expect(fakeUI.Outputs()).To(ContainSubstring("HARDWARE_VIEW")) + }) + It("return a user with logins", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--logins") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("asdfgn@ibm.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("2018-11-08T16:40:58Z 169.60.96.34 true")) + }) + It("return a user with events", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--events") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("11111111 aaaa Ave - Markham ON CA L6G1C7")) + Expect(fakeUI.Outputs()).To(ContainSubstring("IAM Token validation successful")) + Expect(fakeUI.Outputs()).To(ContainSubstring("169.1.98.6")) + Expect(fakeUI.Outputs()).To(ContainSubstring("123_scaparro@ibm.com")) }) + }) - Context("user detail with correct id and without apikey", func() { - BeforeEach(func() { - created, _ := time.Parse(time.RFC3339, "2017-11-08T00:00:00Z") - - testUser = datatypes.User_Customer{ - Id: sl.Int(5555), - Username: sl.String("ATestUser"), - ApiAuthenticationKeys: []datatypes.User_Customer_ApiAuthentication{}, - FirstName: sl.String("Name"), - LastName: sl.String("LastName"), - Email: sl.String("user@email.com"), - OpenIdConnectUserName: sl.String("123456"), - Address1: sl.String("addres with number N 123"), - CompanyName: sl.String("NameCompany"), - CreateDate: sl.Time(created), - OfficePhone: sl.String("123456789"), - PptpVpnAllowedFlag: sl.Bool(true), - SslVpnAllowedFlag: sl.Bool(true), - Parent: &datatypes.User_Customer{ - Username: sl.String("ParentName"), - }, - UserStatus: &datatypes.User_Customer_Status{ - Name: sl.String("ACTIVE"), - }, - } - fakeUserManager.GetUserReturns(testUser, nil) - }) - - It("return a user without apikey", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--keys") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("5555")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("No")) - Expect(fakeUI.Outputs()).To(ContainSubstring("Name LastName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("user@email.com")) - Expect(fakeUI.Outputs()).To(ContainSubstring("123456")) - Expect(fakeUI.Outputs()).To(ContainSubstring("addres with number N 123 - - - - -")) - Expect(fakeUI.Outputs()).To(ContainSubstring("NameCompany")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("123456789")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ParentName")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ACTIVE")) - Expect(fakeUI.Outputs()).To(ContainSubstring("true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("true")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z")) - Expect(fakeUI.Outputs()).To(ContainSubstring("2017-11-08T00:00:00Z")) - }) + Describe("Happy Path with Fake Manager", func() { + var testUser datatypes.User_Customer + BeforeEach(func() { + testUser = datatypes.User_Customer{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_User_Customer", "getObject", nil, nil, &testUser, + ) + Expect(txError).NotTo(HaveOccurred()) + fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) + cliCommand.UserManager = fakeUserManager + }) + It("return a user with hardware", func() { + userHardware := []datatypes.Hardware{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_Account", "getHardware", nil, nil, &userHardware, + ) + testUser.Hardware = userHardware + fakeUserManager.GetUserReturnsOnCall(1, testUser, nil) + Expect(txError).NotTo(HaveOccurred()) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--hardware") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("XXX.ASD@ibm.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("ibmcloud-cli-dev1.ibm.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("ibmcloud-cli-dev2.ibm.com")) + }) + It("return a user with virtual", func() { + userGuests := []datatypes.Virtual_Guest{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_Account", "getVirtualGuests", nil, nil, &userGuests, + ) + testUser.VirtualGuests = userGuests + fakeUserManager.GetUserReturnsOnCall(1, testUser, nil) + Expect(txError).NotTo(HaveOccurred()) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--virtual") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("XXX.ASD@ibm.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("3169-2-stemcell-for-dirtycow.softlayer.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("3263-10-1-stemcell-bluemix.softlayer.com")) + Expect(fakeUI.Outputs()).To(ContainSubstring("3263-10-2-stemcell-bluemix.softlayer.com")) + }) + It("return a user without apikey", func() { + testUser.ApiAuthenticationKeys = []datatypes.User_Customer_ApiAuthentication{} + fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--keys") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("Status Active")) + Expect(fakeUI.Outputs()).To(ContainSubstring("APIKEY No")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Username test")) }) }) }) diff --git a/plugin/testfixtures/SoftLayer_Account/getHardware.json b/plugin/testfixtures/SoftLayer_Account/getHardware.json index aae3805b..f7d7e900 100644 --- a/plugin/testfixtures/SoftLayer_Account/getHardware.json +++ b/plugin/testfixtures/SoftLayer_Account/getHardware.json @@ -1,10 +1,18 @@ [ { "id":123, - "hostname":"ibmcloud-cli-dev" + "hostname":"ibmcloud-cli-dev1", + "fullyQualifiedDomainName": "ibmcloud-cli-dev1.ibm.com", + "primaryIpAddress": "1.2.3.4", + "primaryBackendIpAddress": "9.0.0.1", + "provisionDate": "2024-02-23T21:22:39-06:00" }, { "id":234, - "hostname":"ibmcloud-cli-dev" + "hostname":"ibmcloud-cli-dev2", + "fullyQualifiedDomainName": "ibmcloud-cli-dev2.ibm.com", + "primaryIpAddress": "1.2.3.4", + "primaryBackendIpAddress": "9.0.0.1", + "provisionDate": "2024-02-23T21:22:39-06:00" } ] \ No newline at end of file diff --git a/plugin/testfixtures/SoftLayer_Event_Log/getAllObjects.json b/plugin/testfixtures/SoftLayer_Event_Log/getAllObjects.json index ec747fa4..0c9f304b 100644 --- a/plugin/testfixtures/SoftLayer_Event_Log/getAllObjects.json +++ b/plugin/testfixtures/SoftLayer_Event_Log/getAllObjects.json @@ -1 +1,37 @@ -null \ No newline at end of file +[ + { + "eventCreateDate": "2024-07-18T11:31:59.715644-06:00", + "eventName": "IAM Token validation successful", + "ipAddress": "169.3.2.1", + "label": "SL12345", + "username": "SL12345" + }, + { + "eventCreateDate": "2024-07-17T15:52:18.841040-06:00", + "eventName": "IAM Token validation successful", + "ipAddress": "169.4.2.242", + "label": "SL12345", + "username": "SL12345" + }, + { + "eventCreateDate": "2024-07-17T15:52:07.030382-06:00", + "eventName": "IAM Token validation successful", + "ipAddress": "169.1.98.6", + "label": "SL12345", + "username": "SL12345" + }, + { + "eventCreateDate": "2024-07-17T15:48:32.306644-06:00", + "eventName": "IAM Token validation successful", + "ipAddress": "169.6.98.3", + "label": "SL12345", + "username": "SL12345" + }, + { + "eventCreateDate": "2024-07-17T06:10:20.516610-06:00", + "eventName": "IAM Token validation successful", + "ipAddress": "93.176.5.8", + "label": "123_sc@ibm.com", + "username": "123_scaparro@ibm.com" + } +] diff --git a/plugin/testfixtures/SoftLayer_User_Customer/getObject.json b/plugin/testfixtures/SoftLayer_User_Customer/getObject.json index 34838896..4f4873eb 100644 --- a/plugin/testfixtures/SoftLayer_User_Customer/getObject.json +++ b/plugin/testfixtures/SoftLayer_User_Customer/getObject.json @@ -1,7 +1,7 @@ { "accountId": 5342, "address1": "11111111 aaaa Ave", - "apiAuthenticationKeys": [], + "apiAuthenticationKeys": [{"authenticationKey": "StringKeyAuthentication"}], "city": "Markham", "companyName": "IBM - BlueMix - Internal Dev", "country": "CA", From b1a6f0a4b14c90bf3a68ca72e97d0dcad5b89c1a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 19 Jul 2024 15:29:21 -0500 Subject: [PATCH 02/38] Added JSON output to user detail command, #851 --- plugin/commands/user/details.go | 49 +++++++-- plugin/commands/user/details_test.go | 142 ++++++++++++++++++++++----- 2 files changed, 158 insertions(+), 33 deletions(-) diff --git a/plugin/commands/user/details.go b/plugin/commands/user/details.go index e714a198..9f900764 100644 --- a/plugin/commands/user/details.go +++ b/plugin/commands/user/details.go @@ -52,6 +52,16 @@ func NewDetailsCommand(sl *metadata.SoftlayerCommand) (cmd *DetailsCommand) { return thisCmd } +type UserInformation struct { + User datatypes.User_Customer + Hardware []datatypes.Hardware + Virtual []datatypes.Virtual_Guest + Events []datatypes.Event_Log + Permissions []datatypes.User_Customer_CustomerPermission_Permission + Logins []datatypes.User_Customer_Access_Authentication + DedicatedHosts []datatypes.Virtual_DedicatedHost +} + func (cmd *DetailsCommand) Run(args []string) error { userId := args[0] id, err := strconv.Atoi(userId) @@ -59,6 +69,8 @@ func (cmd *DetailsCommand) Run(args []string) error { return errors.NewInvalidUsageError(T("User ID should be a number.")) } + outputFormat := cmd.GetOutputFlag() + userInfo := UserInformation{} keys := cmd.Keys permissions := cmd.Permissions hardware := cmd.Hardware @@ -68,11 +80,14 @@ func (cmd *DetailsCommand) Run(args []string) error { object_mask := "userStatus[name],parent[id,username],apiAuthenticationKeys[authenticationKey]" user, err := cmd.UserManager.GetUser(id, object_mask) + userInfo.User = user if err != nil { return errors.NewAPIError(T("Failed to show user detail.\n"), err.Error(), 2) } - baseUserPrint(user, keys, cmd.UI) + if outputFormat != "JSON" { + baseUserPrint(user, keys, cmd.UI) + } if permissions { perms, err := cmd.UserManager.GetUserPermissions(id) @@ -84,7 +99,10 @@ func (cmd *DetailsCommand) Run(args []string) error { table.Add(utils.FormatStringPointer(perm.KeyName), utils.FormatStringPointer(perm.Name)) } table.Add("", "") - table.Print() + if outputFormat != "JSON" { + table.Print() + } + userInfo.Permissions = perms } if hardware { @@ -105,7 +123,6 @@ func (cmd *DetailsCommand) Run(args []string) error { table.Add(hostId, hostFqdn, hostCpu, hostMem, hostDisk, hostCreated) } table.Add("", "") - table.Print() tableAccess := cmd.UI.Table([]string{T("ID"), T("Hostname"), T("Primary Public IP"), T("Primary Private IP"), T("Created")}) for _, host := range access.Hardware { @@ -117,7 +134,12 @@ func (cmd *DetailsCommand) Run(args []string) error { tableAccess.Add(hostId, hostFqdn, hostPrimary, hostPrivate, hostCreated) } tableAccess.Add("", "") - tableAccess.Print() + if outputFormat != "JSON" { + table.Print() + tableAccess.Print() + } + userInfo.Hardware = access.Hardware + userInfo.DedicatedHosts = access.DedicatedHosts } if virtual { @@ -137,7 +159,10 @@ func (cmd *DetailsCommand) Run(args []string) error { tableAccess.Add(hostId, hostFqdn, hostPrimary, hostPrivate, hostCreated) } tableAccess.Add("", "") - tableAccess.Print() + if outputFormat != "JSON" { + tableAccess.Print() + } + userInfo.Virtual = access.VirtualGuests } if logins { @@ -156,7 +181,11 @@ func (cmd *DetailsCommand) Run(args []string) error { table.Add(loginData, loginIp, loginSucc) } table.Add("", "") - table.Print() + if outputFormat != "JSON" { + table.Print() + } + userInfo.Logins = loginLog + } if events { @@ -176,9 +205,15 @@ func (cmd *DetailsCommand) Run(args []string) error { table.Add(eventData, eventName, eventIp, eventLabel, eventUsername) } table.Add("", "") - table.Print() + if outputFormat != "JSON" { + table.Print() + } + userInfo.Events = events } + if outputFormat == "JSON" { + utils.PrintPrettyJSON(cmd.UI, userInfo) + } return nil } diff --git a/plugin/commands/user/details_test.go b/plugin/commands/user/details_test.go index 2a5fbb5e..d6c4c750 100644 --- a/plugin/commands/user/details_test.go +++ b/plugin/commands/user/details_test.go @@ -51,7 +51,6 @@ var _ = Describe("sl user detail", func() { }) Describe("API Errors", func() { - It("SoftLayer_User_Customer::getObject Exception", func() { fakeHandler.AddApiError("SoftLayer_User_Customer", "getObject", 500, "Internal Server Error") err := testhelpers.RunCobraCommand(cliCommand.Command, "5555") @@ -78,31 +77,6 @@ var _ = Describe("sl user detail", func() { }) }) - Describe("Tests needing a fake UserManager", func() { - var testUser datatypes.User_Customer - BeforeEach(func() { - testUser = datatypes.User_Customer{} - txError := fakeHandler.DoRequest( - fakeSession, "SoftLayer_User_Customer", "getObject", nil, nil, &testUser, - ) - Expect(txError).NotTo(HaveOccurred()) - fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) - cliCommand.UserManager = fakeUserManager - }) - It("SoftLayer_User_Customer::getObject Second Exception Hardware", func() { - fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD HARDWARE")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--hardware") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show hardware.")) - }) - It("SoftLayer_User_Customer::getObject Second Exception Virtual", func() { - fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD VIRTUAL")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--virtual") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to show virual server.")) - }) - }) - Describe("Happy Path Tests", func() { It("return a user", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "5555") @@ -142,6 +116,32 @@ var _ = Describe("sl user detail", func() { }) }) + // Since this CLI makes 2 calls to the same API, we need to use the Fake Manager to handle that. + Describe("API Errors with a Fake Manager", func() { + var testUser datatypes.User_Customer + BeforeEach(func() { + testUser = datatypes.User_Customer{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_User_Customer", "getObject", nil, nil, &testUser, + ) + Expect(txError).NotTo(HaveOccurred()) + fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) + cliCommand.UserManager = fakeUserManager + }) + It("SoftLayer_User_Customer::getObject Second Exception Hardware", func() { + fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD HARDWARE")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--hardware") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show hardware.")) + }) + It("SoftLayer_User_Customer::getObject Second Exception Virtual", func() { + fakeUserManager.GetUserReturnsOnCall(1, datatypes.User_Customer{}, errors.New("BAD VIRTUAL")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--virtual") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to show virual server.")) + }) + }) + Describe("Happy Path with Fake Manager", func() { var testUser datatypes.User_Customer BeforeEach(func() { @@ -182,6 +182,44 @@ var _ = Describe("sl user detail", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("3263-10-1-stemcell-bluemix.softlayer.com")) Expect(fakeUI.Outputs()).To(ContainSubstring("3263-10-2-stemcell-bluemix.softlayer.com")) }) + It("JSON with --hardware", func() { + userHardware := []datatypes.Hardware{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_Account", "getHardware", nil, nil, &userHardware, + ) + testUser.Hardware = userHardware + fakeUserManager.GetUserReturnsOnCall(1, testUser, nil) + Expect(txError).NotTo(HaveOccurred()) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--output=json", "--hardware") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"User": {`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "XXX.ASD@ibm.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Virtual": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"hostname": "ibmcloud-cli-dev1"`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Events": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Permissions": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Logins": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"DedicatedHosts": null`)) + }) + It("JSON with --virtual", func() { + userGuests := []datatypes.Virtual_Guest{} + txError := fakeHandler.DoRequest( + fakeSession, "SoftLayer_Account", "getVirtualGuests", nil, nil, &userGuests, + ) + testUser.VirtualGuests = userGuests + fakeUserManager.GetUserReturnsOnCall(1, testUser, nil) + Expect(txError).NotTo(HaveOccurred()) + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--output=json", "--virtual") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"User": {`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "XXX.ASD@ibm.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"hostName": "3169-2-stemcell-for-dirtycow",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Hardware": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Events": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Permissions": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Logins": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"DedicatedHosts": null`)) + }) It("return a user without apikey", func() { testUser.ApiAuthenticationKeys = []datatypes.User_Customer_ApiAuthentication{} fakeUserManager.GetUserReturnsOnCall(0, testUser, nil) @@ -192,4 +230,56 @@ var _ = Describe("sl user detail", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Username test")) }) }) + Describe("JSON Output tests", func() { + It("Just user details", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--output=json") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"User": {`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "XXX.ASD@ibm.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Virtual": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Hardware": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Events": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Permissions": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Logins": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"DedicatedHosts": null`)) + }) + It("JSON with --permissions", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--output=json", "--permissions") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"User": {`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "XXX.ASD@ibm.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Virtual": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Hardware": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Events": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"keyName": "ACCESS_ALL_DEDICATEDHOSTS",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Logins": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"DedicatedHosts": null`)) + }) + + It("JSON with --logins", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--output=json", "--logins") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"User": {`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "XXX.ASD@ibm.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Virtual": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Hardware": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Events": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Permissions": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"ipAddress": "169.60.96.34",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Logins": [`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"DedicatedHosts": null`)) + }) + It("JSON with --events", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "5555", "--output=json", "--events") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"User": {`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "XXX.ASD@ibm.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Virtual": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Hardware": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"eventName": "IAM Token validation successful",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Permissions": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"Logins": null,`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"DedicatedHosts": null`)) + }) + }) }) From b0466f0757f202b1e27372b6e9267bcd6bad593f Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 22 Jul 2024 11:57:06 -0500 Subject: [PATCH 03/38] fixed a gosec issue --- plugin/commands/user/details.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/commands/user/details.go b/plugin/commands/user/details.go index 9f900764..12edc2e6 100644 --- a/plugin/commands/user/details.go +++ b/plugin/commands/user/details.go @@ -212,7 +212,8 @@ func (cmd *DetailsCommand) Run(args []string) error { } if outputFormat == "JSON" { - utils.PrintPrettyJSON(cmd.UI, userInfo) + err := utils.PrintPrettyJSON(cmd.UI, userInfo) + return err } return nil From 5d0713149bc218d322992cab6aa754b9e8aacb8a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 22 Jul 2024 17:42:40 -0500 Subject: [PATCH 04/38] updated secrets baseline --- .secrets.baseline | 6 +- plugin/commands/user/list.go | 40 +++-- plugin/commands/user/list_test.go | 149 +++++++----------- plugin/i18n/v2Resources/active.de_DE.json | 4 +- plugin/i18n/v2Resources/active.en-US.json | 4 +- plugin/i18n/v2Resources/active.es_ES.json | 4 +- plugin/i18n/v2Resources/active.fr_FR.json | 4 +- plugin/i18n/v2Resources/active.it_IT.json | 4 +- plugin/i18n/v2Resources/active.ja_JP.json | 4 +- plugin/i18n/v2Resources/active.ko_KR.json | 4 +- plugin/i18n/v2Resources/active.pt_BR.json | 4 +- plugin/i18n/v2Resources/active.zh_Hans.json | 4 +- plugin/i18n/v2Resources/active.zh_Hant.json | 4 +- .../SoftLayer_Account/getUsers.json | 94 ++++++++--- plugin/utils/utils.go | 10 ++ plugin/utils/utils_test.go | 49 +++++- 16 files changed, 236 insertions(+), 152 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 6a7a4dce..e97cc2b1 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "plugin/i18n/v1Resources/|plugin/i18n/v2Resources/|(.*test.*)|(vendor)|(go.sum)|bin/|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-06-06T23:07:46Z", + "generated_at": "2024-07-22T22:42:28Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -296,7 +296,7 @@ "hashed_secret": "bde54745b8d37fae50e777aa46ecae8f40ee2fcf", "is_secret": false, "is_verified": false, - "line_number": 53, + "line_number": 35, "type": "Secret Keyword", "verified_result": null }, @@ -304,7 +304,7 @@ "hashed_secret": "0e7312e100f7e0691b1deb8ffbe98766a1cdb902", "is_secret": false, "is_verified": false, - "line_number": 91, + "line_number": 102, "type": "Secret Keyword", "verified_result": null } diff --git a/plugin/commands/user/list.go b/plugin/commands/user/list.go index fa54454d..3e95d2cc 100644 --- a/plugin/commands/user/list.go +++ b/plugin/commands/user/list.go @@ -1,6 +1,9 @@ package user import ( + "fmt" + "strings" + "github.com/spf13/cobra" "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -20,6 +23,19 @@ type ListCommand struct { Column []string } +var maskMap = map[string]string{ + "id": "id", + "username": "username", + "email": "email", + "displayName": "displayName", + "status": "userStatus.name", + "hardwareCount": "hardwareCount", + "virtualGuestCount": "virtualGuestCount", + "2FA": "externalBindingCount", + "classicAPIKey": "apiAuthenticationKeyCount", + "vpn": "sslVpnAllowedFlag", +} + func NewListCommand(sl *metadata.SoftlayerCommand) (cmd *ListCommand) { thisCmd := &ListCommand{ SoftlayerCommand: sl, @@ -35,29 +51,24 @@ func NewListCommand(sl *metadata.SoftlayerCommand) (cmd *ListCommand) { }, } - cobraCmd.Flags().StringSliceVar(&thisCmd.Column, "column", []string{}, T("Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times")) + columns := "" + for key, _ := range maskMap { + columns = fmt.Sprintf("%s %s,", columns, key) + } + columns = strings.TrimSuffix(columns, ",") + subs := map[string]interface{}{"Columns": columns} + cobraCmd.Flags().StringSliceVar(&thisCmd.Column, "column", []string{}, + T("Column to display. options are: {{.Columns}}. This option can be specified multiple times", subs)) thisCmd.Command = cobraCmd return thisCmd } -var maskMap = map[string]string{ - "id": "id", - "username": "username", - "email": "email", - "displayName": "displayName", - "status": "userStatus.name", - "hardwareCount": "hardwareCount", - "virtualGuestCount": "virtualGuestCount", - "2FA": "externalBindingCount", - "classicAPIKey": "apiAuthenticationKeyCount", -} - func (cmd *ListCommand) Run(args []string) error { columns := cmd.Column - defaultColumns := []string{"id", "username", "email", "displayName", "2FA", "classicAPIKey"} + defaultColumns := []string{"id", "username", "email", "displayName", "2FA", "classicAPIKey", "vpn"} optionalColumns := []string{"status", "hardwareCount", "virtualGuestCount"} showColumns, err := utils.ValidateColumns2("", columns, defaultColumns, optionalColumns, []string{}) @@ -89,6 +100,7 @@ func (cmd *ListCommand) Run(args []string) error { values["2FA"] = utils.ReplaceUIntPointerValue(user.ExternalBindingCount, NO_ZERO_VALUE) values["classicAPIKey"] = utils.ReplaceUIntPointerValue(user.ApiAuthenticationKeyCount, NO_ZERO_VALUE) + values["vpn"] = utils.FormatBoolPointerToYN(user.SslVpnAllowedFlag) if user.UserStatus != nil { values["status"] = utils.FormatStringPointer(user.UserStatus.Name) diff --git a/plugin/commands/user/list_test.go b/plugin/commands/user/list_test.go index 3cd44a80..2d097024 100644 --- a/plugin/commands/user/list_test.go +++ b/plugin/commands/user/list_test.go @@ -1,119 +1,92 @@ package user_test import ( - "errors" - "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/session" - "github.com/softlayer/softlayer-go/sl" + "strings" "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/user" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" ) -var _ = Describe("User List", func() { +var _ = Describe("sl user list", func() { var ( - fakeUI *terminal.FakeUI - fakeUserManager *testhelpers.FakeUserManager - cliCommand *user.ListCommand - fakeSession *session.Session - slCommand *metadata.SoftlayerCommand + fakeUI *terminal.FakeUI + // fakeUserManager *testhelpers.FakeUserManager + cliCommand *user.ListCommand + fakeSession *session.Session + slCommand *metadata.SoftlayerCommand + fakeHandler *testhelpers.FakeTransportHandler ) BeforeEach(func() { fakeUI = terminal.NewFakeUI() - fakeUserManager = new(testhelpers.FakeUserManager) - fakeSession = testhelpers.NewFakeSoftlayerSession([]string{}) + // fakeUserManager = new(testhelpers.FakeUserManager) + fakeSession = testhelpers.NewFakeSoftlayerSession(nil) + fakeHandler = testhelpers.GetSessionHandler(fakeSession) slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) cliCommand = user.NewListCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") - cliCommand.UserManager = fakeUserManager - - testListUser := []datatypes.User_Customer{ - datatypes.User_Customer{ - Id: sl.Int(5555), - Username: sl.String("ATestUser"), - Email: sl.String("user2@email.com"), - DisplayName: sl.String("DisplayedName"), - ExternalBindingCount: sl.Uint(123), - ApiAuthenticationKeyCount: sl.Uint(123456), - UserStatus: &datatypes.User_Customer_Status{ - Name: sl.String("ACTIVE"), - }, - }, - datatypes.User_Customer{ - Id: sl.Int(5556), - Username: sl.String("ATestUser2"), - Email: sl.String("user2@email.com"), - DisplayName: sl.String("DisplayedName2"), - ExternalBindingCount: sl.Uint(1234), - ApiAuthenticationKeyCount: sl.Uint(1234567), - }, - } - fakeUserManager.ListUsersReturns(testListUser, nil) + }) + AfterEach(func() { + // Clear API call logs and any errors that might have been set after every test + fakeHandler.ClearApiCallLogs() + fakeHandler.ClearErrors() }) - Describe("user list ", func() { - Context("user list with unknown column", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--column", "noExist") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: --column noExist is not supported.")) - }) + Describe("Usage Errors ", func() { + It("return error", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "--column", "noExist") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: --column noExist is not supported.")) }) + }) - Context("user list fatal error", func() { - It("return error", func() { - fakeUserManager.ListUsersReturns([]datatypes.User_Customer{}, errors.New("Internal server error")) - err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to list users.")) - }) + Describe("API Errors", func() { + It("SoftLayer_Account::getUsers API Error", func() { + fakeHandler.AddApiError("SoftLayer_Account", "getUsers", 500, "Internal Server Error") + err := testhelpers.RunCobraCommand(cliCommand.Command) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to list users.")) }) + }) - Context("user list", func() { - It("return users list", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("id username email displayName 2FA classicAPIKey")) - Expect(fakeUI.Outputs()).To(ContainSubstring("5555 ATestUser user2@email.com DisplayedName yes yes")) - Expect(fakeUI.Outputs()).To(ContainSubstring("5556 ATestUser2 user2@email.com DisplayedName2 yes yes")) - }) + Describe("Happy Path", func() { + It("return users list", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command) + Expect(err).NotTo(HaveOccurred()) + // Remove whitespace to make testing output a bit less rigid + trimmed := strings.ReplaceAll(fakeUI.Outputs(), " ", "") + Expect(trimmed).To(ContainSubstring("idusernameemaildisplayName2FAclassicAPIKeyvpn")) + Expect(trimmed).To(ContainSubstring("1468361IBM27821sdfasdfasd@one.comazzz---")) + Expect(trimmed).To(ContainSubstring("1468362IBM27832sdfasdfasd@two.comaccc-yesYes")) + Expect(trimmed).To(ContainSubstring("1468363IBM27843sdfasdfasd@three.comabc--No")) }) - - Context("user list with column", func() { - It("return users list", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--column", "username") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("username")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ATestUser")) - Expect(fakeUI.Outputs()).To(ContainSubstring("ATestUser2")) - }) + It("return users list simple columns", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "--column=id", "--column=displayName") + Expect(err).NotTo(HaveOccurred()) + // Remove whitespace to make testing output a bit less rigid + trimmed := strings.ReplaceAll(fakeUI.Outputs(), " ", "") + Expect(trimmed).To(ContainSubstring("iddisplayName")) + Expect(trimmed).To(ContainSubstring("1468361azzz")) + Expect(trimmed).To(ContainSubstring("1468362accc")) + Expect(trimmed).To(ContainSubstring("1468363abc")) }) - - Context("user list in format json", func() { - It("return users list", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--output", "json") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"apiAuthenticationKeyCount": 123456,`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"displayName": "DisplayedName",`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "user2@email.com",`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"externalBindingCount": 123,`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"id": 5555,`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"userStatus": {`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"name": "ACTIVE"`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`},`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"username": "ATestUser"`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"apiAuthenticationKeyCount": 1234567,`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"displayName": "DisplayedName2",`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "user2@email.com",`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"externalBindingCount": 1234,`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"id": 5556,`)) - Expect(fakeUI.Outputs()).To(ContainSubstring(`"username": "ATestUser2"`)) - }) + It("return users list with just username", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "--column", "username") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("username")) + Expect(fakeUI.Outputs()).To(ContainSubstring("IBM27821")) + Expect(fakeUI.Outputs()).To(ContainSubstring("IBM27832")) + }) + It("return users list with json output", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "--output", "json") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"email": "sdfasdfasd@three.com",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"displayName": "azzz",`)) + Expect(fakeUI.Outputs()).To(ContainSubstring(`"apiAuthenticationKeyCount": 1,`)) }) }) }) diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index 0c0455ce..8b3d78d6 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Anzuzeigende Spalte. [Optionen: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [Standardwert: id, hostname, domain, primary_ip, backend_ip, power_state]" }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "Anzuzeigende Spalte. Optionen: id,username,email,displayName,2FA, classicAPIKey, status, hardwareCount, virtualGuestCount. Diese Option kann mehrmals angegeben werden." + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "Anzuzeigende Spalte. Optionen: {{.Columns}}. Diese Option kann mehrmals angegeben werden." }, "Column to sort by": { "other": "Spalte, nach der sortiert werden soll" diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index c0ed85f8..ae1b771f 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -1328,8 +1328,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state]." }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times" + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "Column to display. options are: {{.Columns}}. This option can be specified multiple times" }, "Column to sort by": { "other": "Column to sort by" diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index 6e429b48..7df224a1 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Columna a visualizar. [Las opciones son: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state]." }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "Columna a visualizar. Las opciones son: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. Esta opción se puede especificar varias veces" + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "Columna a visualizar. Las opciones son: {{.Columns}}. Esta opción se puede especificar varias veces" }, "Column to sort by": { "other": "Columna por la que se ordena" diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 7c2ed8f5..43482a4e 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Colonne à afficher. [Les options sont : guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [par défaut : id,hostname,domain,primary_ip,backend_ip,power_state]." }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "Colonne à afficher. Options : id, username, email, displayName, 2FA, classicAPIKey, statut, hardwareCount, virtualGuestCount. Cette option peut être indiquée plusieurs fois." + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "Colonne à afficher. Options : {{.Columns}}. Cette option peut être indiquée plusieurs fois." }, "Column to sort by": { "other": "Colonne de tri" diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index 4c82e4b3..0676f267 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Colonna da visualizzare. [Le opzioni sono: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [valore predefinito: id,hostname,domain,primary_ip,backend_ip,power_state]." }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "Colonna da visualizzare. Le opzioni sono: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. Questa opzione può essere specificata più volte." + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "Colonna da visualizzare. Le opzioni sono: {{.Columns}}. Questa opzione può essere specificata più volte." }, "Column to sort by": { "other": "Colonna in base alla quale ordinare" diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index c337265c..d27d92fe 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "表示する列。 [オプション: guid、cpu、memory、datacenter、primary_ip、backend_ip、created_by、power_state、tags] [デフォルト: id、hostname、domain、primary_ip、backend_ip、power_state]。" }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "表示する列。 オプション: id,username,email,displayName,2FA、classicAPIKey、status、hardwareCount、virtualGuestCount。 このオプションは複数回指定できます" + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "表示する列。 オプション: {{.Columns}}。 このオプションは複数回指定できます" }, "Column to sort by": { "other": "ソート基準の列" diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index f4afdee8..151fb95c 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "표시할 열. [옵션: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [기본값: id,hostname,domain,primary_ip,backend_ip,power_state]." }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "표시할 열. 옵션: id,username,email,displayName,2FA, classicAPIKey, status, hardwareCount, virtualGuestCount. 이 옵션은 여러 번 지정할 수 있습니다." + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "표시할 열. 옵션: {{.Columns}}. 이 옵션은 여러 번 지정할 수 있습니다." }, "Column to sort by": { "other": "정렬 기준 열" diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index b25f9f18..9336afee 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Coluna a ser exibida. [As opções são: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [padrão: id, hostname, domain, primary_ip, backend_ip, power_state]." }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "Coluna a ser exibida. opções são: id,username,email,displayName,2FA,classicAPIKey,status, hardwareCount, virtualGuestCount. Essa opção pode ser especificada várias vezes" + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "Coluna a ser exibida. opções são: {.Columns}}. Essa opção pode ser especificada várias vezes" }, "Column to sort by": { "other": "Coluna pela qual classificar" diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index e86d2dbc..26f49c1a 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "要显示的列。 [选项为:guid、cpu、memory、datacenter、primary_ip、backend_ip、created_by、power_state、tags] [缺省值:id、hostname、domain、primary_ip、backend_ip、power_state]。" }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "要显示的列。 选项为:id、username、email、displayName、2FA、classicAPIKey、status、hardwareCount 或 virtualGuestCount。 此选项可以多次指定" + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "要显示的列。 选项为:{{.Columns}}。 此选项可以多次指定" }, "Column to sort by": { "other": "要作为排序依据的列" diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 53a49410..1ebe2d35 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -1325,8 +1325,8 @@ "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "要顯示的直欄。 [選項包含:guid、cpu、memory、datacenter、primary_ip、backend_ip、created_by、power_state、tags] [預設值:id、hostname、domain、primary_ip、backend_ip、power_state]。" }, - "Column to display. options are: id,username,email,displayName,2FA,classicAPIKey,status,hardwareCount,virtualGuestCount. This option can be specified multiple times": { - "other": "要顯示的直欄。 選項為:id、username、email、displayName、2FA、classicAPIKey、status、hardwareCount、virtualGuestCount。 這個選項可以多次指定" + "Column to display. options are: {{.Columns}}. This option can be specified multiple times": { + "other": "要顯示的直欄。 選項為:{{.Columns}}。 這個選項可以多次指定" }, "Column to sort by": { "other": "直欄排序方式" diff --git a/plugin/testfixtures/SoftLayer_Account/getUsers.json b/plugin/testfixtures/SoftLayer_Account/getUsers.json index e0beffb8..2c27cf25 100644 --- a/plugin/testfixtures/SoftLayer_Account/getUsers.json +++ b/plugin/testfixtures/SoftLayer_Account/getUsers.json @@ -1,21 +1,73 @@ -[{"username": "IBM2782", - "hardwareCount": 177, - "displayName": "a", - "roles": - [ - { "description": "IBM2783_system_role", "createDate": "2014-02-27T06:52:48+08:00", "systemFlag": 1, "newUserDefaultFlag": 0, "accountId": 278444, "id": 641718, "name": "IBM27asd44_system_role"}], "userStatus": {"name": "Active"}, "email": "sdfasdfasd", "id": 146836, "virtualGuestCount": 3}, - {"username": "IBM2783", - "hardwareCount": 177, - "displayName": "a", - "roles": - [ - {"modifyDate": "2014-02-27T06:52:48+08:00", "description": "IBM2783_system_role", "createDate": "2014-02-27T06:52:48+08:00", "systemFlag": 1, "newUserDefaultFlag": 0, "accountId": 278444, "id": 641718, "name": "IBM27asd44_system_role"}], "userStatus": {"name": "Active"}, "email": "sdfasdfasd", "id": 146836, "virtualGuestCount": 3}, - - {"username": "IBM2784", - "hardwareCount": 177, - "displayName": "a", - "roles": - [ - {"description": "IBM2783_system_role", "createDate": "2014-02-27T06:52:48+08:00", "systemFlag": 1, "newUserDefaultFlag": 0, "accountId": 278444, "id": 641718, "name": "IBM27asd44_system_role"}], "userStatus": {"name": "Active"}, "email": "sdfasdfasd", "id": 146836, "virtualGuestCount": 3} - - ] \ No newline at end of file +[ + { + "displayName": "azzz", + "email": "sdfasdfasd@one.com", + "hardwareCount": 1111, + "id": 1468361, + "roles": [ + { + "accountId": 278444, + "createDate": "2014-02-27T06:52:48+08:00", + "description": "IBM2783_system_role", + "id": 641718, + "name": "IBM27asd44_system_role", + "newUserDefaultFlag": 0, + "systemFlag": 1 + } + ], + "userStatus": { + "name": "Active" + }, + "username": "IBM27821", + "virtualGuestCount": 3, + "apiAuthenticationKeyCount": 0 + }, + { + "displayName": "accc", + "email": "sdfasdfasd@two.com", + "hardwareCount": 222, + "id": 1468362, + "roles": [ + { + "accountId": 278444, + "createDate": "2014-02-27T06:52:48+08:00", + "description": "IBM2783_system_role", + "id": 641718, + "modifyDate": "2014-02-27T06:52:48+08:00", + "name": "IBM27asd44_system_role", + "newUserDefaultFlag": 0, + "systemFlag": 1 + } + ], + "userStatus": { + "name": "Active" + }, + "username": "IBM27832", + "virtualGuestCount": 3, + "apiAuthenticationKeyCount": 1, + "sslVpnAllowedFlag": true + }, + { + "displayName": "abc", + "email": "sdfasdfasd@three.com", + "hardwareCount": 333, + "id": 1468363, + "roles": [ + { + "accountId": 278444, + "createDate": "2014-02-27T06:52:48+08:00", + "description": "IBM2783_system_role", + "id": 641718, + "name": "IBM27asd44_system_role", + "newUserDefaultFlag": 0, + "systemFlag": 1 + } + ], + "userStatus": { + "name": "Active" + }, + "username": "IBM27843", + "virtualGuestCount": 3, + "sslVpnAllowedFlag": false + } +] \ No newline at end of file diff --git a/plugin/utils/utils.go b/plugin/utils/utils.go index 4e9f7713..74f93fee 100644 --- a/plugin/utils/utils.go +++ b/plugin/utils/utils.go @@ -185,6 +185,16 @@ func FormatBoolPointer(value *bool) string { return strconv.FormatBool(sl.Get(value).(bool)) } +func FormatBoolPointerToYN(value *bool) string { + if value == nil { + return EMPTY_VALUE + } + if *value == true { + return T("Yes") + } + return T("No") +} + func FormatStringPointer(value *string) string { if value == nil { return EMPTY_VALUE diff --git a/plugin/utils/utils_test.go b/plugin/utils/utils_test.go index d136d494..65f65c91 100644 --- a/plugin/utils/utils_test.go +++ b/plugin/utils/utils_test.go @@ -1,13 +1,48 @@ -package utils +package utils_test import ( - "testing" - - "github.com/softlayer/softlayer-go/datatypes" - "github.com/softlayer/softlayer-go/sl" - . "github.com/stretchr/testify/assert" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/utils" ) +var _ = Describe("Utility Tests", func() { + DescribeTable("FormatBoolPointerToYN Tests", + func(input bool, expected string) { + var result string + // Special case for passing in a nil reference + // go otherwise treans nil as false if it is assigned + if expected == "-" { + result = utils.FormatBoolPointerToYN(nil) + } else { + result = utils.FormatBoolPointerToYN(&input) + } + + Expect(result).To(Equal(expected)) + }, + Entry("Nil Input", nil, "No"), + Entry("True Input", true, "Yes"), + Entry("False Input", false, "No"), + ) + DescribeTable("ResolveVirtualGuestId Tests", + func(input string, expected int, expectedErr string) { + result, err := utils.ResolveVirtualGuestId(input) + if expectedErr != "" { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal(expectedErr)) + } else { + Expect(err).NotTo(HaveOccurred()) + } + Expect(result).To(Equal(expected)) + }, + Entry("Number", "99", 99, nil), + Entry("Large Number", "30000000", 30000000, nil), + Entry("String", "NinteyNine", 0, "strconv.Atoi: parsing \"NinteyNine\": invalid syntax"), + Entry("Nil", nil, 0, "strconv.Atoi: parsing \"\": invalid syntax"), + ) +}) + +/* func TestSliceInSlice(t *testing.T) { source := []string{"id", "hostnamdde"} defaultColumns := []string{"id", "hostname", "domain", "cpu", "memory", "primary_ip", "backend_ip", "datacenter", "action"} @@ -84,3 +119,5 @@ func TestStructToMap1(t *testing.T) { Equal(t, err, nil) Equal(t, accessMap, map[string]string{"id": "a", "name": "b", "type": "c", "private_ip_address": "d", "source_subnet": "e", "host_iqn": "f", "username": "g", "password": "h", "allowed_host_id": "i"}) } + +*/ From 385ba7db161f51d62c2e49fa09e926f3072100aa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 22 Jul 2024 17:59:50 -0500 Subject: [PATCH 05/38] fixed unit tests --- plugin/managers/user_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/managers/user_test.go b/plugin/managers/user_test.go index 40b860bb..4bd35c54 100644 --- a/plugin/managers/user_test.go +++ b/plugin/managers/user_test.go @@ -33,8 +33,8 @@ var _ = Describe("User", func() { It("Return users", func() { users, err := UserManager.ListUsers("") Expect(err).ToNot(HaveOccurred()) - Expect(*users[0].Username).To(Equal("IBM2782")) - Expect(*users[0].HardwareCount).To(Equal(uint(177))) + Expect(*users[0].Username).To(Equal("IBM27821")) + Expect(*users[0].HardwareCount).To(Equal(uint(1111))) }) }) }) From 9c2a27f28fae990a4ccffc7d8b7c7efa7b2800db Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 23 Jul 2024 13:36:47 -0500 Subject: [PATCH 06/38] Removed PPTP VPN from user detail. #848 --- plugin/commands/user/details.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/commands/user/details.go b/plugin/commands/user/details.go index 12edc2e6..0927d2cd 100644 --- a/plugin/commands/user/details.go +++ b/plugin/commands/user/details.go @@ -78,7 +78,7 @@ func (cmd *DetailsCommand) Run(args []string) error { logins := cmd.Logins events := cmd.Events - object_mask := "userStatus[name],parent[id,username],apiAuthenticationKeys[authenticationKey]" + object_mask := "userStatus[name],parent[id,username],apiAuthenticationKeys[authenticationKey],sslVpnAllowedFlag" user, err := cmd.UserManager.GetUser(id, object_mask) userInfo.User = user if err != nil { @@ -249,8 +249,6 @@ func baseUserPrint(user datatypes.User_Customer, keys bool, ui terminal.UI) { if user.UserStatus != nil { table.Add(T("Status"), utils.FormatStringPointer(user.UserStatus.Name)) } - - table.Add(T("PPTP VPN"), utils.FormatBoolPointer(user.PptpVpnAllowedFlag)) table.Add(T("SSL VPN"), utils.FormatBoolPointer(user.SslVpnAllowedFlag)) if len(user.SuccessfulLogins) != 0 { From 23fed91ecf12159a6772b1e3b5136c2c24450fe6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Aug 2024 14:35:06 -0500 Subject: [PATCH 07/38] Added unit test for issues #863 --- plugin/commands/order/place_test.go | 37 + .../getDatacenters_mad02.json | 1 + .../getItems-835.json | 1004 +++++++++++++++++ plugin/testhelpers/fake_softlayer_session.go | 37 +- 4 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 plugin/testfixtures/SoftLayer_Location_Datacenter/getDatacenters_mad02.json create mode 100644 plugin/testfixtures/SoftLayer_Product_Package/getItems-835.json diff --git a/plugin/commands/order/place_test.go b/plugin/commands/order/place_test.go index 2f2452b0..88d62cab 100644 --- a/plugin/commands/order/place_test.go +++ b/plugin/commands/order/place_test.go @@ -27,11 +27,13 @@ var _ = Describe("Place", func() { slCommand *metadata.SoftlayerCommand OrderManager managers.OrderManager fakeOrderManager *testhelpers.FakeOrderManager + fakeHandler *testhelpers.FakeTransportHandler ) BeforeEach(func() { filenames := []string{"getDatacenters_1"} fakeUI = terminal.NewFakeUI() fakeSession = testhelpers.NewFakeSoftlayerSession(filenames) + fakeHandler = testhelpers.GetSessionHandler(fakeSession) OrderManager = managers.NewOrderManager(fakeSession) fakeOrderManager = new(testhelpers.FakeOrderManager) slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) @@ -39,6 +41,11 @@ var _ = Describe("Place", func() { cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.OrderManager = OrderManager }) + AfterEach(func() { + // Clear API call logs and any errors that might have been set after every test + fakeHandler.ClearApiCallLogs() + fakeHandler.ClearErrors() + }) Describe("order verify", func() { for k, _ := range order.TYPEMAP { @@ -236,4 +243,34 @@ var _ = Describe("Place", func() { }) }) }) + Describe("softlayer-cli/issues/863", func() { + BeforeEach(func() { + fakeHandler.ClearApiCallLogs() + fakeHandler.SetFileNames([]string{"getItems-835", "getDatacenters_mad02"}) + }) + It("Finds the correct price IDs", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, + "PUBLIC_CLOUD_SERVER", "MADRID02", "1_GBPS_PRIVATE_NETWORK_UPLINK", "1_IP_ADDRESS", + "GUEST_DISK_100_GB_LOCAL", "OS_RED_HAT_ENTERPRISE_LINUX_9_X_MINIMAL_INSTALL_64_BIT", + "MONITORING_HOST_PING", "NOTIFICATION_EMAIL_AND_TICKET", "AUTOMATED_NOTIFICATION", + "UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT", "REBOOT_REMOTE_CONSOLE", "BANDWIDTH_0_GB", + "--billing=monthly", + `--extras={"virtualGuests":[{"hostname":"testServer","domain":"ibm.com"}]}`, + "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest", + "--preset=BL2_8x32x100", "--verify", + ) + Expect(err).NotTo(HaveOccurred()) + + callLog := fakeHandler.ApiCallLogs + Expect(len(callLog)).To(Equal(9)) + fmt.Printf(callLog[8].String()) + Expect(callLog[8].String()).To(Equal(`SoftLayer_Product_Order::verifyOrder(id=0, mask='', filter='', ` + + `{"parameters":[{"complexType":"SoftLayer_Container_Product_Order_Virtual_Guest",` + + `"location":"3460412","packageId":865,"presetId":785,"prices":[{"id":899},{"id":21},{"id":204637},` + + `{"id":314158},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}],"quantity":1,` + + `"useHourlyPricing":false,"virtualGuests":[{"domain":"ibm.com","hostname":"testServer"}]}]}`, + )) + }) + }) }) diff --git a/plugin/testfixtures/SoftLayer_Location_Datacenter/getDatacenters_mad02.json b/plugin/testfixtures/SoftLayer_Location_Datacenter/getDatacenters_mad02.json new file mode 100644 index 00000000..98c697cc --- /dev/null +++ b/plugin/testfixtures/SoftLayer_Location_Datacenter/getDatacenters_mad02.json @@ -0,0 +1 @@ +[{"id":3460412,"name":"mad02","regions":[{"keyname":"MADRID02"}]}] \ No newline at end of file diff --git a/plugin/testfixtures/SoftLayer_Product_Package/getItems-835.json b/plugin/testfixtures/SoftLayer_Product_Package/getItems-835.json new file mode 100644 index 00000000..bc924061 --- /dev/null +++ b/plugin/testfixtures/SoftLayer_Product_Package/getItems-835.json @@ -0,0 +1,1004 @@ +[ + { + "id": 498, + "itemCategory": { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + }, + "keyName": "1_GBPS_PRIVATE_NETWORK_UPLINK", + "prices": [ + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".01568", + "id": 899, + "itemId": 498, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "10.45", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".01721", + "id": 52425, + "itemId": 498, + "laborFee": "0", + "locationGroupId": 503, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "11.08", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".01767", + "id": 52427, + "itemId": 498, + "laborFee": "0", + "locationGroupId": 505, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "11.81", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".01872", + "id": 52429, + "itemId": 498, + "laborFee": "0", + "locationGroupId": 507, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "12.12", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".01886", + "id": 52431, + "itemId": 498, + "laborFee": "0", + "locationGroupId": 509, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "12.54", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".02022", + "id": 52433, + "itemId": 498, + "laborFee": "0", + "locationGroupId": 545, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "13.48", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + "quantityLimit": 0, + "sortOrder": 30 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".02195", + "id": 151877, + "itemId": 498, + "laborFee": "0", + "locationGroupId": 583, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "14.42", + "setupFee": "0", + "sort": 53, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 15, + "itemCategory": { + "categoryCode": "pri_ip_addresses", + "id": 13, + "name": "Primary IP Addresses", + "quantityLimit": 0, + "sortOrder": 32 + }, + "keyName": "1_IP_ADDRESS", + "prices": [ + { + "categories": [ + { + "categoryCode": "pri_ip_addresses", + "id": 13, + "name": "Primary IP Addresses", + "quantityLimit": 0, + "sortOrder": 32 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 21, + "itemId": 15, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 3876, + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + }, + "keyName": "GUEST_DISK_100_GB_LOCAL", + "prices": [ + { + "categories": [ + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".00627", + "id": 204637, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "4.16", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".00646", + "id": 204639, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": 503, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "4.41", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".00662", + "id": 204641, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": 505, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "4.7", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".00771", + "id": 204643, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": 507, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "4.83", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".00777", + "id": 204645, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": 509, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "4.99", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "categories": [ + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".00787", + "id": 204647, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": 545, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "5.38", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 26421, + "itemCategory": { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + }, + "keyName": "OS_RED_HAT_ENTERPRISE_LINUX_9_X_MINIMAL_INSTALL_64_BIT", + "prices": [ + { + "capacityRestrictionMaximum": "1", + "capacityRestrictionMinimum": "1", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".017", + "id": 314136, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "12.58", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "2", + "capacityRestrictionMinimum": "2", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".034", + "id": 314138, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "25.16", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "4", + "capacityRestrictionMinimum": "4", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".068", + "id": 314140, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "50.32", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "8", + "capacityRestrictionMinimum": "8", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".136", + "id": 314142, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "100.64", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "12", + "capacityRestrictionMinimum": "12", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".132", + "id": 314144, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "105.96", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "16", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".176", + "id": 314146, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "141.28", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "30", + "capacityRestrictionMinimum": "30", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".33", + "id": 314148, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "264.9", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "32", + "capacityRestrictionMinimum": "32", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".352", + "id": 314150, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "282.56", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "48", + "capacityRestrictionMinimum": "48", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".528", + "id": 314152, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "423.84", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "56", + "capacityRestrictionMinimum": "56", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".616", + "id": 314154, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "494.48", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "62", + "capacityRestrictionMinimum": "62", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".682", + "id": 314156, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "547.46", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "capacityRestrictionMaximum": "64", + "capacityRestrictionMinimum": "64", + "capacityRestrictionType": "CORE", + "categories": [ + { + "categoryCode": "os", + "id": 12, + "name": "Operating System", + "quantityLimit": 0, + "sortOrder": 1 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": ".704", + "id": 314158, + "itemId": 26421, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "565.12", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 49, + "itemCategory": { + "categoryCode": "monitoring", + "id": 20, + "name": "Monitoring", + "quantityLimit": 0, + "sortOrder": 109 + }, + "keyName": "MONITORING_HOST_PING", + "prices": [ + { + "categories": [ + { + "categoryCode": "monitoring", + "id": 20, + "name": "Monitoring", + "quantityLimit": 0, + "sortOrder": 109 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 55, + "itemId": 49, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 51, + "itemCategory": { + "categoryCode": "notification", + "id": 21, + "name": "Notification", + "quantityLimit": 0, + "sortOrder": 110 + }, + "keyName": "NOTIFICATION_EMAIL_AND_TICKET", + "prices": [ + { + "categories": [ + { + "categoryCode": "notification", + "id": 21, + "name": "Notification", + "quantityLimit": 0, + "sortOrder": 110 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 57, + "itemId": 51, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 52, + "itemCategory": { + "categoryCode": "response", + "id": 22, + "name": "Response", + "quantityLimit": 0, + "sortOrder": 112 + }, + "keyName": "AUTOMATED_NOTIFICATION", + "prices": [ + { + "categories": [ + { + "categoryCode": "response", + "id": 22, + "name": "Response", + "quantityLimit": 0, + "sortOrder": 112 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 58, + "itemId": 52, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 309, + "itemCategory": { + "categoryCode": "vpn_management", + "id": 31, + "name": "VPN Management - Private Network", + "quantityLimit": 0, + "sortOrder": 115 + }, + "keyName": "UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT", + "prices": [ + { + "categories": [ + { + "categoryCode": "vpn_management", + "id": 31, + "name": "VPN Management - Private Network", + "quantityLimit": 0, + "sortOrder": 115 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 420, + "itemId": 309, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 503, + "itemCategory": { + "categoryCode": "remote_management", + "id": 46, + "name": "Remote Management", + "quantityLimit": 0, + "sortOrder": 31 + }, + "keyName": "REBOOT_REMOTE_CONSOLE", + "prices": [ + { + "categories": [ + { + "categoryCode": "remote_management", + "id": 46, + "name": "Remote Management", + "quantityLimit": 0, + "sortOrder": 31 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 905, + "itemId": 503, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 439, + "itemCategory": { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + "quantityLimit": 0, + "sortOrder": 29 + }, + "keyName": "BANDWIDTH_0_GB_2", + "prices": [ + { + "categories": [ + { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + "quantityLimit": 0, + "sortOrder": 29 + } + ], + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 1800, + "itemId": 439, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "setupFee": "0", + "sort": 99, + "termLength": null, + "tierMinimumThreshold": null + } + ] + }, + { + "id": 4481, + "itemCategory": { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + "quantityLimit": 0, + "sortOrder": 29 + }, + "keyName": "BANDWIDTH_0_GB", + "prices": [ + { + "categories": [ + { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + "quantityLimit": 0, + "sortOrder": 29 + } + ], + "currentPriceFlag": null, + "id": 22505, + "itemId": 4481, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 98, + "termLength": null, + "tierMinimumThreshold": null + } + ] + } +] \ No newline at end of file diff --git a/plugin/testhelpers/fake_softlayer_session.go b/plugin/testhelpers/fake_softlayer_session.go index c98a9fbf..c82eab66 100644 --- a/plugin/testhelpers/fake_softlayer_session.go +++ b/plugin/testhelpers/fake_softlayer_session.go @@ -43,6 +43,41 @@ type ApiCallLog struct { Options *sl.Options } +func (call *ApiCallLog) String() string { + if call.Options == nil { + call.Options = new(sl.Options) + } + default_id := 0 + default_mask := "''" + default_filter := "''" + default_args := "" + if call.Options.Id != nil { + default_id = *call.Options.Id + } + if call.Options.Mask != "" { + default_mask = call.Options.Mask + } + if call.Options.Filter != "" { + default_filter = call.Options.Filter + } + if len(call.Args) > 0 { + // This is what softlayer-go/session/rest.go does + parameters, err := json.Marshal( + map[string]interface{}{ + "parameters": call.Args, + }) + default_args = string(parameters) + if err != nil { + default_args = err.Error() + } + + } + return fmt.Sprintf( + "%s::%s(id=%d, mask=%s, filter=%s, %s", + call.Service, call.Method, default_id, default_mask, default_filter, default_args, + ) +} + func (h *FakeTransportHandler) DoRequest(sess *session.Session, service string, method string, args []interface{}, options *sl.Options, pResult interface{}) error { if options == nil { @@ -56,7 +91,7 @@ func (h *FakeTransportHandler) DoRequest(sess *session.Session, service string, identifier = *options.Id } - // fmt.Printf("%s::%s(id=%d)\n", service, method, identifier) + fmt.Printf("%s::%s(id=%d)\n", service, method, identifier) h.AddApiLog(service, method, args, options) From 6dbf2dd13df8b3443cd18d1640797baf59c77365 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Aug 2024 16:13:21 -0500 Subject: [PATCH 08/38] Fixed an ordering bug related to capacityRestrictions #863 --- plugin/commands/order/place_test.go | 18 +- plugin/managers/order.go | 25 +- .../getActivePresets-835.json | 599 ++++++++++++++++++ 3 files changed, 626 insertions(+), 16 deletions(-) create mode 100644 plugin/testfixtures/SoftLayer_Product_Package/getActivePresets-835.json diff --git a/plugin/commands/order/place_test.go b/plugin/commands/order/place_test.go index 88d62cab..ca03ac1a 100644 --- a/plugin/commands/order/place_test.go +++ b/plugin/commands/order/place_test.go @@ -41,11 +41,11 @@ var _ = Describe("Place", func() { cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.OrderManager = OrderManager }) - AfterEach(func() { - // Clear API call logs and any errors that might have been set after every test - fakeHandler.ClearApiCallLogs() - fakeHandler.ClearErrors() - }) + AfterEach(func() { + // Clear API call logs and any errors that might have been set after every test + fakeHandler.ClearApiCallLogs() + fakeHandler.ClearErrors() + }) Describe("order verify", func() { for k, _ := range order.TYPEMAP { @@ -246,7 +246,7 @@ var _ = Describe("Place", func() { Describe("softlayer-cli/issues/863", func() { BeforeEach(func() { fakeHandler.ClearApiCallLogs() - fakeHandler.SetFileNames([]string{"getItems-835", "getDatacenters_mad02"}) + fakeHandler.SetFileNames([]string{"getItems-835", "getDatacenters_mad02", "getActivePresets-835"}) }) It("Finds the correct price IDs", func() { err := testhelpers.RunCobraCommand( @@ -261,14 +261,14 @@ var _ = Describe("Place", func() { "--preset=BL2_8x32x100", "--verify", ) Expect(err).NotTo(HaveOccurred()) - + callLog := fakeHandler.ApiCallLogs Expect(len(callLog)).To(Equal(9)) fmt.Printf(callLog[8].String()) Expect(callLog[8].String()).To(Equal(`SoftLayer_Product_Order::verifyOrder(id=0, mask='', filter='', ` + `{"parameters":[{"complexType":"SoftLayer_Container_Product_Order_Virtual_Guest",` + - `"location":"3460412","packageId":865,"presetId":785,"prices":[{"id":899},{"id":21},{"id":204637},` + - `{"id":314158},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}],"quantity":1,` + + `"location":"3460412","packageId":865,"presetId":281,"prices":[{"id":899},{"id":21},{"id":204637},` + + `{"id":314142},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}],"quantity":1,` + `"useHourlyPricing":false,"virtualGuests":[{"domain":"ibm.com","hostname":"testServer"}]}]}`, )) }) diff --git a/plugin/managers/order.go b/plugin/managers/order.go index d57f287a..91d05d3b 100644 --- a/plugin/managers/order.go +++ b/plugin/managers/order.go @@ -232,7 +232,9 @@ func (i orderManager) GenerateOrder(packageKeyname, location string, itemKeyname } for _, item := range presetItems.Prices { - if item.Item != nil && item.Item.ItemCategory != nil && item.Item.ItemCategory.CategoryCode != nil && *item.Item.ItemCategory.CategoryCode == "guest_core" { + if item.Item != nil && item.Item.ItemCategory != nil && + item.Item.ItemCategory.CategoryCode != nil && + *item.Item.ItemCategory.CategoryCode == "guest_core" { if item.Item.Capacity != nil { presetCore = float64(*item.Item.Capacity) } @@ -317,8 +319,9 @@ func (i orderManager) GetPresetbyKey(packageKeyname, presetKeyname string) (data return activePresets[0], nil } +// Makes an API call to get packageKeyName +// Finds the given itemKeynames in the result, checking any capacity restrictions for the items if needed func (i orderManager) GetPriceIdList(packageKeyname string, itemKeynames []string, presetCore float64) ([]int, error) { - mask := "id, itemCategory, keyName, prices[categories]" packages, err := i.GetPackageByKey(packageKeyname, "id") if err != nil { @@ -343,16 +346,22 @@ func (i orderManager) GetPriceIdList(packageKeyname string, itemKeynames []strin if len(newItems) != 0 { matchingItem = newItems[0] } else { - return nil, errors.New(T("Item {{.Item}} does not exist for package {{.Package}}", map[string]interface{}{"Item": itemKeyname, "Package": packageKeyname})) + subs := map[string]interface{}{"Item": itemKeyname, "Package": packageKeyname} + return nil, errors.New(T("Item {{.Item}} does not exist for package {{.Package}}", subs)) } + var itemCategory string if matchingItem.ItemCategory != nil && matchingItem.ItemCategory.CategoryCode != nil { itemCategory = *matchingItem.ItemCategory.CategoryCode } + if _, ok := categoryDict[itemCategory]; !ok { for _, p := range matchingItem.Prices { - if ((p.LocationGroupId != nil && *p.LocationGroupId == 0) || p.LocationGroupId == nil) && p.Id != nil { - var capacityMin, capacityMax int + if ((p.LocationGroupId != nil && *p.LocationGroupId == 0) || + p.LocationGroupId == nil) && p.Id != nil { + + capacityMin := -1 + capacityMax := -1 if p.CapacityRestrictionMinimum != nil { capacityMin, err = strconv.Atoi(*p.CapacityRestrictionMinimum) if err != nil { @@ -375,7 +384,6 @@ func (i orderManager) GetPriceIdList(packageKeyname string, itemKeynames []strin priceId = *p.Id } } - } } } else { @@ -383,7 +391,10 @@ func (i orderManager) GetPriceIdList(packageKeyname string, itemKeynames []strin categoryDict[itemCategory] += 1 categoryCode := itemCategory[:len(itemCategory)-1] + strconv.Itoa(categoryDict[itemCategory]) for _, p := range matchingItem.Prices { - if p.LocationGroupId != nil && *p.LocationGroupId == 0 && len(p.Categories) > 0 && p.Categories[0].CategoryCode != nil && *p.Categories[0].CategoryCode == categoryCode { + if p.LocationGroupId != nil && *p.LocationGroupId == 0 && + len(p.Categories) > 0 && p.Categories[0].CategoryCode != nil && + *p.Categories[0].CategoryCode == categoryCode { + PriceIdList = append(PriceIdList, *p.Id) } } diff --git a/plugin/testfixtures/SoftLayer_Product_Package/getActivePresets-835.json b/plugin/testfixtures/SoftLayer_Product_Package/getActivePresets-835.json new file mode 100644 index 00000000..94424f7b --- /dev/null +++ b/plugin/testfixtures/SoftLayer_Product_Package/getActivePresets-835.json @@ -0,0 +1,599 @@ +[ + { + "categories": [ + { + "categoryCode": "guest_disk3", + "id": 93, + "name": "Fourth Disk", + "quantityLimit": 0, + "sortOrder": null + }, + { + "categoryCode": "guest_disk4", + "id": 116, + "name": "Fifth Disk", + "quantityLimit": 0, + "sortOrder": null + }, + { + "categoryCode": "ram", + "id": 3, + "name": "RAM", + "quantityLimit": 0, + "sortOrder": 3 + }, + { + "categoryCode": "guest_disk0", + "id": 81, + "name": "First Disk", + "quantityLimit": 0, + "sortOrder": null + }, + { + "categoryCode": "guest_disk2", + "id": 92, + "name": "Third Disk", + "quantityLimit": 0, + "sortOrder": null + }, + { + "categoryCode": "guest_core", + "id": 80, + "name": "Computing Instance", + "quantityLimit": 20, + "sortOrder": null + }, + { + "categoryCode": "guest_disk1", + "id": 82, + "name": "Second Disk", + "quantityLimit": 0, + "sortOrder": null + } + ], + "description": "BL2.8x32x500", + "id": 281, + "keyName": "BL2_8X32X100", + "locations": [ + { + "id": 1004995, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "San Jose 3", + "name": "sjc03", + "statusId": 2 + }, + { + "id": 2017603, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Washington 7", + "name": "wdc07", + "statusId": 2 + }, + { + "id": 2178495, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "San Jose 4", + "name": "sjc04", + "statusId": 2 + }, + { + "id": 2434695, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Frankfurt 4", + "name": "fra04", + "statusId": 2 + }, + { + "id": 358694, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "London 2", + "name": "lon02", + "statusId": 2 + }, + { + "id": 449506, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + }, + { + "id": 449610, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Montreal 1", + "name": "mon01", + "statusId": 2 + }, + { + "id": 1854795, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Dallas 12", + "name": "dal12", + "statusId": 2 + }, + { + "id": 2017395, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "London 4", + "name": "lon04", + "statusId": 2 + }, + { + "id": 2017695, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Washington 6", + "name": "wdc06", + "statusId": 2 + }, + { + "id": 3023812, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Osaka 21", + "name": "osa21", + "statusId": 2 + }, + { + "id": 3079212, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Toronto 4", + "name": "tor04", + "statusId": 2 + }, + { + "id": 449494, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Dallas 9", + "name": "dal09", + "statusId": 2 + }, + { + "id": 449500, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Paris 1", + "name": "par01", + "statusId": 2 + }, + { + "id": 449604, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Tokyo 2", + "name": "tok02", + "statusId": 2 + }, + { + "id": 1004997, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Chennai 1", + "name": "che01", + "statusId": 2 + }, + { + "id": 1541257, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Oslo 1", + "name": "osl01", + "statusId": 2 + }, + { + "id": 1854895, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Dallas 13", + "name": "dal13", + "statusId": 2 + }, + { + "id": 2979912, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Paris 5", + "name": "par05", + "statusId": 2 + }, + { + "id": 3023912, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Osaka 23", + "name": "osa23", + "statusId": 2 + }, + { + "id": 3083212, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Sao Paulo 5", + "name": "sao05", + "statusId": 2 + }, + { + "id": 814994, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Amsterdam 3", + "name": "ams03", + "statusId": 2 + }, + { + "id": 957095, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Washington 4", + "name": "wdc04", + "statusId": 2 + }, + { + "id": 983497, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Sao Paulo 1", + "name": "sao01", + "statusId": 2 + }, + { + "id": 1441195, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Dallas 10", + "name": "dal10", + "statusId": 2 + }, + { + "id": 2954997, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Paris 4", + "name": "par04", + "statusId": 2 + }, + { + "id": 2979914, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Paris 6", + "name": "par06", + "statusId": 2 + }, + { + "id": 1555995, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Seoul 1", + "name": "seo01", + "statusId": 2 + }, + { + "id": 2123995, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "London 5", + "name": "lon05", + "statusId": 2 + }, + { + "id": 2124095, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "London 6", + "name": "lon06", + "statusId": 2 + }, + { + "id": 2417495, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Frankfurt 5", + "name": "fra05", + "statusId": 2 + }, + { + "id": 3460612, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Madrid 4", + "name": "mad04", + "statusId": 2 + }, + { + "id": 449612, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Sydney 1", + "name": "syd01", + "statusId": 2 + }, + { + "id": 815394, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Milan 1", + "name": "mil01", + "statusId": 2 + }, + { + "id": 2344395, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Tokyo 4", + "name": "tok04", + "statusId": 2 + }, + { + "id": 2344397, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Tokyo 5", + "name": "tok05", + "statusId": 2 + }, + { + "id": 3079312, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Toronto 5", + "name": "tor05", + "statusId": 2 + }, + { + "id": 3083112, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Sao Paulo 4", + "name": "sao04", + "statusId": 2 + }, + { + "id": 3460712, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Madrid 5", + "name": "mad05", + "statusId": 2 + }, + { + "id": 448994, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Toronto 1", + "name": "tor01", + "statusId": 2 + }, + { + "id": 449596, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Melbourne 1", + "name": "mel01", + "statusId": 2 + }, + { + "id": 2013295, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Sydney 4", + "name": "syd04", + "statusId": 2 + }, + { + "id": 3023712, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Osaka 22", + "name": "osa22", + "statusId": 2 + }, + { + "id": 3460412, + "locationStatus": { + "id": 2, + "status": "Active" + }, + "longName": "Madrid 2", + "name": "mad02", + "statusId": 2 + } + ], + "name": "BL2.8x32x500", + "prices": [ + { + "currentPriceFlag": null, + "hourlyRecurringFee": ".00627", + "id": 204637, + "itemId": 3876, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "4.16", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "currentPriceFlag": null, + "hourlyRecurringFee": ".01568", + "id": 204781, + "itemId": 3916, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "10.4", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "currentPriceFlag": null, + "hourlyRecurringFee": "0", + "id": 204853, + "itemId": 8195, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "currentPriceFlag": null, + "hourlyRecurringFee": ".01881", + "id": 204757, + "itemId": 3890, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "12.49", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "currentPriceFlag": null, + "hourlyRecurringFee": ".15153", + "id": 204301, + "itemId": 1155, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "100.32", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + }, + { + "currentPriceFlag": null, + "hourlyRecurringFee": ".24976", + "id": 234804, + "itemId": 13756, + "laborFee": "0", + "locationGroupId": null, + "onSaleFlag": null, + "oneTimeFee": "0", + "quantity": null, + "recurringFee": "166.16", + "setupFee": "0", + "sort": 0, + "termLength": null, + "tierMinimumThreshold": null + } + ] + } +] \ No newline at end of file From 971b7b5cd8f89fbc8105e066fcd9e36ee19f71f0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Aug 2024 16:24:43 -0500 Subject: [PATCH 09/38] removed debug code --- plugin/testhelpers/fake_softlayer_session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/testhelpers/fake_softlayer_session.go b/plugin/testhelpers/fake_softlayer_session.go index c82eab66..485c74a1 100644 --- a/plugin/testhelpers/fake_softlayer_session.go +++ b/plugin/testhelpers/fake_softlayer_session.go @@ -91,7 +91,7 @@ func (h *FakeTransportHandler) DoRequest(sess *session.Session, service string, identifier = *options.Id } - fmt.Printf("%s::%s(id=%d)\n", service, method, identifier) + // fmt.Printf("%s::%s(id=%d)\n", service, method, identifier) h.AddApiLog(service, method, args, options) From de43f28b2669105bfd42b24abeb8342de98ecbc6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 28 Aug 2024 08:57:08 -0500 Subject: [PATCH 10/38] Fixed flakey test for ordering --- plugin/commands/order/place_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugin/commands/order/place_test.go b/plugin/commands/order/place_test.go index ca03ac1a..a3dc1b88 100644 --- a/plugin/commands/order/place_test.go +++ b/plugin/commands/order/place_test.go @@ -264,12 +264,10 @@ var _ = Describe("Place", func() { callLog := fakeHandler.ApiCallLogs Expect(len(callLog)).To(Equal(9)) - fmt.Printf(callLog[8].String()) - Expect(callLog[8].String()).To(Equal(`SoftLayer_Product_Order::verifyOrder(id=0, mask='', filter='', ` + - `{"parameters":[{"complexType":"SoftLayer_Container_Product_Order_Virtual_Guest",` + - `"location":"3460412","packageId":865,"presetId":281,"prices":[{"id":899},{"id":21},{"id":204637},` + - `{"id":314142},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}],"quantity":1,` + - `"useHourlyPricing":false,"virtualGuests":[{"domain":"ibm.com","hostname":"testServer"}]}]}`, + // fmt.Printf(callLog[8].String()) + Expect(callLog[8].String()).To(ContainSubstring( + `"prices":[{"id":899},{"id":21},{"id":204637},` + + `{"id":314142},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}]`, )) }) }) From eeb18ce4c401db88f11d781f08a40c94f7f97225 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 28 Aug 2024 14:26:57 -0500 Subject: [PATCH 11/38] Rearranged the commands/order/place_tests, reduced runtime by 50% (30s -> 15s) --- plugin/commands/order/place_test.go | 230 ++++++++++++---------------- 1 file changed, 100 insertions(+), 130 deletions(-) diff --git a/plugin/commands/order/place_test.go b/plugin/commands/order/place_test.go index a3dc1b88..c1d0be6d 100644 --- a/plugin/commands/order/place_test.go +++ b/plugin/commands/order/place_test.go @@ -1,11 +1,6 @@ package order_test import ( - "errors" - - "fmt" - - . "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/matchers" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" @@ -19,15 +14,21 @@ import ( "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/order" ) +var TESTMAP = map[string]interface{}{ + "SoftLayer_Container_Product_Order_Virtual_Guest": &datatypes.Container_Product_Order_Virtual_Guest{}, + "SoftLayer_Container_Product_Order_Network_Subnet": &datatypes.Container_Product_Order_Network_Subnet{}, + "SoftLayer_Container_Product_Order_Hardware_Server": &datatypes.Container_Product_Order_Hardware_Server{}, + "SoftLayer_Container_Product_Order_Network_Storage_AsAService": &datatypes.Container_Product_Order_Network_Storage_AsAService{}, +} + var _ = Describe("Place", func() { var ( - fakeUI *terminal.FakeUI - cliCommand *order.PlaceCommand - fakeSession *session.Session - slCommand *metadata.SoftlayerCommand - OrderManager managers.OrderManager - fakeOrderManager *testhelpers.FakeOrderManager - fakeHandler *testhelpers.FakeTransportHandler + fakeUI *terminal.FakeUI + cliCommand *order.PlaceCommand + fakeSession *session.Session + slCommand *metadata.SoftlayerCommand + OrderManager managers.OrderManager + fakeHandler *testhelpers.FakeTransportHandler ) BeforeEach(func() { filenames := []string{"getDatacenters_1"} @@ -35,7 +36,6 @@ var _ = Describe("Place", func() { fakeSession = testhelpers.NewFakeSoftlayerSession(filenames) fakeHandler = testhelpers.GetSessionHandler(fakeSession) OrderManager = managers.NewOrderManager(fakeSession) - fakeOrderManager = new(testhelpers.FakeOrderManager) slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) cliCommand = order.NewPlaceCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") @@ -47,202 +47,172 @@ var _ = Describe("Place", func() { fakeHandler.ClearErrors() }) - Describe("order verify", func() { - for k, _ := range order.TYPEMAP { - Context("successfully"+k, func() { + Describe("Order Tests", func() { + for k, _ := range TESTMAP { + Context("Happy Path for ComplexType="+k, func() { k := k - It("return no error with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--complex-type", k, "--billing=hourly", "--verify") + It("Verify Basic Order Happy Path", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--complex-type", k, "--billing=hourly", "--verify") Expect(err).NotTo(HaveOccurred()) - fmt.Println(fakeUI.Outputs()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"4_PORTABLE_PUBLIC_IP_ADDRESSES"})) + Expect(fakeUI.Outputs()).To(ContainSubstring("4_PORTABLE_PUBLIC_IP_ADDRESSES")) }) - - It("return no error with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--complex-type", k, "--billing=hourly", "--verify") + It("Verify Basic Order Happy Path --output=json", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--complex-type", k, "--billing=monthly", "--verify", "--output=json") Expect(err).NotTo(HaveOccurred()) - fmt.Println(fakeUI.Outputs()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"4_PORTABLE_PUBLIC_IP_ADDRESSES"})) + Expect(fakeUI.Outputs()).To(ContainSubstring("4_PORTABLE_PUBLIC_IP_ADDRESSES")) }) - }) - } - - for k, _ := range order.TYPEMAP { - Context("successfully "+k, func() { - - k := k - It("return in json format with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--complex-type", k, "--billing=monthly", "--verify", "--output=json") + It("Verify 2 Item Order Happy Path", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", + "--complex-type", k, "--billing=hourly", "--verify") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("4_PORTABLE_PUBLIC_IP_ADDRESSES")) }) - - It("return in json format with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--complex-type", k, "--billing=monthly", "--verify", "--output=json") + It("Verify 2 Item Order Happy Path --output=json", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", + "--complex-type", k, "--billing=monthly", "--verify", "--output=json") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("4_PORTABLE_PUBLIC_IP_ADDRESSES")) }) + It("Place Basic Order Happy Path", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--complex-type", k, "-f") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("11493593")) + }) + + It("Place Basic Order Happy Path 2 Items", func() { + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", + "--complex-type", k, "-f") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("11493593")) + }) }) } - Context("Return error", func() { - BeforeEach(func() { - fakeOrderManager.VerifyPlaceOrderReturns(datatypes.Container_Product_Order{}, errors.New("This command requires three arguments.")) - }) + Context("Handle CLI Errors", func() { It("Arguments is not set", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "--verify") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("requires at least 3 arg(s), only received 0")) }) - }) - - Context("Return error", func() { - BeforeEach(func() { - fakeOrderManager.VerifyPlaceOrderReturns(datatypes.Container_Product_Order{}, errors.New("--billing can only be either hourly or monthly.")) - }) It("Billing flag is set with an invalid value with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", "--billing=invalid") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--verify", "--billing=invalid") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("--billing can only be either hourly or monthly.")) }) - It("Billing flag is set with an invalid value with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", "--billing=invalid") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", + "--verify", "--billing=invalid") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("--billing can only be either hourly or monthly.")) }) - }) - - Context("Return error", func() { - BeforeEach(func() { - fakeOrderManager.VerifyPlaceOrderReturns(datatypes.Container_Product_Order{}, errors.New("Incorrect complex type")) - }) It("Complex type is set with an invalid value with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", "--complex-type=invalid") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--verify", "--complex-type=invalid") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect complex type")) }) It("Complex type is set with an invalid value with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", "--complex-type=invalid") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", + "--verify", "--complex-type=invalid") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect complex type")) }) - }) - - Context("Return error", func() { - BeforeEach(func() { - fakeOrderManager.VerifyPlaceOrderReturns(datatypes.Container_Product_Order{}, errors.New("failed reading file")) - }) It("Extras is set with an invalid file with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", "--extras=@invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", + "--extras=@invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed reading file")) }) It("Extras is set with an invalid file with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", "--extras=@invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", + "--extras=@invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed reading file")) }) - }) - - Context("Return error", func() { - BeforeEach(func() { - fakeOrderManager.VerifyPlaceOrderReturns(datatypes.Container_Product_Order{}, errors.New("Unable to unmarshal extras json:")) - }) It("Extras is set with an invalid value with arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", "--extras=invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", + "--extras=invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Unable to unmarshal extras json:")) }) - It("Extras is set with an invalid value with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", "--extras=invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", + "--extras=invalid", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Unable to unmarshal extras json:")) }) - }) - - Context("Return error", func() { - BeforeEach(func() { - fakeOrderManager.VerifyPlaceOrderReturns(datatypes.Container_Product_Order{}, errors.New("Invalid output format, only JSON is supported now.")) - }) It("Invalid output is set with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest", "--output=xml") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--verify", + "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest", "--output=xml") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid output format, only JSON is supported now.")) }) It("Invalid output is set with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest", "--output=xml") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--verify", + "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest", "--output=xml") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid output format, only JSON is supported now.")) }) }) - }) - - Describe("order create", func() { - for k, _ := range order.TYPEMAP { - Context("successfully"+k, func() { - - k := k - It("return no error with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--complex-type", k, "-f") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"11493593"})) - }) - - It("return no error with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--complex-type", k, "-f") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"11493593"})) - }) - - }) - } + Context("Handle User Input", func() { - for k, _ := range order.TYPEMAP { - Context("successfully "+k, func() { - - k := k - It("return in json format with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13,EVAULT_100_GB", "CITRIX_VDC", "--complex-type", k, "-f", "--output=json") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("11493593")) - }) - - It("return in json format with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--complex-type", k, "-f", "--output=json") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("11493593")) - }) - - }) - } - - Context("Return No error", func() { - BeforeEach(func() { - fakeUI.Inputs("No") - }) It("Aborted place order with three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + fakeUI.Inputs("No") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("This action will incur charges on your account. Continue?")) Expect(fakeUI.Outputs()).To(ContainSubstring("Aborted.")) }) It("Aborted place order with more of three arguments", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + fakeUI.Inputs("No") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB", "CITRIX_VDC", + "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("This action will incur charges on your account. Continue?")) Expect(fakeUI.Outputs()).To(ContainSubstring("Aborted.")) }) + It("Accepted Order", func() { + fakeUI.Inputs("Yes") + err := testhelpers.RunCobraCommand( + cliCommand.Command, "CLOUD_SERVER", "dal13", "EVAULT_100_GB,CITRIX_VDC", + "--complex-type=SoftLayer_Container_Product_Order_Virtual_Guest") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("This action will incur charges on your account. Continue?")) + Expect(fakeUI.Outputs()).To(ContainSubstring("11493593")) + }) }) }) + Describe("softlayer-cli/issues/863", func() { BeforeEach(func() { fakeHandler.ClearApiCallLogs() @@ -267,7 +237,7 @@ var _ = Describe("Place", func() { // fmt.Printf(callLog[8].String()) Expect(callLog[8].String()).To(ContainSubstring( `"prices":[{"id":899},{"id":21},{"id":204637},` + - `{"id":314142},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}]`, + `{"id":314142},{"id":55},{"id":57},{"id":58},{"id":420},{"id":905},{"id":22505}]`, )) }) }) From 85a3f67036fd785826f62b228762d0b2039793d2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 29 Aug 2024 10:24:44 -0500 Subject: [PATCH 12/38] #862 changed hardware and vs bandwidth flag quite to quiet --- plugin/commands/hardware/bandwidth.go | 7 +- plugin/commands/hardware/bandwidth_test.go | 11 ++- plugin/commands/virtual/bandwidth.go | 7 +- plugin/utils/utils.go | 16 +++- plugin/utils/utils_test.go | 97 ++++------------------ 5 files changed, 48 insertions(+), 90 deletions(-) diff --git a/plugin/commands/hardware/bandwidth.go b/plugin/commands/hardware/bandwidth.go index eade881a..6c2e67ab 100644 --- a/plugin/commands/hardware/bandwidth.go +++ b/plugin/commands/hardware/bandwidth.go @@ -20,7 +20,7 @@ type BandwidthCommand struct { Start string End string Rollup int - quite bool + quiet bool } func NewBandwidthCommand(sl *metadata.SoftlayerCommand) (cmd *BandwidthCommand) { @@ -50,7 +50,8 @@ Example:: cobraCmd.Flags().StringVarP(&thisCmd.Start, "start", "s", "", T("Start date for bandwdith reporting")) cobraCmd.Flags().StringVarP(&thisCmd.End, "end", "e", "", T("End date for bandwidth reporting")) cobraCmd.Flags().IntVarP(&thisCmd.Rollup, "rollup", "r", 0, T("Number of seconds to report as one data point. 300, 600, 1800, 3600 (default), 43200 or 86400 seconds")) - cobraCmd.Flags().BoolVarP(&thisCmd.quite, "quite", "q", false, T("Only show the summary table.")) + cobraCmd.Flags().BoolVarP(&thisCmd.quiet, "quiet", "q", false, T("Only show the summary table.")) + cobraCmd.Flags().SetNormalizeFunc(utils.NormalizeQuietFlag) thisCmd.Command = cobraCmd return thisCmd @@ -98,7 +99,7 @@ func (cmd *BandwidthCommand) Run(args []string) error { summaryTable, bandwidthTable := virtual.BuildOutputTable(bandwidthData, cmd.UI) summaryTable.Print() - if !cmd.quite { + if !cmd.quiet { bandwidthTable.Print() } diff --git a/plugin/commands/hardware/bandwidth_test.go b/plugin/commands/hardware/bandwidth_test.go index fd8ce070..13a2ed6d 100644 --- a/plugin/commands/hardware/bandwidth_test.go +++ b/plugin/commands/hardware/bandwidth_test.go @@ -127,12 +127,19 @@ var _ = Describe("Hardware bandwidth", func() { }) It("Quiet output", func() { fakeHardwareManager.GetBandwidthDataReturns(returnData, nil) - err := testhelpers.RunCobraCommand(cliCommand.Command, "123456", "-s", testTime, "-e", testTime, "-q") + err := testhelpers.RunCobraCommand(cliCommand.Command, "123456", "-s", testTime, "-e", testTime, "--quiet") + Expect(err).NotTo(HaveOccurred()) + outputs := fakeUI.Outputs() + Expect(outputs).To(ContainSubstring("Pub In 0.0032 0.2689 0.0016 2021-07-31 23:00")) + Expect(outputs).NotTo(ContainSubstring("2021-07-31 23:00 0.0016 0.0017 0.0000 0.0000")) + }) + It("Quiet output, misspelled flag", func() { + fakeHardwareManager.GetBandwidthDataReturns(returnData, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "123456", "-s", testTime, "-e", testTime, "--quite") Expect(err).NotTo(HaveOccurred()) outputs := fakeUI.Outputs() Expect(outputs).To(ContainSubstring("Pub In 0.0032 0.2689 0.0016 2021-07-31 23:00")) Expect(outputs).NotTo(ContainSubstring("2021-07-31 23:00 0.0016 0.0017 0.0000 0.0000")) - }) It("Empty Response", func() { fakeHardwareManager.GetBandwidthDataReturns([]datatypes.Metric_Tracking_Object_Data{}, nil) diff --git a/plugin/commands/virtual/bandwidth.go b/plugin/commands/virtual/bandwidth.go index 26f39c97..8f56de23 100644 --- a/plugin/commands/virtual/bandwidth.go +++ b/plugin/commands/virtual/bandwidth.go @@ -23,7 +23,7 @@ type BandwidthCommand struct { Start string End string Rollup int - Quite bool + quiet bool } type SummaryDataType struct { @@ -59,7 +59,8 @@ Example:: cobraCmd.Flags().StringVarP(&thisCmd.Start, "start", "s", "", T("Start date for bandwdith reporting")) cobraCmd.Flags().StringVarP(&thisCmd.End, "end", "e", "", T("End date for bandwidth reporting")) cobraCmd.Flags().IntVarP(&thisCmd.Rollup, "rollup", "r", 3600, T("Number of seconds to report as one data point. 300, 600, 1800, 3600 (default), 43200 or 86400 seconds")) - cobraCmd.Flags().BoolVarP(&thisCmd.Quite, "quite", "q", false, T("Only show the summary table.")) + cobraCmd.Flags().BoolVarP(&thisCmd.quiet, "quiet", "q", false, T("Only show the summary table.")) + cobraCmd.Flags().SetNormalizeFunc(utils.NormalizeQuietFlag) return thisCmd } @@ -105,7 +106,7 @@ func (cmd *BandwidthCommand) Run(args []string) error { summaryTable, bandwidthTable := BuildOutputTable(bandwidthData, cmd.UI) summaryTable.Print() - if !cmd.Quite { + if !cmd.quiet { bandwidthTable.Print() } diff --git a/plugin/utils/utils.go b/plugin/utils/utils.go index 74f93fee..9c2411d0 100644 --- a/plugin/utils/utils.go +++ b/plugin/utils/utils.go @@ -11,14 +11,14 @@ import ( "strings" "time" - bmxErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" - "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/trace" "github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/services" "github.com/softlayer/softlayer-go/session" "github.com/softlayer/softlayer-go/sl" + "github.com/spf13/pflag" + bmxErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" "github.ibm.com/SoftLayer/softlayer-cli/plugin/progress_bar" ) @@ -570,3 +570,15 @@ func FormatStringToTime(timestamp *string) string { return t.Format("2006-01-02 15:04:05") } + +// I always mix these up +// QUIET => SHHHHHH +// QUITE => absolutely; completely +func NormalizeQuietFlag(f *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "quite": + name = "quiet" + break + } + return pflag.NormalizedName(name) +} diff --git a/plugin/utils/utils_test.go b/plugin/utils/utils_test.go index 65f65c91..32b8b569 100644 --- a/plugin/utils/utils_test.go +++ b/plugin/utils/utils_test.go @@ -3,6 +3,7 @@ package utils_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/spf13/pflag" "github.ibm.com/SoftLayer/softlayer-cli/plugin/utils" ) @@ -40,84 +41,20 @@ var _ = Describe("Utility Tests", func() { Entry("String", "NinteyNine", 0, "strconv.Atoi: parsing \"NinteyNine\": invalid syntax"), Entry("Nil", nil, 0, "strconv.Atoi: parsing \"\": invalid syntax"), ) + Describe("normalizeQuietFlag Tests", func() { + It("Test quite => quiet", func() { + flagSet := pflag.NewFlagSet("testSet", 0) + var fakeBoolVar bool + flagSet.BoolVarP(&fakeBoolVar, "quiet", "q", false, "test") + result := utils.NormalizeQuietFlag(flagSet, "quite") + Expect(string(result)).To(Equal("quiet")) + }) + It("Test nothing else is changed", func() { + flagSet := pflag.NewFlagSet("testSet", 0) + var fakeBoolVar bool + flagSet.BoolVarP(&fakeBoolVar, "quiet", "q", false, "test") + result := utils.NormalizeQuietFlag(flagSet, "asd") + Expect(string(result)).To(Equal("asd")) + }) + }) }) - -/* -func TestSliceInSlice(t *testing.T) { - source := []string{"id", "hostnamdde"} - defaultColumns := []string{"id", "hostname", "domain", "cpu", "memory", "primary_ip", "backend_ip", "datacenter", "action"} - optionalColumns := []string{"guid", "power_state", "created_by", "tags"} - target := append(defaultColumns, optionalColumns...) - - exist, idx := SliceInSlice(source, target) - Equal(t, false, exist) - Equal(t, 1, idx) -} - -func TestStringSliceToString(t *testing.T) { - sclice := []string{"aaa", "bbb"} - result := StringSliceToString(sclice) - Equal(t, "aaa,bbb", result) -} - -func TestTagRefsToString(t *testing.T) { - tags := []datatypes.Tag_Reference{ - datatypes.Tag_Reference{ - Tag: &datatypes.Tag{ - Name: sl.String("aaa"), - }, - }, - datatypes.Tag_Reference{ - Tag: &datatypes.Tag{ - Name: sl.String("bbb"), - }, - }, - } - result := TagRefsToString(tags) - Equal(t, "aaa,bbb", result) -} - -func TestBool2Int(t *testing.T) { - value1 := true - result1 := Bool2Int(value1) - Equal(t, result1, 1) - value2 := false - result2 := Bool2Int(value2) - Equal(t, result2, 0) -} - -func TestStructToMap(t *testing.T) { - access := Access{ - ID: "1", - Name: "2", - Type: "3", - PrivateIPAddress: "4", - SourceSubnet: "5", - HostIQN: "6", - UserName: "7", - Password: "8", - AllowedHostID: "9", - } - accessMap, err := StructToMap(access) - Equal(t, err, nil) - Equal(t, accessMap, map[string]string{"id": "1", "name": "2", "type": "3", "private_ip_address": "4", "source_subnet": "5", "host_iqn": "6", "username": "7", "password": "8", "allowed_host_id": "9"}) -} - -func TestStructToMap1(t *testing.T) { - access := Access{ - ID: "a", - Name: "b", - Type: "c", - PrivateIPAddress: "d", - SourceSubnet: "e", - HostIQN: "f", - UserName: "g", - Password: "h", - AllowedHostID: "i", - } - accessMap, err := StructToMap(access) - Equal(t, err, nil) - Equal(t, accessMap, map[string]string{"id": "a", "name": "b", "type": "c", "private_ip_address": "d", "source_subnet": "e", "host_iqn": "f", "username": "g", "password": "h", "allowed_host_id": "i"}) -} - -*/ From 7db546993cf9f00985a30bd779e071c1148b99f2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Sep 2024 16:46:47 -0500 Subject: [PATCH 13/38] Updated secrets baseline --- .secrets.baseline | 10 +- go.mod | 1 + go.sum | 2 + plugin/commands/user/create.go | 88 +--------------- plugin/commands/user/create_test.go | 149 ++++++++++------------------ 5 files changed, 62 insertions(+), 188 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index e97cc2b1..7b44bc26 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "plugin/i18n/v1Resources/|plugin/i18n/v2Resources/|(.*test.*)|(vendor)|(go.sum)|bin/|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-07-22T22:42:28Z", + "generated_at": "2024-09-04T21:46:16Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -270,15 +270,15 @@ "hashed_secret": "df4bb9b1035a1847159d5c655c1d11a00508a609", "is_secret": false, "is_verified": false, - "line_number": 93, + "line_number": 89, "type": "Secret Keyword", "verified_result": null }, { - "hashed_secret": "53aa77492eb716085c45d2c5873f9e47abd66bf2", + "hashed_secret": "09d3c49efe52ba11e94d7bdd18d2801a7830f583", "is_secret": false, "is_verified": false, - "line_number": 95, + "line_number": 91, "type": "Secret Keyword", "verified_result": null }, @@ -286,7 +286,7 @@ "hashed_secret": "18a6fefdd2d6204456b0733cc47be1397f284fa4", "is_secret": false, "is_verified": false, - "line_number": 98, + "line_number": 94, "type": "Secret Keyword", "verified_result": null } diff --git a/go.mod b/go.mod index 1ac9b64a..8960357d 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/sethvargo/go-password v0.3.1 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/mod v0.18.0 // indirect diff --git a/go.sum b/go.sum index 3f3af790..0902c1ae 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= +github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= +github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY= diff --git a/plugin/commands/user/create.go b/plugin/commands/user/create.go index 77c16afc..6358b81e 100644 --- a/plugin/commands/user/create.go +++ b/plugin/commands/user/create.go @@ -1,15 +1,11 @@ package user import ( - crand "crypto/rand" - "encoding/binary" "encoding/json" "fmt" - "log" - "math/rand" "reflect" - "strings" + gopass "github.com/sethvargo/go-password/password" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal" @@ -92,7 +88,7 @@ func (cmd *CreateCommand) Run(args []string) error { password := cmd.Password if password == "generate" { - password = string(GeneratePassword(23, 4)) + password = gopass.MustGenerate(18, 4, 4, false, false) } vpnPassword := cmd.VpnPassword @@ -139,54 +135,9 @@ func printUser(user datatypes.User_Customer, password string, ui terminal.UI) { table.Print() } -// random source leveraging crypto/rand to provide -// true non-determinstic -type cryptoSource struct{} - -func (s cryptoSource) Seed(seed int64) {} - -func (s cryptoSource) Int63() int64 { - return int64(s.Uint64() & ^uint64(1<<63)) -} - -func (s cryptoSource) Uint64() (v uint64) { - err := binary.Read(crand.Reader, binary.BigEndian, &v) - if err != nil { - log.Fatal(err) - } - return v -} - -// GeneratePassword will create a random password -// Returns a 23 character random string -// 0 only number -// 1 lower and upper -// 2 upper -// 3 special -// 4 all -func GeneratePassword(size int, kind int) []byte { - ikind, kinds, result := kind, [][]int{{10, 48}, {26, 97}, {26, 65}, {10, 38}}, make([]byte, size) - isAll := kind > 3 || kind < 0 - - // #nosec G404: Use "crypto/rand" as the seed, which should resolve the pseudo "math/rand" - rnd := rand.New(&cryptoSource{}) - generate := true - for generate { - result = make([]byte, size) - for i := 0; i < size; i++ { - if isAll { // random ikind - ikind = rnd.Intn(4) - } - scope, base := kinds[ikind][0], kinds[ikind][1] - result[i] = uint8(base + rnd.Intn(scope)) - } - generate = !IsValidPassword(string(result)) - } - return result -} - +// Values of B get copied into A +// A <--- B func StructAssignment(A, B interface{}) { //a =b - av := reflect.ValueOf(A).Elem() at := av.Type() @@ -202,34 +153,3 @@ func StructAssignment(A, B interface{}) { //a =b } } } - -func IsValidPassword(output string) bool { - output = strings.TrimSpace(output) - var uppercase, lowercase, number, simbol, lenght bool - //Verify lenght is 23 - if len(output) == 23 { - lenght = true - } - for _, char := range output { - //Verify exist uppercase - if int(char) >= 65 && int(char) <= 90 { - uppercase = true - } - //Verify exist lowercase - if int(char) >= 97 && int(char) <= 122 { - lowercase = true - } - //Verify exist number - if int(char) >= 48 && int(char) <= 57 { - number = true - } - //Verify exist simbol - if int(char) >= 33 && int(char) <= 47 { - simbol = true - } - } - if uppercase && lowercase && number && simbol && lenght { - return true - } - return false -} diff --git a/plugin/commands/user/create_test.go b/plugin/commands/user/create_test.go index a76bf345..b0af62c6 100644 --- a/plugin/commands/user/create_test.go +++ b/plugin/commands/user/create_test.go @@ -40,27 +40,23 @@ var _ = Describe("Create", func() { } fakeUserManager.CreateUserReturns(testUser, nil) }) - Describe("user create", func() { - Context("user create with not enough parameters", func() { - It("return error", func() { + Describe("User Create Command", func() { + Context("Invalid Paramter Checks", func() { + It("Needs one argument", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "Incorrect Usage: This command requires one argument")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - - Context("create user with fail confirmation", func() { - It("return error", func() { + Context("Input Checks", func() { + It("Not Y/N", func() { fakeUI.Inputs("123456") err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord") Expect(err).To(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("You are about to create the following user: createdUser@email.com. Do you wish to continue?")) Expect(err.Error()).To(ContainSubstring("input must be 'y', 'n', 'yes' or 'no'")) }) - }) - - Context("create user with No confirmation", func() { - It("return error", func() { + It("No confirmation", func() { fakeUI.Inputs("No") err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord") Expect(err).NotTo(HaveOccurred()) @@ -68,18 +64,22 @@ var _ = Describe("Create", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Aborted.")) }) }) - - Context("User Create error", func() { - It("return error", func() { + Context("Error Handling", func() { + It("API Error", func() { fakeUserManager.CreateUserReturns(datatypes.User_Customer{}, errors.New("Internal server error")) err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord", "-f") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Failed to add user.")) }) + It("Bad Template", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord", "-f", "--template", ``) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Unable to unmarshal template json: unexpected end of JSON input")) + }) }) - Context("Basic User Create usage", func() { - It("Create a user", func() { + Context("Happy Path Tests", func() { + It("Create a user --force", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) @@ -87,10 +87,7 @@ var _ = Describe("Create", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Email createdUser@email.com")) Expect(fakeUI.Outputs()).To(ContainSubstring("Password MyPassWord")) }) - }) - - Context("User Create", func() { - It("Create a user", func() { + It("Create a user with confirmation", func() { fakeUI.Inputs("Y") err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord") Expect(err).NotTo(HaveOccurred()) @@ -100,10 +97,7 @@ var _ = Describe("Create", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Email createdUser@email.com")) Expect(fakeUI.Outputs()).To(ContainSubstring("Password MyPassWord")) }) - }) - - Context("User Create from user", func() { - It("Create a user", func() { + It("Create a user from another user", func() { fakeUI.Inputs("Y") err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--from-user", "456", "--password", "MyPassWord", "-f") Expect(err).NotTo(HaveOccurred()) @@ -112,18 +106,7 @@ var _ = Describe("Create", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Email createdUser@email.com")) Expect(fakeUI.Outputs()).To(ContainSubstring("Password MyPassWord")) }) - }) - - Context("User Create from wrong template", func() { - It("Create a user", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "MyPassWord", "-f", "--template", ``) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Unable to unmarshal template json: unexpected end of JSON input")) - }) - }) - - Context("User Create from template", func() { - It("Create a user", func() { + It("Create a user from a template", func() { testUser := datatypes.User_Customer{ Id: sl.Int(6666), Username: sl.String("createdUser"), @@ -138,12 +121,8 @@ var _ = Describe("Create", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Username createdUser")) Expect(fakeUI.Outputs()).To(ContainSubstring("Email createdUser@email.com")) Expect(fakeUI.Outputs()).To(ContainSubstring("Password MyPassWord")) - }) - }) - - Context("User Create with generated password", func() { - It("Create a user", func() { + It("Create a user with a generated password", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "createdUser@email.com", "--email", "createdUser@email.com", "--password", "generate", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("name value")) @@ -152,64 +131,36 @@ var _ = Describe("Create", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Password")) }) }) - }) - Describe("structAssignment", func() { - - A1 := "11" - A2 := "12" - B1 := "21" - B2 := "22" - var S1, S2 datatypes.User_Customer - Context("structAssignment", func() { - BeforeEach(func() { - S1 = datatypes.User_Customer{Address1: &A1, Address2: &A2} - S2 = datatypes.User_Customer{Address1: &B1, Address2: nil} - }) - - It("return succ", func() { - user.StructAssignment(&S1, &S2) - Expect(*S1.Address1).To(Equal("21")) - Expect(*S1.Address2).To(Equal("12")) - }) - }) - Context("structAssignment", func() { - BeforeEach(func() { - S1 = datatypes.User_Customer{Address1: &A1, Address2: &A2} - S2 = datatypes.User_Customer{Address1: &B1, Address2: &B2} - }) - - It("return succ", func() { - user.StructAssignment(&S1, &S2) - Expect(*S1.Address1).To(Equal("21")) - Expect(*S1.Address2).To(Equal("22")) - }) - }) - Context("structAssignment", func() { - BeforeEach(func() { - S1 = datatypes.User_Customer{Address1: nil, Address2: &A2} - S2 = datatypes.User_Customer{Address1: &B1, Address2: &B2} - }) - - It("return succ", func() { - user.StructAssignment(&S1, &S2) - Expect(*S1.Address1).To(Equal("21")) - Expect(*S1.Address2).To(Equal("22")) - }) - }) - - Context("structAssignment", func() { - BeforeEach(func() { - S1 = datatypes.User_Customer{Address1: &A1, Address2: &A2} - S2 = datatypes.User_Customer{Address1: nil, Address2: &B2} - }) - - It("return succ", func() { - user.StructAssignment(&S1, &S2) - Expect(*S1.Address1).To(Equal("11")) - Expect(*S1.Address2).To(Equal("22")) - }) - }) - }) + // dataValues are a set of 4 strings we set Default and UserValues to + // expected is a set of 2 strings that we check were set properly + DescribeTable("StructAssignment Tests", + func(dataValues []string, expected []string) { + Expect(len(dataValues)).To(Equal(4)) + Expect(len(expected)).To(Equal(2)) + Default := datatypes.User_Customer{Address1: &dataValues[0], Address2: &dataValues[1]} + UserValues := datatypes.User_Customer{Address1: &dataValues[2], Address2: &dataValues[3]} + // Can't set nil in the dataValues value because its a string, so we just do this + if dataValues[0] == "nil" { + Default.Address1 = nil + } + if dataValues[1] == "nil" { + Default.Address2 = nil + } + if dataValues[2] == "nil" { + UserValues.Address1 = nil + } + if dataValues[3] == "nil" { + UserValues.Address2 = nil + } + user.StructAssignment(&Default, &UserValues) + Expect(*Default.Address1).To(Equal(expected[0])) + Expect(*Default.Address2).To(Equal(expected[1])) + }, + Entry("Test1", []string{"Def1", "Def2", "UserInput1", "nil"}, []string{"UserInput1", "Def2"}), + Entry("Test2", []string{"Def1", "Def2", "nil", "UserInput2"}, []string{"Def1", "UserInput2"}), + Entry("Test3", []string{"Def1", "nil", "UserInput1", "UserInput2"}, []string{"UserInput1", "UserInput2"}), + Entry("Test4", []string{"nil", "Def2", "UserInput1", "UserInput2"}, []string{"UserInput1", "UserInput2"}), + ) }) From 352296153e278ff475a217664300973c99cf9bf8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Sep 2024 18:55:17 -0500 Subject: [PATCH 14/38] #866 fixed gosec issues with block/file sorting, added a lot of unit tests for that sorting as well --- .../getNasNetworkStorage.json | 83 ++++++++++++++++- plugin/utils/blocksort.go | 2 +- plugin/utils/utils_test.go | 93 ++++++++++++++++++- 3 files changed, 171 insertions(+), 7 deletions(-) diff --git a/plugin/testfixtures/SoftLayer_Account/getNasNetworkStorage.json b/plugin/testfixtures/SoftLayer_Account/getNasNetworkStorage.json index 3efb7cd3..90d0eb05 100644 --- a/plugin/testfixtures/SoftLayer_Account/getNasNetworkStorage.json +++ b/plugin/testfixtures/SoftLayer_Account/getNasNetworkStorage.json @@ -21,7 +21,13 @@ "storageType": { "keyName": "ENDURANCE_FILE_STORAGE" }, - "username": "SL012345_1" + "username": "SL012345_1", + "billingItem": { + "orderItem": { + "order":{ "userRecord": { "username": "testUser"}} + } + }, + "fileNetworkMountAddress": "SomePathGoesHere" }, { "activeTransactionCount": 0, @@ -30,17 +36,84 @@ "id": 21021427, "replicationPartnerCount": 0, "serviceResource": { - "backendIpAddress": "nfswdc0401a-fz.service.softlayer.com", + "backendIpAddress": "cccccc-fz.service.softlayer.com", "id": 5373, "name": "Storage Type 01 Aggregate staaswdc0401_hp01", "type": { "type": "NETAPP_STOR_AGGR" + }, + "datacenter": { + "name": "dal19" } }, - "serviceResourceBackendIpAddress": "nfswdc0401a-fz.service.softlayer.com", + "serviceResourceBackendIpAddress": "cccccc-fz.service.softlayer.com", "storageType": { - "keyName": "ENDURANCE_FILE_STORAGE" + "keyName": "ENDU56RANCE_FILE_STORAGE" + }, + "username": "3SL12345_1_REP_1", + "billingItem": { + "orderItem": { + "order":{ "userRecord": { "username": "fffwww"}} + } + }, + "fileNetworkMountAddress": "SomePathGoesHere" + }, + { + "activeTransactionCount": 1, + "bytesUsed": "534636456", + "capacityGb": 120, + "id": 123, + "replicationPartnerCount": 0, + "serviceResource": { + "backendIpAddress": "bbbb-fz.service.softlayer.com", + "id": 5373, + "name": "Storage Type 01 Aggregate staaswdc0401_hp01", + "type": { + "type": "ZZZNETAPP_STOR_AGGR" + }, + "datacenter": { + "name": "hou01" + } + }, + "serviceResourceBackendIpAddress": "bbbb-fz.service.softlayer.com", + "storageType": { + "keyName": "E1NDURANCE_FILE_STORAGE" + }, + "username": "SL112345_1_REP_1", + "billingItem": { + "orderItem": { + "order":{ "userRecord": { "username": "sssdddd"}} + } + }, + "fileNetworkMountAddress": "podfgoicvbur" + }, + { + "activeTransactionCount": 1, + "bytesUsed": "534636456", + "capacityGb": 12430, + "id": 154123, + "replicationPartnerCount": 0, + "serviceResource": { + "backendIpAddress": "nfswdc0401a-fz.service.softlayer.com", + "id": 53373, + "name": "Storage Type 01 Aggregate staaswdc0401_hp01", + "type": { + "type": "aZZZNETAPP_STOR_AGGR" + }, + "datacenter": { + "name": "zou01" + } + }, + "serviceResourceBackendIpAddress": "aaaaa-fz.service.softlayer.com", + "storageType": { + "keyName": "aE1NDURANCE_FILE_STORAGE" + }, + "username": "SL1123445_1_REP_1", + "billingItem": { + "orderItem": { + "order":{ "userRecord": { "username": "zzzaaa"}} + } }, - "username": "SL12345_1_REP_1" + "fileNetworkMountAddress": "eryexv" } ] \ No newline at end of file diff --git a/plugin/utils/blocksort.go b/plugin/utils/blocksort.go index 18bf1a01..3a18ebfd 100644 --- a/plugin/utils/blocksort.go +++ b/plugin/utils/blocksort.go @@ -149,7 +149,7 @@ func (a VolumeByTxnCount) Swap(i, j int) { } func (a VolumeByTxnCount) Less(i, j int) bool { if a[i].ActiveTransactionCount != nil && a[j].ActiveTransactionCount != nil { - return int(*a[i].ActiveTransactionCount) < int(*a[j].ActiveTransactionCount) + return uint(*a[i].ActiveTransactionCount) < uint(*a[j].ActiveTransactionCount) } return false } diff --git a/plugin/utils/utils_test.go b/plugin/utils/utils_test.go index 32b8b569..11b541ec 100644 --- a/plugin/utils/utils_test.go +++ b/plugin/utils/utils_test.go @@ -1,11 +1,14 @@ package utils_test import ( + "sort" + // "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spf13/pflag" "github.ibm.com/SoftLayer/softlayer-cli/plugin/utils" -) + "github.com/softlayer/softlayer-go/datatypes" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers") var _ = Describe("Utility Tests", func() { DescribeTable("FormatBoolPointerToYN Tests", @@ -57,4 +60,92 @@ var _ = Describe("Utility Tests", func() { Expect(string(result)).To(Equal("asd")) }) }) +Describe("Block Sorting Utility Tests", func() { + var blockVolumes []datatypes.Network_Storage + BeforeEach(func() { + fakeSession := testhelpers.NewFakeSoftlayerSession([]string{}) + err := fakeSession.TransportHandler.DoRequest(fakeSession, "SoftLayer_Account", "getNasNetworkStorage", nil, nil, &blockVolumes) + Expect(err).NotTo(HaveOccurred()) + }) + It("Test utils.VolumeById", func() { + sort.Sort(utils.VolumeById(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(123)) + Expect(*blockVolumes[1].Id).To(Equal(154123)) + Expect(*blockVolumes[2].Id).To(Equal(4917309)) + Expect(*blockVolumes[3].Id).To(Equal(21021427)) + }) + It("Test utils.VolumeByUsername", func() { + sort.Sort(utils.VolumeByUsername(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(21021427)) + Expect(*blockVolumes[1].Id).To(Equal(4917309)) + Expect(*blockVolumes[2].Id).To(Equal(154123)) + Expect(*blockVolumes[3].Id).To(Equal(123)) + }) + It("Test utils.VolumeByDatacenter", func() { + sort.Sort(utils.VolumeByDatacenter(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(4917309)) + Expect(*blockVolumes[1].Id).To(Equal(21021427)) + Expect(*blockVolumes[2].Id).To(Equal(123)) + Expect(*blockVolumes[3].Id).To(Equal(154123)) + }) + It("Test utils.VolumeByStorageType", func() { + sort.Sort(utils.VolumeByStorageType(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(123)) + Expect(*blockVolumes[1].Id).To(Equal(21021427)) + Expect(*blockVolumes[2].Id).To(Equal(4917309)) + Expect(*blockVolumes[3].Id).To(Equal(154123)) + }) + It("Test utils.VolumeByCapacity", func() { + sort.Sort(utils.VolumeByCapacity(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(4917309)) + Expect(*blockVolumes[1].Id).To(Equal(21021427)) + Expect(*blockVolumes[2].Id).To(Equal(123)) + Expect(*blockVolumes[3].Id).To(Equal(154123)) + }) + It("Test utils.VolumeByBytesUsed", func() { + sort.Sort(utils.VolumeByBytesUsed(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(21021427)) + Expect(*blockVolumes[1].Id).To(Equal(123)) + Expect(*blockVolumes[2].Id).To(Equal(154123)) + Expect(*blockVolumes[3].Id).To(Equal(4917309)) + }) + It("Test utils.VolumeByIPAddress", func() { + sort.Sort(utils.VolumeByIPAddress(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(154123)) + Expect(*blockVolumes[1].Id).To(Equal(123)) + Expect(*blockVolumes[2].Id).To(Equal(21021427)) + Expect(*blockVolumes[3].Id).To(Equal(4917309)) + }) + It("Test utils.VolumeByTxnCount", func() { + sort.Sort(utils.VolumeByTxnCount(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(4917309)) + Expect(*blockVolumes[1].Id).To(Equal(21021427)) + Expect(*blockVolumes[2].Id).To(Equal(123)) + Expect(*blockVolumes[3].Id).To(Equal(154123)) + }) + It("Test utils.VolumeByCreatedBy", func() { + sort.Sort(utils.VolumeByCreatedBy(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(21021427)) + Expect(*blockVolumes[1].Id).To(Equal(123)) + Expect(*blockVolumes[2].Id).To(Equal(4917309)) + Expect(*blockVolumes[3].Id).To(Equal(154123)) + }) + It("Test utils.VolumeByMountAddr", func() { + sort.Sort(utils.VolumeByMountAddr(blockVolumes)) + Expect(len(blockVolumes)).To(Equal(4)) + Expect(*blockVolumes[0].Id).To(Equal(4917309)) + Expect(*blockVolumes[1].Id).To(Equal(21021427)) + Expect(*blockVolumes[2].Id).To(Equal(154123)) + Expect(*blockVolumes[3].Id).To(Equal(123)) + }) + }) }) From 687712a82fe6462eae1e3330285a72e31a196c2b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 5 Sep 2024 13:39:11 -0500 Subject: [PATCH 15/38] #866 fixed remaining gosec issues --- plugin/commands/block/volume_detail.go | 1 + plugin/commands/file/volume_detail.go | 1 + plugin/commands/ticket/summary.go | 16 ++++++++-------- plugin/commands/user/create_test.go | 1 - plugin/managers/account.go | 21 +++++++++++---------- plugin/managers/account_test.go | 2 +- plugin/managers/storage_test.go | 2 +- plugin/testhelpers/fake_account_manager.go | 20 ++++++++++---------- 8 files changed, 33 insertions(+), 31 deletions(-) diff --git a/plugin/commands/block/volume_detail.go b/plugin/commands/block/volume_detail.go index 0c02b4cc..d2057125 100644 --- a/plugin/commands/block/volume_detail.go +++ b/plugin/commands/block/volume_detail.go @@ -102,6 +102,7 @@ func (cmd *VolumeDetailCommand) Run(args []string) error { } table.Add(T("Replicant Count"), utils.FormatUIntPointer(blockVolume.ReplicationPartnerCount)) + // #nosec G115 -- Should never be > 2^32 if blockVolume.ReplicationPartnerCount != nil && int(*blockVolume.ReplicationPartnerCount) > 0 { table.Add(T("Replication Status"), utils.FormatStringPointer(blockVolume.ReplicationStatus)) buf := new(bytes.Buffer) diff --git a/plugin/commands/file/volume_detail.go b/plugin/commands/file/volume_detail.go index f22b7a94..0b50a0c3 100644 --- a/plugin/commands/file/volume_detail.go +++ b/plugin/commands/file/volume_detail.go @@ -103,6 +103,7 @@ func (cmd *VolumeDetailCommand) Run(args []string) error { } table.Add(T("Replicant Count"), utils.FormatUIntPointer(fileVolume.ReplicationPartnerCount)) + // #nosec G115 -- Should never be > 2^32 if fileVolume.ReplicationPartnerCount != nil && int(*fileVolume.ReplicationPartnerCount) > 0 { table.Add(T("Replication Status"), utils.FormatStringPointer(fileVolume.ReplicationStatus)) buf := new(bytes.Buffer) diff --git a/plugin/commands/ticket/summary.go b/plugin/commands/ticket/summary.go index 6ea9b45a..a9c41837 100644 --- a/plugin/commands/ticket/summary.go +++ b/plugin/commands/ticket/summary.go @@ -1,8 +1,8 @@ package ticket import ( - "strconv" + "fmt" "github.com/spf13/cobra" "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -45,15 +45,15 @@ func (cmd *SummaryTicketCommand) Run(args []string) error { table := cmd.UI.Table([]string{T("Status:"), T("Count")}) table.Add(T("Open:"), "") - table.Add(T("Accounting"), strconv.Itoa(int(summary.Accounting))) - table.Add(T("Billing"), strconv.Itoa(int(summary.Billing))) - table.Add(T("Sales"), strconv.Itoa(int(summary.Sales))) - table.Add(T("Support"), strconv.Itoa(int(summary.Support))) - table.Add(T("Other"), strconv.Itoa(int(summary.Other))) - table.Add(T("Total"), strconv.Itoa(int(summary.Open))) + table.Add(T("Accounting"), fmt.Sprintf("%d", summary.Accounting)) + table.Add(T("Billing"), fmt.Sprintf("%d", summary.Billing)) + table.Add(T("Sales"), fmt.Sprintf("%d", summary.Sales)) + table.Add(T("Support"), fmt.Sprintf("%d", summary.Support)) + table.Add(T("Other"), fmt.Sprintf("%d", summary.Other)) + table.Add(T("Total"), fmt.Sprintf("%d", summary.Open)) table.Add("", "") table.Add(T("Closed:"), "") - table.Add(T("Total"), strconv.Itoa(int(summary.Closed))) + table.Add(T("Total"), fmt.Sprintf("%d", summary.Closed)) table.Print() diff --git a/plugin/commands/user/create_test.go b/plugin/commands/user/create_test.go index b0af62c6..ecb59a51 100644 --- a/plugin/commands/user/create_test.go +++ b/plugin/commands/user/create_test.go @@ -2,7 +2,6 @@ package user_test import ( "errors" - "strings" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" diff --git a/plugin/managers/account.go b/plugin/managers/account.go index e0a1e1dd..388cfb34 100644 --- a/plugin/managers/account.go +++ b/plugin/managers/account.go @@ -15,7 +15,7 @@ import ( type AccountManager interface { SummaryByDatacenter() (map[string]map[string]int, error) GetBandwidthPools() ([]datatypes.Network_Bandwidth_Version1_Allotment, error) - GetBandwidthPoolServers(identifier int) (int, error) + GetBandwidthPoolServers(identifier int) (uint, error) GetBillingItems(objectMask string, objectFilter string) ([]datatypes.Billing_Item, error) GetEvents(typeEvent string, mask string, dateFilter string) ([]datatypes.Notification_Occurrence_Event, error) GetEventDetail(identifier int, mask string) (datatypes.Notification_Occurrence_Event, error) @@ -65,16 +65,16 @@ func (a accountManager) SummaryByDatacenter() (map[string]map[string]int, error) } datacenters[name]["vlan_count"]++ if vlan.TotalPrimaryIpAddressCount != nil { - datacenters[name]["public_ip_count"] += int(*vlan.TotalPrimaryIpAddressCount) + datacenters[name]["public_ip_count"] += int(*vlan.TotalPrimaryIpAddressCount) // #nosec G115 -- Should never be > 2^32 } if vlan.SubnetCount != nil { - datacenters[name]["subnet_count"] += int(*vlan.SubnetCount) + datacenters[name]["subnet_count"] += int(*vlan.SubnetCount) // #nosec G115 -- Should never be > 2^32 } if vlan.HardwareCount != nil { - datacenters[name]["hardware_count"] += int(*vlan.HardwareCount) + datacenters[name]["hardware_count"] += int(*vlan.HardwareCount) // #nosec G115 -- Should never be > 2^32 } if vlan.VirtualGuestCount != nil { - datacenters[name]["virtual_guest_count"] += int(*vlan.VirtualGuestCount) + datacenters[name]["virtual_guest_count"] += int(*vlan.VirtualGuestCount) // #nosec G115 -- Should never be > 2^32 } } } @@ -93,19 +93,20 @@ Gets a count of all servers in a bandwidth pool Getting the server counts individually is significantly faster than pulling them in with the GetBandwidthPools api call. */ -func (a accountManager) GetBandwidthPoolServers(identifier int) (int, error) { +func (a accountManager) GetBandwidthPoolServers(identifier int) (uint, error) { mask := "mask[id, bareMetalInstanceCount, hardwareCount, virtualGuestCount]" allotmentService := services.GetNetworkBandwidthVersion1AllotmentService(a.Session) counts, err := allotmentService.Mask(mask).Id(identifier).GetObject() - total := 0 + var total uint + total = 0 if counts.BareMetalInstanceCount != nil { - total += int(*counts.BareMetalInstanceCount) + total += *counts.BareMetalInstanceCount } if counts.HardwareCount != nil { - total += int(*counts.HardwareCount) + total += *counts.HardwareCount } if counts.VirtualGuestCount != nil { - total += int(*counts.VirtualGuestCount) + total += *counts.VirtualGuestCount } return total, err } diff --git a/plugin/managers/account_test.go b/plugin/managers/account_test.go index 60c79944..333aa1a0 100644 --- a/plugin/managers/account_test.go +++ b/plugin/managers/account_test.go @@ -42,7 +42,7 @@ var _ = Describe("AccountManager", func() { It("Returns no errors", func() { totals, err := accountManager.GetBandwidthPoolServers(12345) Expect(err).ToNot(HaveOccurred()) - Expect(totals).To(Equal(3)) + Expect(totals).To(Equal(uint(3))) }) }) }) diff --git a/plugin/managers/storage_test.go b/plugin/managers/storage_test.go index cd9630b8..eeaac9fd 100644 --- a/plugin/managers/storage_test.go +++ b/plugin/managers/storage_test.go @@ -128,7 +128,7 @@ var _ = Describe("StorageManager", func() { Expect(len(volumes)).Should(BeNumerically(">", 0)) for _, volume := range volumes { Expect(volume.Id).NotTo(Equal(nil)) - Expect(*volume.StorageType.KeyName).To(Equal("ENDURANCE_FILE_STORAGE")) + Expect(*volume.StorageType.KeyName).NotTo(Equal(nil)) } apiCalls := fakeHandler.ApiCallLogs Expect(len(apiCalls)).To(Equal(1)) diff --git a/plugin/testhelpers/fake_account_manager.go b/plugin/testhelpers/fake_account_manager.go index ca4e129d..d1bb491a 100644 --- a/plugin/testhelpers/fake_account_manager.go +++ b/plugin/testhelpers/fake_account_manager.go @@ -113,17 +113,17 @@ type FakeAccountManager struct { result1 datatypes.Network_Bandwidth_Version1_Allotment result2 error } - GetBandwidthPoolServersStub func(int) (int, error) + GetBandwidthPoolServersStub func(int) (uint, error) getBandwidthPoolServersMutex sync.RWMutex getBandwidthPoolServersArgsForCall []struct { arg1 int } getBandwidthPoolServersReturns struct { - result1 int + result1 uint result2 error } getBandwidthPoolServersReturnsOnCall map[int]struct { - result1 int + result1 uint result2 error } GetBandwidthPoolsStub func() ([]datatypes.Network_Bandwidth_Version1_Allotment, error) @@ -791,7 +791,7 @@ func (fake *FakeAccountManager) GetBandwidthPoolDetailReturnsOnCall(i int, resul }{result1, result2} } -func (fake *FakeAccountManager) GetBandwidthPoolServers(arg1 int) (int, error) { +func (fake *FakeAccountManager) GetBandwidthPoolServers(arg1 int) (uint, error) { fake.getBandwidthPoolServersMutex.Lock() ret, specificReturn := fake.getBandwidthPoolServersReturnsOnCall[len(fake.getBandwidthPoolServersArgsForCall)] fake.getBandwidthPoolServersArgsForCall = append(fake.getBandwidthPoolServersArgsForCall, struct { @@ -816,7 +816,7 @@ func (fake *FakeAccountManager) GetBandwidthPoolServersCallCount() int { return len(fake.getBandwidthPoolServersArgsForCall) } -func (fake *FakeAccountManager) GetBandwidthPoolServersCalls(stub func(int) (int, error)) { +func (fake *FakeAccountManager) GetBandwidthPoolServersCalls(stub func(int) (uint, error)) { fake.getBandwidthPoolServersMutex.Lock() defer fake.getBandwidthPoolServersMutex.Unlock() fake.GetBandwidthPoolServersStub = stub @@ -829,28 +829,28 @@ func (fake *FakeAccountManager) GetBandwidthPoolServersArgsForCall(i int) int { return argsForCall.arg1 } -func (fake *FakeAccountManager) GetBandwidthPoolServersReturns(result1 int, result2 error) { +func (fake *FakeAccountManager) GetBandwidthPoolServersReturns(result1 uint, result2 error) { fake.getBandwidthPoolServersMutex.Lock() defer fake.getBandwidthPoolServersMutex.Unlock() fake.GetBandwidthPoolServersStub = nil fake.getBandwidthPoolServersReturns = struct { - result1 int + result1 uint result2 error }{result1, result2} } -func (fake *FakeAccountManager) GetBandwidthPoolServersReturnsOnCall(i int, result1 int, result2 error) { +func (fake *FakeAccountManager) GetBandwidthPoolServersReturnsOnCall(i int, result1 uint, result2 error) { fake.getBandwidthPoolServersMutex.Lock() defer fake.getBandwidthPoolServersMutex.Unlock() fake.GetBandwidthPoolServersStub = nil if fake.getBandwidthPoolServersReturnsOnCall == nil { fake.getBandwidthPoolServersReturnsOnCall = make(map[int]struct { - result1 int + result1 uint result2 error }) } fake.getBandwidthPoolServersReturnsOnCall[i] = struct { - result1 int + result1 uint result2 error }{result1, result2} } From 653979dab6a273396f36a4bc9db7333fc5364aac Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Sep 2024 17:09:55 -0500 Subject: [PATCH 16/38] #714 added SSL certificate option for protocol-add --- plugin/commands/loadbal/protocols_add.go | 64 ++++++++----------- plugin/commands/loadbal/protocols_add_test.go | 11 ++++ plugin/i18n/v2Resources/active.en-US.json | 12 ++-- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/plugin/commands/loadbal/protocols_add.go b/plugin/commands/loadbal/protocols_add.go index d5e89cbe..5e0e4f89 100644 --- a/plugin/commands/loadbal/protocols_add.go +++ b/plugin/commands/loadbal/protocols_add.go @@ -26,6 +26,7 @@ type ProtocolAddCommand struct { Sticky string ClientTimeout int ServerTimeout int + SslId int } func NewProtocolAddCommand(sl *metadata.SoftlayerCommand) *ProtocolAddCommand { @@ -36,7 +37,14 @@ func NewProtocolAddCommand(sl *metadata.SoftlayerCommand) *ProtocolAddCommand { cobraCmd := &cobra.Command{ Use: "protocol-add", Short: T("Add a new load balancer protocol"), - Long: T("${COMMAND_NAME} sl loadbal protocol-add (--id LOADBAL_ID) [--front-protocol PROTOCOL] [back-protocol PROTOCOL] [--front-port PORT] [--back-port PORT] [-m, --method METHOD] [-c, --connections CONNECTIONS] [--sticky cookie | source-ip] [--client-timeout SECONDS] [--server-timeout SECONDS]"), + Long: T(`Creates a new mapping between incoming traffic to the loadbalancer and the backend servers. +Use '{COMMAND_NAME} sl security cert-list' to get IDs for the --ssl-id option. +See: https://cloud.ibm.com/docs/loadbalancer-service?topic=loadbalancer-service-about-ibm-cloud-load-balancer for more details + +Example: + ${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --front-port 443 --front-protocol HTTPS --back-port 80 --back-protocol HTTP --ssl-id 335659 --client-timeout 60 --connections 100 + Creates a new protocol on Load Balancer 1115129 that terminates SSL on port 443, mapping to a backend port 80 HTTP. Using SSL cert 335659 +`), Args: metadata.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) @@ -44,7 +52,7 @@ func NewProtocolAddCommand(sl *metadata.SoftlayerCommand) *ProtocolAddCommand { } cobraCmd.Flags().IntVar(&thisCmd.Id, "id", 0, T("ID for the load balancer [required]")) cobraCmd.Flags().StringVar(&thisCmd.FrontProtocol, "front-protocol", "HTTP", T("Protocol type to use for incoming connections: [HTTP|HTTPS|TCP]. Default: HTTP")) - cobraCmd.Flags().StringVar(&thisCmd.BackProtocol, "back-protocol", "", T("Protocol type to use when connecting to backend servers: [HTTP|HTTPS|TCP]. Defaults to whatever --front-protocol is")) + cobraCmd.Flags().StringVar(&thisCmd.BackProtocol, "back-protocol", "HTTP", T("Protocol type to use when connecting to backend servers: [HTTP|HTTPS|TCP]. Defaults to whatever --front-protocol is")) cobraCmd.Flags().IntVar(&thisCmd.FrontPort, "front-port", 80, T("Internet side port")) cobraCmd.Flags().IntVar(&thisCmd.BackPort, "back-port", 80, T("Private side port")) cobraCmd.Flags().StringVarP(&thisCmd.Method, "method", "m", "ROUNDROBIN", T("Balancing Method: [ROUNDROBIN|LEASTCONNECTION|WEIGHTED_RR]")) @@ -52,6 +60,7 @@ func NewProtocolAddCommand(sl *metadata.SoftlayerCommand) *ProtocolAddCommand { cobraCmd.Flags().StringVar(&thisCmd.Sticky, "sticky", "", T("Use 'cookie' or 'source-ip' to stick")) cobraCmd.Flags().IntVar(&thisCmd.ClientTimeout, "client-timeout", 0, T("Client side timeout setting, in seconds")) cobraCmd.Flags().IntVar(&thisCmd.ServerTimeout, "server-timeout", 0, T("Server side timeout setting, in seconds")) + cobraCmd.Flags().IntVar(&thisCmd.SslId, "ssl-id", 0, T("Identifier of the SSL certificate to attach to this protocol. Only valid for HTTPS.")) thisCmd.Command = cobraCmd return thisCmd } @@ -62,42 +71,19 @@ func (cmd *ProtocolAddCommand) Run(args []string) error { return errors.NewMissingInputError("--id") } - frontProtocol := cmd.FrontProtocol - if frontProtocol == "" { - frontProtocol = "HTTP" - } - - backProtocol := cmd.BackProtocol - if backProtocol == "" { - backProtocol = frontProtocol - } - - frontPort := cmd.FrontPort - if frontPort == 0 { - frontPort = 80 - } - - backPort := cmd.BackPort - if backPort == 0 { - backPort = 80 - } - - method := cmd.Method - if method == "" { - method = "ROUNDROBIN" - } loadbalancerUUID, err := cmd.LoadBalancerManager.GetLoadBalancerUUID(loadbalID) if err != nil { return errors.New(T("Failed to get load balancer: {{.ERR}}.", map[string]interface{}{"ERR": err.Error()})) } + // Sets up all the required parameters protocolConfigurations := datatypes.Network_LBaaS_LoadBalancerProtocolConfiguration{ - BackendPort: &backPort, - BackendProtocol: &backProtocol, - FrontendPort: &frontPort, - FrontendProtocol: &frontProtocol, - LoadBalancingMethod: &method, + BackendPort: &cmd.BackPort, + BackendProtocol: &cmd.BackProtocol, + FrontendPort: &cmd.FrontPort, + FrontendProtocol: &cmd.FrontProtocol, + LoadBalancingMethod: &cmd.Method, } var sessionType string @@ -112,21 +98,23 @@ func (cmd *ProtocolAddCommand) Run(args []string) error { } if cmd.Connections != 0 { - connections := cmd.Connections - protocolConfigurations.MaxConn = &connections + protocolConfigurations.MaxConn = &cmd.Connections } if cmd.ClientTimeout != 0 { - cTimeout := cmd.ClientTimeout - protocolConfigurations.ClientTimeout = &cTimeout + protocolConfigurations.ClientTimeout = &cmd.ClientTimeout } if cmd.ServerTimeout != 0 { - sTimeout := cmd.ServerTimeout - protocolConfigurations.ServerTimeout = &sTimeout + protocolConfigurations.ServerTimeout = &cmd.ServerTimeout } - _, err = cmd.LoadBalancerManager.AddLoadBalancerListener(&loadbalancerUUID, []datatypes.Network_LBaaS_LoadBalancerProtocolConfiguration{protocolConfigurations}) + if cmd.SslId != 0 { + protocolConfigurations.TlsCertificateId = &cmd.SslId + } + _, err = cmd.LoadBalancerManager.AddLoadBalancerListener( + &loadbalancerUUID, []datatypes.Network_LBaaS_LoadBalancerProtocolConfiguration{protocolConfigurations}, + ) if err != nil { return errors.New(T("Failed to add protocol: {{.Error}}.\n", map[string]interface{}{"Error": err.Error()})) } diff --git a/plugin/commands/loadbal/protocols_add_test.go b/plugin/commands/loadbal/protocols_add_test.go index 83e861ff..f136aa06 100644 --- a/plugin/commands/loadbal/protocols_add_test.go +++ b/plugin/commands/loadbal/protocols_add_test.go @@ -87,6 +87,17 @@ var _ = Describe("LoadBal_protocol-add_Test", func() { Expect(*argsForCall[0].LoadBalancingMethod).To(Equal("ROUNDROBIN")) Expect(fakeUI.Outputs()).To(ContainSubstring("OK")) }) + It("--ssl-id option", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345", "--ssl-id=9999", "--front-protocol=HTTPS") + Expect(err).NotTo(HaveOccurred()) + lbUUID, argsForCall := fakeLBManager.AddLoadBalancerListenerArgsForCall(0) + Expect(*lbUUID).To(Equal("aaa-bbb-111")) + Expect(len(argsForCall)).To(Equal(1)) + Expect(*argsForCall[0].FrontendProtocol).To(Equal("HTTPS")) + Expect(*argsForCall[0].BackendProtocol).To(Equal("HTTP")) + Expect(*argsForCall[0].TlsCertificateId).To(Equal(9999)) + Expect(fakeUI.Outputs()).To(ContainSubstring("OK")) + }) It("with sticky as cookie", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345", "--sticky", "cookie") Expect(err).NotTo(HaveOccurred()) diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index ae1b771f..7003aa19 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -185,9 +185,6 @@ "${COMMAND_NAME} sl loadbal order-options [-d, --datacenter DATACENTER]": { "other": "${COMMAND_NAME} sl loadbal order-options [-d, --datacenter DATACENTER]" }, - "${COMMAND_NAME} sl loadbal protocol-add (--id LOADBAL_ID) [--front-protocol PROTOCOL] [back-protocol PROTOCOL] [--front-port PORT] [--back-port PORT] [-m, --method METHOD] [-c, --connections CONNECTIONS] [--sticky cookie | source-ip] [--client-timeout SECONDS] [--server-timeout SECONDS]": { - "other": "${COMMAND_NAME} sl loadbal protocol-add (--id LOADBAL_ID) [--front-protocol PROTOCOL] [back-protocol PROTOCOL] [--front-port PORT] [--back-port PORT] [-m, --method METHOD] [-c, --connections CONNECTIONS] [--sticky cookie | source-ip] [--client-timeout SECONDS] [--server-timeout SECONDS]" - }, "${COMMAND_NAME} sl loadbal protocol-delete (--lb-id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID)": { "other": "${COMMAND_NAME} sl loadbal protocol-delete (--lb-id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID)" }, @@ -1568,6 +1565,9 @@ "Created translation from {{.StaticIP}} to {{.RemoteIP}} #{{.ID}}.": { "other": "Created translation from {{.StaticIP}} to {{.RemoteIP}} #{{.ID}}." }, + "Creates a new mapping between incoming traffic to the loadbalancer and the backend servers.\nUse '{COMMAND_NAME} sl security cert-list' to get IDs for the --ssl-id option.\nSee: https://cloud.ibm.com/docs/loadbalancer-service?topic=loadbalancer-service-about-ibm-cloud-load-balancer for more details\n\nExample:\n\t${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --front-port 443 --front-protocol HTTPS --back-port 80 --back-protocol HTTP --ssl-id 335659 --client-timeout 60 --connections 100\n\tCreates a new protocol on Load Balancer 1115129 that terminates SSL on port 443, mapping to a backend port 80 HTTP. Using SSL cert 335659\n": { + "other": "Creates a new mapping between incoming traffic to the loadbalancer and the backend servers.\nUse '{COMMAND_NAME} sl security cert-list' to get IDs for the --ssl-id option.\nSee: https://cloud.ibm.com/docs/loadbalancer-service?topic=loadbalancer-service-about-ibm-cloud-load-balancer for more details\n\nExample:\n\t${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --front-port 443 --front-protocol HTTPS --back-port 80 --back-protocol HTTP --ssl-id 335659 --client-timeout 60 --connections 100\n\tCreates a new protocol on Load Balancer 1115129 that terminates SSL on port 443, mapping to a backend port 80 HTTP. Using SSL cert 335659\n" + }, "Creates a purge record and also initiates the purge call.": { "other": "Creates a purge record and also initiates the purge call." }, @@ -3914,6 +3914,9 @@ "Identifier": { "other": "Identifier" }, + "Identifier of the SSL certificate to attach to this protocol. Only valid for HTTPS.": { + "other": "Identifier of the SSL certificate to attach to this protocol. Only valid for HTTPS." + }, "If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately\nfailover to an available replica in another location. This method does not allow for fail back via the API.\nTo fail back to the original volume after using this method, open a support ticket.\nTo test failover, use '${COMMAND_NAME} sl {{.storageType}} replica-failover' instead.\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} disaster-recovery-failover 12345678 87654321\n\tThis command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately\nfailover to an available replica in another location. This method does not allow for fail back via the API.\nTo fail back to the original volume after using this method, open a support ticket.\nTo test failover, use '${COMMAND_NAME} sl {{.storageType}} replica-failover' instead.\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} disaster-recovery-failover 12345678 87654321\n\tThis command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." }, @@ -5009,9 +5012,6 @@ "POOL_UUID, URL or HTTPS_PROTOCOL_UUID . It's only available in REDIRECT_POOL | REDIRECT_URL | REDIRECT_HTTPS action": { "other": "POOL_UUID, URL or HTTPS_PROTOCOL_UUID . It's only available in REDIRECT_POOL | REDIRECT_URL | REDIRECT_HTTPS action" }, - "PPTP VPN": { - "other": "PPTP VPN" - }, "Package": { "other": "Package" }, From fbc54985359367a9c788ab095b56e1becb27c41e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Sep 2024 17:34:22 -0500 Subject: [PATCH 17/38] #714 added ability to edit a LB protocol with SSL id. Also addressed #715 --- plugin/commands/loadbal/protocol_edit.go | 77 ++++++++++--------- plugin/commands/loadbal/protocol_edit_test.go | 24 +++++- plugin/commands/loadbal/protocols_add.go | 7 +- plugin/i18n/v2Resources/active.en-US.json | 6 +- 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/plugin/commands/loadbal/protocol_edit.go b/plugin/commands/loadbal/protocol_edit.go index 281409ea..eeabcd59 100644 --- a/plugin/commands/loadbal/protocol_edit.go +++ b/plugin/commands/loadbal/protocol_edit.go @@ -27,6 +27,7 @@ type ProtocolEditCommand struct { Sticky string ClientTimeout int ServerTimeout int + SslId int } func NewProtocolEditCommand(sl *metadata.SoftlayerCommand) *ProtocolEditCommand { @@ -37,23 +38,30 @@ func NewProtocolEditCommand(sl *metadata.SoftlayerCommand) *ProtocolEditCommand cobraCmd := &cobra.Command{ Use: "protocol-edit", Short: T("Edit load balancer protocol"), - Long: T("${COMMAND_NAME} sl loadbal protocol-edit (--id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID) [--front-protocol PROTOCOL] [back-protocol PROTOCOL] [--front-port PORT] [--back-port PORT] [-m, --method METHOD] [-c, --connections CONNECTIONS] [--sticky cookie | source-ip] [--client-timeout SECONDS] [--server-timeout SECONDS]"), - Args: metadata.NoArgs, + Long: T(`Use '${COMMAND_NAME} sl loadbal detail' to find the --protocol-uuid values for a loadbalancer +Example: + ${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --protocol-uuid 8ec8911a-c32d-4678-89fe-979f182c822f --ssl-id 123 + This command changes the SSL certificate +`), + Args: metadata.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) }, } - cobraCmd.Flags().IntVar(&thisCmd.Id, "id", 0, T("ID for the load balancer [required]")) + cobraCmd.Flags().IntVar(&thisCmd.Id, "id", -1, T("ID for the load balancer [required]")) cobraCmd.Flags().StringVar(&thisCmd.ProtocolUuid, "protocol-uuid", "", T("UUID of the protocol you want to edit.")) - cobraCmd.Flags().StringVar(&thisCmd.FrontProtocol, "front-protocol", "HTTP", T("Protocol type to use for incoming connections: [HTTP|HTTPS|TCP]. Default: HTTP")) + cobraCmd.Flags().StringVar(&thisCmd.FrontProtocol, "front-protocol", "", T("Protocol type to use for incoming connections: [HTTP|HTTPS|TCP]. Default: HTTP")) cobraCmd.Flags().StringVar(&thisCmd.BackProtocol, "back-protocol", "", T("Protocol type to use when connecting to backend servers: [HTTP|HTTPS|TCP]. Defaults to whatever --front-protocol is")) - cobraCmd.Flags().IntVar(&thisCmd.FrontPort, "front-port", 80, T("Internet side port")) - cobraCmd.Flags().IntVar(&thisCmd.BackPort, "back-port", 80, T("Private side port")) - cobraCmd.Flags().StringVarP(&thisCmd.Method, "method", "m", "ROUNDROBIN", T("Balancing Method: [ROUNDROBIN|LEASTCONNECTION|WEIGHTED_RR]")) - cobraCmd.Flags().IntVarP(&thisCmd.Connections, "connections", "c", 0, T("Maximum number of connections to allow")) + cobraCmd.Flags().IntVar(&thisCmd.FrontPort, "front-port", -1, T("Internet side port")) + cobraCmd.Flags().IntVar(&thisCmd.BackPort, "back-port", -1, T("Private side port")) + cobraCmd.Flags().StringVarP(&thisCmd.Method, "method", "m", "", T("Balancing Method: [ROUNDROBIN|LEASTCONNECTION|WEIGHTED_RR]")) + cobraCmd.Flags().IntVarP(&thisCmd.Connections, "connections", "c", -1, T("Maximum number of connections to allow")) cobraCmd.Flags().StringVar(&thisCmd.Sticky, "sticky", "", T("Use 'cookie' or 'source-ip' to stick")) - cobraCmd.Flags().IntVar(&thisCmd.ClientTimeout, "client-timeout", 0, T("Client side timeout setting, in seconds")) - cobraCmd.Flags().IntVar(&thisCmd.ServerTimeout, "server-timeout", 0, T("Server side timeout setting, in seconds")) + cobraCmd.Flags().IntVar(&thisCmd.ClientTimeout, "client-timeout", -1, T("Client side timeout setting, in seconds")) + cobraCmd.Flags().IntVar(&thisCmd.ServerTimeout, "server-timeout", -1, T("Server side timeout setting, in seconds")) + cobraCmd.Flags().IntVar(&thisCmd.SslId, "ssl-id", -1, T("Identifier of the SSL certificate to attach to this protocol. Only valid for HTTPS.")) + cobraCmd.MarkFlagRequired("id") + cobraCmd.MarkFlagRequired("protocol-uuid") thisCmd.Command = cobraCmd return thisCmd } @@ -62,7 +70,7 @@ func (cmd *ProtocolEditCommand) Run(args []string) error { protocolConfiguration := datatypes.Network_LBaaS_LoadBalancerProtocolConfiguration{} loadbalID := cmd.Id - if loadbalID == 0 { + if loadbalID == -1 { return errors.NewMissingInputError("--id") } @@ -71,45 +79,37 @@ func (cmd *ProtocolEditCommand) Run(args []string) error { return errors.New(T("Failed to get load balancer: {{.ERR}}.", map[string]interface{}{"ERR": err.Error()})) } - protoUUID := cmd.ProtocolUuid - if protoUUID == "" { + if cmd.ProtocolUuid == "" { return errors.NewMissingInputError("--protocol-uuid") } - protocolConfiguration.ListenerUuid = &protoUUID + protocolConfiguration.ListenerUuid = &cmd.ProtocolUuid if cmd.FrontProtocol != "" { - frontProtocol := cmd.FrontProtocol - protocolConfiguration.FrontendProtocol = &frontProtocol + protocolConfiguration.FrontendProtocol = &cmd.FrontProtocol } if cmd.BackProtocol != "" { - backProtocol := cmd.BackProtocol - protocolConfiguration.BackendProtocol = &backProtocol + protocolConfiguration.BackendProtocol = &cmd.BackProtocol } - if cmd.FrontPort != 0 { - frontPort := cmd.FrontPort - protocolConfiguration.FrontendPort = &frontPort + if cmd.FrontPort != -1 { + protocolConfiguration.FrontendPort = &cmd.FrontPort } - if cmd.BackPort != 0 { - backPort := cmd.BackPort - protocolConfiguration.BackendPort = &backPort + if cmd.BackPort != -1 { + protocolConfiguration.BackendPort = &cmd.BackPort } if cmd.Method != "" { - method := cmd.Method - protocolConfiguration.LoadBalancingMethod = &method + protocolConfiguration.LoadBalancingMethod = &cmd.Method } - if cmd.ClientTimeout != 0 { - cTimeout := cmd.ClientTimeout - protocolConfiguration.ClientTimeout = &cTimeout + if cmd.ClientTimeout != -1 { + protocolConfiguration.ClientTimeout = &cmd.ClientTimeout } - if cmd.ServerTimeout != 0 { - sTimeout := cmd.ServerTimeout - protocolConfiguration.ServerTimeout = &sTimeout + if cmd.ServerTimeout != -1 { + protocolConfiguration.ServerTimeout = &cmd.ServerTimeout } var sessionType string @@ -123,12 +123,17 @@ func (cmd *ProtocolEditCommand) Run(args []string) error { return errors.NewInvalidUsageError(T("Value of option '--sticky' should be cookie or source-ip")) } - if cmd.Connections != 0 { - connections := cmd.Connections - protocolConfiguration.MaxConn = &connections + if cmd.Connections != -1 { + protocolConfiguration.MaxConn = &cmd.Connections } - _, err = cmd.LoadBalancerManager.AddLoadBalancerListener(&loadbalancerUUID, []datatypes.Network_LBaaS_LoadBalancerProtocolConfiguration{protocolConfiguration}) + if cmd.SslId != 0 { + protocolConfiguration.TlsCertificateId = &cmd.SslId + } + + _, err = cmd.LoadBalancerManager.AddLoadBalancerListener( + &loadbalancerUUID, []datatypes.Network_LBaaS_LoadBalancerProtocolConfiguration{protocolConfiguration}, + ) if err != nil { return errors.New(T("Failed to edit protocol: {{.Error}}.\n", map[string]interface{}{"Error": err.Error()})) } diff --git a/plugin/commands/loadbal/protocol_edit_test.go b/plugin/commands/loadbal/protocol_edit_test.go index 599e9c28..8cc3d9db 100644 --- a/plugin/commands/loadbal/protocol_edit_test.go +++ b/plugin/commands/loadbal/protocol_edit_test.go @@ -2,7 +2,7 @@ package loadbal_test import ( "errors" - + "fmt" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -41,11 +41,11 @@ var _ = Describe("LoadBal_protocol-edit_Test", func() { It("Error No Id", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: '--id' is required")) + Expect(err.Error()).To(ContainSubstring(`required flag(s) "id", "protocol-uuid" not set`)) }) It("Error unable to find Id", func() { fakeLBManager.GetLoadBalancerUUIDReturns("-", errors.New("SoftLayer_Exception_ApiError")) - err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345") + err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345", "--protocol-uuid=aaaa") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Failed to get load balancer: SoftLayer_Exception_ApiError")) }) @@ -55,11 +55,14 @@ var _ = Describe("LoadBal_protocol-edit_Test", func() { It("Error no UUID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("'--protocol-uuid' is required")) + Expect(err.Error()).To(ContainSubstring(`required flag(s) "protocol-uuid" not set`)) }) }) Context("Testing Options", func() { + BeforeEach(func() { + fakeLBManager.GetLoadBalancerUUIDReturns("aaa-bbb-111", nil) + }) It("with all arguments", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345", "--protocol-uuid", "abc123", "--front-protocol", "HTTP", "--back-protocol", "HTTP", "--front-port", "80", "--back-port", "80", "--method", "ROUNDROBIN", "--client-timeout", "100", "--server-timeout", "100", "--sticky", "cookie", "--connections", "5") Expect(err).NotTo(HaveOccurred()) @@ -77,6 +80,19 @@ var _ = Describe("LoadBal_protocol-edit_Test", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Value of option '--sticky' should be cookie or source-ip")) }) + It("--ssl-id option", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "--id", "12345", "--protocol-uuid=aaaa", "--ssl-id=9999", "--front-protocol=HTTPS") + Expect(err).NotTo(HaveOccurred()) + lbUUID, argsForCall := fakeLBManager.AddLoadBalancerListenerArgsForCall(0) + Expect(*lbUUID).To(Equal("aaa-bbb-111")) + Expect(len(argsForCall)).To(Equal(1)) + // Making sure we are not sending in options we did not specify + Expect(argsForCall[0].BackendProtocol).To(BeNil()) + + Expect(*argsForCall[0].FrontendProtocol).To(Equal("HTTPS")) + Expect(*argsForCall[0].TlsCertificateId).To(Equal(9999)) + Expect(fakeUI.Outputs()).To(ContainSubstring("OK")) + }) }) Context("API Error", func() { diff --git a/plugin/commands/loadbal/protocols_add.go b/plugin/commands/loadbal/protocols_add.go index 5e0e4f89..0fb75b15 100644 --- a/plugin/commands/loadbal/protocols_add.go +++ b/plugin/commands/loadbal/protocols_add.go @@ -26,7 +26,7 @@ type ProtocolAddCommand struct { Sticky string ClientTimeout int ServerTimeout int - SslId int + SslId int } func NewProtocolAddCommand(sl *metadata.SoftlayerCommand) *ProtocolAddCommand { @@ -37,7 +37,7 @@ func NewProtocolAddCommand(sl *metadata.SoftlayerCommand) *ProtocolAddCommand { cobraCmd := &cobra.Command{ Use: "protocol-add", Short: T("Add a new load balancer protocol"), - Long: T(`Creates a new mapping between incoming traffic to the loadbalancer and the backend servers. + Long: T(`Creates a new mapping between incoming traffic to the loadbalancer and the backend servers. Use '{COMMAND_NAME} sl security cert-list' to get IDs for the --ssl-id option. See: https://cloud.ibm.com/docs/loadbalancer-service?topic=loadbalancer-service-about-ibm-cloud-load-balancer for more details @@ -45,7 +45,7 @@ Example: ${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --front-port 443 --front-protocol HTTPS --back-port 80 --back-protocol HTTP --ssl-id 335659 --client-timeout 60 --connections 100 Creates a new protocol on Load Balancer 1115129 that terminates SSL on port 443, mapping to a backend port 80 HTTP. Using SSL cert 335659 `), - Args: metadata.NoArgs, + Args: metadata.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) }, @@ -71,7 +71,6 @@ func (cmd *ProtocolAddCommand) Run(args []string) error { return errors.NewMissingInputError("--id") } - loadbalancerUUID, err := cmd.LoadBalancerManager.GetLoadBalancerUUID(loadbalID) if err != nil { return errors.New(T("Failed to get load balancer: {{.ERR}}.", map[string]interface{}{"ERR": err.Error()})) diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 7003aa19..c262e992 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -188,9 +188,6 @@ "${COMMAND_NAME} sl loadbal protocol-delete (--lb-id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID)": { "other": "${COMMAND_NAME} sl loadbal protocol-delete (--lb-id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID)" }, - "${COMMAND_NAME} sl loadbal protocol-edit (--id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID) [--front-protocol PROTOCOL] [back-protocol PROTOCOL] [--front-port PORT] [--back-port PORT] [-m, --method METHOD] [-c, --connections CONNECTIONS] [--sticky cookie | source-ip] [--client-timeout SECONDS] [--server-timeout SECONDS]": { - "other": "${COMMAND_NAME} sl loadbal protocol-edit (--id LOADBAL_ID) (--protocol-uuid PROTOCOL_UUID) [--front-protocol PROTOCOL] [back-protocol PROTOCOL] [--front-port PORT] [--back-port PORT] [-m, --method METHOD] [-c, --connections CONNECTIONS] [--sticky cookie | source-ip] [--client-timeout SECONDS] [--server-timeout SECONDS]" - }, "${COMMAND_NAME} sl object-storage accounts": { "other": "${COMMAND_NAME} sl object-storage accounts" }, @@ -7097,6 +7094,9 @@ "Usage information.": { "other": "Usage information." }, + "Use '${COMMAND_NAME} sl loadbal detail' to find the --protocol-uuid values for a loadbalancer\nExample:\n\t${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --protocol-uuid 8ec8911a-c32d-4678-89fe-979f182c822f --ssl-id 123\n\tThis command changes the SSL certificate\n": { + "other": "Use '${COMMAND_NAME} sl loadbal detail' to find the --protocol-uuid values for a loadbalancer\nExample:\n\t${COMMAND_NAME} sl loadbal protocol-add --id 1115129 --protocol-uuid 8ec8911a-c32d-4678-89fe-979f182c822f --ssl-id 123\n\tThis command changes the SSL certificate\n" + }, "Use 'cookie' or 'source-ip' to stick": { "other": "Use 'cookie' or 'source-ip' to stick" }, From ba5a8baf8441488e975e3ab30c9dec54d5575c2d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Sep 2024 19:18:39 -0500 Subject: [PATCH 18/38] #714 fixed gosec issue --- plugin/commands/loadbal/protocol_edit.go | 4 ++-- plugin/commands/loadbal/protocol_edit_test.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin/commands/loadbal/protocol_edit.go b/plugin/commands/loadbal/protocol_edit.go index eeabcd59..f13fabdb 100644 --- a/plugin/commands/loadbal/protocol_edit.go +++ b/plugin/commands/loadbal/protocol_edit.go @@ -60,8 +60,8 @@ Example: cobraCmd.Flags().IntVar(&thisCmd.ClientTimeout, "client-timeout", -1, T("Client side timeout setting, in seconds")) cobraCmd.Flags().IntVar(&thisCmd.ServerTimeout, "server-timeout", -1, T("Server side timeout setting, in seconds")) cobraCmd.Flags().IntVar(&thisCmd.SslId, "ssl-id", -1, T("Identifier of the SSL certificate to attach to this protocol. Only valid for HTTPS.")) - cobraCmd.MarkFlagRequired("id") - cobraCmd.MarkFlagRequired("protocol-uuid") + cobraCmd.MarkFlagRequired("id") //#nosec G104 + cobraCmd.MarkFlagRequired("protocol-uuid") //#nosec G104 thisCmd.Command = cobraCmd return thisCmd } diff --git a/plugin/commands/loadbal/protocol_edit_test.go b/plugin/commands/loadbal/protocol_edit_test.go index 8cc3d9db..4e4c6a1b 100644 --- a/plugin/commands/loadbal/protocol_edit_test.go +++ b/plugin/commands/loadbal/protocol_edit_test.go @@ -2,7 +2,6 @@ package loadbal_test import ( "errors" - "fmt" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" From 78f30f2d0ed234427991fc857542126ff632903e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 17 Sep 2024 16:43:02 -0500 Subject: [PATCH 19/38] Fixed #728, updated some doc strings --- plugin/commands/block/replica_locations.go | 3 +-- plugin/i18n/v2Resources/active.en-US.json | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugin/commands/block/replica_locations.go b/plugin/commands/block/replica_locations.go index e8f6c790..43b32dfe 100644 --- a/plugin/commands/block/replica_locations.go +++ b/plugin/commands/block/replica_locations.go @@ -26,8 +26,7 @@ func NewReplicaLocationsCommand(sl *metadata.SoftlayerStorageCommand) *ReplicaLo cobraCmd := &cobra.Command{ Use: "replica-locations " + T("IDENTIFIER"), Short: T("List suitable replication datacenters for the given volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS] - + Long: T(` EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678 This command lists suitable replication data centers for block volume with ID 12345678.`, sl.StorageI18n), diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index c262e992..f05fcf4f 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -1,4 +1,7 @@ { + "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { + "other": "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678." + }, "\nLUN name": { "other": "\nLUN name" }, @@ -356,9 +359,6 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." }, - "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678." - }, "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." }, From ab4243560ffbfe799d96c3e1087a7b80b5e9fc60 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 17 Sep 2024 17:31:00 -0500 Subject: [PATCH 20/38] #730 Updated block|file snapshot-enable to support INTERVAL snapshots, and updated the help messages a bit --- plugin/commands/block/snapshot_enable.go | 57 +++++++++++------- plugin/commands/block/snapshot_enable_test.go | 59 ++++++------------- plugin/i18n/v2Resources/active.en-US.json | 21 ++++--- 3 files changed, 67 insertions(+), 70 deletions(-) diff --git a/plugin/commands/block/snapshot_enable.go b/plugin/commands/block/snapshot_enable.go index 9461d59d..1f754140 100644 --- a/plugin/commands/block/snapshot_enable.go +++ b/plugin/commands/block/snapshot_enable.go @@ -1,9 +1,10 @@ package block import ( - "strconv" - "github.com/spf13/cobra" + "slices" + "strconv" + "strings" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -21,6 +22,8 @@ var DAY_OF_WEEK = map[int]string{ 6: "SATURDAY", } +var SCHEDULES = []string{"INTERVAL", "HOURLY", "DAILY", "WEEKLY"} + type SnapshotEnableCommand struct { *metadata.SoftlayerStorageCommand Command *cobra.Command @@ -40,8 +43,7 @@ func NewSnapshotEnableCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotEna cobraCmd := &cobra.Command{ Use: "snapshot-enable " + T("IDENTIFIER"), Short: T("Enable snapshots for a given volume on the specified schedule"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} snapshot-enable VOLUME_ID [OPTIONS] - + Long: T(`See https://sldn.softlayer.com/reference/services/SoftLayer_Network_Storage/enableSnapshots/ for more details about these options. EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0 This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained.`, sl.StorageI18n), @@ -50,11 +52,20 @@ EXAMPLE: return thisCmd.Run(args) }, } - cobraCmd.Flags().StringVarP(&thisCmd.ScheduleType, "schedule-type", "s", "", T("Snapshot schedule [required], options are: HOURLY,DAILY,WEEKLY")) - cobraCmd.Flags().IntVarP(&thisCmd.RetentionCount, "retention-count", "c", 0, T("Number of snapshots to retain [required]")) - cobraCmd.Flags().IntVarP(&thisCmd.Minute, "minute", "m", 0, T("Minute of the hour when snapshots should be taken, integer between 0 to 59")) - cobraCmd.Flags().IntVarP(&thisCmd.Hour, "hour", "r", 0, T("Hour of the day when snapshots should be taken, integer between 0 to 23")) - cobraCmd.Flags().IntVarP(&thisCmd.DayOfWeek, "day-of-week", "d", 0, T("Day of the week when snapshots should be taken, integer between 0 to 6. \n 0 means Sunday,1 means Monday,2 means Tuesday,3 means Wendesday,4 means Thursday,5 means Friday,6 means Saturday")) + subs := map[string]interface{}{"ScheduleTypes": strings.Join(SCHEDULES, ", ")} + cobraCmd.Flags().StringVarP(&thisCmd.ScheduleType, "schedule-type", "s", "", + T("Snapshot schedule, options are: {{.ScheduleTypes}}", subs)) + cobraCmd.Flags().IntVarP(&thisCmd.RetentionCount, "retention-count", "c", 0, T("Number of snapshots to retain")) + cobraCmd.Flags().IntVarP(&thisCmd.Minute, "minute", "m", 0, + T("Minute of the hour when snapshots should be taken, integer between 0 to 59")) + cobraCmd.Flags().IntVarP(&thisCmd.Hour, "hour", "r", 0, + T("Hour of the day when snapshots should be taken, integer between 0 to 23")) + cobraCmd.Flags().IntVarP(&thisCmd.DayOfWeek, "day-of-week", "d", 0, + T(`Day of the week when snapshots should be taken, integer between 0 to 6. + 0 means Sunday,1 means Monday,2 means Tuesday,3 means Wendesday,4 means Thursday,5 means Friday,6 means Saturday`)) + cobraCmd.MarkFlagRequired("schedule-type") //#nosec G104 -- Doesn't matter if this errors + cobraCmd.MarkFlagRequired("retention-count") //#nosec G104 -- Doesn't matter if this errors + thisCmd.Command = cobraCmd return thisCmd } @@ -68,9 +79,11 @@ func (cmd *SnapshotEnableCommand) Run(args []string) error { if cmd.ScheduleType == "" { return slErr.NewInvalidUsageError(T("[-s|--schedule-type] is required, options are: HOURLY, DAILY, WEEKLY.")) } - scheduleType := cmd.ScheduleType - if scheduleType != "HOURLY" && scheduleType != "DAILY" && scheduleType != "WEEKLY" { - return slErr.NewInvalidUsageError(T("[-s|--schedule-type] must be HOURLY, DAILY, or WEEKLY.")) + + if !slices.Contains(SCHEDULES, cmd.ScheduleType) { + subs := map[string]interface{}{"ScheduleTypes": strings.Join(SCHEDULES, ", "), "Selected": cmd.ScheduleType} + return slErr.NewInvalidUsageError( + T("[-s|--schedule-type] needs to be one of {{.ScheduleTypes}}, not {{.Selected}}.", subs)) } retentionCount := cmd.RetentionCount @@ -78,22 +91,24 @@ func (cmd *SnapshotEnableCommand) Run(args []string) error { return slErr.NewMissingInputError("-c|--retention-count") } - minute := cmd.Minute - if minute < 0 || minute > 59 { + if cmd.Minute < 0 || cmd.Minute > 59 { return slErr.NewInvalidUsageError(T("[-m|--minute] value must be between 0 and 59.")) } - hour := cmd.Hour - if hour < 0 || hour > 23 { + + if cmd.Hour < 0 || cmd.Hour > 23 { return slErr.NewInvalidUsageError(T("[-r|--hour] value must be between 0 and 23.")) } - dayOfWeek := cmd.DayOfWeek - if dayOfWeek < 0 || dayOfWeek > 6 { + + if cmd.DayOfWeek < 0 || cmd.DayOfWeek > 6 { return slErr.NewInvalidUsageError(T("[-d|--day-of-week] value must be between 0 and 6.")) } - err = cmd.StorageManager.EnableSnapshot(volumeID, scheduleType, retentionCount, minute, hour, DAY_OF_WEEK[dayOfWeek]) - subs := map[string]interface{}{"ScheduleType": scheduleType, "VolumeID": volumeID} + + err = cmd.StorageManager.EnableSnapshot( + volumeID, cmd.ScheduleType, retentionCount, cmd.Minute, cmd.Hour, DAY_OF_WEEK[cmd.DayOfWeek]) + subs := map[string]interface{}{"ScheduleType": cmd.ScheduleType, "VolumeID": volumeID} if err != nil { - return slErr.NewAPIError(T("Failed to enable {{.ScheduleType}} snapshot for volume {{.VolumeID}}.\n", subs), err.Error(), 2) + return slErr.NewAPIError( + T("Failed to enable {{.ScheduleType}} snapshot for volume {{.VolumeID}}.\n", subs), err.Error(), 2) } cmd.UI.Ok() cmd.UI.Print(T("{{.ScheduleType}} snapshots have been enabled for volume {{.VolumeID}}.", subs)) diff --git a/plugin/commands/block/snapshot_enable_test.go b/plugin/commands/block/snapshot_enable_test.go index 59150f35..55650f88 100644 --- a/plugin/commands/block/snapshot_enable_test.go +++ b/plugin/commands/block/snapshot_enable_test.go @@ -32,63 +32,38 @@ var _ = Describe("Snapshot enable", func() { }) Describe("Snapshot enable", func() { - Context("Snapshot enable without volume id", func() { - It("return error", func() { + Context("Incorrect Usage Tests", func() { + It("No arguments", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - }) - Context("Snapshot enable with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") + It("Bad Volume ID", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "a1234", "-c=100", "-s=INTERVAL") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) }) - }) - - Context("Snapshot enable without -s", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") + It("Bad Interval", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--schedule-type=FAKE", "-c=100") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-s|--schedule-type] is required, options are: HOURLY, DAILY, WEEKLY.")) + Expect(err.Error()).To(ContainSubstring("needs to be one of INTERVAL, HOURLY, DAILY, WEEKLY, not FAKE.")) }) - }) - - Context("Snapshot enable with wrong -s", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "MONTHLY") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-s|--schedule-type] must be HOURLY, DAILY, or WEEKLY.")) - }) - }) - - Context("Snapshot enable without -c", func() { - It("return error", func() { + It("No Retention Count", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "HOURLY") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: '-c|--retention-count' is required")) + Expect(err.Error()).To(ContainSubstring(`required flag(s) "retention-count" not set`)) }) - }) - - Context("Snapshot enable with wrong -m", func() { - It("return error", func() { + It("Bad Minutes Value", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "HOURLY", "-c", "3", "-m", "100") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-m|--minute] value must be between 0 and 59.")) }) - }) - - Context("Snapshot enable with wrong --hour", func() { - It("return error", func() { + It("Wrong Hour", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "HOURLY", "-c", "3", "-m", "20", "--hour", "50") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-r|--hour] value must be between 0 and 23.")) }) - }) - - Context("Snapshot enable with wrong -d", func() { - It("return error", func() { + It("Wrong Days of the Week", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "HOURLY", "-c", "3", "-m", "20", "--hour", "10", "-d", "10") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-d|--day-of-week] value must be between 0 and 6.")) @@ -99,22 +74,26 @@ var _ = Describe("Snapshot enable", func() { BeforeEach(func() { FakeStorageManager.EnableSnapshotReturns(nil) }) - It("return no error", func() { + It("Happy Path All Options", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "HOURLY", "-c", "3", "-m", "20", "--hour", "10", "-d", "0") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("HOURLY snapshots have been enabled for volume 1234.")) }) + It("Happy Path min Options", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "9999", "-s", "INTERVAL", "-c", "500") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("INTERVAL snapshots have been enabled for volume 9999.")) + }) }) Context("Snapshot enable with correct parameters but server API call fails", func() { BeforeEach(func() { FakeStorageManager.EnableSnapshotReturns(errors.New("Internal Server Error")) }) - It("return error", func() { + It("API error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "HOURLY", "-c", "3", "-m", "20", "--hour", "10", "-d", "0") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Failed to enable HOURLY snapshot for volume 1234.")) - }) }) }) diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index f05fcf4f..7a451ba3 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -374,9 +374,6 @@ "${COMMAND_NAME} sl {{.storageType}} snapshot-disable VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-disable 12345678 -s DAILY\n This command disables daily snapshot for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-disable VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-disable 12345678 -s DAILY\n This command disables daily snapshot for volume with ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-enable VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0\n This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-enable VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0\n This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained." - }, "${COMMAND_NAME} sl {{.storageType}} snapshot-list VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id \n This command lists all snapshots of volume with ID 12345678 and sorts them by ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-list VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id \n This command lists all snapshots of volume with ID 12345678 and sorts them by ID." }, @@ -1640,8 +1637,8 @@ "Date time": { "other": "Date time" }, - "Day of the week when snapshots should be taken, integer between 0 to 6. \n 0 means Sunday,1 means Monday,2 means Tuesday,3 means Wendesday,4 means Thursday,5 means Friday,6 means Saturday": { - "other": "Day of the week when snapshots should be taken, integer between 0 to 6. \n 0 means Sunday,1 means Monday,2 means Tuesday,3 means Wendesday,4 means Thursday,5 means Friday,6 means Saturday" + "Day of the week when snapshots should be taken, integer between 0 to 6. \n\t 0 means Sunday,1 means Monday,2 means Tuesday,3 means Wendesday,4 means Thursday,5 means Friday,6 means Saturday": { + "other": "Day of the week when snapshots should be taken, integer between 0 to 6. \n\t 0 means Sunday,1 means Monday,2 means Tuesday,3 means Wendesday,4 means Thursday,5 means Friday,6 means Saturday" }, "Dedicated Access": { "other": "Dedicated Access" @@ -4751,8 +4748,8 @@ "Number of seconds to report as one data point. 300, 600, 1800, 3600 (default), 43200 or 86400 seconds": { "other": "Number of seconds to report as one data point. 300, 600, 1800, 3600 (default), 43200 or 86400 seconds" }, - "Number of snapshots to retain [required]": { - "other": "Number of snapshots to retain [required]" + "Number of snapshots to retain": { + "other": "Number of snapshots to retain" }, "Number of times before marking as DOWN. [1-10]": { "other": "Number of times before marking as DOWN. [1-10]" @@ -5786,6 +5783,9 @@ "Security group {{.ID}} is updated.": { "other": "Security group {{.ID}} is updated." }, + "See https://sldn.softlayer.com/reference/services/SoftLayer_Network_Storage/enableSnapshots/ for more details about these options.\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0\n This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained.": { + "other": "See https://sldn.softlayer.com/reference/services/SoftLayer_Network_Storage/enableSnapshots/ for more details about these options.\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0\n This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained." + }, "Serial #": { "other": "Serial #" }, @@ -5993,6 +5993,9 @@ "Snapshot schedule to use for replication. Options are: HOURLY,DAILY,WEEKLY [required]": { "other": "Snapshot schedule to use for replication. Options are: HOURLY,DAILY,WEEKLY [required]" }, + "Snapshot schedule, options are: {{.ScheduleTypes}}": { + "other": "Snapshot schedule, options are: {{.ScheduleTypes}}" + }, "Snapshot space not found for original volume, origin snapshot space is needed for duplication": { "other": "Snapshot space not found for original volume, origin snapshot space is needed for duplication" }, @@ -7361,8 +7364,8 @@ "[-s|--schedule-type] is required, options are: HOURLY, DAILY, WEEKLY.": { "other": "[-s|--schedule-type] is required, options are: HOURLY, DAILY, WEEKLY." }, - "[-s|--schedule-type] must be HOURLY, DAILY, or WEEKLY.": { - "other": "[-s|--schedule-type] must be HOURLY, DAILY, or WEEKLY." + "[-s|--schedule-type] needs to be one of {{.ScheduleTypes}}, not {{.Selected}}.": { + "other": "[-s|--schedule-type] needs to be one of {{.ScheduleTypes}}, not {{.Selected}}." }, "[-s|--size] is required.\nRun '{{.CommandName}} sl block volume-options' to get available options.": { "other": "[-s|--size] is required.\nRun '{{.CommandName}} sl block volume-options' to get available options." From ba8b7d36df915a07657717b8d17a26792939dc09 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 18 Sep 2024 16:38:24 -0500 Subject: [PATCH 21/38] #730 updated snapshotorder documentation --- plugin/commands/block/snapshot_list.go | 3 +- plugin/commands/block/snapshot_list_test.go | 46 ++---- plugin/commands/block/snapshot_order.go | 47 +++--- plugin/commands/block/snapshot_order_test.go | 105 ++++++++++---- plugin/commands/block/snapshot_restore.go | 3 +- .../commands/block/snapshot_restore_test.go | 20 +-- plugin/commands/file/file.go | 2 +- plugin/commands/file/snapshot_order.go | 113 --------------- plugin/commands/file/snapshot_order_test.go | 136 ------------------ plugin/i18n/v2Resources/active.en-US.json | 37 ++--- plugin/metadata/sl.go | 11 +- 11 files changed, 145 insertions(+), 378 deletions(-) delete mode 100644 plugin/commands/file/snapshot_order.go delete mode 100644 plugin/commands/file/snapshot_order_test.go diff --git a/plugin/commands/block/snapshot_list.go b/plugin/commands/block/snapshot_list.go index 70c4260c..74d480b3 100644 --- a/plugin/commands/block/snapshot_list.go +++ b/plugin/commands/block/snapshot_list.go @@ -28,8 +28,7 @@ func NewSnapshotListCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotListC cobraCmd := &cobra.Command{ Use: "snapshot-list " + T("IDENTIFIER"), Short: T("List {{.storageType}} storage snapshots", sl.StorageI18n), - Long: T(`${COMMAND_NAME} sl {{.storageType}} snapshot-list VOLUME_ID [OPTIONS] - + Long: T(` EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id This command lists all snapshots of volume with ID 12345678 and sorts them by ID.`, sl.StorageI18n), diff --git a/plugin/commands/block/snapshot_list_test.go b/plugin/commands/block/snapshot_list_test.go index 1ddb7e48..fc38746c 100644 --- a/plugin/commands/block/snapshot_list_test.go +++ b/plugin/commands/block/snapshot_list_test.go @@ -56,27 +56,19 @@ var _ = Describe("Snapshot list", func() { cliCommand.StorageManager = FakeStorageManager }) - Describe("Snapshot list", func() { - Context("Snapshot list without volume id", func() { - It("return error", func() { + Describe("Snapshot list tests", func() { + Context("Usage Errors", func() { + It("No volumeid", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - }) - Context("Snapshot list with wrong volume id", func() { - It("return error", func() { + It("Bad volume id", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) }) - }) - - Context("Snapshot list with wrong --sortby", func() { - BeforeEach(func() { - FakeStorageManager.GetVolumeSnapshotListReturns(nil, nil) - }) - It("return error", func() { + It("Bad --sortby", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--sortby", "bcd") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: --sortby bcd is not supported.")) @@ -87,7 +79,7 @@ var _ = Describe("Snapshot list", func() { BeforeEach(func() { FakeStorageManager.GetVolumeSnapshotListReturns(nil, errors.New("Internal Server Error")) }) - It("return error", func() { + It("SL API ERROR", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Failed to get snapshot list on your account.")) @@ -99,7 +91,7 @@ var _ = Describe("Snapshot list", func() { BeforeEach(func() { FakeStorageManager.GetVolumeSnapshotListReturns(fakeReturn, nil) }) - It("return no error", func() { + It("Happy Path", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).NotTo(HaveOccurred()) // I don't like ContainSubstrings, but its useful for checking for multiple strings in a single line @@ -109,13 +101,7 @@ var _ = Describe("Snapshot list", func() { Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"3", "sp-0003", "2016-12-28T00:12:00", "100"})) }) - }) - - Context("Snapshot list with correct volume id and --sortby=size_bytes", func() { - BeforeEach(func() { - FakeStorageManager.GetVolumeSnapshotListReturns(fakeReturn, nil) - }) - It("return no error", func() { + It("Sorted by size_bytes", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--sortby", "size_bytes") Expect(err).NotTo(HaveOccurred()) rows := strings.Split(fakeUI.Outputs(), "\n") @@ -123,13 +109,7 @@ var _ = Describe("Snapshot list", func() { Expect(rows[2]).To(ContainSubstring("500")) Expect(rows[3]).To(ContainSubstring("540")) }) - }) - - Context("Snapshot list with correct volume id and --sortby=created", func() { - BeforeEach(func() { - FakeStorageManager.GetVolumeSnapshotListReturns(fakeReturn, nil) - }) - It("return no error", func() { + It("Sorted by created", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--sortby", "created") Expect(err).NotTo(HaveOccurred()) rows := strings.Split(fakeUI.Outputs(), "\n") @@ -137,13 +117,7 @@ var _ = Describe("Snapshot list", func() { Expect(rows[2]).To(ContainSubstring("2016-12-26T00:12:00")) Expect(rows[3]).To(ContainSubstring("2016-12-28T00:12:00")) }) - }) - - Context("Snapshot list with correct volume id and --sortby=created", func() { - BeforeEach(func() { - FakeStorageManager.GetVolumeSnapshotListReturns(fakeReturn, nil) - }) - It("return no error", func() { + It("Sorted by name", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--sortby", "name") Expect(err).NotTo(HaveOccurred()) rows := strings.Split(fakeUI.Outputs(), "\n") diff --git a/plugin/commands/block/snapshot_order.go b/plugin/commands/block/snapshot_order.go index 03857254..850308b7 100644 --- a/plugin/commands/block/snapshot_order.go +++ b/plugin/commands/block/snapshot_order.go @@ -2,6 +2,7 @@ package block import ( "fmt" + "slices" "strconv" "github.com/spf13/cobra" @@ -24,6 +25,8 @@ type SnapshotOrderCommand struct { Force bool } +var TIERS = []float64{0.25, 2, 4, 10} + func NewSnapshotOrderCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotOrderCommand { thisCmd := &SnapshotOrderCommand{ SoftlayerStorageCommand: sl, @@ -32,7 +35,8 @@ func NewSnapshotOrderCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotOrde cobraCmd := &cobra.Command{ Use: "snapshot-order " + T("IDENTIFIER"), Short: T("Order snapshot space for a block storage volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} snapshot-order VOLUME_ID [OPTIONS] + Long: T(`See https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-getting-started for sizing options. +${COMMAND_NAME} sl block volume-options' to get available options. EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 @@ -42,11 +46,15 @@ EXAMPLE: return thisCmd.Run(args) }, } - cobraCmd.Flags().IntVarP(&thisCmd.Size, "size", "s", 0, T("Size of snapshot space to create in GB [required]")) - cobraCmd.Flags().Float64VarP(&thisCmd.Tier, "tier", "t", 0, T("Endurance Storage Tier (IOPS per GB) of the block volume for which space is ordered [optional], options are: 0.25,2,4,10")) - cobraCmd.Flags().IntVarP(&thisCmd.Iops, "iops", "i", 0, T("Performance Storage IOPs, between 100 and 6000 in multiples of 100")) - cobraCmd.Flags().BoolVarP(&thisCmd.Upgrade, "upgrade", "u", false, T("Flag to indicate that the order is an upgrade")) + cobraCmd.Flags().IntVarP(&thisCmd.Size, "size", "s", 0, T("Size of snapshot space to create in GB")) + cobraCmd.Flags().Float64VarP(&thisCmd.Tier, "tier", "t", 0, + T("Endurance Storage Tier (IOPS per GB) of the block volume for which space is ordered [optional], options are: 0.25,2,4,10")) + cobraCmd.Flags().IntVarP(&thisCmd.Iops, "iops", "i", 0, + T("Performance Storage IOPs, between 100 and 6000 in multiples of 100")) + cobraCmd.Flags().BoolVarP(&thisCmd.Upgrade, "upgrade", "u", false, + T("Flag to indicate that the order is an upgrade")) cobraCmd.Flags().BoolVarP(&thisCmd.Force, "force", "f", false, T("Force operation without confirmation")) + cobraCmd.MarkFlagRequired("size") // #nosec G104 -- Doesn't matter if this errors thisCmd.Command = cobraCmd return thisCmd } @@ -57,27 +65,16 @@ func (cmd *SnapshotOrderCommand) Run(args []string) error { if err != nil { return slErr.NewInvalidSoftlayerIdInputError("Volume ID") } - subs := map[string]interface{}{"CommandName": "ibmcloud"} - if cmd.Size == 0 { - return slErr.NewInvalidUsageError(T("[-s|--size] is required.\nRun '{{.CommandName}} sl block volume-options' to get available options.", subs)) - } - size := cmd.Size - tier := cmd.Tier - if tier > 0 { - if tier != 0 && tier != 0.25 && tier != 2 && tier != 4 && tier != 10 { + if cmd.Tier > 0 { + if !slices.Contains(TIERS, cmd.Tier) { return slErr.NewInvalidUsageError(T("[-t|--tier] is optional, options are: 0.25,2,4,10.")) } } - iops := cmd.Iops - if iops > 0 { - if iops < 100 || iops > 6000 { - return slErr.NewInvalidUsageError(T("-i|--iops must be between 100 and 6000, inclusive.\nRun '{{.CommandName}} sl block volume-options' to check available options.", subs)) - - } - if iops%100 != 0 { - return slErr.NewInvalidUsageError(T("-i|--iops must be a multiple of 100.\nRun '{{.CommandName}} sl block volume-options' to check available options.", subs)) + if cmd.Iops > 0 { + if cmd.Iops%100 != 0 { + return slErr.NewInvalidUsageError(T("-i|--iops must be a multiple of 100.")) } } @@ -93,10 +90,12 @@ func (cmd *SnapshotOrderCommand) Run(args []string) error { return nil } } - orderReceipt, err := cmd.StorageManager.OrderSnapshotSpace("block", volumeID, size, tier, iops, cmd.Upgrade) + orderReceipt, err := cmd.StorageManager.OrderSnapshotSpace( + cmd.GetStorageType(), volumeID, cmd.Size, cmd.Tier, cmd.Iops, cmd.Upgrade) if err != nil { - return slErr.NewAPIError(T("Failed to order snapshot space for volume {{.VolumeID}}.Please verify your options and try again.\n", - map[string]interface{}{"VolumeID": volumeID}), err.Error(), 2) + return slErr.NewAPIError( + T("Failed to order snapshot space for volume {{.VolumeID}}.Please verify your options and try again.\n", + map[string]interface{}{"VolumeID": volumeID}), err.Error(), 2) } if outputFormat == "JSON" { diff --git a/plugin/commands/block/snapshot_order_test.go b/plugin/commands/block/snapshot_order_test.go index 097b5b79..f5521627 100644 --- a/plugin/commands/block/snapshot_order_test.go +++ b/plugin/commands/block/snapshot_order_test.go @@ -15,7 +15,7 @@ import ( "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" ) -var _ = Describe("Snapshot order", func() { +var _ = Describe("Block Snapshot order", func() { var ( fakeUI *terminal.FakeUI cliCommand *block.SnapshotOrderCommand @@ -34,40 +34,28 @@ var _ = Describe("Snapshot order", func() { }) Describe("Snapshot order", func() { - Context("Snapshot order without volume id", func() { - It("return error", func() { + Context("Bad Usage", func() { + It("No Volume ID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - }) - Context("Snapshot order with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") + It("Bad Volume ID", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "-s=100") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) }) - }) - - Context("Snapshot order without -s", func() { - It("return error", func() { + It("No --size", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-s|--size] is required.")) - Expect(err.Error()).To(ContainSubstring("sl block volume-options' to get available options.")) + Expect(err.Error()).To(ContainSubstring(`required flag(s) "size" not set`)) }) - }) - - Context("Snapshot order with wrong tier", func() { - It("return error", func() { + It("Bad Tier", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.3") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-t|--tier] is optional, options are: 0.25,2,4,10.")) }) - }) - - Context("Snapshot order with -f and not continue", func() { - It("return no error", func() { + It("No confirmation", func() { fakeUI.Inputs("No") err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25") Expect(err).NotTo(HaveOccurred()) @@ -91,14 +79,59 @@ var _ = Describe("Snapshot order", func() { }, }, nil) }) - It("return no error", func() { + It("Normal Order Happy Path", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) }) + It("Upgrade Order Happy Path", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "4567", "-s", "1000", "-t", "10", "-u", "-f") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) + storage_type, volumeId, size, tier, iops, upgrade := FakeStorageManager.OrderSnapshotSpaceArgsForCall(0) + Expect(storage_type).To(Equal("block")) + Expect(volumeId).To(Equal(4567)) + Expect(size).To(Equal(1000)) + Expect(tier).To(Equal(10.0)) + Expect(iops).To(Equal(0)) + Expect(upgrade).To(BeTrue()) + }) }) - Context("Snapshot order with -f and continue and upgrade", func() { + Context("Snapshot order with correct parameters but server API call fails", func() { + BeforeEach(func() { + FakeStorageManager.OrderSnapshotSpaceReturns( + datatypes.Container_Product_Order_Receipt{}, errors.New("Internal Server Error")) + }) + It("return no error", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-f") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to order snapshot space for volume 1234.Please verify your options and try again.")) + }) + }) + }) +}) + +var _ = Describe("File Snapshot order", func() { + var ( + fakeUI *terminal.FakeUI + cliCommand *block.SnapshotOrderCommand + fakeSession *session.Session + slCommand *metadata.SoftlayerStorageCommand + FakeStorageManager *testhelpers.FakeStorageManager + ) + BeforeEach(func() { + fakeUI = terminal.NewFakeUI() + fakeSession = testhelpers.NewFakeSoftlayerSession([]string{}) + FakeStorageManager = new(testhelpers.FakeStorageManager) + slCommand = metadata.NewSoftlayerStorageCommand(fakeUI, fakeSession, "file") + cliCommand = block.NewSnapshotOrderCommand(slCommand) + cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") + cliCommand.StorageManager = FakeStorageManager + }) + + Describe("Snapshot order", func() { + Context("Snapshot order with -f and continue", func() { BeforeEach(func() { FakeStorageManager.OrderSnapshotSpaceReturns(datatypes.Container_Product_Order_Receipt{ OrderId: sl.Int(123456), @@ -114,16 +147,36 @@ var _ = Describe("Snapshot order", func() { }, }, nil) }) - It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-u", "-f") + It("Normal Order Happy Path", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-f") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) + storage_type, volumeId, size, tier, iops, upgrade := FakeStorageManager.OrderSnapshotSpaceArgsForCall(0) + Expect(storage_type).To(Equal("file")) + Expect(volumeId).To(Equal(1234)) + Expect(size).To(Equal(100)) + Expect(tier).To(Equal(0.25)) + Expect(iops).To(Equal(0)) + Expect(upgrade).To(BeFalse()) + }) + It("Upgrade Order Happy Path", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "4567", "-s", "1000", "-t", "10", "-u", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) + storage_type, volumeId, size, tier, iops, upgrade := FakeStorageManager.OrderSnapshotSpaceArgsForCall(0) + Expect(storage_type).To(Equal("file")) + Expect(volumeId).To(Equal(4567)) + Expect(size).To(Equal(1000)) + Expect(tier).To(Equal(10.0)) + Expect(iops).To(Equal(0)) + Expect(upgrade).To(BeTrue()) }) }) Context("Snapshot order with correct parameters but server API call fails", func() { BeforeEach(func() { - FakeStorageManager.OrderSnapshotSpaceReturns(datatypes.Container_Product_Order_Receipt{}, errors.New("Internal Server Error")) + FakeStorageManager.OrderSnapshotSpaceReturns( + datatypes.Container_Product_Order_Receipt{}, errors.New("Internal Server Error")) }) It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-f") diff --git a/plugin/commands/block/snapshot_restore.go b/plugin/commands/block/snapshot_restore.go index 33493503..b6278649 100644 --- a/plugin/commands/block/snapshot_restore.go +++ b/plugin/commands/block/snapshot_restore.go @@ -25,8 +25,7 @@ func NewSnapshotRestoreCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotRe cobraCmd := &cobra.Command{ Use: "snapshot-restore " + T("IDENTIFIER") + " " + T("SNAPSHOT_ID"), Short: T("Restore {{.storageType}} volume using a given snapshot", sl.StorageI18n), - Long: T(`${COMMAND_NAME} sl {{.storageType}} snapshot-restore VOLUME_ID SNAPSHOT_ID - + Long: T(` EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} snapshot-restore 12345678 87654321 This command restores volume with ID 12345678 from snapshot with ID 87654321.`, sl.StorageI18n), diff --git a/plugin/commands/block/snapshot_restore_test.go b/plugin/commands/block/snapshot_restore_test.go index 55288ee4..4b44a391 100644 --- a/plugin/commands/block/snapshot_restore_test.go +++ b/plugin/commands/block/snapshot_restore_test.go @@ -32,29 +32,23 @@ var _ = Describe("Snapshot restore", func() { }) Describe("Snapshot restore", func() { - Context("Snapshot restore without volume id", func() { - It("return error", func() { + Context("Input Validation", func() { + It("No arguments", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires two arguments.")) }) - }) - Context("Snapshot order without snapshot id", func() { - It("return error", func() { + It("Missing snapshot", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires two arguments.")) }) - }) - Context("Snapshot order with wrong volume id", func() { - It("return error", func() { + It("Bad VolumeID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "123") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) }) - }) - Context("Snapshot order with wrong snapshot id", func() { - It("return error", func() { + It("Bad SnapshotId", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "abc") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Invalid input for 'Snapshot ID'. It must be a positive integer.")) @@ -65,7 +59,7 @@ var _ = Describe("Snapshot restore", func() { BeforeEach(func() { FakeStorageManager.RestoreFromSnapshotReturns(nil) }) - It("return no error", func() { + It("Happy path", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "456") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Block volume 123 is being restored using snapshot 456.")) @@ -76,7 +70,7 @@ var _ = Describe("Snapshot restore", func() { BeforeEach(func() { FakeStorageManager.RestoreFromSnapshotReturns(errors.New("Internal Server Error")) }) - It("return error", func() { + It("API Error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "456") Expect(err).To(HaveOccurred()) Expect(fakeUI.Outputs()).NotTo(ContainSubstring("OK")) diff --git a/plugin/commands/file/file.go b/plugin/commands/file/file.go index be6b4872..4d77aad0 100644 --- a/plugin/commands/file/file.go +++ b/plugin/commands/file/file.go @@ -50,13 +50,13 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { cobraCmd.AddCommand(block.NewVolumeLimitCommand(StorageCommand).Command) cobraCmd.AddCommand(block.NewVolumeRefreshCommand(StorageCommand).Command) cobraCmd.AddCommand(block.NewVolumeConvertCommand(StorageCommand).Command) + cobraCmd.AddCommand(block.NewSnapshotOrderCommand(StorageCommand).Command) // Unique File Commands, even these can likely be merged in a later version. cobraCmd.AddCommand(NewAccessAuthorizeCommand(StorageCommand).Command) cobraCmd.AddCommand(NewAccessRevokeCommand(StorageCommand).Command) cobraCmd.AddCommand(NewReplicaOrderCommand(StorageCommand).Command) cobraCmd.AddCommand(NewSnapshotCancelCommand(StorageCommand).Command) - cobraCmd.AddCommand(NewSnapshotOrderCommand(StorageCommand).Command) cobraCmd.AddCommand(NewVolumeCancelCommand(StorageCommand).Command) cobraCmd.AddCommand(NewVolumeCountCommand(StorageCommand).Command) cobraCmd.AddCommand(NewVolumeDetailCommand(StorageCommand).Command) diff --git a/plugin/commands/file/snapshot_order.go b/plugin/commands/file/snapshot_order.go deleted file mode 100644 index ea1c8ca3..00000000 --- a/plugin/commands/file/snapshot_order.go +++ /dev/null @@ -1,113 +0,0 @@ -package file - -import ( - "fmt" - "strconv" - - "github.com/spf13/cobra" - - slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" - . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/utils" -) - -type SnapshotOrderCommand struct { - *metadata.SoftlayerStorageCommand - Command *cobra.Command - StorageManager managers.StorageManager - Size int - Tier float64 - Iops int - Upgrade bool - Force bool -} - -func NewSnapshotOrderCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotOrderCommand { - thisCmd := &SnapshotOrderCommand{ - SoftlayerStorageCommand: sl, - StorageManager: managers.NewStorageManager(sl.Session), - } - cobraCmd := &cobra.Command{ - Use: "snapshot-order " + T("IDENTIFIER"), - Short: T("Order snapshot space for a file storage volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} snapshot-order VOLUME_ID [OPTIONS] - -EXAMPLE: - ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 - This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB.`, sl.StorageI18n), - Args: metadata.OneArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return thisCmd.Run(args) - }, - } - cobraCmd.Flags().IntVarP(&thisCmd.Size, "size", "s", 0, T("Size of snapshot space to create in GB [required]")) - cobraCmd.Flags().Float64VarP(&thisCmd.Tier, "tier", "t", 0, T("Endurance Storage Tier (IOPS per GB) of the file volume for which space is ordered [optional], options are: 0.25,2,4,10")) - cobraCmd.Flags().IntVarP(&thisCmd.Iops, "iops", "i", 0, T("Performance Storage IOPs, between 100 and 6000 in multiples of 100")) - cobraCmd.Flags().BoolVarP(&thisCmd.Upgrade, "upgrade", "u", false, T("Flag to indicate that the order is an upgrade")) - cobraCmd.Flags().BoolVarP(&thisCmd.Force, "force", "f", false, T("Force operation without confirmation")) - thisCmd.Command = cobraCmd - return thisCmd -} - -func (cmd *SnapshotOrderCommand) Run(args []string) error { - - volumeID, err := strconv.Atoi(args[0]) - if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") - } - subs := map[string]interface{}{"CommandName": "ibmcloud"} - if cmd.Size == 0 { - return slErr.NewInvalidUsageError(T("[-s|--size] is required.\nRun '{{.CommandName}} sl file volume-options' to get available options.", subs)) - } - size := cmd.Size - - tier := cmd.Tier - if tier > 0 { - if tier != 0 && tier != 0.25 && tier != 2 && tier != 4 && tier != 10 { - return slErr.NewInvalidUsageError(T("[-t|--tier] is optional, options are: 0.25,2,4,10.")) - } - } - iops := cmd.Iops - if iops > 0 { - if iops < 100 || iops > 6000 { - return slErr.NewInvalidUsageError(T("-i|--iops must be between 100 and 6000, inclusive.\nRun '{{.CommandName}} sl file volume-options' to check available options.", subs)) - - } - if iops%100 != 0 { - return slErr.NewInvalidUsageError(T("-i|--iops must be a multiple of 100.\nRun '{{.CommandName}} sl file volume-options' to check available options.", subs)) - - } - } - - outputFormat := cmd.GetOutputFlag() - - if !cmd.Force && outputFormat != "JSON" { - confirm, err := cmd.UI.Confirm(T("This action will incur charges on your account. Continue?")) - if err != nil { - return err - } - if !confirm { - cmd.UI.Print(T("Aborted.")) - return nil - } - } - orderReceipt, err := cmd.StorageManager.OrderSnapshotSpace("file", volumeID, size, tier, iops, cmd.Upgrade) - if err != nil { - return slErr.NewAPIError(T("Failed to order snapshot space for volume {{.VolumeID}}.Please verify your options and try again.\n", - map[string]interface{}{"VolumeID": volumeID}), err.Error(), 2) - } - - if outputFormat == "JSON" { - return utils.PrintPrettyJSON(cmd.UI, orderReceipt) - } - - cmd.UI.Ok() - cmd.UI.Print(T("Order {{.OrderID}} was placed.", map[string]interface{}{"OrderID": *orderReceipt.OrderId})) - for _, item := range orderReceipt.PlacedOrder.Items { - cmd.UI.Print(fmt.Sprintf(" > %s", *item.Description)) - cmd.UI.Print("") - } - return nil -} diff --git a/plugin/commands/file/snapshot_order_test.go b/plugin/commands/file/snapshot_order_test.go deleted file mode 100644 index d3aa29a8..00000000 --- a/plugin/commands/file/snapshot_order_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package file_test - -import ( - "errors" - - "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/softlayer/softlayer-go/datatypes" - "github.com/softlayer/softlayer-go/session" - "github.com/softlayer/softlayer-go/sl" - - "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/file" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" -) - -var _ = Describe("Snapshot order", func() { - var ( - fakeUI *terminal.FakeUI - FakeStorageManager *testhelpers.FakeStorageManager - cliCommand *file.SnapshotOrderCommand - fakeSession *session.Session - slCommand *metadata.SoftlayerStorageCommand - ) - BeforeEach(func() { - fakeUI = terminal.NewFakeUI() - FakeStorageManager = new(testhelpers.FakeStorageManager) - slCommand = metadata.NewSoftlayerStorageCommand(fakeUI, fakeSession, "file") - cliCommand = file.NewSnapshotOrderCommand(slCommand) - cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") - cliCommand.StorageManager = FakeStorageManager - }) - - Describe("Snapshot order", func() { - Context("Snapshot order without volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) - }) - }) - Context("Snapshot order with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) - - Context("Snapshot order without -s", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-s|--size] is required.")) - Expect(err.Error()).To(ContainSubstring("sl file volume-options' to get available options.")) - }) - }) - - Context("Snapshot order with wrong tier", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.3") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-t|--tier] is optional, options are: 0.25,2,4,10.")) - }) - }) - - Context("Snapshot order with -f and not continue", func() { - It("return no error", func() { - fakeUI.Inputs("No") - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("This action will incur charges on your account. Continue?")) - }) - }) - - Context("Snapshot order with -f and continue", func() { - BeforeEach(func() { - FakeStorageManager.OrderSnapshotSpaceReturns(datatypes.Container_Product_Order_Receipt{ - OrderId: sl.Int(123456), - PlacedOrder: &datatypes.Billing_Order{ - Items: []datatypes.Billing_Order_Item{ - datatypes.Billing_Order_Item{ - Description: sl.String("Item1 description"), - }, - datatypes.Billing_Order_Item{ - Description: sl.String("Item2 description"), - }, - }, - }, - }, nil) - }) - It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-f") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) - }) - }) - - Context("Snapshot order with -f and continue and upgrade", func() { - BeforeEach(func() { - FakeStorageManager.OrderSnapshotSpaceReturns(datatypes.Container_Product_Order_Receipt{ - OrderId: sl.Int(123456), - PlacedOrder: &datatypes.Billing_Order{ - Items: []datatypes.Billing_Order_Item{ - datatypes.Billing_Order_Item{ - Description: sl.String("Item1 description"), - }, - datatypes.Billing_Order_Item{ - Description: sl.String("Item2 description"), - }, - }, - }, - }, nil) - }) - It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-u", "-f") - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) - }) - }) - - Context("Snapshot order with correct parameters but server API call fails", func() { - BeforeEach(func() { - FakeStorageManager.OrderSnapshotSpaceReturns(datatypes.Container_Product_Order_Receipt{}, errors.New("Internal Server Error")) - }) - It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "100", "-t", "0.25", "-f") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to order snapshot space for volume 1234.Please verify your options and try again.")) - Expect(err.Error()).To(ContainSubstring("Internal Server Error")) - }) - }) - }) -}) diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 7a451ba3..0e54fc7d 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -2,6 +2,12 @@ "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678." }, + "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id \n This command lists all snapshots of volume with ID 12345678 and sorts them by ID.": { + "other": "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id \n This command lists all snapshots of volume with ID 12345678 and sorts them by ID." + }, + "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-restore 12345678 87654321\n This command restores volume with ID 12345678 from snapshot with ID 87654321.": { + "other": "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-restore 12345678 87654321\n This command restores volume with ID 12345678 from snapshot with ID 87654321." + }, "\nLUN name": { "other": "\nLUN name" }, @@ -374,15 +380,6 @@ "${COMMAND_NAME} sl {{.storageType}} snapshot-disable VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-disable 12345678 -s DAILY\n This command disables daily snapshot for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-disable VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-disable 12345678 -s DAILY\n This command disables daily snapshot for volume with ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-list VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id \n This command lists all snapshots of volume with ID 12345678 and sorts them by ID.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-list VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-list 12345678 --sortby id \n This command lists all snapshots of volume with ID 12345678 and sorts them by ID." - }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-order VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 \n This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-order VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 \n This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB." - }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-restore VOLUME_ID SNAPSHOT_ID\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-restore 12345678 87654321\n This command restores volume with ID 12345678 from snapshot with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-restore VOLUME_ID SNAPSHOT_ID\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-restore 12345678 87654321\n This command restores volume with ID 12345678 from snapshot with ID 87654321." - }, "${COMMAND_NAME} sl {{.storageType}} subnets-assign ACCESS_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} subnets-assign 111111 --subnet-id 222222\n ${COMMAND_NAME} sl {{.storageType}} subnets-assign 111111 --subnet-id 222222 --subnet-id 333333\n ACCESS_ID is the host_id obtained by: ibmcloud sl {{.storageType}} access-list ": { "other": "${COMMAND_NAME} sl {{.storageType}} subnets-assign ACCESS_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} subnets-assign 111111 --subnet-id 222222\n ${COMMAND_NAME} sl {{.storageType}} subnets-assign 111111 --subnet-id 222222 --subnet-id 333333\n ACCESS_ID is the host_id obtained by: ibmcloud sl {{.storageType}} access-list " }, @@ -530,6 +527,9 @@ "-i|--iops is required with performance volume.\nRun '{{.CommandName}} sl file volume-options' to check available options.": { "other": "-i|--iops is required with performance volume.\nRun '{{.CommandName}} sl file volume-options' to check available options." }, + "-i|--iops must be a multiple of 100.": { + "other": "-i|--iops must be a multiple of 100." + }, "-i|--iops must be a multiple of 100.\nRun '{{.CommandName}} sl block volume-options' to check available options.": { "other": "-i|--iops must be a multiple of 100.\nRun '{{.CommandName}} sl block volume-options' to check available options." }, @@ -2060,9 +2060,6 @@ "Endurance Storage Tier (IOPS per GB) of the block volume for which space is ordered [optional], options are: 0.25,2,4,10": { "other": "Endurance Storage Tier (IOPS per GB) of the block volume for which space is ordered [optional], options are: 0.25,2,4,10" }, - "Endurance Storage Tier (IOPS per GB) of the file volume for which space is ordered [optional], options are: 0.25,2,4,10": { - "other": "Endurance Storage Tier (IOPS per GB) of the file volume for which space is ordered [optional], options are: 0.25,2,4,10" - }, "Endurance Storage Tier (IOPS per GB) of the primary volume for which a replica is ordered [optional], options are: 0.25,2,4,10,if no tier is specified, the tier of the original volume will be used": { "other": "Endurance Storage Tier (IOPS per GB) of the primary volume for which a replica is ordered [optional], options are: 0.25,2,4,10,if no tier is specified, the tier of the original volume will be used" }, @@ -4907,9 +4904,6 @@ "Order snapshot space for a block storage volume": { "other": "Order snapshot space for a block storage volume" }, - "Order snapshot space for a file storage volume": { - "other": "Order snapshot space for a file storage volume" - }, "Order {{.ID}} was placed to create a firewall.": { "other": "Order {{.ID}} was placed to create a firewall." }, @@ -5783,6 +5777,9 @@ "Security group {{.ID}} is updated.": { "other": "Security group {{.ID}} is updated." }, + "See https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-getting-started for sizing options.\n${COMMAND_NAME} sl block volume-options' to get available options.\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 \n This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB.": { + "other": "See https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-getting-started for sizing options.\n${COMMAND_NAME} sl block volume-options' to get available options.\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 \n This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB." + }, "See https://sldn.softlayer.com/reference/services/SoftLayer_Network_Storage/enableSnapshots/ for more details about these options.\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0\n This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained.": { "other": "See https://sldn.softlayer.com/reference/services/SoftLayer_Network_Storage/enableSnapshots/ for more details about these options.\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-enable 12345678 -s WEEKLY -c 5 -m 0 --hour 2 -d 0\n This command enables snapshot for volume with ID 12345678, snapshot is taken weekly on every Sunday at 2:00, and up to 5 snapshots are retained." }, @@ -5966,8 +5963,8 @@ "Size of duplicate file volume in GB, if no size is specified, the size of the original volume will be used": { "other": "Size of duplicate file volume in GB, if no size is specified, the size of the original volume will be used" }, - "Size of snapshot space to create in GB [required]": { - "other": "Size of snapshot space to create in GB [required]" + "Size of snapshot space to create in GB": { + "other": "Size of snapshot space to create in GB" }, "Size of storage volume in GB [required]": { "other": "Size of storage volume in GB [required]" @@ -7367,12 +7364,6 @@ "[-s|--schedule-type] needs to be one of {{.ScheduleTypes}}, not {{.Selected}}.": { "other": "[-s|--schedule-type] needs to be one of {{.ScheduleTypes}}, not {{.Selected}}." }, - "[-s|--size] is required.\nRun '{{.CommandName}} sl block volume-options' to get available options.": { - "other": "[-s|--size] is required.\nRun '{{.CommandName}} sl block volume-options' to get available options." - }, - "[-s|--size] is required.\nRun '{{.CommandName}} sl file volume-options' to get available options.": { - "other": "[-s|--size] is required.\nRun '{{.CommandName}} sl file volume-options' to get available options." - }, "[-s|--snapshot-schedule] is required, options are: HOURLY, DAILY, WEEKLY.": { "other": "[-s|--snapshot-schedule] is required, options are: HOURLY, DAILY, WEEKLY." }, diff --git a/plugin/metadata/sl.go b/plugin/metadata/sl.go index b9c995c6..ca245365 100644 --- a/plugin/metadata/sl.go +++ b/plugin/metadata/sl.go @@ -31,6 +31,7 @@ var SupportedOutputFormat = []string{ //define supported output format here in UPPER case... } +// SoftLayer Base Command type SoftlayerCommand struct { UI terminal.UI Session *session.Session @@ -44,21 +45,27 @@ func NewSoftlayerCommand(ui terminal.UI, session *session.Session) *SoftlayerCom OutputFlag: &CobraOutputFlag{""}, } } +func (slcmd *SoftlayerCommand) GetOutputFlag() string { + return slcmd.OutputFlag.String() +} +// SoftLayer Storage Command type SoftlayerStorageCommand struct { *SoftlayerCommand StorageI18n map[string]interface{} + StorageType string } func NewSoftlayerStorageCommand(ui terminal.UI, session *session.Session, storageType string) *SoftlayerStorageCommand { return &SoftlayerStorageCommand{ SoftlayerCommand: NewSoftlayerCommand(ui, session), StorageI18n: map[string]interface{}{"storageType": storageType}, + StorageType: storageType, } } -func (slcmd *SoftlayerCommand) GetOutputFlag() string { - return slcmd.OutputFlag.String() +func (slcmd *SoftlayerStorageCommand) GetStorageType() string { + return slcmd.StorageType } func SoftlayerNamespace() plugin.Namespace { From 74bc7e83cc0247782d9e98c842b27a05ad06dfc9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 8 Oct 2024 17:15:42 -0500 Subject: [PATCH 22/38] Added a doc builder to buildAndDeploy, updated doc builder to take in path for output --- README.md | 10 ++++++++++ bin/buildAndDeploy.py | 30 ++++++++++++++++++++++++++++++ docs/main.go | 15 ++++++++++++++- go.mod | 4 ++-- go.sum | 4 ++-- plugin/commands/user/list.go | 10 +++++----- 6 files changed, 63 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eab03df8..b8446106 100644 --- a/README.md +++ b/README.md @@ -467,6 +467,16 @@ ALSO: ## CLI Documentation To make changes to the cli documentation, do so here: https://github.ibm.com/cloud-docs/cli/tree/draft/reference/ibmcloud +Docs for the `sl` plugin specifically live in this repo: https://github.ibm.com/cloud-docs/cli + +in `docs/` there is a utility that will generate the appropriate pages for the sl plugin. + +(assuming `~/Code/ibm-cloud-docs-cl` is where the cloud-docs have been checked out to.) +``` +./bin/buildAndDeploy.py docs ~/Code/ibm-cloud-docs-cli + Building documentation builder: go build -o docBuilder docs/main.go + Building documentation: ./docBuilder -o C:/Users/allmi/Code/ibm-cloud-docs-cli/_include-segments -v +``` # Code Patterns diff --git a/bin/buildAndDeploy.py b/bin/buildAndDeploy.py index 3b36a3d1..ab91856c 100755 --- a/bin/buildAndDeploy.py +++ b/bin/buildAndDeploy.py @@ -321,6 +321,25 @@ def goBuild(self, theOs: str, theArch: str) -> None: # This command basically requires shell=True on mac because -ldflags doesn't get parsed properly withoutit. subprocess.run(buildCmd, shell=True) + def buildDocs(self, out_path: str) -> None: + """Generates the IBM Cloud CLI Documentation + + :param str out_path: location of https://github.ibm.com/cloud-docs/cli repositry + """ + + # Step 1, build the builder: + buildCmd = f" go build -o docBuilder docs/main.go" + print(f"[turquoise2] Building documentation builder: {buildCmd}") + subprocess.run(buildCmd, shell=True) + + # Check if out_dir is good: + if os.path.isdir(f"{out_path}/_include-segments") == False: + raise Exception(f"{out_path}/_include-segments does not exist, or not reachable from {self.cwd}") + + docsCmd = f"./docBuilder -o {out_path}/_include-segments -v" + print(f"[turquoise2] Building documentation: {docsCmd}") + subprocess.run(docsCmd) + @click.group() @click.pass_context def cli(ctx): @@ -379,6 +398,17 @@ def i18n(ctx): runI18n4go(ctx.obj.getdir()) # genBinData() +@cli.command() +@click.argument("out_path") +@click.pass_context +def docs(ctx, out_path): + """Generate the documentation files. + + Requires a copy of https://github.ibm.com/cloud-docs/cli checked out. + out_path should point to the root directory of the cli repository. + """ + ctx.obj.buildDocs(out_path) + if __name__ == '__main__': cli() # try: diff --git a/docs/main.go b/docs/main.go index 8c3e2867..9d5d455a 100644 --- a/docs/main.go +++ b/docs/main.go @@ -16,6 +16,8 @@ import ( ) var fileName string +var outputPath string +var debug bool var rootCmd = &cobra.Command{ Use: "doc-gen", Short: "Generate the documentation for the sl plugin", @@ -25,7 +27,10 @@ var rootCmd = &cobra.Command{ }, } + func main() { + rootCmd.Flags().StringVarP(&outputPath, "output", "o", "./docs", "Output path, default to ./docs .") + rootCmd.Flags().BoolVarP(&debug, "verbose", "v", false, "Enable Debug logging.") err := rootCmd.Execute() checkError(err) return @@ -84,6 +89,7 @@ func CliDocs() { // This is a single command, like 'call-api' and doesn't have sub-commands thisCmdGroup.Commands = []SlCmdDoc{cobraToSl(iCmd, "")} } + printDebug(fmt.Sprintf("Working on command group %s\n", shortName)) PrintMakrdown(thisCmdGroup) CmdGroups = append(CmdGroups, thisCmdGroup) } @@ -121,7 +127,8 @@ ibmcloud {{.Use}} mdTemplate, err := template.New("cmd template").Parse(cmdTemplate) checkError(err) - filename := fmt.Sprintf("%v.md", cmd.CommandShortLink) + filename := fmt.Sprintf("%s/%v.md", outputPath, cmd.CommandShortLink) + printDebug(fmt.Sprintf("\tCreating file %s\n", filename)) outfile, err := os.Create(filename) //#nosec G304 -- This is a false positive defer outfile.Close() err = mdTemplate.Execute(outfile, cmd) @@ -201,3 +208,9 @@ func buildSlCmdFlag(topCommand *cobra.Command) []SlCmdFlag { }) return flags } + +func printDebug(output string) { + if debug { + fmt.Printf(output) + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 8960357d..4303a343 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,8 @@ require ( github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.33.0 - github.com/softlayer/softlayer-go v1.1.5 + github.com/sethvargo/go-password v0.3.1 + github.com/softlayer/softlayer-go v1.1.6 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 @@ -34,7 +35,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/sethvargo/go-password v0.3.1 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/mod v0.18.0 // indirect diff --git a/go.sum b/go.sum index 0902c1ae..36e4d28b 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY= github.com/smartystreets/goconvey v1.6.7/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/softlayer/softlayer-go v1.1.5 h1:UFFtgKxiw0yIuUw93XBCFIiIMYR5eLgmm4a5DqMHXGg= -github.com/softlayer/softlayer-go v1.1.5/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw= +github.com/softlayer/softlayer-go v1.1.6 h1:VRNXiXZTpb7cfKjimU5E7W9zzKYzWMr/xtqlJ0pHwkQ= +github.com/softlayer/softlayer-go v1.1.6/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= diff --git a/plugin/commands/user/list.go b/plugin/commands/user/list.go index 3e95d2cc..138775b2 100644 --- a/plugin/commands/user/list.go +++ b/plugin/commands/user/list.go @@ -1,7 +1,7 @@ package user import ( - "fmt" + "sort" "strings" "github.com/spf13/cobra" @@ -51,12 +51,12 @@ func NewListCommand(sl *metadata.SoftlayerCommand) (cmd *ListCommand) { }, } - columns := "" + columns := []string{} for key, _ := range maskMap { - columns = fmt.Sprintf("%s %s,", columns, key) + columns = append(columns, key) } - columns = strings.TrimSuffix(columns, ",") - subs := map[string]interface{}{"Columns": columns} + sort.Strings(columns) + subs := map[string]interface{}{"Columns": strings.Join(columns, ", ")} cobraCmd.Flags().StringSliceVar(&thisCmd.Column, "column", []string{}, T("Column to display. options are: {{.Columns}}. This option can be specified multiple times", subs)) From f08b94df5a39649fc339e6ea4322ca1577218ac6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 15 Oct 2024 15:43:40 -0500 Subject: [PATCH 23/38] Adding volume username lookups to block and file commands. #722 --- plugin/commands/block/block.go | 1 + plugin/commands/block/volume_detail.go | 14 +++++++++++++- plugin/commands/file/file.go | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugin/commands/block/block.go b/plugin/commands/block/block.go index ed3e85e9..4ea92b36 100644 --- a/plugin/commands/block/block.go +++ b/plugin/commands/block/block.go @@ -12,6 +12,7 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { StorageCommand := &metadata.SoftlayerStorageCommand{ SoftlayerCommand: sl, StorageI18n: map[string]interface{}{"storageType": "block"}, + StorageType: "block", } cobraCmd := &cobra.Command{ Use: "block", diff --git a/plugin/commands/block/volume_detail.go b/plugin/commands/block/volume_detail.go index d2057125..2462952f 100644 --- a/plugin/commands/block/volume_detail.go +++ b/plugin/commands/block/volume_detail.go @@ -45,7 +45,19 @@ func (cmd *VolumeDetailCommand) Run(args []string) error { volumeID, err := strconv.Atoi(args[0]) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + // Maybe this is a volume username + volumes, err := cmd.StorageManager.ListVolumes(cmd.StorageType, "", args[0], "", "", 0, "mask[id,username]") + if err != nil { + fmt.Printf("=============== ERROR: %s\n", err.Error()) + return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + } + if len(volumes) != 1 { + subs := map[string]interface{}{"VolumeName": args[0], "VolumeCount": len(volumes)} + return slErr.New( + T("Search for volume {{.VolumeName}} found {{.VolumeCount}} volumes, expected 1.", subs), + ) + } + volumeID = *volumes[0].Id } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/file/file.go b/plugin/commands/file/file.go index 4d77aad0..8fefd4cc 100644 --- a/plugin/commands/file/file.go +++ b/plugin/commands/file/file.go @@ -21,6 +21,7 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { StorageCommand := &metadata.SoftlayerStorageCommand{ SoftlayerCommand: sl, StorageI18n: map[string]interface{}{"storageType": "file"}, + StorageType: "file", } cobraCmd := &cobra.Command{ Use: "file", From a62dcaee30d037a50f9c5e219f53b142d223d3b9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Oct 2024 15:57:30 -0500 Subject: [PATCH 24/38] #722 added StorageManager.GetVolumeId function, used it in volume-detail to resolve IDENTIFIER --- plugin/commands/block/volume_detail.go | 17 +---- plugin/commands/block/volume_detail_test.go | 13 +--- plugin/managers/file_test.go | 1 - plugin/managers/storage.go | 30 ++++++++ plugin/testhelpers/fake_storage_manager.go | 81 +++++++++++++++++++++ 5 files changed, 116 insertions(+), 26 deletions(-) delete mode 100644 plugin/managers/file_test.go diff --git a/plugin/commands/block/volume_detail.go b/plugin/commands/block/volume_detail.go index 2462952f..12ecc771 100644 --- a/plugin/commands/block/volume_detail.go +++ b/plugin/commands/block/volume_detail.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "net/url" - "strconv" "strings" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal" @@ -43,21 +42,9 @@ func NewVolumeDetailCommand(sl *metadata.SoftlayerStorageCommand) *VolumeDetailC func (cmd *VolumeDetailCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - // Maybe this is a volume username - volumes, err := cmd.StorageManager.ListVolumes(cmd.StorageType, "", args[0], "", "", 0, "mask[id,username]") - if err != nil { - fmt.Printf("=============== ERROR: %s\n", err.Error()) - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") - } - if len(volumes) != 1 { - subs := map[string]interface{}{"VolumeName": args[0], "VolumeCount": len(volumes)} - return slErr.New( - T("Search for volume {{.VolumeName}} found {{.VolumeCount}} volumes, expected 1.", subs), - ) - } - volumeID = *volumes[0].Id + return err } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/block/volume_detail_test.go b/plugin/commands/block/volume_detail_test.go index dfe72a36..c2271fdf 100644 --- a/plugin/commands/block/volume_detail_test.go +++ b/plugin/commands/block/volume_detail_test.go @@ -27,10 +27,9 @@ var _ = Describe("Block Volume-Detail Tests", func() { slCommand = metadata.NewSoftlayerStorageCommand(fakeUI, fakeSession, "block") cliCommand = block.NewVolumeDetailCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") - }) - Describe("Volume detail", func() { + Describe("Volume detail usage tests", func() { Context("Volume detail without volume id", func() { It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) @@ -38,14 +37,8 @@ var _ = Describe("Block Volume-Detail Tests", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Volume detail with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) - + }) + Describe("Volume Detail API response tests", func() { Context("Volume detail with correct volume id but server API call fails", func() { BeforeEach(func() { fakeHandler.AddApiError("SoftLayer_Network_Storage", "getObject", 500, "Internal Server Error") diff --git a/plugin/managers/file_test.go b/plugin/managers/file_test.go deleted file mode 100644 index 9227b2b0..00000000 --- a/plugin/managers/file_test.go +++ /dev/null @@ -1 +0,0 @@ -package managers_test diff --git a/plugin/managers/storage.go b/plugin/managers/storage.go index 0c7ad493..dab25dc8 100644 --- a/plugin/managers/storage.go +++ b/plugin/managers/storage.go @@ -112,6 +112,7 @@ type StorageManager interface { ListItems(packageId int, categoryCode string, mask string) ([]datatypes.Product_Item, error) GetRegions(packageId int) ([]datatypes.Location_Region, error) GetNetworkMessageDeliveryAccounts(storageId int, mask string) (datatypes.Network_Storage_Hub_Cleversafe_Account, error) + GetVolumeId(indentifier string, storageType string) (int, error) } type storageManager struct { @@ -964,3 +965,32 @@ func (s storageManager) GetEndpoints(storageId int) ([]datatypes.Container_Netwo return NetworkStorageHubCleversafeAccountService.Id(storageId).GetEndpoints(nil) } + +// Trys to resolve identifier to a block volume ID +// if identifier is a int, will return that, otherwise does a API lookup assuming identifier is a volume username +func (s storageManager) GetVolumeId(identifier string, storageType string) (int, error) { + var volumeId int + var err error + // identifier is a number, which means its already an ID + volumeId, err = strconv.Atoi(identifier) + + // If there was an error, identifier is likely a string and username, search the API for it + if err != nil { + // Maybe this is a volume username + volumes, err := s.ListVolumes(storageType, "", identifier, "", "", 0, "mask[id,username]") + if err != nil { + // API error + return 0, err + } + // If we don't get 1 volume back, something went wrong. Either 0 volumes, or too many and the + // user should pick a better identifier + if len(volumes) != 1 { + subs := map[string]interface{}{"VolumeName": identifier, "VolumeCount": len(volumes)} + return 0, errors.New( + T("Search for volume {{.VolumeName}} found {{.VolumeCount}} volumes, expected 1.", subs), + ) + } + volumeId = *volumes[0].Id + } + return volumeId, nil +} diff --git a/plugin/testhelpers/fake_storage_manager.go b/plugin/testhelpers/fake_storage_manager.go index 686761dd..03ebb317 100644 --- a/plugin/testhelpers/fake_storage_manager.go +++ b/plugin/testhelpers/fake_storage_manager.go @@ -384,6 +384,20 @@ type FakeStorageManager struct { result1 datatypes.Network_Storage result2 error } + GetVolumeIdStub func(string, string) (int, error) + getVolumeIdMutex sync.RWMutex + getVolumeIdArgsForCall []struct { + arg1 string + arg2 string + } + getVolumeIdReturns struct { + result1 int + result2 error + } + getVolumeIdReturnsOnCall map[int]struct { + result1 int + result2 error + } GetVolumeSnapshotListStub func(int) ([]datatypes.Network_Storage, error) getVolumeSnapshotListMutex sync.RWMutex getVolumeSnapshotListArgsForCall []struct { @@ -2457,6 +2471,71 @@ func (fake *FakeStorageManager) GetVolumeDetailsReturnsOnCall(i int, result1 dat }{result1, result2} } +func (fake *FakeStorageManager) GetVolumeId(arg1 string, arg2 string) (int, error) { + fake.getVolumeIdMutex.Lock() + ret, specificReturn := fake.getVolumeIdReturnsOnCall[len(fake.getVolumeIdArgsForCall)] + fake.getVolumeIdArgsForCall = append(fake.getVolumeIdArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + stub := fake.GetVolumeIdStub + fakeReturns := fake.getVolumeIdReturns + fake.recordInvocation("GetVolumeId", []interface{}{arg1, arg2}) + fake.getVolumeIdMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorageManager) GetVolumeIdCallCount() int { + fake.getVolumeIdMutex.RLock() + defer fake.getVolumeIdMutex.RUnlock() + return len(fake.getVolumeIdArgsForCall) +} + +func (fake *FakeStorageManager) GetVolumeIdCalls(stub func(string, string) (int, error)) { + fake.getVolumeIdMutex.Lock() + defer fake.getVolumeIdMutex.Unlock() + fake.GetVolumeIdStub = stub +} + +func (fake *FakeStorageManager) GetVolumeIdArgsForCall(i int) (string, string) { + fake.getVolumeIdMutex.RLock() + defer fake.getVolumeIdMutex.RUnlock() + argsForCall := fake.getVolumeIdArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStorageManager) GetVolumeIdReturns(result1 int, result2 error) { + fake.getVolumeIdMutex.Lock() + defer fake.getVolumeIdMutex.Unlock() + fake.GetVolumeIdStub = nil + fake.getVolumeIdReturns = struct { + result1 int + result2 error + }{result1, result2} +} + +func (fake *FakeStorageManager) GetVolumeIdReturnsOnCall(i int, result1 int, result2 error) { + fake.getVolumeIdMutex.Lock() + defer fake.getVolumeIdMutex.Unlock() + fake.GetVolumeIdStub = nil + if fake.getVolumeIdReturnsOnCall == nil { + fake.getVolumeIdReturnsOnCall = make(map[int]struct { + result1 int + result2 error + }) + } + fake.getVolumeIdReturnsOnCall[i] = struct { + result1 int + result2 error + }{result1, result2} +} + func (fake *FakeStorageManager) GetVolumeSnapshotList(arg1 int) ([]datatypes.Network_Storage, error) { fake.getVolumeSnapshotListMutex.Lock() ret, specificReturn := fake.getVolumeSnapshotListReturnsOnCall[len(fake.getVolumeSnapshotListArgsForCall)] @@ -3633,6 +3712,8 @@ func (fake *FakeStorageManager) Invocations() map[string][][]interface{} { defer fake.getVolumeCountLimitsMutex.RUnlock() fake.getVolumeDetailsMutex.RLock() defer fake.getVolumeDetailsMutex.RUnlock() + fake.getVolumeIdMutex.RLock() + defer fake.getVolumeIdMutex.RUnlock() fake.getVolumeSnapshotListMutex.RLock() defer fake.getVolumeSnapshotListMutex.RUnlock() fake.getVolumeSnapshotSchedulesMutex.RLock() From 4160d74b80f3cbf0cb8a3d5911a48d793de6e35c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Oct 2024 16:46:41 -0500 Subject: [PATCH 25/38] #722 expanding usage of GetVolumeId, updating some translations to remove redundant usage examples --- .secrets.baseline | 10 ++--- plugin/commands/block/access_authorize.go | 10 ++--- .../commands/block/access_authorize_test.go | 9 +---- plugin/commands/block/access_list.go | 10 ++--- plugin/commands/block/access_list_test.go | 8 +--- plugin/commands/block/access_password.go | 2 - plugin/commands/block/access_password_test.go | 13 +------ plugin/commands/block/access_revoke.go | 6 +-- plugin/commands/block/access_revoke_test.go | 39 +++++++------------ .../block/disaster_recovery_failover.go | 4 +- .../block/disaster_recovery_failover_test.go | 8 +--- .../block/duplicate-convert-status.go | 6 +-- .../block/duplicate_convert_status_test.go | 6 --- plugin/commands/block/replica_failback.go | 6 +-- .../commands/block/replica_failback_test.go | 8 +--- plugin/commands/block/replica_failover.go | 8 ++-- .../commands/block/replica_failover_test.go | 8 +--- plugin/commands/block/replica_locations.go | 7 +--- .../commands/block/replica_locations_test.go | 1 + plugin/i18n/v2Resources/active.de_DE.json | 6 +-- plugin/i18n/v2Resources/active.en-US.json | 12 +++--- plugin/i18n/v2Resources/active.es_ES.json | 8 ++-- plugin/i18n/v2Resources/active.fr_FR.json | 6 +-- plugin/i18n/v2Resources/active.it_IT.json | 6 +-- plugin/i18n/v2Resources/active.ja_JP.json | 12 +++--- plugin/i18n/v2Resources/active.ko_KR.json | 8 ++-- plugin/i18n/v2Resources/active.pt_BR.json | 12 +++--- plugin/i18n/v2Resources/active.zh_Hans.json | 12 +++--- plugin/i18n/v2Resources/active.zh_Hant.json | 12 +++--- 29 files changed, 93 insertions(+), 170 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 7b44bc26..e901656b 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "plugin/i18n/v1Resources/|plugin/i18n/v2Resources/|(.*test.*)|(vendor)|(go.sum)|bin/|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-09-04T21:46:16Z", + "generated_at": "2024-10-16T21:46:33Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -124,7 +124,7 @@ "hashed_secret": "49f18203810a56503e8b16bea101314713ce1a0c", "is_secret": false, "is_verified": false, - "line_number": 86, + "line_number": 82, "type": "Secret Keyword", "verified_result": null }, @@ -132,7 +132,7 @@ "hashed_secret": "4283f377a2ad3c107b4a3c9920c6ab508c6b1099", "is_secret": false, "is_verified": false, - "line_number": 106, + "line_number": 102, "type": "Secret Keyword", "verified_result": null }, @@ -140,7 +140,7 @@ "hashed_secret": "aa1f122c9be20b5a2aff7f2c513e3b065c033894", "is_secret": false, "is_verified": false, - "line_number": 137, + "line_number": 133, "type": "Secret Keyword", "verified_result": null }, @@ -148,7 +148,7 @@ "hashed_secret": "b2b24235714bf178e052e52a4af5a21a1f17c445", "is_secret": false, "is_verified": false, - "line_number": 164, + "line_number": 160, "type": "Secret Keyword", "verified_result": null } diff --git a/plugin/commands/block/access_authorize.go b/plugin/commands/block/access_authorize.go index eae0939c..2512ca3d 100644 --- a/plugin/commands/block/access_authorize.go +++ b/plugin/commands/block/access_authorize.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -32,9 +30,7 @@ func NewAccessAuthorizeCommand(sl *metadata.SoftlayerStorageCommand) *AccessAuth cobraCmd := &cobra.Command{ Use: "access-authorize " + T("IDENTIFIER"), Short: T("Authorize hosts to access a given volume."), - Long: T(`${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321 This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.`, sl.StorageI18n), Args: metadata.OneArgs, @@ -54,9 +50,9 @@ EXAMPLE: func (cmd *AccessAuthorizeCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } IPIds := cmd.Ip_address_id IPs := cmd.Ip_address diff --git a/plugin/commands/block/access_authorize_test.go b/plugin/commands/block/access_authorize_test.go index 7561a0be..2e874452 100644 --- a/plugin/commands/block/access_authorize_test.go +++ b/plugin/commands/block/access_authorize_test.go @@ -36,6 +36,7 @@ var _ = Describe("Access Authorize", func() { cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager cliCommand.NetworkManager = fakeNetworkManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Access Authorize", func() { @@ -47,14 +48,6 @@ var _ = Describe("Access Authorize", func() { Expect(strings.Contains(err.Error(), "Incorrect Usage: This command requires one argument")).To(BeTrue()) }) }) - Context("Access Authorize with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) - Context("Access Authorize with correct volume id and virtual server id", func() { BeforeEach(func() { FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) diff --git a/plugin/commands/block/access_list.go b/plugin/commands/block/access_list.go index ae030734..cdec771a 100644 --- a/plugin/commands/block/access_list.go +++ b/plugin/commands/block/access_list.go @@ -2,8 +2,6 @@ package block import ( "sort" - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -29,9 +27,7 @@ func NewAccessListCommand(sl *metadata.SoftlayerStorageCommand) *AccessListComma cobraCmd := &cobra.Command{ Use: "access-list " + T("IDENTIFIER"), Short: T("List hosts that are authorized to access the volume."), - Long: T(`${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.`, sl.StorageI18n), Args: metadata.OneArgs, @@ -47,9 +43,9 @@ EXAMPLE: func (cmd *AccessListCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } sortby := cmd.Sortby diff --git a/plugin/commands/block/access_list_test.go b/plugin/commands/block/access_list_test.go index 44f08d1c..e278f86a 100644 --- a/plugin/commands/block/access_list_test.go +++ b/plugin/commands/block/access_list_test.go @@ -33,6 +33,7 @@ var _ = Describe("Access List Tests", func() { cliCommand = block.NewAccessListCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Access List", func() { @@ -43,13 +44,6 @@ var _ = Describe("Access List Tests", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Access Authorize with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) Context("Access Authorize with correct volume id", func() { BeforeEach(func() { diff --git a/plugin/commands/block/access_password.go b/plugin/commands/block/access_password.go index d6ddb59f..2fdb6463 100644 --- a/plugin/commands/block/access_password.go +++ b/plugin/commands/block/access_password.go @@ -1,7 +1,6 @@ package block import ( - "fmt" "strconv" "github.com/spf13/cobra" @@ -45,7 +44,6 @@ func NewAccessPasswordCommand(sl *metadata.SoftlayerStorageCommand) *AccessPassw func (cmd *AccessPasswordCommand) Run(args []string) error { - fmt.Printf("===AccessPasswordCommand===") hostID, err := strconv.Atoi(args[0]) if err != nil { return slErr.NewInvalidSoftlayerIdInputError("allowed access host ID") diff --git a/plugin/commands/block/access_password_test.go b/plugin/commands/block/access_password_test.go index 81e10030..559668d7 100644 --- a/plugin/commands/block/access_password_test.go +++ b/plugin/commands/block/access_password_test.go @@ -2,7 +2,6 @@ package block_test import ( "errors" - "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -31,7 +30,6 @@ var _ = Describe("Access Password", func() { cliCommand = block.NewAccessPasswordCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager - }) Describe("Access password", func() { @@ -49,13 +47,6 @@ var _ = Describe("Access Password", func() { Expect(err.Error()).To(ContainSubstring(`required flag(s) "password" not set`)) }) }) - Context("Access password with wrong hostId", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "--password", "abcdefg") - Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "Invalid input for 'allowed access host ID'. It must be a positive integer.")).To(BeTrue()) - }) - }) Context("Access password with server fails", func() { BeforeEach(func() { FakeStorageManager.SetCredentialPasswordReturns(errors.New("Internal Server Error")) @@ -64,8 +55,8 @@ var _ = Describe("Access Password", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--password", "abcdefg") Expect(err).To(HaveOccurred()) Expect(fakeUI.Outputs()).NotTo(ContainSubstring("OK")) - Expect(strings.Contains(err.Error(), "Failed to set password for host 1234.")).To(BeTrue()) - Expect(strings.Contains(err.Error(), "Internal Server Error")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("Failed to set password for host 1234.")) + Expect(err.Error()).To(ContainSubstring("Internal Server Error")) }) }) Context("Access password", func() { diff --git a/plugin/commands/block/access_revoke.go b/plugin/commands/block/access_revoke.go index daec0c6f..aa0146b4 100644 --- a/plugin/commands/block/access_revoke.go +++ b/plugin/commands/block/access_revoke.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -52,9 +50,9 @@ EXAMPLE: } func (cmd *AccessRevokeCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } if len(cmd.Hardware_id) == 0 && len(cmd.Virtual_id) == 0 && len(cmd.Ip_address_id) == 0 && len(cmd.Ip_address) == 0 { diff --git a/plugin/commands/block/access_revoke_test.go b/plugin/commands/block/access_revoke_test.go index 85ab4bd2..bc2a8251 100644 --- a/plugin/commands/block/access_revoke_test.go +++ b/plugin/commands/block/access_revoke_test.go @@ -2,9 +2,7 @@ package block_test import ( "errors" - "strings" - . "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/matchers" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -36,6 +34,7 @@ var _ = Describe("Access Revoke", func() { cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager cliCommand.NetworkManager = fakeNetworkManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Access Revoke", func() { @@ -43,14 +42,7 @@ var _ = Describe("Access Revoke", func() { It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "Incorrect Usage: This command requires one argument")).To(BeTrue()) - }) - }) - Context("Access revoke with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "Invalid input for 'Volume ID'. It must be a positive integer.")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) @@ -61,8 +53,7 @@ var _ = Describe("Access Revoke", func() { It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--virtual-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"Access to 1234 was revoked for virtual server 5678"})) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for virtual server 5678")) }) }) @@ -73,8 +64,7 @@ var _ = Describe("Access Revoke", func() { It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--hardware-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"Access to 1234 was revoked for hardware server 5678."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for hardware server 5678.")) }) }) @@ -85,8 +75,7 @@ var _ = Describe("Access Revoke", func() { It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"Access to 1234 was revoked for IP address 5678."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for IP address 5678.")) }) }) @@ -98,8 +87,7 @@ var _ = Describe("Access Revoke", func() { It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address", "1.2.3.4") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"Access to 1234 was revoked for IP address 5678."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for IP address 5678.")) }) }) @@ -111,22 +99,23 @@ var _ = Describe("Access Revoke", func() { It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address", "1.2.3.4") Expect(err).To(HaveOccurred()) - Expect(fakeUI.Outputs()).NotTo(ContainSubstrings([]string{"OK"})) - Expect(strings.Contains(err.Error(), "IP address 1.2.3.4 is not found on your account.Please confirm IP and try again.")).To(BeTrue()) - Expect(strings.Contains(err.Error(), "Not Found")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("IP address 1.2.3.4 is not found on your account.Please confirm IP and try again.")) + Expect(err.Error()).To(ContainSubstring("Not Found")) }) }) Context("Access Authorize with correct volume id but server API call fails", func() { BeforeEach(func() { - FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, errors.New("Internal Server Error")) + FakeStorageManager.DeauthorizeHostToVolumeReturns( + []datatypes.Network_Storage_Allowed_Host{}, + errors.New("Internal Server Error"), + ) }) It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--virtual-id", "5678") Expect(err).To(HaveOccurred()) - Expect(fakeUI.Outputs()).NotTo(ContainSubstrings([]string{"OK"})) - Expect(strings.Contains(err.Error(), "Failed to revoke access to volume 1234.")).To(BeTrue()) - Expect(strings.Contains(err.Error(), "Internal Server Error")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("Failed to revoke access to volume 1234.")) + Expect(err.Error()).To(ContainSubstring("Internal Server Error")) }) }) }) diff --git a/plugin/commands/block/disaster_recovery_failover.go b/plugin/commands/block/disaster_recovery_failover.go index 47b8ba6d..ab4809b2 100644 --- a/plugin/commands/block/disaster_recovery_failover.go +++ b/plugin/commands/block/disaster_recovery_failover.go @@ -44,9 +44,9 @@ EXAMPLE: func (cmd *DisasterRecoveryFailoverCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } replicaID, err := strconv.Atoi(args[1]) if err != nil { diff --git a/plugin/commands/block/disaster_recovery_failover_test.go b/plugin/commands/block/disaster_recovery_failover_test.go index 1875325e..b0a8f1c7 100644 --- a/plugin/commands/block/disaster_recovery_failover_test.go +++ b/plugin/commands/block/disaster_recovery_failover_test.go @@ -28,6 +28,7 @@ var _ = Describe("Disaster Recovery Failover", func() { cliCommand = block.NewDisasterRecoveryFailoverCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant failover", func() { @@ -45,13 +46,6 @@ var _ = Describe("Disaster Recovery Failover", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires two arguments.")) }) }) - Context("Replicant fail over with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "123") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) Context("Replicant fail over with wrong replica id", func() { It("error resolving volume ID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "abc") diff --git a/plugin/commands/block/duplicate-convert-status.go b/plugin/commands/block/duplicate-convert-status.go index 1558156e..04219129 100644 --- a/plugin/commands/block/duplicate-convert-status.go +++ b/plugin/commands/block/duplicate-convert-status.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -37,9 +35,9 @@ func NewDuplicateConvertStatusCommand(sl *metadata.SoftlayerStorageCommand) *Dup func (cmd *DuplicateConvertStatusCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/block/duplicate_convert_status_test.go b/plugin/commands/block/duplicate_convert_status_test.go index e16e1315..6e79a903 100644 --- a/plugin/commands/block/duplicate_convert_status_test.go +++ b/plugin/commands/block/duplicate_convert_status_test.go @@ -42,12 +42,6 @@ var _ = Describe("block duplicate-convert-status", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - It("Set command with an invalid Id", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abcde") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - It("Set invalid output", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123456", "--output=xml") Expect(err).To(HaveOccurred()) diff --git a/plugin/commands/block/replica_failback.go b/plugin/commands/block/replica_failback.go index c31795cd..c6fb2f5c 100644 --- a/plugin/commands/block/replica_failback.go +++ b/plugin/commands/block/replica_failback.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -42,9 +40,9 @@ EXAMPLE: func (cmd *ReplicaFailbackCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } err = cmd.StorageManager.FailBackFromReplicant(volumeID) subs := map[string]interface{}{"VolumeID": volumeID} diff --git a/plugin/commands/block/replica_failback_test.go b/plugin/commands/block/replica_failback_test.go index ad7dc892..86b1b06f 100644 --- a/plugin/commands/block/replica_failback_test.go +++ b/plugin/commands/block/replica_failback_test.go @@ -29,6 +29,7 @@ var _ = Describe("Replica failback", func() { cliCommand = block.NewReplicaFailbackCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant failback", func() { @@ -39,13 +40,6 @@ var _ = Describe("Replica failback", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Replicant fail back with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) Context("Replicant fail back with correct volume id", func() { It("return no error", func() { diff --git a/plugin/commands/block/replica_failover.go b/plugin/commands/block/replica_failover.go index b191ab41..66bd504d 100644 --- a/plugin/commands/block/replica_failover.go +++ b/plugin/commands/block/replica_failover.go @@ -25,9 +25,7 @@ func NewReplicaFailoverCommand(sl *metadata.SoftlayerStorageCommand) *ReplicaFai cobraCmd := &cobra.Command{ Use: "replica-failover " + T("IDENTIFIER") + " " + T("REPLICA_ID"), Short: T("Failover a {{.storageType}} volume to the given replica volume", sl.StorageI18n), - Long: T(`${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321 This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.`, sl.StorageI18n), Args: metadata.TwoArgs, @@ -42,9 +40,9 @@ EXAMPLE: func (cmd *ReplicaFailoverCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } replicaID, err := strconv.Atoi(args[1]) if err != nil { diff --git a/plugin/commands/block/replica_failover_test.go b/plugin/commands/block/replica_failover_test.go index 2d270aa2..e56240aa 100644 --- a/plugin/commands/block/replica_failover_test.go +++ b/plugin/commands/block/replica_failover_test.go @@ -29,6 +29,7 @@ var _ = Describe("Replica failover", func() { cliCommand = block.NewReplicaFailoverCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant failover", func() { @@ -46,13 +47,6 @@ var _ = Describe("Replica failover", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires two arguments.")) }) }) - Context("Replicant fail over with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "123") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) Context("Replicant fail over with wrong replica id", func() { It("error resolving volume ID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "abc") diff --git a/plugin/commands/block/replica_locations.go b/plugin/commands/block/replica_locations.go index 43b32dfe..ea7abf63 100644 --- a/plugin/commands/block/replica_locations.go +++ b/plugin/commands/block/replica_locations.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -42,11 +40,10 @@ EXAMPLE: func (cmd *ReplicaLocationsCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } - outputFormat := cmd.GetOutputFlag() datacenters, err := cmd.StorageManager.GetReplicationLocations(volumeID) diff --git a/plugin/commands/block/replica_locations_test.go b/plugin/commands/block/replica_locations_test.go index 09d8e897..c0f3c899 100644 --- a/plugin/commands/block/replica_locations_test.go +++ b/plugin/commands/block/replica_locations_test.go @@ -31,6 +31,7 @@ var _ = Describe("Replica locations", func() { cliCommand = block.NewReplicaLocationsCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant locations", func() { diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index 8b3d78d6..0811fc4f 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -344,10 +344,10 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONEN]\nVerwendungsinformationen eines virtuellen Servers \nBeispiel: \n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize DATENTRÄGER_ID [OPTIONEN]\n\nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Dieser Befehl autorisiert den virtuellen Server mit der ID 87654321 für den Zugriff auf den Datenträger mit der ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-list DATENTRÄGER_ID [OPTIONEN]\n\nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n Dieser Befehl listet alle Hosts auf, die für den Zugriff auf den Datenträger mit der ID 12345678 berechtigt sind und sortiert sie nach ID." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { @@ -359,7 +359,7 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback DATENTRÄGER_ID\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Dieser Befehl führt eine Failback-Operation für einen Datenträger mit der ID 12345678 durch." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover DATENTRÄGER_ID REPLIKAT_ID\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n Dieser Befehl führt eine Failover-Operation für den Datenträger mit der ID 12345678 auf den Replikatdatenträger mit der ID 87654321 aus." }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 0e54fc7d..d3883a94 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -347,11 +347,11 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'" @@ -362,8 +362,8 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." }, "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index 7df224a1..949a80a8 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -344,10 +344,10 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nInformación de uso de un servidor virtual.\nEjemplo:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Este mandato autoriza al servidor virtual con ID 87654321 a acceder al volumen con ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Este mandato autoriza al servidor virtual con ID 87654321 a acceder al volumen con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-list ID_VOLUME [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n Este mandato lista todos los hosts autorizados a acceder al volumen con ID 12345678 y los ordena por ID." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { @@ -359,7 +359,7 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback ID_VOLUMEN\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Este mandato realiza una operación de restablecimiento para el volumen con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover ID_VOLUMEN ID_REPLICA\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n Este mandato realiza una operación de migración tras error para el volumen con ID 12345678 a la réplica de volumen con ID 87654321." }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 43482a4e..029a1a71 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -344,10 +344,10 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFICATEUR [OPTIONS]\nInformations sur l'utilisation d'un serveur virtuel.\nExemple :\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Cette commande autorise le serveur virtuel d'ID 87654321 à accéder au volume dont l'ID est 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-list ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n Cette commande répertorie tous les hôtes autorisés à accéder au volume dont l'ID est 12345678 et les trie par ID." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { @@ -359,7 +359,7 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback ID_VOLUME\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Cette commande effectue une opération de reprise par restauration pour le volume dont l'ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover ID_VOLUME ID_REPLIQUE\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n Cette commande effectue une opération de reprise en ligne pour le volume dont l'ID est 12345678 sur le volume réplique dont l'ID est 87654321." }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index 0676f267..ac530512 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -344,10 +344,10 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFICATIVO [OPZIONI]\nInformazioni sull'utilizzo di un server virtuale.\nEsempio:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Questo comando autorizza il server virtuale con ID 87654321 ad accedere al volume con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-list ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n Questo comando elenca tutti gli host autorizzati ad accedere al volume con ID 12345678 e li ordina per ID." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { @@ -359,7 +359,7 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback ID_VOLUME\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Questo comando esegue l'operazione failback per il volume con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover ID_VOLUME ID_REPLICA\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n Questo comando esegue l'operazione failover per il volume con ID 12345678 sul volume di replica con ID 87654321." }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index d27d92fe..3b7c2fd6 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -344,11 +344,11 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS] \n 仮想サーバーの使用法情報。\n 例: \n ${COMMAND_NAME} sl {{.Command}} usage 1234 -- start 2006-01-02 -- end 2006-01-02 -- valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 -- virtual-id 87654321\n このコマンドは、ID 87654321 の仮想サーバーに ID 12345678 のボリュームへのアクセスを許可します。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": " \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 -- virtual-id 87654321\n このコマンドは、ID 87654321 の仮想サーバーに ID 12345678 のボリュームへのアクセスを許可します。" }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 -- sortby id \n このコマンドは、ID 12345678 のボリュームへのアクセスを許可されているすべてのホストをリストし、ID でソートします。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "other": "例: \n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 -- sortby id \n このコマンドは、ID 12345678 のボリュームへのアクセスを許可されているすべてのホストをリストし、ID でソートします。" }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME}sl {{.storageType}} access-password ACCESS_ID--password PASSWORD \n \n ACCESS_ID は、「${COMMAND_NAME} sl {{.storageType}} access-list」からの allowed_host_id です。" @@ -359,8 +359,8 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n このコマンドは、ID 12345678 のボリュームのフェイルバック操作を実行します。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n このコマンドは、ID 12345678 のボリュームから ID 87654321 のレプリカ・ボリュームへのフェイルオーバー操作を実行します。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "例: \n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n このコマンドは、ID 12345678 のボリュームから ID 87654321 のレプリカ・ボリュームへのフェイルオーバー操作を実行します。" }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n このコマンドは、ID 12345678 のブロック・ボリュームの適切なレプリケーション・データ・センターをリストします。" diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index 151fb95c..24eb36fd 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -344,10 +344,10 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [옵션]\n가상 서버의 사용량 정보입니다.\n예:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n 이 명령은 볼륨(ID 12345678)에 액세스하도록 가상 서버(ID 87654321)에 권한을 부여합니다." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n 이 명령은 볼륨(ID 12345678)에 액세스하도록 권한이 부여된 모든 호스트를 나열하고 ID별로 정렬합니다." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { @@ -359,8 +359,8 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n 이 명령은 볼륨(ID 12345678)에 대한 장애 복구 조작을 수행합니다." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n 이 명령은 복제본 볼륨(ID 87654321)에 대한 볼륨(ID 12345678)의 장애 복구 조작을 수행합니다." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n 이 명령은 복제본 볼륨(ID 87654321)에 대한 볼륨(ID 12345678)의 장애 복구 조작을 수행합니다." }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n 이 명령은 블록 볼륨(ID 12345678)에 적합한 복제 데이터 센터를 나열합니다." diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index 9336afee..015f65c3 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -344,11 +344,11 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\n Informações de uso de um servidor virtual.\nExemplo:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Esse comando autoriza o servidor virtual com o ID 87654321 a acessar o volume com o ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n Esse comando autoriza o servidor virtual com o ID 87654321 a acessar o volume com o ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n Esse comando lista todos os hosts que estão autorizados a acessar o volume com o ID 12345678 e os classifica por ID." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n Esse comando lista todos os hosts que estão autorizados a acessar o volume com o ID 12345678 e os classifica por ID." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID é o allowed_host_id a partir de '${COMMAND_NAME} sl {{.storageType}} access-list'" @@ -359,8 +359,8 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Esse comando executa a operação de failback para o volume com o ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n Esse comando executa a operação de failover do volume com o ID 12345678 para o volume de réplica com o ID 87654321." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n Esse comando executa a operação de failover do volume com o ID 12345678 para o volume de réplica com o ID 87654321." }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n Esse comando lista os data centers de replicação adequados para o volume de bloco com o ID 12345678." diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index 26f49c1a..a7969896 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -344,11 +344,11 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\n 虚拟服务器的用法信息。\n示例:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n 此命令授权标识为 87654321 的虚拟服务器访问标识为 12345678 的卷。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n 此命令授权标识为 87654321 的虚拟服务器访问标识为 12345678 的卷。" }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n此命令列出所有有权访问标识为 12345678 的卷的主机,并按标识对它们进行排序。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n此命令列出所有有权访问标识为 12345678 的卷的主机,并按标识对它们进行排序。" }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID 是来自“${COMMAND_NAME} sl {{.storageType}} access-list”的 allowed_host_id" @@ -359,8 +359,8 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n 此命令对标识为 12345678 的卷执行故障恢复操作。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n 此命令对标识为 12345678 的卷执行故障转移操作,以将其复制到标识为 87654321 的副本卷。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n 此命令对标识为 12345678 的卷执行故障转移操作,以将其复制到标识为 87654321 的副本卷。" }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n 此命令列出适用于标识为 12345678 的块卷的复制数据中心。" diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 1ebe2d35..0692434f 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -344,11 +344,11 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\n虛擬伺服器的用法資訊。\n範例:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n 這個指令會授權虛擬伺服器(其 ID 為 87654321)存取磁區(其 ID 為 12345678)。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n 這個指令會授權虛擬伺服器(其 ID 為 87654321)存取磁區(其 ID 為 12345678)。" }, - "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-list VOLUME_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n 這個指令會列出獲授權存取磁區(其 ID 為 12345678)的所有主機,並依 ID 排序。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n 這個指令會列出獲授權存取磁區(其 ID 為 12345678)的所有主機,並依 ID 排序。" }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID 是來自 '${COMMAND_NAME} sl {{.storageType}} access-list' 的 allowed_host_id" @@ -359,8 +359,8 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n 這個指令會針對磁區(其 ID 為 12345678)執行失效回復作業。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-failover VOLUME_ID REPLICA_ID\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n 這個指令會針對磁區(其 ID 為 12345678)對抄本磁區(其 ID 為 87654321)執行失效接手作業。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n 這個指令會針對磁區(其 ID 為 12345678)對抄本磁區(其 ID 為 87654321)執行失效接手作業。" }, "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n 這個指令會列出區塊磁區(其 ID 為 12345678)的適當抄寫資料中心。" From 5476a00acfa01728ce4d02d81d51c6cc4a34b819 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Oct 2024 17:04:09 -0500 Subject: [PATCH 26/38] #722 more command using GetVolumeId --- plugin/commands/block/replica_order.go | 9 +++------ plugin/commands/block/replica_order_test.go | 10 ++-------- plugin/commands/block/replica_partners.go | 11 ++++------- plugin/commands/block/replica_partners_test.go | 1 + .../block/snapshot-get_notification_status.go | 7 +++---- .../block/snapshot-get_notification_status_test.go | 9 +-------- plugin/commands/block/snapshot_cancel.go | 10 +++------- plugin/commands/block/snapshot_cancel_test.go | 8 +------- plugin/i18n/v2Resources/active.de_DE.json | 4 ++-- plugin/i18n/v2Resources/active.en-US.json | 8 ++++---- plugin/i18n/v2Resources/active.es_ES.json | 8 ++++---- plugin/i18n/v2Resources/active.fr_FR.json | 4 ++-- plugin/i18n/v2Resources/active.it_IT.json | 4 ++-- plugin/i18n/v2Resources/active.ja_JP.json | 12 ++++++------ plugin/i18n/v2Resources/active.ko_KR.json | 4 ++-- plugin/i18n/v2Resources/active.pt_BR.json | 8 ++++---- plugin/i18n/v2Resources/active.zh_Hans.json | 8 ++++---- plugin/i18n/v2Resources/active.zh_Hant.json | 6 +++--- 18 files changed, 51 insertions(+), 80 deletions(-) diff --git a/plugin/commands/block/replica_order.go b/plugin/commands/block/replica_order.go index a3c1ba3d..58d2dc9f 100644 --- a/plugin/commands/block/replica_order.go +++ b/plugin/commands/block/replica_order.go @@ -2,7 +2,6 @@ package block import ( "fmt" - "strconv" "github.com/spf13/cobra" @@ -33,9 +32,7 @@ func NewReplicaOrderCommand(sl *metadata.SoftlayerStorageCommand) *ReplicaOrderC cobraCmd := &cobra.Command{ Use: "replica-order " + T("IDENTIFIER"), Short: T("Order a block storage replica volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.`, sl.StorageI18n), Args: metadata.OneArgs, @@ -55,9 +52,9 @@ EXAMPLE: func (cmd *ReplicaOrderCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return errors.NewInvalidSoftlayerIdInputError("Volume ID") + return err } snapshotSchedule := cmd.SnapshotSchedule diff --git a/plugin/commands/block/replica_order_test.go b/plugin/commands/block/replica_order_test.go index dd4314cb..93b3e16a 100644 --- a/plugin/commands/block/replica_order_test.go +++ b/plugin/commands/block/replica_order_test.go @@ -31,6 +31,7 @@ var _ = Describe("Replica order", func() { cliCommand = block.NewReplicaOrderCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant order", func() { @@ -41,13 +42,6 @@ var _ = Describe("Replica order", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Replicant order with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) Context("Replicant order without -s", func() { It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123") @@ -112,7 +106,7 @@ var _ = Describe("Replica order", func() { It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-t", "4", "-o", "LINUX", "-f") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to order replicant for volume 123.Please verify your options and try again.")) + Expect(err.Error()).To(ContainSubstring("Failed to order replicant for volume 1234.Please verify your options and try again.")) }) }) diff --git a/plugin/commands/block/replica_partners.go b/plugin/commands/block/replica_partners.go index 0976f0e2..cb2e2443 100644 --- a/plugin/commands/block/replica_partners.go +++ b/plugin/commands/block/replica_partners.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -26,9 +24,7 @@ func NewReplicaPartnersCommand(sl *metadata.SoftlayerStorageCommand) *ReplicaPar cobraCmd := &cobra.Command{ Use: "replica-partners " + T("IDENTIFIER"), Short: T("List existing replicant volumes for a block volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678 This command lists existing replicant volumes for block volume with ID 12345678.`, sl.StorageI18n), Args: metadata.OneArgs, @@ -43,10 +39,11 @@ EXAMPLE: func (cmd *ReplicaPartnersCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } + outputFormat := cmd.GetOutputFlag() partners, err := cmd.StorageManager.GetReplicationPartners(volumeID) diff --git a/plugin/commands/block/replica_partners_test.go b/plugin/commands/block/replica_partners_test.go index 01fa6adc..9928d461 100644 --- a/plugin/commands/block/replica_partners_test.go +++ b/plugin/commands/block/replica_partners_test.go @@ -31,6 +31,7 @@ var _ = Describe("Replica partners", func() { cliCommand = block.NewReplicaPartnersCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant partners", func() { diff --git a/plugin/commands/block/snapshot-get_notification_status.go b/plugin/commands/block/snapshot-get_notification_status.go index 24850cfa..ca7a6f86 100644 --- a/plugin/commands/block/snapshot-get_notification_status.go +++ b/plugin/commands/block/snapshot-get_notification_status.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -38,11 +36,12 @@ func NewSnapshotGetNotificationStatusCommand(sl *metadata.SoftlayerStorageComman func (cmd *SnapshotGetNotificationStatusCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } + outputFormat := cmd.GetOutputFlag() subs := map[string]interface{}{"ID": volumeID} enabled, err := cmd.StorageManager.GetSnapshotNotificationStatus(volumeID) diff --git a/plugin/commands/block/snapshot-get_notification_status_test.go b/plugin/commands/block/snapshot-get_notification_status_test.go index 693a503a..d5c6d297 100644 --- a/plugin/commands/block/snapshot-get_notification_status_test.go +++ b/plugin/commands/block/snapshot-get_notification_status_test.go @@ -29,6 +29,7 @@ var _ = Describe("Volume snapshot notification status", func() { cliCommand = block.NewSnapshotGetNotificationStatusCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234567, nil) }) Describe("Get volume snapshot notification status", func() { @@ -40,14 +41,6 @@ var _ = Describe("Volume snapshot notification status", func() { }) }) - Context("Volume get notification status with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) - Context("Volume get notification status with an error", func() { BeforeEach(func() { FakeStorageManager.GetSnapshotNotificationStatusReturns(-1, errors.New("Internal Server Error")) diff --git a/plugin/commands/block/snapshot_cancel.go b/plugin/commands/block/snapshot_cancel.go index ae06b096..99dcb651 100644 --- a/plugin/commands/block/snapshot_cancel.go +++ b/plugin/commands/block/snapshot_cancel.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -28,9 +26,7 @@ func NewSnapshotCancelCommand(sl *metadata.SoftlayerStorageCommand) *SnapshotCan cobraCmd := &cobra.Command{ Use: "snapshot-cancel " + T("IDENTIFIER"), Short: T("Cancel existing snapshot space for a given volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f This command cancels snapshot with ID 12345678 immediately without asking for confirmation.`, sl.StorageI18n), Args: metadata.OneArgs, @@ -48,9 +44,9 @@ EXAMPLE: func (cmd *SnapshotCancelCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } subs := map[string]interface{}{"ID": volumeID} if !cmd.Force { diff --git a/plugin/commands/block/snapshot_cancel_test.go b/plugin/commands/block/snapshot_cancel_test.go index bfb18af9..b2c2d2e9 100644 --- a/plugin/commands/block/snapshot_cancel_test.go +++ b/plugin/commands/block/snapshot_cancel_test.go @@ -29,6 +29,7 @@ var _ = Describe("Snapshot Cancel", func() { cliCommand = block.NewSnapshotCancelCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot cancel", func() { @@ -39,13 +40,6 @@ var _ = Describe("Snapshot Cancel", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Snapshot cancel with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) Context("Snapshot cancel with correct volume id without -f and not continue", func() { BeforeEach(func() { diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index 0811fc4f..ed90cfbe 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -365,10 +365,10 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations DATENTRÄGER_ID [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n Dieser Befehl listet geeignete Replikationsrechenzentren für den Blockdatenträger mit der ID 12345678 auf." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order DATENTRÄGER_ID [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s TÄGLICH -d dal09 --tier 4 --os-type LINUX\n Dieser Befehl bestellt ein Replikat für den Datenträger mit der ID 12345678, der die Replikation TÄGLICH ausführt, und befindet sich auf dal09, Ebene der Schicht ist 4, Betriebssystemtyp ist Linux." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners DATENTRÄGER_ID [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Dieser Befehl listet vorhandene Replikatdatenträger für Blockdatenträger mit der ID 12345678 auf." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index d3883a94..6090a578 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -365,11 +365,11 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index 949a80a8..d3365b24 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -365,11 +365,11 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n Este mandato lista los centros de datos adecuados al volumen en bloques con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n Este mandato solicita una réplica del volumen con ID 12345678, que realiza una réplica diaria (DAILY) y está ubicado en dal09, nivel 4, tipo de SO Linux." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n Este mandato solicita una réplica del volumen con ID 12345678, que realiza una réplica diaria (DAILY) y está ubicado en dal09, nivel 4, tipo de SO Linux." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Este mandato lista los volúmenes replicantes existentes del volumen en bloques con ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Este mandato lista los volúmenes replicantes existentes del volumen en bloques con ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Este mandato cancela la instantánea con ID 12345678 de forma inmediata, sin pedir confirmación." diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 029a1a71..0f9ff9b0 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -365,10 +365,10 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n Cette commande répertorie les centres de données de réplication appropriés pour le volume de blocs dont l'ID est 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n Cette commande commande une réplique pour le volume d'ID 12345678, qui effectue une réplication quotidienne (DAILY), se trouve à l'emplacement dal09, au niveau 4, avec le type de système d'exploitation Linux." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Cette commande répertorie les volumes réplicants existants pour le volume de blocs dont l'ID est 12345678." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index ac530512..09af1797 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -365,10 +365,10 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n Questo comando elenca i data center di replica idonei per il volume di blocco con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n Questo comando ordina una replica per il volume con ID 12345678, che esegue la replica QUOTIDIANA (DAILY), è ubicato in dal09, il livello è 4, il tipo di OS è Linux." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Questo comando elenca i volumi replicanti esistenti per il volume di blocco con ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index 3b7c2fd6..08b00056 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -365,14 +365,14 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n このコマンドは、ID 12345678 のブロック・ボリュームの適切なレプリケーション・データ・センターをリストします。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\n例:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n このコマンドは、 層レベルが4、OSタイプがLinux (dal09に配置)でDAILYレプリケーションを実施する、ID 12345678のボリュームのレプリカを注文します。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "例:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n このコマンドは、 層レベルが4、OSタイプがLinux (dal09に配置)でDAILYレプリケーションを実施する、ID 12345678のボリュームのレプリカを注文します。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n このコマンドは、ID 12345678 のブロック・ボリュームの既存レプリカ・ボリュームをリストします。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "例: \n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n このコマンドは、ID 12345678 のブロック・ボリュームの既存レプリカ・ボリュームをリストします。" }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 -- immediate -f \n このコマンドは、確認を求めずに、ID 12345678 のスナップショットを即時に取り消します。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "例: \n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 -- immediate -f \n このコマンドは、確認を求めずに、ID 12345678 のスナップショットを即時に取り消します。" }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 -- note snapshotforibmcloud \n このコマンドは、ID 12345678 のボリュームのスナップショットを作成し、追加メモとして snapshotforibmcloud を作成します。" diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index 24eb36fd..e1add0c0 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -365,10 +365,10 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n 이 명령은 블록 볼륨(ID 12345678)에 적합한 복제 데이터 센터를 나열합니다." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n 이 명령은 볼륨(ID 12345678)에 대한 복제본을 주문하고, 일일 복제를 수행하며, dal09에 위치합니다. 계층 레벨은 4이고, OS 유형은 Linux입니다." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 이 명령은 블록 볼륨(ID 12345678)에 대한 기존 복제 볼륨을 나열합니다." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index 015f65c3..1121599a 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -365,11 +365,11 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n Esse comando lista os data centers de replicação adequados para o volume de bloco com o ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n Esse comando solicita uma réplica para o volume com o ID 12345678, que executa a replicação DIÁRIA, está localizado em dal09, o nível de camada é 4 e o tipo de S.O. é Linux." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n Esse comando solicita uma réplica para o volume com o ID 12345678, que executa a replicação DIÁRIA, está localizado em dal09, o nível de camada é 4 e o tipo de S.O. é Linux." }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Esse comando lista os volumes replicantes existentes para o volume de bloco com o ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Esse comando lista os volumes replicantes existentes para o volume de bloco com o ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Esse comando cancela a captura instantânea com o ID 12345678 imediatamente sem solicitar confirmação." diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index a7969896..927cc3c4 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -365,11 +365,11 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n 此命令列出适用于标识为 12345678 的块卷的复制数据中心。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n 此命令订购标识为 12345678 的卷的副本,用于执行日常复制,位于 dal09,层级别为 4,操作系统类型为 Linux。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n 此命令订购标识为 12345678 的卷的副本,用于执行日常复制,位于 dal09,层级别为 4,操作系统类型为 Linux。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 此命令列出标识为 12345678 的块卷的现有复制卷。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 此命令列出标识为 12345678 的块卷的现有复制卷。" }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 此命令立即取消标识为 12345678 的快照,而不要求确认。" diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 0692434f..400cf03a 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -365,11 +365,11 @@ "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-locations VOLUME_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n 這個指令會列出區塊磁區(其 ID 為 12345678)的適當抄寫資料中心。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n 這個指令會為磁區(其 ID 為 12345678)訂購抄本(執行每日抄寫),位於 dal09,層級層次為 4,OS 類型為 Linux。" }, - "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 這個指令會列出區塊磁區(其 ID 為 12345678)的現有抄本磁區。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 這個指令會列出區塊磁區(其 ID 為 12345678)的現有抄本磁區。" }, "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 這個指令會立即取消 Snapshot(其 ID 為 12345678),而不要求確認。" From ad63bcb5f2f276f91f179de4889941feb7f6f863 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Oct 2024 16:07:42 -0500 Subject: [PATCH 27/38] #722 finished adding GetVolumeId looks to block commands --- plugin/commands/block/access_list.go | 2 +- plugin/commands/block/block.go | 2 +- .../block/snapshot-get_notification_status.go | 1 - plugin/commands/block/snapshot_create.go | 7 ++----- plugin/commands/block/snapshot_create_test.go | 9 +-------- plugin/commands/block/snapshot_disable.go | 6 ++---- .../commands/block/snapshot_disable_test.go | 9 +-------- plugin/commands/block/snapshot_enable.go | 5 ++--- plugin/commands/block/snapshot_enable_test.go | 10 +++------- plugin/commands/block/snapshot_list.go | 9 +++------ plugin/commands/block/snapshot_list_test.go | 6 +----- plugin/commands/block/snapshot_order.go | 8 +++----- plugin/commands/block/snapshot_order_test.go | 15 ++++++-------- plugin/commands/block/snapshot_restore.go | 5 +++-- .../commands/block/snapshot_restore_test.go | 12 ++++++----- .../commands/block/snapshot_schedule_list.go | 5 ++--- .../block/snapshot_schedule_list_test.go | 9 +++++++++ .../block/snapshot_set_notification.go | 6 ++---- .../block/snapshot_set_notification_test.go | 20 ++++++++++--------- plugin/commands/block/volume_cancel.go | 11 +++++----- plugin/commands/block/volume_cancel_test.go | 4 +++- plugin/commands/block/volume_convert.go | 7 ++----- plugin/commands/block/volume_convert_test.go | 4 +++- plugin/commands/block/volume_detail_test.go | 8 ++++++++ plugin/commands/block/volume_duplicate.go | 5 ++--- .../commands/block/volume_duplicate_test.go | 4 +++- plugin/commands/block/volume_lun.go | 11 +++++----- plugin/commands/block/volume_lun_test.go | 4 +++- plugin/commands/block/volume_modify.go | 7 ++----- plugin/commands/block/volume_modify_test.go | 5 ++++- plugin/commands/block/volume_refresh.go | 4 ++-- plugin/commands/block/volume_refresh_test.go | 4 +++- plugin/commands/block/volume_set_note.go | 6 ++---- plugin/commands/block/volume_set_note_test.go | 3 ++- 34 files changed, 110 insertions(+), 123 deletions(-) diff --git a/plugin/commands/block/access_list.go b/plugin/commands/block/access_list.go index cdec771a..da4b71c3 100644 --- a/plugin/commands/block/access_list.go +++ b/plugin/commands/block/access_list.go @@ -1,8 +1,8 @@ package block import ( - "sort" "github.com/spf13/cobra" + "sort" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" diff --git a/plugin/commands/block/block.go b/plugin/commands/block/block.go index 4ea92b36..56a910bb 100644 --- a/plugin/commands/block/block.go +++ b/plugin/commands/block/block.go @@ -12,7 +12,7 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { StorageCommand := &metadata.SoftlayerStorageCommand{ SoftlayerCommand: sl, StorageI18n: map[string]interface{}{"storageType": "block"}, - StorageType: "block", + StorageType: "block", } cobraCmd := &cobra.Command{ Use: "block", diff --git a/plugin/commands/block/snapshot-get_notification_status.go b/plugin/commands/block/snapshot-get_notification_status.go index ca7a6f86..760c3890 100644 --- a/plugin/commands/block/snapshot-get_notification_status.go +++ b/plugin/commands/block/snapshot-get_notification_status.go @@ -41,7 +41,6 @@ func (cmd *SnapshotGetNotificationStatusCommand) Run(args []string) error { return err } - outputFormat := cmd.GetOutputFlag() subs := map[string]interface{}{"ID": volumeID} enabled, err := cmd.StorageManager.GetSnapshotNotificationStatus(volumeID) diff --git a/plugin/commands/block/snapshot_create.go b/plugin/commands/block/snapshot_create.go index 75aab363..b513478b 100644 --- a/plugin/commands/block/snapshot_create.go +++ b/plugin/commands/block/snapshot_create.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -44,11 +42,10 @@ EXAMPLE: func (cmd *SnapshotCreateCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } - outputFormat := cmd.GetOutputFlag() snapshot, err := cmd.StorageManager.CreateSnapshot(volumeID, cmd.Note) diff --git a/plugin/commands/block/snapshot_create_test.go b/plugin/commands/block/snapshot_create_test.go index 8be1dd69..ad9825b9 100644 --- a/plugin/commands/block/snapshot_create_test.go +++ b/plugin/commands/block/snapshot_create_test.go @@ -31,6 +31,7 @@ var _ = Describe("Snapshot Create", func() { cliCommand = block.NewSnapshotCreateCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot create", func() { @@ -41,14 +42,6 @@ var _ = Describe("Snapshot Create", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Snapshot create with wrong volume id", func() { - It("error resolving volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) - Context("Snapshot create with correct volume id", func() { BeforeEach(func() { FakeStorageManager.CreateSnapshotReturns(datatypes.Network_Storage{Id: sl.Int(5678)}, nil) diff --git a/plugin/commands/block/snapshot_disable.go b/plugin/commands/block/snapshot_disable.go index 797b4934..a0bcbf09 100644 --- a/plugin/commands/block/snapshot_disable.go +++ b/plugin/commands/block/snapshot_disable.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -43,9 +41,9 @@ EXAMPLE: func (cmd *SnapshotDisableCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } if cmd.Schedule_type == "" { return slErr.NewInvalidUsageError(T("[--schedule-type] is required, options are: HOURLY, DAILY, WEEKLY.")) diff --git a/plugin/commands/block/snapshot_disable_test.go b/plugin/commands/block/snapshot_disable_test.go index 6a302c40..4e326ae6 100644 --- a/plugin/commands/block/snapshot_disable_test.go +++ b/plugin/commands/block/snapshot_disable_test.go @@ -30,6 +30,7 @@ var _ = Describe("Snapshot disable", func() { cliCommand = block.NewSnapshotDisableCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot disable", func() { Context("Snapshot disable without volume id", func() { @@ -39,14 +40,6 @@ var _ = Describe("Snapshot disable", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Snapshot disable with wrong volume id", func() { - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) - }) - Context("Snapshot disable without -s", func() { It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123") diff --git a/plugin/commands/block/snapshot_enable.go b/plugin/commands/block/snapshot_enable.go index 1f754140..bffbb34a 100644 --- a/plugin/commands/block/snapshot_enable.go +++ b/plugin/commands/block/snapshot_enable.go @@ -3,7 +3,6 @@ package block import ( "github.com/spf13/cobra" "slices" - "strconv" "strings" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -72,9 +71,9 @@ EXAMPLE: func (cmd *SnapshotEnableCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } if cmd.ScheduleType == "" { return slErr.NewInvalidUsageError(T("[-s|--schedule-type] is required, options are: HOURLY, DAILY, WEEKLY.")) diff --git a/plugin/commands/block/snapshot_enable_test.go b/plugin/commands/block/snapshot_enable_test.go index 55650f88..0a81b253 100644 --- a/plugin/commands/block/snapshot_enable_test.go +++ b/plugin/commands/block/snapshot_enable_test.go @@ -29,6 +29,7 @@ var _ = Describe("Snapshot enable", func() { cliCommand = block.NewSnapshotEnableCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot enable", func() { @@ -38,11 +39,6 @@ var _ = Describe("Snapshot enable", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - It("Bad Volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "a1234", "-c=100", "-s=INTERVAL") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) It("Bad Interval", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--schedule-type=FAKE", "-c=100") Expect(err).To(HaveOccurred()) @@ -80,9 +76,9 @@ var _ = Describe("Snapshot enable", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("HOURLY snapshots have been enabled for volume 1234.")) }) It("Happy Path min Options", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "9999", "-s", "INTERVAL", "-c", "500") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "INTERVAL", "-c", "500") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("INTERVAL snapshots have been enabled for volume 9999.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("INTERVAL snapshots have been enabled for volume 1234.")) }) }) diff --git a/plugin/commands/block/snapshot_list.go b/plugin/commands/block/snapshot_list.go index 74d480b3..ed630788 100644 --- a/plugin/commands/block/snapshot_list.go +++ b/plugin/commands/block/snapshot_list.go @@ -1,10 +1,8 @@ package block import ( - "sort" - "strconv" - "github.com/spf13/cobra" + "sort" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -43,11 +41,10 @@ EXAMPLE: } func (cmd *SnapshotListCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } - outputFormat := cmd.GetOutputFlag() snapshots, err := cmd.StorageManager.GetVolumeSnapshotList(volumeID) diff --git a/plugin/commands/block/snapshot_list_test.go b/plugin/commands/block/snapshot_list_test.go index fc38746c..427d3470 100644 --- a/plugin/commands/block/snapshot_list_test.go +++ b/plugin/commands/block/snapshot_list_test.go @@ -54,6 +54,7 @@ var _ = Describe("Snapshot list", func() { cliCommand = block.NewSnapshotListCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot list tests", func() { @@ -63,11 +64,6 @@ var _ = Describe("Snapshot list", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - It("Bad volume id", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) It("Bad --sortby", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--sortby", "bcd") Expect(err).To(HaveOccurred()) diff --git a/plugin/commands/block/snapshot_order.go b/plugin/commands/block/snapshot_order.go index 850308b7..8ed0e704 100644 --- a/plugin/commands/block/snapshot_order.go +++ b/plugin/commands/block/snapshot_order.go @@ -2,10 +2,8 @@ package block import ( "fmt" - "slices" - "strconv" - "github.com/spf13/cobra" + "slices" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -61,9 +59,9 @@ EXAMPLE: func (cmd *SnapshotOrderCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } if cmd.Tier > 0 { diff --git a/plugin/commands/block/snapshot_order_test.go b/plugin/commands/block/snapshot_order_test.go index f5521627..e208bb0f 100644 --- a/plugin/commands/block/snapshot_order_test.go +++ b/plugin/commands/block/snapshot_order_test.go @@ -31,6 +31,7 @@ var _ = Describe("Block Snapshot order", func() { cliCommand = block.NewSnapshotOrderCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot order", func() { @@ -40,11 +41,6 @@ var _ = Describe("Block Snapshot order", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) - It("Bad Volume ID", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "-s=100") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) - }) It("No --size", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).To(HaveOccurred()) @@ -85,12 +81,12 @@ var _ = Describe("Block Snapshot order", func() { Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) }) It("Upgrade Order Happy Path", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "4567", "-s", "1000", "-t", "10", "-u", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "1000", "-t", "10", "-u", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) storage_type, volumeId, size, tier, iops, upgrade := FakeStorageManager.OrderSnapshotSpaceArgsForCall(0) Expect(storage_type).To(Equal("block")) - Expect(volumeId).To(Equal(4567)) + Expect(volumeId).To(Equal(1234)) Expect(size).To(Equal(1000)) Expect(tier).To(Equal(10.0)) Expect(iops).To(Equal(0)) @@ -128,6 +124,7 @@ var _ = Describe("File Snapshot order", func() { cliCommand = block.NewSnapshotOrderCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot order", func() { @@ -160,12 +157,12 @@ var _ = Describe("File Snapshot order", func() { Expect(upgrade).To(BeFalse()) }) It("Upgrade Order Happy Path", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "4567", "-s", "1000", "-t", "10", "-u", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "1000", "-t", "10", "-u", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) storage_type, volumeId, size, tier, iops, upgrade := FakeStorageManager.OrderSnapshotSpaceArgsForCall(0) Expect(storage_type).To(Equal("file")) - Expect(volumeId).To(Equal(4567)) + Expect(volumeId).To(Equal(1234)) Expect(size).To(Equal(1000)) Expect(tier).To(Equal(10.0)) Expect(iops).To(Equal(0)) diff --git a/plugin/commands/block/snapshot_restore.go b/plugin/commands/block/snapshot_restore.go index b6278649..12c0db16 100644 --- a/plugin/commands/block/snapshot_restore.go +++ b/plugin/commands/block/snapshot_restore.go @@ -40,10 +40,11 @@ EXAMPLE: func (cmd *SnapshotRestoreCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } + snapshotID, err := strconv.Atoi(args[1]) if err != nil { return slErr.NewInvalidSoftlayerIdInputError("Snapshot ID") diff --git a/plugin/commands/block/snapshot_restore_test.go b/plugin/commands/block/snapshot_restore_test.go index 4b44a391..99bed022 100644 --- a/plugin/commands/block/snapshot_restore_test.go +++ b/plugin/commands/block/snapshot_restore_test.go @@ -29,6 +29,7 @@ var _ = Describe("Snapshot restore", func() { cliCommand = block.NewSnapshotRestoreCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot restore", func() { @@ -44,9 +45,10 @@ var _ = Describe("Snapshot restore", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires two arguments.")) }) It("Bad VolumeID", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "123") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) It("Bad SnapshotId", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "abc") @@ -60,9 +62,9 @@ var _ = Describe("Snapshot restore", func() { FakeStorageManager.RestoreFromSnapshotReturns(nil) }) It("Happy path", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "456") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "456") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Block volume 123 is being restored using snapshot 456.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Block volume 1234 is being restored using snapshot 456.")) }) }) @@ -71,10 +73,10 @@ var _ = Describe("Snapshot restore", func() { FakeStorageManager.RestoreFromSnapshotReturns(errors.New("Internal Server Error")) }) It("API Error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "456") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "456") Expect(err).To(HaveOccurred()) Expect(fakeUI.Outputs()).NotTo(ContainSubstring("OK")) - Expect(err.Error()).To(ContainSubstring("Failed to restore volume 123 from snapshot 456.")) + Expect(err.Error()).To(ContainSubstring("Failed to restore volume 1234 from snapshot 456.")) Expect(err.Error()).To(ContainSubstring("Internal Server Error")) }) }) diff --git a/plugin/commands/block/snapshot_schedule_list.go b/plugin/commands/block/snapshot_schedule_list.go index bfcd4911..fbdedc2c 100644 --- a/plugin/commands/block/snapshot_schedule_list.go +++ b/plugin/commands/block/snapshot_schedule_list.go @@ -1,7 +1,6 @@ package block import ( - "strconv" "strings" "github.com/spf13/cobra" @@ -38,9 +37,9 @@ func NewSnapshotScheduleListCommand(sl *metadata.SoftlayerStorageCommand) (cmd * func (cmd *SnapshotScheduleListCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/block/snapshot_schedule_list_test.go b/plugin/commands/block/snapshot_schedule_list_test.go index 6000d40e..e63d55b2 100644 --- a/plugin/commands/block/snapshot_schedule_list_test.go +++ b/plugin/commands/block/snapshot_schedule_list_test.go @@ -31,6 +31,7 @@ var _ = Describe("block Snapshot Schedule List", func() { cliCommand = block.NewSnapshotScheduleListCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("block Snapshot Schedule List", func() { @@ -41,6 +42,14 @@ var _ = Describe("block Snapshot Schedule List", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) + Context("Bad Arguments Error", func() { + It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "zzz") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) + }) + }) Context("Proper Usage", func() { It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") diff --git a/plugin/commands/block/snapshot_set_notification.go b/plugin/commands/block/snapshot_set_notification.go index 3e895ac4..f076b1fa 100644 --- a/plugin/commands/block/snapshot_set_notification.go +++ b/plugin/commands/block/snapshot_set_notification.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -39,9 +37,9 @@ func NewSnapshotSetNotificationCommand(sl *metadata.SoftlayerStorageCommand) *Sn func (cmd *SnapshotSetNotificationCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } if cmd.Enable && cmd.Disable { diff --git a/plugin/commands/block/snapshot_set_notification_test.go b/plugin/commands/block/snapshot_set_notification_test.go index 33d8b9b8..9b720a9a 100644 --- a/plugin/commands/block/snapshot_set_notification_test.go +++ b/plugin/commands/block/snapshot_set_notification_test.go @@ -29,6 +29,7 @@ var _ = Describe("Volume set snapshot notification status", func() { cliCommand = block.NewSnapshotSetNotificationCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Volume set snapshot notification status", func() { Context("Volume set snapshot notification status without volume id", func() { @@ -40,15 +41,16 @@ var _ = Describe("Volume set snapshot notification status", func() { }) Context("Volume set snapshot notification status with wrong volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Volume set snapshot notification status without --enable or --disable", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "1234567") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Either '--enable' or '--disable' is required.")) }) @@ -56,7 +58,7 @@ var _ = Describe("Volume set snapshot notification status", func() { Context("Volume set snapshot notification status with --enable and --disable", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--enable", "--disable", "1234567") + err := testhelpers.RunCobraCommand(cliCommand.Command, "--enable", "--disable", "1234") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: '--enable', '--disable' are exclusive")) }) @@ -67,9 +69,9 @@ var _ = Describe("Volume set snapshot notification status", func() { FakeStorageManager.SetSnapshotNotificationReturns(errors.New("Internal Server Error")) }) It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--enable", "1234567") + err := testhelpers.RunCobraCommand(cliCommand.Command, "--enable", "1234") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to set the snapshort notification for volume '1234567'.\n")) + Expect(err.Error()).To(ContainSubstring("Failed to set the snapshort notification for volume '1234'.\n")) }) }) @@ -78,9 +80,9 @@ var _ = Describe("Volume set snapshot notification status", func() { FakeStorageManager.SetSnapshotNotificationReturns(nil) }) It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--enable", "1234567") + err := testhelpers.RunCobraCommand(cliCommand.Command, "--enable", "1234") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Snapshots space usage threshold warning notification has been set to 'true' for volume '1234567'.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Snapshots space usage threshold warning notification has been set to 'true' for volume '1234'.")) }) }) @@ -89,9 +91,9 @@ var _ = Describe("Volume set snapshot notification status", func() { FakeStorageManager.SetSnapshotNotificationReturns(nil) }) It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "--disable", "1234567") + err := testhelpers.RunCobraCommand(cliCommand.Command, "--disable", "1234") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Snapshots space usage threshold warning notification has been set to 'false' for volume '1234567'.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Snapshots space usage threshold warning notification has been set to 'false' for volume '1234'.")) }) }) }) diff --git a/plugin/commands/block/volume_cancel.go b/plugin/commands/block/volume_cancel.go index 1b394bcd..d3efabd5 100644 --- a/plugin/commands/block/volume_cancel.go +++ b/plugin/commands/block/volume_cancel.go @@ -1,10 +1,8 @@ package block import ( - "strconv" - "strings" - "github.com/spf13/cobra" + "strings" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -48,11 +46,12 @@ EXAMPLE: func (cmd *VolumeCancelCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) - subs := map[string]interface{}{"ID": volumeID, "VolumeId": volumeID} + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } + subs := map[string]interface{}{"ID": volumeID, "VolumeId": volumeID} + if !cmd.Force { confirm, err := cmd.UI.Confirm(T("This will cancel the block volume: {{.ID}} and cannot be undone. Continue?", subs)) if err != nil { diff --git a/plugin/commands/block/volume_cancel_test.go b/plugin/commands/block/volume_cancel_test.go index 2883ad96..b9007f0b 100644 --- a/plugin/commands/block/volume_cancel_test.go +++ b/plugin/commands/block/volume_cancel_test.go @@ -29,6 +29,7 @@ var _ = Describe("Volume cancel", func() { cliCommand = block.NewVolumeCancelCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Volume cancel", func() { @@ -41,9 +42,10 @@ var _ = Describe("Volume cancel", func() { }) Context("Volume cancel with wrong volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) diff --git a/plugin/commands/block/volume_convert.go b/plugin/commands/block/volume_convert.go index 09a28eeb..d439aede 100644 --- a/plugin/commands/block/volume_convert.go +++ b/plugin/commands/block/volume_convert.go @@ -1,11 +1,8 @@ package block import ( - "strconv" - "github.com/spf13/cobra" - slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" @@ -36,9 +33,9 @@ func NewVolumeConvertCommand(sl *metadata.SoftlayerStorageCommand) *VolumeConver func (cmd *VolumeConvertCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } err = cmd.StorageManager.VolumeConvert(volumeID) diff --git a/plugin/commands/block/volume_convert_test.go b/plugin/commands/block/volume_convert_test.go index 99e661ef..11b75692 100644 --- a/plugin/commands/block/volume_convert_test.go +++ b/plugin/commands/block/volume_convert_test.go @@ -29,6 +29,7 @@ var _ = Describe("block Volume Convert", func() { cliCommand = block.NewVolumeConvertCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("block Volume Convert", func() { @@ -41,9 +42,10 @@ var _ = Describe("block Volume Convert", func() { }) Context("Bad VolumeId", func() { It("error resolving volume ID", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Proper Usage", func() { diff --git a/plugin/commands/block/volume_detail_test.go b/plugin/commands/block/volume_detail_test.go index c2271fdf..c249f5fb 100644 --- a/plugin/commands/block/volume_detail_test.go +++ b/plugin/commands/block/volume_detail_test.go @@ -37,6 +37,14 @@ var _ = Describe("Block Volume-Detail Tests", func() { Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) + Context("Bad VolumeId", func() { + It("error resolving volume ID", func() { + fakeHandler.AddApiError("SoftLayer_Account", "getIscsiNetworkStorage", 500, "BAD Volume ID") + err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) + }) + }) }) Describe("Volume Detail API response tests", func() { Context("Volume detail with correct volume id but server API call fails", func() { diff --git a/plugin/commands/block/volume_duplicate.go b/plugin/commands/block/volume_duplicate.go index a51da1f9..f49a08e7 100644 --- a/plugin/commands/block/volume_duplicate.go +++ b/plugin/commands/block/volume_duplicate.go @@ -2,7 +2,6 @@ package block import ( "fmt" - "strconv" "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -63,9 +62,9 @@ EXAMPLE: func (cmd *VolumeDuplicateCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } billing := cmd.Billing diff --git a/plugin/commands/block/volume_duplicate_test.go b/plugin/commands/block/volume_duplicate_test.go index 759eaf9c..6f88de49 100644 --- a/plugin/commands/block/volume_duplicate_test.go +++ b/plugin/commands/block/volume_duplicate_test.go @@ -42,6 +42,7 @@ var _ = Describe("Volume duplicate", func() { cliCommand = block.NewVolumeDuplicateCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Volume duplicate", func() { @@ -54,9 +55,10 @@ var _ = Describe("Volume duplicate", func() { }) Context("Bad volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "ZZZ") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Volume ID")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Volume duplicate with 0 DuplicateSnapshotSize", func() { diff --git a/plugin/commands/block/volume_lun.go b/plugin/commands/block/volume_lun.go index 78842645..cf67ab5a 100644 --- a/plugin/commands/block/volume_lun.go +++ b/plugin/commands/block/volume_lun.go @@ -44,17 +44,18 @@ func NewVolumeLunCommand(sl *metadata.SoftlayerStorageCommand) *VolumeLunCommand func (cmd *VolumeLunCommand) Run(args []string) error { - volumeId, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } + lunId, err := strconv.Atoi(args[1]) if err != nil { return slErr.NewInvalidSoftlayerIdInputError("LUN ID") } - prop, err := cmd.StorageManager.SetLunId(volumeId, lunId) + prop, err := cmd.StorageManager.SetLunId(volumeID, lunId) - subs := map[string]interface{}{"VolumeID": volumeId, "VolumeId": volumeId} + subs := map[string]interface{}{"VolumeID": volumeID, "VolumeId": volumeID} if err != nil { return slErr.NewAPIError(T("Failed to set LUN ID for volume {{.VolumeID}}.\n", subs), err.Error(), 2) } @@ -63,7 +64,7 @@ func (cmd *VolumeLunCommand) Run(args []string) error { if err == nil && newLunId == lunId { cmd.UI.Ok() cmd.UI.Print(T("Block volume {{.VolumeId}} is reporting LUN ID {{.LunID}}.", - map[string]interface{}{"VolumeId": volumeId, "LunID": lunId})) + map[string]interface{}{"VolumeId": volumeID, "LunID": lunId})) return nil } } diff --git a/plugin/commands/block/volume_lun_test.go b/plugin/commands/block/volume_lun_test.go index a314e429..7d188de5 100644 --- a/plugin/commands/block/volume_lun_test.go +++ b/plugin/commands/block/volume_lun_test.go @@ -30,6 +30,7 @@ var _ = Describe("Volume lun", func() { cliCommand = block.NewVolumeLunCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Volume lun", func() { @@ -49,9 +50,10 @@ var _ = Describe("Volume lun", func() { }) Context("Volume lun with wrong volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "123") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Volume lun with wrong lun id", func() { diff --git a/plugin/commands/block/volume_modify.go b/plugin/commands/block/volume_modify.go index d8f6d23c..a7462786 100644 --- a/plugin/commands/block/volume_modify.go +++ b/plugin/commands/block/volume_modify.go @@ -2,11 +2,8 @@ package block import ( "fmt" - "strconv" - "github.com/spf13/cobra" - slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" @@ -53,9 +50,9 @@ func NewVolumeModifyCommand(sl *metadata.SoftlayerStorageCommand) *VolumeModifyC func (cmd *VolumeModifyCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } newTier := cmd.NewTier diff --git a/plugin/commands/block/volume_modify_test.go b/plugin/commands/block/volume_modify_test.go index 2d16a5c5..d76b67be 100644 --- a/plugin/commands/block/volume_modify_test.go +++ b/plugin/commands/block/volume_modify_test.go @@ -1,6 +1,7 @@ package block_test import ( + "errors" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -42,6 +43,7 @@ var _ = Describe("Volume Modify", func() { cliCommand = block.NewVolumeModifyCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("sl block volume-modify", func() { @@ -54,9 +56,10 @@ var _ = Describe("Volume Modify", func() { }) Context("Bad Id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "Abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Happy Path", func() { diff --git a/plugin/commands/block/volume_refresh.go b/plugin/commands/block/volume_refresh.go index 98229ea7..59692dae 100644 --- a/plugin/commands/block/volume_refresh.go +++ b/plugin/commands/block/volume_refresh.go @@ -44,9 +44,9 @@ EXAMPLE: func (cmd *VolumeRefreshCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } snapshotId, err := strconv.Atoi(args[1]) if err != nil { diff --git a/plugin/commands/block/volume_refresh_test.go b/plugin/commands/block/volume_refresh_test.go index 6498dc3c..d478a284 100644 --- a/plugin/commands/block/volume_refresh_test.go +++ b/plugin/commands/block/volume_refresh_test.go @@ -29,6 +29,7 @@ var _ = Describe("Block Volume Refresh", func() { cliCommand = block.NewVolumeRefreshCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Block Volume Refresh", func() { @@ -41,9 +42,10 @@ var _ = Describe("Block Volume Refresh", func() { }) Context("Bad VolumeId", func() { It("error resolving volume ID", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "1234") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Bad SnapshotId", func() { diff --git a/plugin/commands/block/volume_set_note.go b/plugin/commands/block/volume_set_note.go index 58cd408b..abaef7ca 100644 --- a/plugin/commands/block/volume_set_note.go +++ b/plugin/commands/block/volume_set_note.go @@ -1,8 +1,6 @@ package block import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -45,9 +43,9 @@ EXAMPLE: func (cmd *VolumeSetNoteCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/block/volume_set_note_test.go b/plugin/commands/block/volume_set_note_test.go index 37898401..182eb129 100644 --- a/plugin/commands/block/volume_set_note_test.go +++ b/plugin/commands/block/volume_set_note_test.go @@ -56,11 +56,12 @@ var _ = Describe("Block Volume Set Note", func() { Context("Bad VolumeId", func() { BeforeEach(func() { FakeStorageManager.VolumeSetNoteReturns(false, errors.New("Invalid input for 'Volume ID'. It must be a positive integer.")) + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) }) It("error resolving volume ID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "abc", "--note=thisismynote") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) From ff83c781a99cb40e695f12e29ddc0df8e7ee0041 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Oct 2024 16:22:38 -0500 Subject: [PATCH 28/38] #722 Updated file commands to use getVolumeId --- plugin/commands/file/access_authorize.go | 6 ++--- plugin/commands/file/access_authorize_test.go | 4 +++- plugin/commands/file/access_revoke.go | 6 ++--- plugin/commands/file/access_revoke_test.go | 4 +++- plugin/commands/file/file.go | 2 +- plugin/commands/file/replica_order.go | 6 ++--- plugin/commands/file/replica_order_test.go | 24 ++++++++++--------- plugin/commands/file/snapshot_cancel.go | 6 ++--- plugin/commands/file/snapshot_cancel_test.go | 4 +++- plugin/commands/file/volume_cancel.go | 9 ++++--- plugin/commands/file/volume_cancel_test.go | 4 +++- plugin/commands/file/volume_detail.go | 5 ++-- plugin/commands/file/volume_detail_test.go | 3 ++- plugin/commands/file/volume_duplicate.go | 5 ++-- plugin/commands/file/volume_duplicate_test.go | 10 ++++---- plugin/commands/file/volume_modify.go | 6 ++--- plugin/commands/file/volume_modify_test.go | 4 +++- plugin/i18n/v2Resources/active.de_DE.json | 2 +- plugin/i18n/v2Resources/active.en-US.json | 4 ++-- plugin/i18n/v2Resources/active.es_ES.json | 4 ++-- plugin/i18n/v2Resources/active.fr_FR.json | 2 +- plugin/i18n/v2Resources/active.it_IT.json | 2 +- plugin/i18n/v2Resources/active.ko_KR.json | 2 +- plugin/i18n/v2Resources/active.pt_BR.json | 4 ++-- plugin/i18n/v2Resources/active.zh_Hans.json | 4 ++-- plugin/i18n/v2Resources/active.zh_Hant.json | 4 ++-- 26 files changed, 69 insertions(+), 67 deletions(-) diff --git a/plugin/commands/file/access_authorize.go b/plugin/commands/file/access_authorize.go index 4e229e65..ab24edbd 100644 --- a/plugin/commands/file/access_authorize.go +++ b/plugin/commands/file/access_authorize.go @@ -1,8 +1,6 @@ package file import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -56,9 +54,9 @@ EXAMPLE: func (cmd *AccessAuthorizeCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } IPIds := cmd.Ip_address_id IPs := cmd.Ip_address diff --git a/plugin/commands/file/access_authorize_test.go b/plugin/commands/file/access_authorize_test.go index 58e01fe1..d3dc21d0 100644 --- a/plugin/commands/file/access_authorize_test.go +++ b/plugin/commands/file/access_authorize_test.go @@ -35,6 +35,7 @@ var _ = Describe("Access Authorize", func() { cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager cliCommand.NetworkManager = fakeNetworkManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Access Authorize", func() { @@ -48,9 +49,10 @@ var _ = Describe("Access Authorize", func() { }) Context("Access Authorize with wrong volume id", func() { It("error resolving volume ID", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) diff --git a/plugin/commands/file/access_revoke.go b/plugin/commands/file/access_revoke.go index 3b3b73b6..7998ce51 100644 --- a/plugin/commands/file/access_revoke.go +++ b/plugin/commands/file/access_revoke.go @@ -1,8 +1,6 @@ package file import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -55,9 +53,9 @@ EXAMPLE: } func (cmd *AccessRevokeCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } if len(cmd.Hardware_id) == 0 && len(cmd.Virtual_id) == 0 && len(cmd.Ip_address_id) == 0 && len(cmd.Ip_address) == 0 && len(cmd.Subnet_id) == 0 { diff --git a/plugin/commands/file/access_revoke_test.go b/plugin/commands/file/access_revoke_test.go index 8709aaf3..af497f83 100644 --- a/plugin/commands/file/access_revoke_test.go +++ b/plugin/commands/file/access_revoke_test.go @@ -35,6 +35,7 @@ var _ = Describe("Access Authorize", func() { cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager cliCommand.NetworkManager = fakeNetworkManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Access Revoke", func() { @@ -47,9 +48,10 @@ var _ = Describe("Access Authorize", func() { }) Context("Access revoke with wrong volume id", func() { It("error resolving volume ID", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) diff --git a/plugin/commands/file/file.go b/plugin/commands/file/file.go index 8fefd4cc..aeb37d50 100644 --- a/plugin/commands/file/file.go +++ b/plugin/commands/file/file.go @@ -21,7 +21,7 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { StorageCommand := &metadata.SoftlayerStorageCommand{ SoftlayerCommand: sl, StorageI18n: map[string]interface{}{"storageType": "file"}, - StorageType: "file", + StorageType: "file", } cobraCmd := &cobra.Command{ Use: "file", diff --git a/plugin/commands/file/replica_order.go b/plugin/commands/file/replica_order.go index 7882602f..36be845b 100644 --- a/plugin/commands/file/replica_order.go +++ b/plugin/commands/file/replica_order.go @@ -2,8 +2,6 @@ package file import ( "fmt" - "strconv" - "github.com/spf13/cobra" "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -53,9 +51,9 @@ EXAMPLE: func (cmd *ReplicaOrderCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return errors.NewInvalidSoftlayerIdInputError("Volume ID") + return err } snapshotSchedule := cmd.SnapshotSchedule diff --git a/plugin/commands/file/replica_order_test.go b/plugin/commands/file/replica_order_test.go index 2c0b372c..1917b487 100644 --- a/plugin/commands/file/replica_order_test.go +++ b/plugin/commands/file/replica_order_test.go @@ -31,6 +31,7 @@ var _ = Describe("Replica order", func() { cliCommand = file.NewReplicaOrderCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Replicant order", func() { @@ -43,14 +44,15 @@ var _ = Describe("Replica order", func() { }) Context("Replicant order with wrong volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Replicant order without -s", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-s|--snapshot-schedule] is required, options are: HOURLY, DAILY, WEEKLY.")) }) @@ -58,7 +60,7 @@ var _ = Describe("Replica order", func() { Context("Replicant order with wrong -s", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "yearly") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "yearly") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-s|--snapshot-schedule] is required, options are: HOURLY, DAILY, WEEKLY.")) }) @@ -66,7 +68,7 @@ var _ = Describe("Replica order", func() { Context("Replicant order without -d", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-d|--datacenter] is required.")) }) @@ -74,7 +76,7 @@ var _ = Describe("Replica order", func() { Context("Replicant order with wrong tier", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-t", "0.3") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY", "-d", "dal09", "-t", "0.3") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: [-t|--tier] is optional, options are: 0.25,2,4,10.")) }) @@ -82,7 +84,7 @@ var _ = Describe("Replica order", func() { Context("Replicant order with wrong iops", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-i", "9") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY", "-d", "dal09", "-i", "9") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: -i|--iops must be between 100 and 6000, inclusive.")) }) @@ -90,7 +92,7 @@ var _ = Describe("Replica order", func() { Context("Replicant order with wrong iops", func() { It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-i", "1234") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY", "-d", "dal09", "-i", "1234") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: -i|--iops must be a multiple of 100.")) }) @@ -101,9 +103,9 @@ var _ = Describe("Replica order", func() { FakeStorageManager.OrderReplicantVolumeReturns(datatypes.Container_Product_Order_Receipt{}, errors.New("Internal Server Error")) }) It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-t", "4", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY", "-d", "dal09", "-t", "4", "-f") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to order replicant for volume 123.Please verify your options and try again.")) + Expect(err.Error()).To(ContainSubstring("Failed to order replicant for volume 1234.Please verify your options and try again.")) Expect(err.Error()).To(ContainSubstring("Internal Server Error")) }) }) @@ -127,12 +129,12 @@ var _ = Describe("Replica order", func() { nil) }) It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-t", "4", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY", "-d", "dal09", "-t", "4", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) }) It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "123", "-s", "DAILY", "-d", "dal09", "-i", "3000", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-s", "DAILY", "-d", "dal09", "-i", "3000", "-f") Expect(err).NotTo(HaveOccurred()) Expect(fakeUI.Outputs()).To(ContainSubstring("Order 123456 was placed.")) }) diff --git a/plugin/commands/file/snapshot_cancel.go b/plugin/commands/file/snapshot_cancel.go index 26330136..4eb6f3d5 100644 --- a/plugin/commands/file/snapshot_cancel.go +++ b/plugin/commands/file/snapshot_cancel.go @@ -1,8 +1,6 @@ package file import ( - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -48,9 +46,9 @@ EXAMPLE: func (cmd *SnapshotCancelCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } subs := map[string]interface{}{"ID": volumeID} if !cmd.Force { diff --git a/plugin/commands/file/snapshot_cancel_test.go b/plugin/commands/file/snapshot_cancel_test.go index f69785f3..b62db1a8 100644 --- a/plugin/commands/file/snapshot_cancel_test.go +++ b/plugin/commands/file/snapshot_cancel_test.go @@ -29,6 +29,7 @@ var _ = Describe("Snapshot Cancel", func() { cliCommand = file.NewSnapshotCancelCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Snapshot cancel", func() { @@ -41,9 +42,10 @@ var _ = Describe("Snapshot Cancel", func() { }) Context("Snapshot cancel with wrong volume id", func() { It("error resolving volume ID", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) diff --git a/plugin/commands/file/volume_cancel.go b/plugin/commands/file/volume_cancel.go index b8ee89eb..87a5d735 100644 --- a/plugin/commands/file/volume_cancel.go +++ b/plugin/commands/file/volume_cancel.go @@ -1,7 +1,6 @@ package file import ( - "strconv" "strings" "github.com/spf13/cobra" @@ -47,12 +46,12 @@ EXAMPLE: } func (cmd *VolumeCancelCommand) Run(args []string) error { - - volumeID, err := strconv.Atoi(args[0]) - subs := map[string]interface{}{"ID": volumeID, "VolumeId": volumeID} + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } + subs := map[string]interface{}{"ID": volumeID, "VolumeId": volumeID} + if !cmd.Force { confirm, err := cmd.UI.Confirm(T("This will cancel the file volume: {{.ID}} and cannot be undone. Continue?", subs)) if err != nil { diff --git a/plugin/commands/file/volume_cancel_test.go b/plugin/commands/file/volume_cancel_test.go index ed84ebf7..fbc14eda 100644 --- a/plugin/commands/file/volume_cancel_test.go +++ b/plugin/commands/file/volume_cancel_test.go @@ -29,6 +29,7 @@ var _ = Describe("Volume cancel", func() { cliCommand = file.NewVolumeCancelCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Volume cancel", func() { @@ -41,9 +42,10 @@ var _ = Describe("Volume cancel", func() { }) Context("Volume cancel with wrong volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) diff --git a/plugin/commands/file/volume_detail.go b/plugin/commands/file/volume_detail.go index 0b50a0c3..41d186fd 100644 --- a/plugin/commands/file/volume_detail.go +++ b/plugin/commands/file/volume_detail.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "net/url" - "strconv" "strings" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal" @@ -43,9 +42,9 @@ func NewVolumeDetailCommand(sl *metadata.SoftlayerStorageCommand) *VolumeDetailC func (cmd *VolumeDetailCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/file/volume_detail_test.go b/plugin/commands/file/volume_detail_test.go index 4ad2ed1e..05555bf3 100644 --- a/plugin/commands/file/volume_detail_test.go +++ b/plugin/commands/file/volume_detail_test.go @@ -38,9 +38,10 @@ var _ = Describe("Volume detail", func() { }) Context("Volume detail with wrong volume id", func() { It("return error", func() { + fakeHandler.AddApiError("SoftLayer_Account", "getNasNetworkStorage", 500, "BAD Volume ID") err := testhelpers.RunCobraCommand(cliCommand.Command, "abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'. It must be a positive integer.")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) diff --git a/plugin/commands/file/volume_duplicate.go b/plugin/commands/file/volume_duplicate.go index d673eee3..5b2d8ad7 100644 --- a/plugin/commands/file/volume_duplicate.go +++ b/plugin/commands/file/volume_duplicate.go @@ -2,7 +2,6 @@ package file import ( "fmt" - "strconv" "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -56,9 +55,9 @@ EXAMPLE: func (cmd *VolumeDuplicateCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } outputFormat := cmd.GetOutputFlag() diff --git a/plugin/commands/file/volume_duplicate_test.go b/plugin/commands/file/volume_duplicate_test.go index 2d033b77..499f9633 100644 --- a/plugin/commands/file/volume_duplicate_test.go +++ b/plugin/commands/file/volume_duplicate_test.go @@ -43,6 +43,7 @@ var _ = Describe("Volume duplicate", func() { cliCommand = file.NewVolumeDuplicateCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("Volume duplicate", func() { @@ -55,9 +56,10 @@ var _ = Describe("Volume duplicate", func() { }) Context("Bad volume id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "ZZZ") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Volume ID")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Volume duplicate with 0 DuplicateSnapshotSize", func() { @@ -65,7 +67,7 @@ var _ = Describe("Volume duplicate", func() { FakeStorageManager.OrderDuplicateVolumeReturns(FakeOrderReceipt, nil) }) It("No snapshot size ordered", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "12345", "--duplicate-snapshot-size", "0", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--duplicate-snapshot-size", "0", "-f") Expect(err).NotTo(HaveOccurred()) results := fakeUI.Outputs() calledWith := FakeStorageManager.OrderDuplicateVolumeArgsForCall(0) @@ -78,7 +80,7 @@ var _ = Describe("Volume duplicate", func() { FakeStorageManager.OrderDuplicateVolumeReturns(FakeOrderReceipt, nil) }) It("No snapshot size ordered", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "12345", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-f") Expect(err).NotTo(HaveOccurred()) results := fakeUI.Outputs() calledWith := FakeStorageManager.OrderDuplicateVolumeArgsForCall(0) @@ -91,7 +93,7 @@ var _ = Describe("Volume duplicate", func() { FakeStorageManager.OrderDuplicateVolumeReturns(FakeOrderReceipt, errors.New("SoftLayer_Exception_ApiError")) }) It("Print Error Output", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command, "12345", "-f") + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "-f") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("SoftLayer_Exception_ApiError")) }) diff --git a/plugin/commands/file/volume_modify.go b/plugin/commands/file/volume_modify.go index 740c5ed0..78d2fb40 100644 --- a/plugin/commands/file/volume_modify.go +++ b/plugin/commands/file/volume_modify.go @@ -2,8 +2,6 @@ package file import ( "fmt" - "strconv" - "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -53,9 +51,9 @@ func NewVolumeModifyCommand(sl *metadata.SoftlayerStorageCommand) *VolumeModifyC func (cmd *VolumeModifyCommand) Run(args []string) error { - volumeID, err := strconv.Atoi(args[0]) + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { - return slErr.NewInvalidSoftlayerIdInputError("Volume ID") + return err } newTier := cmd.NewTier diff --git a/plugin/commands/file/volume_modify_test.go b/plugin/commands/file/volume_modify_test.go index f8029cb4..99389fd4 100644 --- a/plugin/commands/file/volume_modify_test.go +++ b/plugin/commands/file/volume_modify_test.go @@ -42,6 +42,7 @@ var _ = Describe("Volume Modify", func() { cliCommand = file.NewVolumeModifyCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") cliCommand.StorageManager = FakeStorageManager + FakeStorageManager.GetVolumeIdReturns(1234, nil) }) Describe("sl file volume-modify", func() { @@ -54,9 +55,10 @@ var _ = Describe("Volume Modify", func() { }) Context("Bad Id", func() { It("return error", func() { + FakeStorageManager.GetVolumeIdReturns(0, errors.New("BAD Volume ID")) err := testhelpers.RunCobraCommand(cliCommand.Command, "Abc") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid input for 'Volume ID'")) + Expect(err.Error()).To(ContainSubstring("BAD Volume ID")) }) }) Context("Happy Path", func() { diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index ed90cfbe..4842b173 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -371,7 +371,7 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners DATENTRÄGER_ID [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Dieser Befehl listet vorhandene Replikatdatenträger für Blockdatenträger mit der ID 12345678 auf." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Dieser Befehl bricht den Snapshot mit der ID 12345678 sofort ab, ohne eine Bestätigung anzufordern." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 6090a578..eea283ee 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -371,8 +371,8 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud." diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index d3365b24..801d1f4e 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -371,8 +371,8 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Este mandato lista los volúmenes replicantes existentes del volumen en bloques con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Este mandato cancela la instantánea con ID 12345678 de forma inmediata, sin pedir confirmación." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Este mandato cancela la instantánea con ID 12345678 de forma inmediata, sin pedir confirmación." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n Este mandato crea una instantánea del volumen con ID 12345678 y con la nota de adición snapshotforibmcloud." diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 0f9ff9b0..1ca5a236 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -371,7 +371,7 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Cette commande répertorie les volumes réplicants existants pour le volume de blocs dont l'ID est 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel ID_INSTANTANE [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Cette commande annule immédiatement l'instantané dont l'ID est 12345678 sans demander de confirmation." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index 09af1797..ef59d317 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -371,7 +371,7 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Questo comando elenca i volumi replicanti esistenti per il volume di blocco con ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel ID_ISTANTANEA [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Questo comando annulla l'istantanea con ID 12345678 immediatamente senza chiedere conferma." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index e1add0c0..42baa33c 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -371,7 +371,7 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-partners VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 이 명령은 블록 볼륨(ID 12345678)에 대한 기존 복제 볼륨을 나열합니다." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 이 명령은 확인 요청 없이 스냅샷(ID 12345678)을 즉시 취소합니다." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index 1121599a..7e9d214d 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -371,8 +371,8 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n Esse comando lista os volumes replicantes existentes para o volume de bloco com o ID 12345678." }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Esse comando cancela a captura instantânea com o ID 12345678 imediatamente sem solicitar confirmação." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n Esse comando cancela a captura instantânea com o ID 12345678 imediatamente sem solicitar confirmação." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n Esse comando cria uma captura instantânea para o volume com o ID 12345678 e com a nota de inclusão como snapshotforibmcloud." diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index 927cc3c4..51fbb2f9 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -371,8 +371,8 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 此命令列出标识为 12345678 的块卷的现有复制卷。" }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 此命令立即取消标识为 12345678 的快照,而不要求确认。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 此命令立即取消标识为 12345678 的快照,而不要求确认。" }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n 此命令为标识为 12345678 的卷创建快照,并附加注释 snapshotforibmcloud。" diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 400cf03a..88d0cba4 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -371,8 +371,8 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n 這個指令會列出區塊磁區(其 ID 為 12345678)的現有抄本磁區。" }, - "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 這個指令會立即取消 Snapshot(其 ID 為 12345678),而不要求確認。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n 這個指令會立即取消 Snapshot(其 ID 為 12345678),而不要求確認。" }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n 這個指令會為磁區(其 ID 為 12345678)建立 Snapshot,並新增附註 snapshotforibmcloud。" From e87b49c3b58fbcfacf509c3ef484d024e2a73365 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Oct 2024 17:50:06 -0500 Subject: [PATCH 29/38] #722 fixed up i18n file --- plugin/commands/file/volume_modify.go | 1 - plugin/commands/file/volume_modify_test.go | 1 + plugin/i18n/v2Resources/active.en-US.json | 42 ++++++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/plugin/commands/file/volume_modify.go b/plugin/commands/file/volume_modify.go index 78d2fb40..6ee14777 100644 --- a/plugin/commands/file/volume_modify.go +++ b/plugin/commands/file/volume_modify.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" diff --git a/plugin/commands/file/volume_modify_test.go b/plugin/commands/file/volume_modify_test.go index 99389fd4..a6cc6def 100644 --- a/plugin/commands/file/volume_modify_test.go +++ b/plugin/commands/file/volume_modify_test.go @@ -1,6 +1,7 @@ package file_test import ( + "errors" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index eea283ee..11a3e3ea 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -347,11 +347,8 @@ "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0": { "other": "${COMMAND_NAME} sl {{.Command}} usage IDENTIFIER [OPTIONS]\nUsage information of a virtual server.\nExample:\n ${COMMAND_NAME} sl {{.Command}} usage 1234 --start 2006-01-02 --end 2006-01-02 --valid-data cpu0" }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678." - }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID." + "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "${COMMAND_NAME} sl {{.storageType}} access-authorize VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'" @@ -362,17 +359,11 @@ "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678." }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." + "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "${COMMAND_NAME} sl {{.storageType}} replica-order VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." - }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678." - }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." + "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-cancel SNAPSHOT_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." }, "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud.": { "other": "${COMMAND_NAME} sl {{.storageType}} snapshot-create VOLUME_ID [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-create 12345678 --note snapshotforibmcloud\n This command creates a snapshot for volume with ID 12345678 and with addition note as snapshotforibmcloud." @@ -1886,6 +1877,24 @@ "Duplicate Volume Properties": { "other": "Duplicate Volume Properties" }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321\n This command authorizes virtual server with ID 87654321 to access volume with ID 12345678." + }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID." + }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." + }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-order 12345678 -s DAILY -d dal09 --tier 4 --os-type LINUX\n This command orders a replica for volume with ID 12345678, which performs DAILY replication, is located at dal09, tier level is 4, OS type is Linux." + }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-partners 12345678\n This command lists existing replicant volumes for block volume with ID 12345678." + }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." + }, "EXAMPLE: \n\t${COMMAND_NAME} sl order place CLOUD_SERVER DALLAS13 GUEST_CORES_4 RAM_16_GB REBOOT_REMOTE_CONSOLE 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS BANDWIDTH_0_GB_2 1_IP_ADDRESS GUEST_DISK_100_GB_SAN OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI MONITORING_HOST_PING NOTIFICATION_EMAIL_AND_TICKET AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING --billing hourly --extras '{\"virtualGuests\": [{\"hostname\": \"test\", \"domain\": \"softlayer.com\"}]}' --complex-type SoftLayer_Container_Product_Order_Virtual_Guest\n\tThis command orders an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, Ubuntu 16.04, and 1 Gbps public & private uplink in dal13": { "other": "EXAMPLE: \n\t${COMMAND_NAME} sl order place CLOUD_SERVER DALLAS13 GUEST_CORES_4 RAM_16_GB REBOOT_REMOTE_CONSOLE 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS BANDWIDTH_0_GB_2 1_IP_ADDRESS GUEST_DISK_100_GB_SAN OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI MONITORING_HOST_PING NOTIFICATION_EMAIL_AND_TICKET AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING --billing hourly --extras '{\"virtualGuests\": [{\"hostname\": \"test\", \"domain\": \"softlayer.com\"}]}' --complex-type SoftLayer_Container_Product_Order_Virtual_Guest\n\tThis command orders an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, Ubuntu 16.04, and 1 Gbps public & private uplink in dal13" }, @@ -5756,6 +5765,9 @@ "Schedule": { "other": "Schedule" }, + "Search for volume {{.VolumeName}} found {{.VolumeCount}} volumes, expected 1.": { + "other": "Search for volume {{.VolumeName}} found {{.VolumeCount}} volumes, expected 1." + }, "Seconds between checks. [2-60]": { "other": "Seconds between checks. [2-60]" }, From b24db356e578c1375c2a1bdbf97d7eff862feee3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 23 Oct 2024 16:11:53 -0500 Subject: [PATCH 30/38] Updated help message for volume_modify to include links to IBM documentation about sizes --- plugin/commands/block/volume_modify.go | 6 ++++-- plugin/i18n/v2Resources/active.de_DE.json | 2 +- plugin/i18n/v2Resources/active.en-US.json | 6 ++++++ plugin/i18n/v2Resources/active.es_ES.json | 4 ++-- plugin/i18n/v2Resources/active.fr_FR.json | 2 +- plugin/i18n/v2Resources/active.it_IT.json | 2 +- plugin/i18n/v2Resources/active.ja_JP.json | 4 ++-- plugin/i18n/v2Resources/active.ko_KR.json | 2 +- plugin/i18n/v2Resources/active.pt_BR.json | 4 ++-- plugin/i18n/v2Resources/active.zh_Hans.json | 4 ++-- plugin/i18n/v2Resources/active.zh_Hant.json | 4 ++-- 11 files changed, 24 insertions(+), 16 deletions(-) diff --git a/plugin/commands/block/volume_modify.go b/plugin/commands/block/volume_modify.go index a7462786..1d182fdf 100644 --- a/plugin/commands/block/volume_modify.go +++ b/plugin/commands/block/volume_modify.go @@ -28,7 +28,9 @@ func NewVolumeModifyCommand(sl *metadata.SoftlayerStorageCommand) *VolumeModifyC cobraCmd := &cobra.Command{ Use: "volume-modify " + T("IDENTIFIER"), Short: T("Modify an existing block storage volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS] + Long: T(`Valid size and iops options can be found here: +https://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations +https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 @@ -42,7 +44,7 @@ func NewVolumeModifyCommand(sl *metadata.SoftlayerStorageCommand) *VolumeModifyC } cobraCmd.Flags().IntVarP(&thisCmd.NewSize, "new-size", "c", 0, T("New Size of block volume in GB. ***If no size is given, the original size of volume is used.***\n Potential Sizes: [20, 40, 80, 100, 250, 500, 1000, 2000, 4000, 8000, 12000]\n Minimum: [the original size of the volume]")) cobraCmd.Flags().IntVarP(&thisCmd.NewIops, "new-iops", "i", 0, T("Performance Storage IOPS, between 100 and 6000 in multiples of 100 [only for performance volumes] ***If no IOPS value is specified, the original IOPS value of the volume will be used.***")) - cobraCmd.Flags().Float64VarP(&thisCmd.NewTier, "new-tier", "t", 0, T("Endurance Storage Tier (IOPS per GB) [only for endurance volumes] ***If no tier is specified, the original tier of the volume will be used.***")) + cobraCmd.Flags().Float64VarP(&thisCmd.NewTier, "new-tier", "t", 0, T("Endurance Storage Tier (IOPS per GB) [only for endurance volumes] ***If no tier is specified, the original tier of the volume will be used.***") + "\n" + T("Tiers: [0.25, 2, 4, 10]")) cobraCmd.Flags().BoolVarP(&thisCmd.Force, "force", "f", false, T("Force operation without confirmation")) thisCmd.Command = cobraCmd return thisCmd diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index 4842b173..a6493c22 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Dieser Befehl listet alle Endurance-Datenträger für das aktuelle Konto in dal09 auf und sortiert sie nach Kapazität." }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify DATENTRÄGER_ID [OPTIONEN]\n\n BEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Dieser Befehl ändert einen Datenträger 12345678 mit der Größe 1000 GB, E/A-Operationen pro Sekunde sind 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Dieser Befehl ändert einen Datenträger 12345678 mit einer Größe von 500 GB und einer Tierebene von 4 E/A-Operationen pro Sekunde pro GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 11a3e3ea..894e5fdc 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -6845,6 +6845,9 @@ "Tier": { "other": "Tier" }, + "Tiers: [0.25, 2, 4, 10]": { + "other": "Tiers: [0.25, 2, 4, 10]" + }, "Title": { "other": "Title" }, @@ -7169,6 +7172,9 @@ "Valid quantities vary by type.\n\t- public IPv4: 4, 8, 16, 32\n\t- private IPv4: 4, 8, 16, 32, 64\n\t- public IPv6: 64\n\nEXAMPLE:\n\t${COMMAND_NAME} sl subnet create public 16 567\n\tThis command creates a public subnet with 16 IPv4 addresses and places it on vlan with ID 567.": { "other": "Valid quantities vary by type.\n\t- public IPv4: 4, 8, 16, 32\n\t- private IPv4: 4, 8, 16, 32, 64\n\t- public IPv6: 64\n\nEXAMPLE:\n\t${COMMAND_NAME} sl subnet create public 16 567\n\tThis command creates a public subnet with 16 IPv4 addresses and places it on vlan with ID 567." }, + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB." + }, "Value": { "other": "Value" }, diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index 801d1f4e..a6dc9498 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Este mandato lista todos los volúmenes de resistencia de la cuenta actual que están ubicados en dal09 y los ordena por capacidad." }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Este comando modifica el volumen 12345678 con tamaño 1000 GB, IOPS 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Este comando modifica el volumen 12345678 con tamaño 500 GB, nivel 4 IOPS por GB." + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Este comando modifica el volumen 12345678 con tamaño 1000 GB, IOPS 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Este comando modifica el volumen 12345678 con tamaño 500 GB, nivel 4 IOPS por GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Este comando lista todas las opciones de creación de un volumen de almacenamiento en bloques, incluidos el tipo de almacenamiento, el tamaño del volumen, el tipo de SO, las IOPS, el nivel, el centro de datos y el tamaño de la instantánea." diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 1ca5a236..2912784d 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Cette commande répertorie tous les volumes d'endurance du compte courant qui se trouvent dans le centre de données dal09 et les trie par capacité." }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify ID_VOLUME [OPTIONS]\n\n EXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Cette commande modifie le volume 12345678 avec une taille de 1000 Go et 4000 IOPS.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Cette commande modifie le volume 12345678 avec une taille de 500 Go et le niveau 4 IOPS par Go." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index ef59d317..23784e2a 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Questo comando elenca tutti i volumi di endurance sull'account corrente che si trovano in dal09 e li ordina per capacità." }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify ID_VOLUME [OPZIONI]\n\n ESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Questo comando modifica un volume 12345678 con dimensione 1000GB, IOPS 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Questo comando modifica un volume 12345678 con dimensione 500GB, livello 4 IOPS per GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index 08b00056..42100da2 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance -- sortby capacity_gb \n このコマンドは、 dal09にある現行アカウントのすべてのエンデュランス・ボリュームをリストし、それらを容量でソートします。" }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n 例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n このコマンドは、サイズが1000GB、IOPSが4000のボリュームを変更します。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n このコマンドは、サイズが500GB、層レベルが4 IOPS/GBのボリューム 12345678を変更します。" + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n このコマンドは、サイズが1000GB、IOPSが4000のボリュームを変更します。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n このコマンドは、サイズが500GB、層レベルが4 IOPS/GBのボリューム 12345678を変更します。" }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-options \n このコマンドは、ストレージ・タイプ、ボリューム・サイズ、OS タイプ、IOPS、層レベル、データ・センター、およびスナップショット・サイズなど、ブロック・ストレージ・ボリュームを作成するためのすべてのオプションをリストします。" diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index 42baa33c..d60bf5ce 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n 이 명령은 dal09에 위치한 현재 계정에서 모든 내구성 볼륨을 나열하고 용량별로 정렬합니다." }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [옵션]\n\n 예:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 이 명령은 볼륨 12345678을 수정합니다. 크기는 1000GB이고, IOPS는 4000입니다.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n 이 명령은 볼륨 12345678을 수정합니다. 크기는 500GB이고, 계층 레벨은 GB당 4 IOPS입니다." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index 7e9d214d..fd7a1d0c 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Esse comando lista todos os volumes de resistência na conta atual que estão localizados em dal09 e os classifica por capacidade." }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Esse comando modifica um volume 12345678 com o tamanho de 1.000 GB e o IOPS de 4.000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Esse comando modifica um volume 12345678 com o tamanho de 500 GB e o nível de camada de 4 IOPS por GB." + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Esse comando modifica um volume 12345678 com o tamanho de 1.000 GB e o IOPS de 4.000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Esse comando modifica um volume 12345678 com o tamanho de 500 GB e o nível de camada de 4 IOPS por GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n \nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Esse comando lista todas as opções para criar um volume de armazenamento de bloco, incluindo o tipo de armazenamento, o tamanho do volume, o tipo de S.O., o IOPS, o nível de camada, o data center e o tamanho da captura instantânea." diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index 51fbb2f9..f728bcd5 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n 此命令列出当前帐户上位于 dal09 处的所有耐久性卷,并按容量进行排序。" }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n 示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 此命令修改卷 12345678,其大小为 1000GB,IOPS 为 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 -- new-size 500 -- new-tier 4\n 此命令修改卷 12345678,其大小为 500GB,层级别为 4 IOPS/GB。" + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 此命令修改卷 12345678,其大小为 1000GB,IOPS 为 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 -- new-size 500 -- new-tier 4\n 此命令修改卷 12345678,其大小为 500GB,层级别为 4 IOPS/GB。" }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n 此命令列出用于创建块存储卷的所有选项,包括存储类型、卷大小、操作系统类型、IOPS、层级别、数据中心和快照大小。" diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 88d0cba4..409c8d5b 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n 這個指令會列出現行帳戶位於 dal09 的所有耐久性磁區,並依容量排序。" }, - "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 這個指令會修改磁區 12345678,其大小為 1000GB,IOPS 是 4000。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n 這個指令會修改磁區 12345678,其大小為 500GB,層級層次為每 GB 4 IOPS。" + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 這個指令會修改磁區 12345678,其大小為 1000GB,IOPS 是 4000。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n 這個指令會修改磁區 12345678,其大小為 500GB,層級層次為每 GB 4 IOPS。" }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-options \n 這個指令會列出用於建立區塊儲存空間磁區的所有選項,包括儲存空間類型、磁區大小、OS 類型、IOPS、層級層次、資料中心及 Snapshot 大小。" From b08cf853e5636489b61732b9c613ae28aa06ac7c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 23 Oct 2024 17:16:07 -0500 Subject: [PATCH 31/38] #733 updated help messages for block and file volume-order and volume-options --- plugin/commands/block/volume_options.go | 11 ++- plugin/commands/block/volume_order.go | 4 +- plugin/commands/file/file.go | 2 +- plugin/commands/file/volume_options.go | 90 --------------------- plugin/commands/file/volume_options_test.go | 69 ---------------- plugin/commands/file/volume_order.go | 4 +- plugin/i18n/v2Resources/active.de_DE.json | 6 +- plugin/i18n/v2Resources/active.en-US.json | 12 +-- plugin/i18n/v2Resources/active.es_ES.json | 10 +-- plugin/i18n/v2Resources/active.fr_FR.json | 8 +- plugin/i18n/v2Resources/active.it_IT.json | 8 +- plugin/i18n/v2Resources/active.ja_JP.json | 10 +-- plugin/i18n/v2Resources/active.ko_KR.json | 6 +- plugin/i18n/v2Resources/active.pt_BR.json | 10 +-- plugin/i18n/v2Resources/active.zh_Hans.json | 10 +-- plugin/i18n/v2Resources/active.zh_Hant.json | 10 +-- 16 files changed, 53 insertions(+), 217 deletions(-) delete mode 100644 plugin/commands/file/volume_options.go delete mode 100644 plugin/commands/file/volume_options_test.go diff --git a/plugin/commands/block/volume_options.go b/plugin/commands/block/volume_options.go index 089686eb..127f5652 100644 --- a/plugin/commands/block/volume_options.go +++ b/plugin/commands/block/volume_options.go @@ -31,12 +31,11 @@ func NewVolumeOptionsCommand(sl *metadata.SoftlayerStorageCommand) *VolumeOption } cobraCmd := &cobra.Command{ Use: "volume-options", - Short: T("List all options for ordering a block storage"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} volume-options - -EXAMPLE: - ${COMMAND_NAME} sl {{.storageType}} volume-options - This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.`, sl.StorageI18n), + Short: T("List all options for ordering a block or file storage volume"), + Long: T("List all options for ordering a block or file storage volume") + "\n\n" + + T("See Also:") + "\n" + + "\thttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\n" + + "\thttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n" , Args: metadata.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) diff --git a/plugin/commands/block/volume_order.go b/plugin/commands/block/volume_order.go index fb470598..44458992 100644 --- a/plugin/commands/block/volume_order.go +++ b/plugin/commands/block/volume_order.go @@ -38,9 +38,7 @@ func NewVolumeOrderCommand(sl *metadata.SoftlayerStorageCommand) *VolumeOrderCom cobraCmd := &cobra.Command{ Use: "volume-order", Short: T("Order a block storage volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09 This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09. ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500 diff --git a/plugin/commands/file/file.go b/plugin/commands/file/file.go index aeb37d50..34ece948 100644 --- a/plugin/commands/file/file.go +++ b/plugin/commands/file/file.go @@ -52,6 +52,7 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { cobraCmd.AddCommand(block.NewVolumeRefreshCommand(StorageCommand).Command) cobraCmd.AddCommand(block.NewVolumeConvertCommand(StorageCommand).Command) cobraCmd.AddCommand(block.NewSnapshotOrderCommand(StorageCommand).Command) + cobraCmd.AddCommand(block.NewVolumeOptionsCommand(StorageCommand).Command) // Unique File Commands, even these can likely be merged in a later version. cobraCmd.AddCommand(NewAccessAuthorizeCommand(StorageCommand).Command) @@ -65,6 +66,5 @@ func SetupCobraCommands(sl *metadata.SoftlayerCommand) *cobra.Command { cobraCmd.AddCommand(NewVolumeListCommand(StorageCommand).Command) cobraCmd.AddCommand(NewVolumeOrderCommand(StorageCommand).Command) cobraCmd.AddCommand(NewVolumeModifyCommand(StorageCommand).Command) - cobraCmd.AddCommand(NewVolumeOptionsCommand(StorageCommand).Command) return cobraCmd } diff --git a/plugin/commands/file/volume_options.go b/plugin/commands/file/volume_options.go deleted file mode 100644 index 2978ed9b..00000000 --- a/plugin/commands/file/volume_options.go +++ /dev/null @@ -1,90 +0,0 @@ -package file - -import ( - "bytes" - "strings" - - "github.com/spf13/cobra" - - "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal" - - slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" - . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/utils" -) - -var ( - volumeSizes = []string{"20", "40", "80", "100", "250", "500", "1000", "2000-3000", "4000-7000", "8000-9000", "10000-12000"} -) - -type VolumeOptionsCommand struct { - *metadata.SoftlayerStorageCommand - Command *cobra.Command - StorageManager managers.StorageManager -} - -func NewVolumeOptionsCommand(sl *metadata.SoftlayerStorageCommand) *VolumeOptionsCommand { - thisCmd := &VolumeOptionsCommand{ - SoftlayerStorageCommand: sl, - StorageManager: managers.NewStorageManager(sl.Session), - } - cobraCmd := &cobra.Command{ - Use: "volume-options", - Short: T("List all options for ordering a file storage"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} volume-options - -EXAMPLE: - ${COMMAND_NAME} sl {{.storageType}} volume-options - This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.`, sl.StorageI18n), - Args: metadata.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return thisCmd.Run(args) - }, - } - - thisCmd.Command = cobraCmd - return thisCmd -} - -func (cmd *VolumeOptionsCommand) Run(args []string) error { - table := cmd.UI.Table([]string{"name", "value"}) - locations, err := cmd.StorageManager.GetAllDatacenters() - if err != nil { - return slErr.NewAPIError(T("Failed to get all datacenters.\n"), err.Error(), 2) - } - table.Add(T("Storage Type"), "performance,endurance") - table.Add(T("Size (GB)"), utils.StringSliceToString(volumeSizes)) - - buf := new(bytes.Buffer) - iopsTable := terminal.NewTable(buf, append([]string{T("Size (GB)")}, volumeSizes...)) - iopsTable.Add(T("Min IOPS"), "100", "100", "100", "100", "100", "100", "100", "200", "300", "500", "1000") - iopsTable.Add(T("Max IOPS"), "1000", "2000", "4000", "6000", "6000", "6000 or 10000", "6000 or 20000", "6000 or 40000", "6000 or 48000", "6000 or 48000", "6000 or 48000") - iopsTable.Print() - table.Add(T("IOPS"), buf.String()) - table.Add(T("Tier"), "0.25,2,4,10") - datacentersName := []string{} - for _, location := range locations { - datacentersName = append(datacentersName, utils.FormatStringPointer(location.Name)) - } - table.Add(T("Location"), strings.Join(datacentersName, ",")) - buf = new(bytes.Buffer) - snapshotTable := terminal.NewTable(buf, []string{T("Storage Size (GB)"), T("Available Snapshot Size (GB)")}) - snapshotTable.Add(volumeSizes[0], "0,5,10,20") - snapshotTable.Add(volumeSizes[1], "0,5,10,20,40") - snapshotTable.Add(volumeSizes[2], "0,5,10,20,40,60,80") - snapshotTable.Add(volumeSizes[3], "0,5,10,20,40,60,80,100") - snapshotTable.Add(volumeSizes[4], "0,5,10,20,40,60,80,100,150,200,250") - snapshotTable.Add(volumeSizes[5], "0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500") - snapshotTable.Add(volumeSizes[6], "0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000") - snapshotTable.Add(volumeSizes[7], "0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000") - snapshotTable.Add(volumeSizes[8], "0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000") - snapshotTable.Add(volumeSizes[9], "0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000") - snapshotTable.Add(volumeSizes[10], "0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000") - snapshotTable.Print() - table.Add(T("Snapshot Size (GB)"), buf.String()) - table.Add(T("Note:"), T("IOPs limit above 6000 available in select data centers, refer to:http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features")) - table.Print() - return nil -} diff --git a/plugin/commands/file/volume_options_test.go b/plugin/commands/file/volume_options_test.go deleted file mode 100644 index 3311ce17..00000000 --- a/plugin/commands/file/volume_options_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package file_test - -import ( - "errors" - - "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/softlayer/softlayer-go/datatypes" - "github.com/softlayer/softlayer-go/session" - "github.com/softlayer/softlayer-go/sl" - - "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/file" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" -) - -var _ = Describe("Volume options", func() { - var ( - fakeUI *terminal.FakeUI - FakeStorageManager *testhelpers.FakeStorageManager - cliCommand *file.VolumeOptionsCommand - fakeSession *session.Session - slCommand *metadata.SoftlayerStorageCommand - ) - BeforeEach(func() { - fakeUI = terminal.NewFakeUI() - FakeStorageManager = new(testhelpers.FakeStorageManager) - slCommand = metadata.NewSoftlayerStorageCommand(fakeUI, fakeSession, "file") - cliCommand = file.NewVolumeOptionsCommand(slCommand) - cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") - cliCommand.StorageManager = FakeStorageManager - }) - - Describe("Volume options", func() { - Context("Volume options with server API call fails", func() { - BeforeEach(func() { - FakeStorageManager.GetAllDatacentersReturns([]datatypes.Location{}, errors.New("Internal Server Error")) - }) - It("return error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Failed to get all datacenters.")) - Expect(err.Error()).To(ContainSubstring("Internal Server Error")) - }) - }) - - Context("Volume options", func() { - BeforeEach(func() { - datacenters := []datatypes.Location{ - {Name: sl.String("ams01")}, - {Name: sl.String("dal06")}, - {Name: sl.String("lon02")}, - {Name: sl.String("sao01")}, - {Name: sl.String("tok02")}, - } - FakeStorageManager.GetAllDatacentersReturns(datacenters, nil) - }) - It("return no error", func() { - err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Storage Type")) - Expect(fakeUI.Outputs()).To(ContainSubstring("0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000")) - - }) - }) - }) -}) diff --git a/plugin/commands/file/volume_order.go b/plugin/commands/file/volume_order.go index e2f196ca..abe587b4 100644 --- a/plugin/commands/file/volume_order.go +++ b/plugin/commands/file/volume_order.go @@ -38,9 +38,7 @@ func NewVolumeOrderCommand(sl *metadata.SoftlayerStorageCommand) *VolumeOrderCom cobraCmd := &cobra.Command{ Use: "volume-order", Short: T("Order a file storage volume"), - Long: T(`${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09 This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09. ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500 diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index a6493c22..6ec06288 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Dieser Befehl listet alle Endurance-Datenträger für das aktuelle Konto in dal09 auf und sortiert sie nach Kapazität." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify DATENTRÄGER_ID [OPTIONEN]\n\n BEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Dieser Befehl ändert einen Datenträger 12345678 mit der Größe 1000 GB, E/A-Operationen pro Sekunde sind 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Dieser Befehl ändert einen Datenträger 12345678 mit einer Größe von 500 GB und einer Tierebene von 4 E/A-Operationen pro Sekunde pro GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { @@ -422,7 +422,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} volume-options \n Dieser Befehl listet alle Optionen für die Erstellung eines Dateispeicherdatenträgers auf, einschließlich Speichertyp, Datenträgergröße, IOPS, Tierebene, Rechenzentrum und Snapshotgröße." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONEN]\n\nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Dieser Befehl bestellt einen Leistungsdatenträger mit der Größe 1000 GB, IOPS 4000, OS LINUX, dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Dieser Befehl bestellt einen Endurance-Datenträger mit der Größe 500 GB, die Tierebene ist 4 E/A-Operationen pro Sekunde pro GB, der Betriebssystemtyp ist XEN, befindet sich in dal09 und die Größe des zusätzlichen Snapshotbereichs ist 500 GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "Alle Images für eigenes Konto auflisten" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "Alle Optionen für die Bestellung eines Blockspeichers auflisten" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 894e5fdc..e21ed64f 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -401,8 +401,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID." @@ -4259,8 +4259,8 @@ "List all images on your account": { "other": "List all images on your account" }, - "List all options for ordering a block storage": { - "other": "List all options for ordering a block storage" + "List all options for ordering a block or file storage volume": { + "other": "List all options for ordering a block or file storage volume" }, "List all options for ordering a file storage": { "other": "List all options for ordering a file storage" @@ -7172,8 +7172,8 @@ "Valid quantities vary by type.\n\t- public IPv4: 4, 8, 16, 32\n\t- private IPv4: 4, 8, 16, 32, 64\n\t- public IPv6: 64\n\nEXAMPLE:\n\t${COMMAND_NAME} sl subnet create public 16 567\n\tThis command creates a public subnet with 16 IPv4 addresses and places it on vlan with ID 567.": { "other": "Valid quantities vary by type.\n\t- public IPv4: 4, 8, 16, 32\n\t- private IPv4: 4, 8, 16, 32, 64\n\t- public IPv6: 64\n\nEXAMPLE:\n\t${COMMAND_NAME} sl subnet create public 16 567\n\tThis command creates a public subnet with 16 IPv4 addresses and places it on vlan with ID 567." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB." + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB." }, "Value": { "other": "Value" diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index a6dc9498..5c7c30f9 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Este mandato lista todos los volúmenes de resistencia de la cuenta actual que están ubicados en dal09 y los ordena por capacidad." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Este comando modifica el volumen 12345678 con tamaño 1000 GB, IOPS 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Este comando modifica el volumen 12345678 con tamaño 500 GB, nivel 4 IOPS por GB." + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Este comando modifica el volumen 12345678 con tamaño 1000 GB, IOPS 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Este comando modifica el volumen 12345678 con tamaño 500 GB, nivel 4 IOPS por GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Este comando lista todas las opciones de creación de un volumen de almacenamiento en bloques, incluidos el tipo de almacenamiento, el tamaño del volumen, el tipo de SO, las IOPS, el nivel, el centro de datos y el tamaño de la instantánea." @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Este comando lista todas las opciones de creación de un volumen de almacenamiento en archivos, incluidos el tipo de almacenamiento, el tamaño del volumen, las IOPS, el nivel, el centro de datos y el tamaño de la instantánea." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Este comando solicita un volumen de rendimiento de tamaño 1000 GB, IOPS 4000, tipo de SO LINUX, ubicado en dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Este comando solicita un volumen de resistencia de tamaño 500 GB, nivel 4 IOPS por GB, tipo de SO XEN, ubicado en dal09 y espacio de instantánea adicional de 500 GB." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Este comando solicita un volumen de rendimiento de tamaño 1000 GB, IOPS 4000, tipo de SO LINUX, ubicado en dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Este comando solicita un volumen de resistencia de tamaño 500 GB, nivel 4 IOPS por GB, tipo de SO XEN, ubicado en dal09 y espacio de instantánea adicional de 500 GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEJEMPLO:\n${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\nRenovar un VOLUME_ID duplicado con una instantánea tomada del SNAPSHOT_ID padre." @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "Listar todas las imágenes de su cuenta" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "Listar todas las opciones para solicitar un almacenamiento en bloque" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 2912784d..755d0e03 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Cette commande répertorie tous les volumes d'endurance du compte courant qui se trouvent dans le centre de données dal09 et les trie par capacité." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify ID_VOLUME [OPTIONS]\n\n EXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Cette commande modifie le volume 12345678 avec une taille de 1000 Go et 4000 IOPS.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Cette commande modifie le volume 12345678 avec une taille de 500 Go et le niveau 4 IOPS par Go." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Cette commande répertorie toutes les options de création d'un volume de stockage de fichiers : le type de stockage, la taille de volume, les IOPS, le niveau, le centre de données et la taille d'instantané." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Cette commande commande un volume de performance avec une taille de 1000 Go, 4000 IOPS, le type de système d'exploitation LINUX, et situé dans le centre de données dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Cette commande commande un volume d'endurance avec une taille de 500 Go, le niveau 4 IOPS par Go, le type de système d'exploitation XEN, situé dans le centre de données dal09, avec un espace d'instantanés supplémentaire de 500 Go." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "EXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Cette commande commande un volume de performance avec une taille de 1000 Go, 4000 IOPS, le type de système d'exploitation LINUX, et situé dans le centre de données dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Cette commande commande un volume d'endurance avec une taille de 500 Go, le niveau 4 IOPS par Go, le type de système d'exploitation XEN, situé dans le centre de données dal09, avec un espace d'instantanés supplémentaire de 500 Go." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh ID_VOLUME ID_INSTANTANE\n\nEXEMPLE :\n${COMMAND_NAME} sl {{.storageType}} volume-refresh ID_VOLUME ID_INSTANTANE\nActualise un ID de volume en double avec un instantané de son ID d'instantané parent." @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "Afficher la liste de toutes les images sur votre compte" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "Afficher la liste de toutes les options pour la commande d'un stockage par blocs" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index 23784e2a..ac3cb4dc 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Questo comando elenca tutti i volumi di endurance sull'account corrente che si trovano in dal09 e li ordina per capacità." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify ID_VOLUME [OPZIONI]\n\n ESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Questo comando modifica un volume 12345678 con dimensione 1000GB, IOPS 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Questo comando modifica un volume 12345678 con dimensione 500GB, livello 4 IOPS per GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Questo comando elenca tutte le opzioni per la creazione di un volume di storage file, inclusi tipo di storage, dimensione del volume IOPS, livello, data center e dimensione dell'istantanea." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Questo comando ordina un volume delle prestazioni con dimensione di 1000 GB, IOPS pari a 4000, tipo di SO LINUX, ubicato in dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Questo comando ordina un volume di endurance con dimensione di 500 GB, livello 4 IOPS per GB, tipo di SO XEN, ubicato in dal09 e dimensione dello spazio istantanea aggiuntivo di 500 GB." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "ESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Questo comando ordina un volume delle prestazioni con dimensione di 1000 GB, IOPS pari a 4000, tipo di SO LINUX, ubicato in dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Questo comando ordina un volume di endurance con dimensione di 500 GB, livello 4 IOPS per GB, tipo di SO XEN, ubicato in dal09 e dimensione dello spazio istantanea aggiuntivo di 500 GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh ID_VOLUME ID_ISTANTANEA\n\nESEMPIO:\n${COMMAND_NAME} sl {{.storageType}} volume-refresh ID_VOLUME ID_ISTANTANEA\nAggiorna un ID_VOLUME duplicato con un'istantanea dal relativo ID_ISTANTANEA parent." @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "Elenca tutte le immagini per il tuo account" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "Elenca tutte le opzioni per l'ordinamento di un archivio blocchi" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index 42100da2..77f57a53 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance -- sortby capacity_gb \n このコマンドは、 dal09にある現行アカウントのすべてのエンデュランス・ボリュームをリストし、それらを容量でソートします。" }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n このコマンドは、サイズが1000GB、IOPSが4000のボリュームを変更します。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n このコマンドは、サイズが500GB、層レベルが4 IOPS/GBのボリューム 12345678を変更します。" + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n このコマンドは、サイズが1000GB、IOPSが4000のボリュームを変更します。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n このコマンドは、サイズが500GB、層レベルが4 IOPS/GBのボリューム 12345678を変更します。" }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-options \n このコマンドは、ストレージ・タイプ、ボリューム・サイズ、OS タイプ、IOPS、層レベル、データ・センター、およびスナップショット・サイズなど、ブロック・ストレージ・ボリュームを作成するためのすべてのオプションをリストします。" @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-options \n このコマンドは、ストレージ・タイプ、ボリューム・サイズ、IOPS、層レベル、データ・センター、およびスナップショット・サイズを含む、ファイル・ストレージ・ボリュームを作成するためのすべてのオプションをリストします。" }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-order -- storage-type performance -- size 1000 -- iops 4000 -- os-type LINUX -d dal09\n このコマンドは、サイズが 1000GB、IOPS が 4000、OS タイプが LINUX (dal09に配置)のパフォーマンス・ボリュームを注文します。\n ${COMMAND_NAME} sl {{.storageType}} volume-order -- storage-type endurance -- size 500 -- tier 4 -- os-type XEN -d dal09 -- snapshot-size 500\n このコマンドは、サイズが 500GB、層レベルが 4 IOPS/GB、OS タイプが XEN ( dal09に配置)、追加のスナップショット・スペース・サイズが 500GBのエンデュランス・ボリュームを注文します。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": " 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-order -- storage-type performance -- size 1000 -- iops 4000 -- os-type LINUX -d dal09\n このコマンドは、サイズが 1000GB、IOPS が 4000、OS タイプが LINUX (dal09に配置)のパフォーマンス・ボリュームを注文します。\n ${COMMAND_NAME} sl {{.storageType}} volume-order -- storage-type endurance -- size 500 -- tier 4 -- os-type XEN -d dal09 -- snapshot-size 500\n このコマンドは、サイズが 500GB、層レベルが 4 IOPS/GB、OS タイプが XEN ( dal09に配置)、追加のスナップショット・スペース・サイズが 500GBのエンデュランス・ボリュームを注文します。" }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID \n 親 SNAPSHOT_ID からのスナップショットを使用して重複 VOLUME_ID をリフレッシュします。" @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "ご使用のアカウントのすべてのイメージをリストします" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "Block Storage の注文に関するすべてのオプションをリストします" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index d60bf5ce..5011f0c0 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -413,7 +413,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n 이 명령은 dal09에 위치한 현재 계정에서 모든 내구성 볼륨을 나열하고 용량별로 정렬합니다." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [옵션]\n\n 예:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 이 명령은 볼륨 12345678을 수정합니다. 크기는 1000GB이고, IOPS는 4000입니다.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n 이 명령은 볼륨 12345678을 수정합니다. 크기는 500GB이고, 계층 레벨은 GB당 4 IOPS입니다." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { @@ -422,7 +422,7 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n 이 명령은 스토리지 유형, 볼륨 크기, IOPS, 계층 레벨, 데이터 센터 및 스냅샷 크기를 포함하여 파일 스토리지 볼륨 작성을 위한 모든 옵션을 나열합니다." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n 이 명령은 성능 볼륨을 주문합니다. 크기는 1000GB이고, IOPS는 4000이고, OS 유형은 LINUX이고, dal09에 위치합니다.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n 이 명령은 내구성 볼륨을 주문합니다. 크기는 500GB이고, 계층 레벨은 GB당 4 IOPS이고, OS 유형은 XEN이고, dal09에 위치하고, 추가 스냅샷 공간 크기는 500GB입니다." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "계정의 모든 이미지 나열" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "블록 스토리지 주문에 대한 모든 옵션 나열" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index fd7a1d0c..fac9bec2 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n Esse comando lista todos os volumes de resistência na conta atual que estão localizados em dal09 e os classifica por capacidade." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Esse comando modifica um volume 12345678 com o tamanho de 1.000 GB e o IOPS de 4.000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Esse comando modifica um volume 12345678 com o tamanho de 500 GB e o nível de camada de 4 IOPS por GB." + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n Esse comando modifica um volume 12345678 com o tamanho de 1.000 GB e o IOPS de 4.000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n Esse comando modifica um volume 12345678 com o tamanho de 500 GB e o nível de camada de 4 IOPS por GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n \nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Esse comando lista todas as opções para criar um volume de armazenamento de bloco, incluindo o tipo de armazenamento, o tamanho do volume, o tipo de S.O., o IOPS, o nível de camada, o data center e o tamanho da captura instantânea." @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n \nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n Esse comando lista todas as opções para criar um volume de armazenamento de arquivo, incluindo o tipo de armazenamento, o tamanho do volume, o IOPS, o nível de camada, o data center e o tamanho da captura instantânea." }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Esse comando solicita um volume de desempenho com o tamanho de 1.000 GB, o IOPS de 4.000, o tipo de S.O. LINUX, localizado em dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Esse comando solicita um volume de resistência com o tamanho de 500 GB, o nível de camada de 4 IOPS por GB, o tipo de S.O. XEN, localizado em dal09 e com o tamanho do espaço de captura instantânea adicional de 500 GB." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n Esse comando solicita um volume de desempenho com o tamanho de 1.000 GB, o IOPS de 4.000, o tipo de S.O. LINUX, localizado em dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n Esse comando solicita um volume de resistência com o tamanho de 500 GB, o nível de camada de 4 IOPS por GB, o tipo de S.O. XEN, localizado em dal09 e com o tamanho do espaço de captura instantânea adicional de 500 GB." }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n Atualizar um VOLUME_ID duplicado com uma captura instantânea do respectivo SNAPSHOT_ID pai." @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "Listar todas as imagens em sua conta" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "Listar todas as opções para pedir um armazenamento de bloco" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index f728bcd5..64f60fd1 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n 此命令列出当前帐户上位于 dal09 处的所有耐久性卷,并按容量进行排序。" }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 此命令修改卷 12345678,其大小为 1000GB,IOPS 为 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 -- new-size 500 -- new-tier 4\n 此命令修改卷 12345678,其大小为 500GB,层级别为 4 IOPS/GB。" + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 此命令修改卷 12345678,其大小为 1000GB,IOPS 为 4000.\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 -- new-size 500 -- new-tier 4\n 此命令修改卷 12345678,其大小为 500GB,层级别为 4 IOPS/GB。" }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n 此命令列出用于创建块存储卷的所有选项,包括存储类型、卷大小、操作系统类型、IOPS、层级别、数据中心和快照大小。" @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n 此命令列出用于创建文件存储卷的所有选项,包括存储类型、卷大小、IOPS、层级别、数据中心和快照大小。" }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n 此命令订购一个性能卷,大小为 1000GB,IOPS 为 4000,操作系统类型为 LINUX,位于 dal09。\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n 此命令订购一个耐久性卷,大小为 500GB,层级别为 4 IOPS/GB,操作系统类型为 XEN,位于 dal09,其他快照空间大小为 500GB。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n 此命令订购一个性能卷,大小为 1000GB,IOPS 为 4000,操作系统类型为 LINUX,位于 dal09。\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n 此命令订购一个耐久性卷,大小为 500GB,层级别为 4 IOPS/GB,操作系统类型为 XEN,位于 dal09,其他快照空间大小为 500GB。" }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n 使用来自其父 SNAPSHOT_ID 的快照刷新重复的 VOLUME_ID。" @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "列出您帐户的所有映像" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "列出用于订购块存储器的所有选项" }, "List all options for ordering a file storage": { diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 409c8d5b..0fef0dfb 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -413,8 +413,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n This command lists all endurance volumes on current account that are located at dal09, and sorts them by capacity.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-list [OPTIONS]\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-list -d dal09 -t endurance --sortby capacity_gb\n 這個指令會列出現行帳戶位於 dal09 的所有耐久性磁區,並依容量排序。" }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cliEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 這個指令會修改磁區 12345678,其大小為 1000GB,IOPS 是 4000。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n 這個指令會修改磁區 12345678,其大小為 500GB,層級層次為每 GB 4 IOPS。" + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\nEXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n 這個指令會修改磁區 12345678,其大小為 1000GB,IOPS 是 4000。\n ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n 這個指令會修改磁區 12345678,其大小為 500GB,層級層次為每 GB 4 IOPS。" }, "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-options \n 這個指令會列出用於建立區塊儲存空間磁區的所有選項,包括儲存空間類型、磁區大小、OS 類型、IOPS、層級層次、資料中心及 Snapshot 大小。" @@ -422,8 +422,8 @@ "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-options \n 這個指令會列出用於建立檔案儲存空間磁區的所有選項,包括儲存空間類型、磁區大小、IOPS、層級層次、資料中心及 Snapshot 大小。" }, - "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-order [OPTIONS]\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n 這個指令會訂購效能磁區,其大小為 1000GB,IOPS 為 4000,OS 類型為 LINUX,位於 dal09。\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n 這個指令會訂購耐久性磁區,其大小為 500GB,層級層次為每 GB 4 IOPS,OS 類型為 XEN,位於 dal09,以及額外的 Snapshot 空間大小 500GB。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n 這個指令會訂購效能磁區,其大小為 1000GB,IOPS 為 4000,OS 類型為 LINUX,位於 dal09。\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n 這個指令會訂購耐久性磁區,其大小為 500GB,層級層次為每 GB 4 IOPS,OS 類型為 XEN,位於 dal09,以及額外的 Snapshot 空間大小 500GB。" }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\n範例:\n ${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n 以來自其母項 SNAPSHOT_ID 的 Snapshot 重新整理重複的 VOLUME_ID。" @@ -4235,7 +4235,7 @@ "List all images on your account": { "other": "列出您帳戶中的所有映像檔" }, - "List all options for ordering a block storage": { + "List all options for ordering a block or file storage volume": { "other": "列出用來訂購區塊儲存空間的所有選項" }, "List all options for ordering a file storage": { From d8adb87d6640a59f221200c2456cfc4809dd70f0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 6 Nov 2024 15:51:13 -0600 Subject: [PATCH 32/38] #740 added dedicated host cancel command --- README.md | 19 +++++ plugin/commands/dedicatedhost/cancel.go | 51 +++++++++++++ plugin/commands/dedicatedhost/cancel_test.go | 60 +++++++++++++++ plugin/commands/dedicatedhost/detail_test.go | 36 +++------ plugin/managers/dedicatedhost.go | 7 ++ plugin/managers/dedicatedhost_test.go | 22 ++++++ .../deleteObject.json | 1 + .../fake_dedicated_host_manager.go | 74 +++++++++++++++++++ 8 files changed, 243 insertions(+), 27 deletions(-) create mode 100644 plugin/commands/dedicatedhost/cancel.go create mode 100644 plugin/commands/dedicatedhost/cancel_test.go create mode 100644 plugin/testfixtures/SoftLayer_Virtual_DedicatedHost/deleteObject.json diff --git a/README.md b/README.md index b8446106..f907cf13 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,25 @@ Expect(slOptions.Filter).To(ContainSubstring(`"id":{"operation":"orderBy","optio Check testhelpers/fake_softlayer_session.go for all the fields that get recorded with an API call. +Heres a fancy way to test an API call matches a few different properties at the same time: + +```go +// This is where the MatchFields/PointTo come from +. "github.com/onsi/gomega/gstruct" + +It("it returns dedicatedhost verify response", func() { + err := dedicatedhostManager.DeleteHost(12345) + Expect(err).NotTo(HaveOccurred()) + apiCalls := fakeHandler.ApiCallLogs + Expect(len(apiCalls)).To(Equal(1)) + Expect(apiCalls[0]).To(MatchFields(IgnoreExtras, Fields{ + "Service": Equal("SoftLayer_Virtual_DedicatedHost"), + "Method": Equal("deleteObject"), + "Options": PointTo(MatchFields(IgnoreExtras, Fields{"Id": PointTo(Equal(12345))})), + })) +}) + +``` ### Test Fakes diff --git a/plugin/commands/dedicatedhost/cancel.go b/plugin/commands/dedicatedhost/cancel.go new file mode 100644 index 00000000..a27c77c6 --- /dev/null +++ b/plugin/commands/dedicatedhost/cancel.go @@ -0,0 +1,51 @@ +package dedicatedhost + +import ( + "strconv" + "github.com/spf13/cobra" + + . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" + slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" +) + +type CancellHostCommand struct { + *metadata.SoftlayerCommand + DedicatedHostManager managers.DedicatedHostManager + Command *cobra.Command +} + +func NewCancelHostCommand(sl *metadata.SoftlayerCommand) *CancellHostCommand { + thisCmd := &CancellHostCommand{ + SoftlayerCommand: sl, + DedicatedHostManager: managers.NewDedicatedhostManager(sl.Session), + } + cobraCmd := &cobra.Command{ + Use: "cancel " + T("IDENTIFIER"), + Short: T("Cancel a dedicated host server immediately."), + Long: T(`If there are any guests on this Dedicated Host, this command will fail until those guests are deleted. +Use 'sl dedicatedhost cancel-guests [IDENTIFIER]' to remove all guests from a host. +Use 'sl vs delete' to remove a specific guest.`), + Args: metadata.OneArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return thisCmd.Run(args) + }, + } + + thisCmd.Command = cobraCmd + return thisCmd +} + +func (cmd *CancellHostCommand) Run(args []string) error { + hardwareID, err := strconv.Atoi(args[0]) + if err != nil { + return slErr.NewInvalidSoftlayerIdInputError("IDENTIFIER") + } + err = cmd.DedicatedHostManager.DeleteHost(hardwareID) + if err != nil { + return err + } + cmd.UI.Print(T("Dedicated Host {{.ID}} was cancelled", map[string]interface{}{"ID": hardwareID})) + return nil +} diff --git a/plugin/commands/dedicatedhost/cancel_test.go b/plugin/commands/dedicatedhost/cancel_test.go new file mode 100644 index 00000000..8d4f5be0 --- /dev/null +++ b/plugin/commands/dedicatedhost/cancel_test.go @@ -0,0 +1,60 @@ +package dedicatedhost_test + +import ( + "errors" + "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/softlayer/softlayer-go/session" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/dedicatedhost" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" +) + +var _ = Describe("Dedicated host cancel", func() { + var ( + fakeUI *terminal.FakeUI + cliCommand *dedicatedhost.CancellHostCommand + fakeSession *session.Session + slCommand *metadata.SoftlayerCommand + FakeDedicatedhostManager *testhelpers.FakeDedicatedHostManager + ) + BeforeEach(func() { + fakeUI = terminal.NewFakeUI() + fakeSession = testhelpers.NewFakeSoftlayerSession([]string{}) + slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) + cliCommand = dedicatedhost.NewCancelHostCommand(slCommand) + cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") + FakeDedicatedhostManager = new(testhelpers.FakeDedicatedHostManager) + cliCommand.DedicatedHostManager = FakeDedicatedhostManager + }) + + Describe("Dedicatedhost cancel usage errors", func() { + Context("Dedicatedhost cancel without ID", func() { + It("return error", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) + }) + }) + }) + Describe("Dedicatedhost cancel usage", func() { + Context("Dedicatedhost cancel Happy Path", func() { + It("Succss", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "12345") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("Dedicated Host 12345 was cancelled")) + }) + }) + Context("Dedicatedhost cancel API errors", func() { + BeforeEach(func() { + FakeDedicatedhostManager.DeleteHostReturns(errors.New("API ERROR")) + }) + It("Handle API error", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "12345") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("API ERROR")) + }) + }) + }) +}) diff --git a/plugin/commands/dedicatedhost/detail_test.go b/plugin/commands/dedicatedhost/detail_test.go index 2830db0e..7ea7965a 100644 --- a/plugin/commands/dedicatedhost/detail_test.go +++ b/plugin/commands/dedicatedhost/detail_test.go @@ -4,7 +4,6 @@ import ( "errors" "time" - . "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/matchers" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -26,7 +25,7 @@ var _ = Describe("Dedicated host detail", func() { ) BeforeEach(func() { fakeUI = terminal.NewFakeUI() - fakeSession = testhelpers.NewFakeSoftlayerSession([]string{}) + fakeSession = testhelpers.NewFakeSoftlayerSession(nil) slCommand = metadata.NewSoftlayerCommand(fakeUI, fakeSession) cliCommand = dedicatedhost.NewDetailCommand(slCommand) cliCommand.Command.PersistentFlags().Var(cliCommand.OutputFlag, "output", "--output=JSON for json output.") @@ -111,35 +110,18 @@ var _ = Describe("Dedicated host detail", func() { It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"1234"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"dedicatedhost"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"56"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"1200"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"242"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"2022-02-01T00:00:00Z"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"2022-02-01T00:00:00Z"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"3"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"dal13"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"wilmawang"})) + Expect(fakeUI.Outputs()).To(ContainSubstring("1234")) + Expect(fakeUI.Outputs()).To(ContainSubstring("dedicatedhost")) + Expect(fakeUI.Outputs()).To(ContainSubstring("dal13")) + Expect(fakeUI.Outputs()).To(ContainSubstring("wilmawang")) }) It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--guests", "--price") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"1234"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"dedicatedhost"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"56"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"1200"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"242"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"2022-02-01T00:00:00Z"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"2022-02-01T00:00:00Z"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"3"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"dal13"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"10"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"wilmawang"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"test.com"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"test"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"9131111-2222-6a10-3333-992c544444"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"1234567"})) + Expect(fakeUI.Outputs()).To(ContainSubstring("1234")) + Expect(fakeUI.Outputs()).To(ContainSubstring("dedicatedhost")) + Expect(fakeUI.Outputs()).To(ContainSubstring("9131111-2222-6a10-3333-992c544444")) + Expect(fakeUI.Outputs()).To(ContainSubstring("1234567")) }) }) }) diff --git a/plugin/managers/dedicatedhost.go b/plugin/managers/dedicatedhost.go index 6cf78f14..e72fad97 100644 --- a/plugin/managers/dedicatedhost.go +++ b/plugin/managers/dedicatedhost.go @@ -45,6 +45,7 @@ type DedicatedHostManager interface { GetCreateOptions(productPackage datatypes.Product_Package) map[string]map[string]string GetVlansOptions(datacenter string, flavor string, productPackage datatypes.Product_Package) ([]datatypes.Network_Vlan, error) ListDedicatedHost(name, datacenter, owner string, orderId int) ([]datatypes.Virtual_DedicatedHost, error) + DeleteHost(identifier int) error } type dedicatedhostManager struct { @@ -302,3 +303,9 @@ func (d dedicatedhostManager) ListDedicatedHost(name, datacenter, owner string, } return resourceList, nil } + +// Calls https://sldn.softlayer.com/reference/services/SoftLayer_Virtual_DedicatedHost/deleteObject/ +func (d dedicatedhostManager) DeleteHost(identifier int) error { + _, err := d.VirtualDedicatedHost.Id(identifier).DeleteObject() + return err +} \ No newline at end of file diff --git a/plugin/managers/dedicatedhost_test.go b/plugin/managers/dedicatedhost_test.go index 496b7f2c..59df1fee 100644 --- a/plugin/managers/dedicatedhost_test.go +++ b/plugin/managers/dedicatedhost_test.go @@ -3,6 +3,7 @@ package managers_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" "github.com/softlayer/softlayer-go/session" "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" @@ -11,13 +12,19 @@ import ( var _ = Describe("DedicatedhostManager", func() { var ( fakeSLSession *session.Session + fakeHandler *testhelpers.FakeTransportHandler dedicatedhostManager managers.DedicatedHostManager ) BeforeEach(func() { fakeSLSession = testhelpers.NewFakeSoftlayerSession(nil) + fakeHandler = testhelpers.GetSessionHandler(fakeSLSession) dedicatedhostManager = managers.NewDedicatedhostManager(fakeSLSession) }) + AfterEach(func() { + fakeHandler.ClearApiCallLogs() + fakeHandler.ClearErrors() + }) Describe("Genereate a dedicatedhost order template", func() { Context("not found Package", func() { @@ -80,4 +87,19 @@ var _ = Describe("DedicatedhostManager", func() { }) }) }) + Describe("Dedicated Host Manager Simple functions", func() { + Context("DeleteHost", func() { + It("it returns dedicatedhost verify response", func() { + err := dedicatedhostManager.DeleteHost(12345) + Expect(err).NotTo(HaveOccurred()) + apiCalls := fakeHandler.ApiCallLogs + Expect(len(apiCalls)).To(Equal(1)) + Expect(apiCalls[0]).To(MatchFields(IgnoreExtras, Fields{ + "Service": Equal("SoftLayer_Virtual_DedicatedHost"), + "Method": Equal("deleteObject"), + "Options": PointTo(MatchFields(IgnoreExtras, Fields{"Id": PointTo(Equal(12345))})), + })) + }) + }) + }) }) diff --git a/plugin/testfixtures/SoftLayer_Virtual_DedicatedHost/deleteObject.json b/plugin/testfixtures/SoftLayer_Virtual_DedicatedHost/deleteObject.json new file mode 100644 index 00000000..f32a5804 --- /dev/null +++ b/plugin/testfixtures/SoftLayer_Virtual_DedicatedHost/deleteObject.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/plugin/testhelpers/fake_dedicated_host_manager.go b/plugin/testhelpers/fake_dedicated_host_manager.go index eba60c80..989d56a9 100644 --- a/plugin/testhelpers/fake_dedicated_host_manager.go +++ b/plugin/testhelpers/fake_dedicated_host_manager.go @@ -22,6 +22,17 @@ type FakeDedicatedHostManager struct { result1 []managers.StatusInfo result2 error } + DeleteHostStub func(int) error + deleteHostMutex sync.RWMutex + deleteHostArgsForCall []struct { + arg1 int + } + deleteHostReturns struct { + result1 error + } + deleteHostReturnsOnCall map[int]struct { + result1 error + } GenerateOrderTemplateStub func(string, string, string, string, string, int) (datatypes.Container_Product_Order_Virtual_DedicatedHost, error) generateOrderTemplateMutex sync.RWMutex generateOrderTemplateArgsForCall []struct { @@ -221,6 +232,67 @@ func (fake *FakeDedicatedHostManager) CancelGuestsReturnsOnCall(i int, result1 [ }{result1, result2} } +func (fake *FakeDedicatedHostManager) DeleteHost(arg1 int) error { + fake.deleteHostMutex.Lock() + ret, specificReturn := fake.deleteHostReturnsOnCall[len(fake.deleteHostArgsForCall)] + fake.deleteHostArgsForCall = append(fake.deleteHostArgsForCall, struct { + arg1 int + }{arg1}) + stub := fake.DeleteHostStub + fakeReturns := fake.deleteHostReturns + fake.recordInvocation("DeleteHost", []interface{}{arg1}) + fake.deleteHostMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDedicatedHostManager) DeleteHostCallCount() int { + fake.deleteHostMutex.RLock() + defer fake.deleteHostMutex.RUnlock() + return len(fake.deleteHostArgsForCall) +} + +func (fake *FakeDedicatedHostManager) DeleteHostCalls(stub func(int) error) { + fake.deleteHostMutex.Lock() + defer fake.deleteHostMutex.Unlock() + fake.DeleteHostStub = stub +} + +func (fake *FakeDedicatedHostManager) DeleteHostArgsForCall(i int) int { + fake.deleteHostMutex.RLock() + defer fake.deleteHostMutex.RUnlock() + argsForCall := fake.deleteHostArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDedicatedHostManager) DeleteHostReturns(result1 error) { + fake.deleteHostMutex.Lock() + defer fake.deleteHostMutex.Unlock() + fake.DeleteHostStub = nil + fake.deleteHostReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeDedicatedHostManager) DeleteHostReturnsOnCall(i int, result1 error) { + fake.deleteHostMutex.Lock() + defer fake.deleteHostMutex.Unlock() + fake.DeleteHostStub = nil + if fake.deleteHostReturnsOnCall == nil { + fake.deleteHostReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.deleteHostReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeDedicatedHostManager) GenerateOrderTemplate(arg1 string, arg2 string, arg3 string, arg4 string, arg5 string, arg6 int) (datatypes.Container_Product_Order_Virtual_DedicatedHost, error) { fake.generateOrderTemplateMutex.Lock() ret, specificReturn := fake.generateOrderTemplateReturnsOnCall[len(fake.generateOrderTemplateArgsForCall)] @@ -813,6 +885,8 @@ func (fake *FakeDedicatedHostManager) Invocations() map[string][][]interface{} { defer fake.invocationsMutex.RUnlock() fake.cancelGuestsMutex.RLock() defer fake.cancelGuestsMutex.RUnlock() + fake.deleteHostMutex.RLock() + defer fake.deleteHostMutex.RUnlock() fake.generateOrderTemplateMutex.RLock() defer fake.generateOrderTemplateMutex.RUnlock() fake.getCreateOptionsMutex.RLock() From d36bd9a059da268ab15c2b92b9088445d88337c7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 6 Nov 2024 16:08:56 -0600 Subject: [PATCH 33/38] Added output for gosec in buildAndDeploy --- bin/buildAndDeploy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/buildAndDeploy.py b/bin/buildAndDeploy.py index ab91856c..050492c0 100755 --- a/bin/buildAndDeploy.py +++ b/bin/buildAndDeploy.py @@ -102,6 +102,7 @@ def runTests() -> None: except FileNotFoundError: gosec_instal = "curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b $GOPATH/bin" print(f"[red]gosec not found. Try running:\n{gosec_instal}") + print('[turquoise2] go sec OK.') ### Section for i18n4go stuff ### def runI18n4go(path: str) -> None: From 64bb5bb5917b7944067e34c37ea3749dfb50538c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 8 Nov 2024 16:45:03 -0600 Subject: [PATCH 34/38] #878 Added subnet authorizations to access-list and access-authorize --- .secrets.baseline | 18 ++- README.md | 22 ++++ plugin/commands/block/access_authorize.go | 92 ++++++++++++-- .../commands/block/access_authorize_test.go | 118 +++++++++--------- plugin/commands/block/access_list.go | 78 ++++++++++-- plugin/i18n/v2Resources/active.de_DE.json | 4 +- plugin/i18n/v2Resources/active.en-US.json | 94 +++++++------- plugin/i18n/v2Resources/active.es_ES.json | 4 +- plugin/i18n/v2Resources/active.fr_FR.json | 4 +- plugin/i18n/v2Resources/active.it_IT.json | 4 +- plugin/i18n/v2Resources/active.ja_JP.json | 4 +- plugin/i18n/v2Resources/active.ko_KR.json | 4 +- plugin/i18n/v2Resources/active.pt_BR.json | 4 +- plugin/i18n/v2Resources/active.zh_Hans.json | 4 +- plugin/i18n/v2Resources/active.zh_Hant.json | 4 +- plugin/managers/storage.go | 9 +- 16 files changed, 313 insertions(+), 154 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index e901656b..81644833 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "plugin/i18n/v1Resources/|plugin/i18n/v2Resources/|(.*test.*)|(vendor)|(go.sum)|bin/|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-10-16T21:46:33Z", + "generated_at": "2024-11-08T22:44:41Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -124,7 +124,7 @@ "hashed_secret": "49f18203810a56503e8b16bea101314713ce1a0c", "is_secret": false, "is_verified": false, - "line_number": 82, + "line_number": 97, "type": "Secret Keyword", "verified_result": null }, @@ -132,7 +132,7 @@ "hashed_secret": "4283f377a2ad3c107b4a3c9920c6ab508c6b1099", "is_secret": false, "is_verified": false, - "line_number": 102, + "line_number": 117, "type": "Secret Keyword", "verified_result": null }, @@ -140,7 +140,7 @@ "hashed_secret": "aa1f122c9be20b5a2aff7f2c513e3b065c033894", "is_secret": false, "is_verified": false, - "line_number": 133, + "line_number": 151, "type": "Secret Keyword", "verified_result": null }, @@ -148,7 +148,15 @@ "hashed_secret": "b2b24235714bf178e052e52a4af5a21a1f17c445", "is_secret": false, "is_verified": false, - "line_number": 160, + "line_number": 178, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "55839213174fc8f39a78d97deeaeecbd69f6de27", + "is_secret": false, + "is_verified": false, + "line_number": 265, "type": "Secret Keyword", "verified_result": null } diff --git a/README.md b/README.md index f907cf13..2501e3f3 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,28 @@ T("This is some output for a {{.CMDTYPE}} command", subs) *NOTICE* goi18n/v2 has some newer features that can make this a bit easier to deal with, but I'm not sure they are currently supported, so procede with caution in you make use of them. +Commands that accept a list of options to display should have those subbed into the string, not hard coded. + +BAD: +```go +cobraCmd.Flags().StringVar(&thisCmd.Sortby, "sortby", "id", T("Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.")) + +``` + +GOOD: + +```go +defaultColumns := []string{ + "id", "name", "type", "private_ip_address", "source_subnet", + "host_iqn", "username", "password","allowed_host_id" +} + +default_subs := map[string]interface{}{"COLUMNS": strings.join(defaultColumns, ", ")} + +cobraCmd.Flags().StringVar(&thisCmd.Sortby, "sortby", "id", + T("Column to sort by. Options are: {{.COLUMNS}}.", default_subs)) +``` + ### Useful Scripts #### `./bin/buildAndDeploy.py i18n` diff --git a/plugin/commands/block/access_authorize.go b/plugin/commands/block/access_authorize.go index 2512ca3d..498bce8a 100644 --- a/plugin/commands/block/access_authorize.go +++ b/plugin/commands/block/access_authorize.go @@ -1,6 +1,7 @@ package block import ( + "strconv" "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" @@ -19,6 +20,7 @@ type AccessAuthorizeCommand struct { Virtual_id []int Ip_address_id []int Ip_address []string + Subnet_id int } func NewAccessAuthorizeCommand(sl *metadata.SoftlayerStorageCommand) *AccessAuthorizeCommand { @@ -32,17 +34,27 @@ func NewAccessAuthorizeCommand(sl *metadata.SoftlayerStorageCommand) *AccessAuth Short: T("Authorize hosts to access a given volume."), Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} access-authorize 12345678 --virtual-id 87654321 - This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.`, sl.StorageI18n), + This command authorizes virtual server with ID 87654321 to access volume with ID 12345678.`, sl.StorageI18n) + "\n" + + T(` + ${COMMAND_NAME} sl {{.storageType}} access-authorize 5555 --subnet-id 1111 + This command adds subnet with id 1111 to the Allowed Host with id 5555. Use 'access-list' to find this id. + SoftLayer_Account::iscsiIsolationDisabled must be False for this command to do anything.`, sl.StorageI18n), Args: metadata.OneArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) }, } - cobraCmd.Flags().IntSliceVarP(&thisCmd.Hardware_id, "hardware-id", "d", []int{}, T("The ID of one hardware server to authorize.")) - cobraCmd.Flags().IntSliceVarP(&thisCmd.Virtual_id, "virtual-id", "v", []int{}, T("The ID of one virtual server to authorize.")) - cobraCmd.Flags().IntSliceVarP(&thisCmd.Ip_address_id, "ip-address-id", "i", []int{}, T("The ID of one IP address to authorize.")) - cobraCmd.Flags().StringSliceVarP(&thisCmd.Ip_address, "ip-address", "p", []string{}, T("An IP address to authorize.")) + cobraCmd.Flags().IntSliceVarP(&thisCmd.Hardware_id, "hardware-id", "d", []int{}, + T("The ID of one hardware server to authorize.")) + cobraCmd.Flags().IntSliceVarP(&thisCmd.Virtual_id, "virtual-id", "v", []int{}, + T("The ID of one virtual server to authorize.")) + cobraCmd.Flags().IntSliceVarP(&thisCmd.Ip_address_id, "ip-address-id", "i", []int{}, + T("The ID of one IP address to authorize.")) + cobraCmd.Flags().StringSliceVarP(&thisCmd.Ip_address, "ip-address", "p", []string{}, + T("An IP address to authorize.")) + cobraCmd.Flags().IntVarP(&thisCmd.Subnet_id, "subnet-id", "s", 0, + T("A Subnet Id. With this option IDENTIFIER should be an 'allowed_host_id' from the access-list command.")) thisCmd.Command = cobraCmd return thisCmd @@ -50,6 +62,14 @@ func NewAccessAuthorizeCommand(sl *metadata.SoftlayerStorageCommand) *AccessAuth func (cmd *AccessAuthorizeCommand) Run(args []string) error { + // Subnets have to get added to an existing authorized host. + if cmd.Subnet_id > 0 { + hostId, err := strconv.Atoi(args[0]) + if err != nil { + return slErr.NewInvalidSoftlayerIdInputError("Allowed Host IDENTIFIER") + } + return cmd.AddSubnetToHost(hostId) + } volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { return err @@ -60,7 +80,8 @@ func (cmd *AccessAuthorizeCommand) Run(args []string) error { for _, ip := range IPs { ipRecord, err := cmd.NetworkManager.IPLookup(ip) if err != nil { - return slErr.NewAPIError(T("IP address {{.IP}} is not found on your account.Please confirm IP and try again.\n", + return slErr.NewAPIError( + T("IP address {{.IP}} is not found on your account.Please confirm IP and try again.\n", map[string]interface{}{"IP": ip}), err.Error(), 2) } if ipRecord.Id != nil { @@ -82,14 +103,61 @@ func (cmd *AccessAuthorizeCommand) Run(args []string) error { } cmd.UI.Ok() - for _, vsID := range cmd.Virtual_id { - cmd.UI.Print(T("The virtual server {{.VsID}} was authorized to access {{.VolumeId}}.", map[string]interface{}{"VolumeId": volumeID, "VsID": vsID})) + subs := map[string]interface{}{ + "VolumeId": volumeID, + "SL_ID": 0, + "SL_Object": "", } - for _, hwID := range cmd.Hardware_id { - cmd.UI.Print(T("The hardware server {{.HwID}} was authorized to access {{.VolumeId}}.", map[string]interface{}{"VolumeId": volumeID, "HwID": hwID})) + for _, sl_id := range cmd.Virtual_id { + subs["SL_Object"] = T("Virtual Server") + subs["SL_ID"] = sl_id + cmd.UI.Print(T("The {{.SL_Object}} {{.SL_ID}} was authorized to access {{.VolumeId}}.", subs)) } - for _, ip := range IPIds { - cmd.UI.Print(T("The IP address {{.IP}} was authorized to access {{.VolumeId}}.", map[string]interface{}{"VolumeId": volumeID, "IP": ip})) + for _, sl_id := range cmd.Hardware_id { + subs["SL_Object"] = T("Hardware Server") + subs["SL_ID"] = sl_id + cmd.UI.Print(T("The {{.SL_Object}} {{.SL_ID}} was authorized to access {{.VolumeId}}.", subs)) + } + for _, sl_id := range IPIds { + subs["SL_Object"] = T("IP Address") + subs["SL_ID"] = sl_id + cmd.UI.Print(T("The {{.SL_Object}} {{.SL_ID}} was authorized to access {{.VolumeId}}.", subs)) } return nil } + +func (cmd *AccessAuthorizeCommand) AddSubnetToHost(host_id int) error { + + outputFormat := cmd.GetOutputFlag() + subnet_ids := []int{cmd.Subnet_id} + resp, err := cmd.StorageManager.AssignSubnetsToAcl(host_id, subnet_ids) + if err != nil { + subs := map[string]interface{}{"subnetID": cmd.Subnet_id, "accessID": host_id} + return slErr.NewAPIError( + T("Failed to assign subnet id: {{.subnetID}} to allowed host id: {{.accessID}}", subs), + err.Error(), 2) + } + // If the API returns an empty array, that means it didn't add the subnet we asked for. + // Likely because ISCSI Isolation is disabled on the account. + // ibmcloud sl call-api SoftLayer_Account getObject --mask="mask[id,iscsiIsolationDisabled]" + if len(resp) == 0 || utils.IntInSlice(cmd.Subnet_id, resp) == -1 { + subs := map[string]interface{}{"subnetID": cmd.Subnet_id, "accessID": host_id} + return slErr.NewAPIError( + T("Failed to assign subnet id: {{.subnetID}} to allowed host id: {{.accessID}}", subs) + "\n" + + T("Make sure ISCSI Isolation is enabled for this account."), + "", 2, + ) + } + if outputFormat == "JSON" { + return utils.PrintPrettyJSON(cmd.UI, resp) + } + + cmd.UI.Ok() + subs := map[string]interface{}{ + "VolumeId": host_id, + "SL_ID": cmd.Subnet_id, + "SL_Object": T("Subnet"), + } + cmd.UI.Print(T("The {{.SL_Object}} {{.SL_ID}} was authorized to access {{.VolumeId}}.", subs)) + return nil +} \ No newline at end of file diff --git a/plugin/commands/block/access_authorize_test.go b/plugin/commands/block/access_authorize_test.go index 2e874452..2280b19f 100644 --- a/plugin/commands/block/access_authorize_test.go +++ b/plugin/commands/block/access_authorize_test.go @@ -2,16 +2,13 @@ package block_test import ( "errors" - "strings" - . "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/matchers" "github.com/IBM-Cloud/ibm-cloud-cli-sdk/testhelpers/terminal" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/session" "github.com/softlayer/softlayer-go/sl" - "github.ibm.com/SoftLayer/softlayer-cli/plugin/commands/block" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" "github.ibm.com/SoftLayer/softlayer-cli/plugin/testhelpers" @@ -28,7 +25,7 @@ var _ = Describe("Access Authorize", func() { ) BeforeEach(func() { fakeUI = terminal.NewFakeUI() - fakeSession = testhelpers.NewFakeSoftlayerSession([]string{}) + fakeSession = testhelpers.NewFakeSoftlayerSession(nil) FakeStorageManager = new(testhelpers.FakeStorageManager) fakeNetworkManager = new(testhelpers.FakeNetworkManager) slCommand = metadata.NewSoftlayerStorageCommand(fakeUI, fakeSession, "block") @@ -40,101 +37,102 @@ var _ = Describe("Access Authorize", func() { }) Describe("Access Authorize", func() { - Context("Access Authorize without volume id", func() { - It("return error", func() { + Context("Syntax Errors", func() { + It("Require One Argument", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) - Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "Incorrect Usage: This command requires one argument")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) + }) + It("Valid allowed host id", func() { + err := testhelpers.RunCobraCommand(cliCommand.Command, "test", "--subnet-id=1234") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Invalid input for 'Allowed Host IDENTIFIER'")) }) }) - Context("Access Authorize with correct volume id and virtual server id", func() { + Context("Successful Authorizations", func() { BeforeEach(func() { FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) }) - It("return no error", func() { + It("Virtual Server", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--virtual-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"The virtual server 5678 was authorized to access 1234."})) - }) - }) - - Context("Access Authorize with correct volume id and hardware server id", func() { - BeforeEach(func() { - FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) + Expect(fakeUI.Outputs()).To(ContainSubstring("The Virtual Server 5678 was authorized to access 1234.")) }) - It("return no error", func() { + It("Hardware Server", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--hardware-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"The hardware server 5678 was authorized to access 1234."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("The Hardware Server 5678 was authorized to access 1234.")) }) - }) - - Context("Access Authorize with correct volume id and ip address id", func() { - BeforeEach(func() { - FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) - }) - It("Success with multipl IP Ids", func() { + It("Multiple IP Address IDs", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address-id", "5678", "--ip-address-id", "9999") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"The IP address 5678 was authorized to access 1234."})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"The IP address 9999 was authorized to access 1234."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("The IP Address 5678 was authorized to access 1234.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("The IP Address 9999 was authorized to access 1234.")) volId, _, _, ipArg, _ := FakeStorageManager.AuthorizeHostToVolumeArgsForCall(0) Expect(ipArg).To(Equal([]int{5678, 9999})) Expect(volId).To(Equal(1234)) }) - It("Success with single IP Ids", func() { + It("Single IP Address ID", func() { // Testing this because when splitting out sl into its own module, intSlices seem to be duplicating first value err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"The IP address 5678 was authorized to access 1234."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("The IP Address 5678 was authorized to access 1234.")) volId, _, _, ipArg, _ := FakeStorageManager.AuthorizeHostToVolumeArgsForCall(0) Expect(ipArg).To(Equal([]int{5678})) Expect(volId).To(Equal(1234)) }) - }) - - Context("Access Authorize with correct volume id and ip address", func() { - BeforeEach(func() { - FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) + It("IP Address", func() { fakeNetworkManager.IPLookupReturns(datatypes.Network_Subnet_IpAddress{Id: sl.Int(5678)}, nil) - }) - It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address", "1.2.3.4") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"OK"})) - Expect(fakeUI.Outputs()).To(ContainSubstrings([]string{"The IP address 5678 was authorized to access 1234."})) + Expect(fakeUI.Outputs()).To(ContainSubstring("The IP Address 5678 was authorized to access 1234.")) + }) + It("Subnet", func() { + FakeStorageManager.AssignSubnetsToAclReturns([]int{5678}, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).NotTo(HaveOccurred()) + Expect(fakeUI.Outputs()).To(ContainSubstring("The Subnet 5678 was authorized to access 1234.")) }) }) - Context("Access Authorize with correct volume id and wrong ip address", func() { - BeforeEach(func() { + Context("Error Handling", func() { + It("IP Address not found", func() { FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) fakeNetworkManager.IPLookupReturns(datatypes.Network_Subnet_IpAddress{}, errors.New("Not Found")) - }) - It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address", "1.2.3.4") Expect(err).To(HaveOccurred()) - Expect(fakeUI.Outputs()).NotTo(ContainSubstrings([]string{"OK"})) - Expect(strings.Contains(err.Error(), "IP address 1.2.3.4 is not found on your account.Please confirm IP and try again.")).To(BeTrue()) - Expect(strings.Contains(err.Error(), "Not Found")).To(BeTrue()) - }) - }) - - Context("Access Authorize with correct volume id but server API call fails", func() { - BeforeEach(func() { - FakeStorageManager.AuthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, errors.New("Internal Server Error")) + Expect(fakeUI.Outputs()).NotTo(ContainSubstring("OK")) + Expect(err.Error()).To(ContainSubstring("IP address 1.2.3.4 is not found on your account.")) + Expect(err.Error()).To(ContainSubstring("Not Found")) }) - It("return error", func() { + It("Other API Error", func() { + FakeStorageManager.AuthorizeHostToVolumeReturns( + []datatypes.Network_Storage_Allowed_Host{}, errors.New("Internal Server Error"), + ) err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--virtual-id", "5678") Expect(err).To(HaveOccurred()) - Expect(fakeUI.Outputs()).NotTo(ContainSubstrings([]string{"OK"})) - Expect(strings.Contains(err.Error(), "Failed to authorize host to volume")).To(BeTrue()) - Expect(strings.Contains(err.Error(), "Internal Server Error")).To(BeTrue()) + Expect(fakeUI.Outputs()).NotTo(ContainSubstring("OK")) + Expect(err.Error()).To(ContainSubstring("Failed to authorize host to volume")) + Expect(err.Error()).To(ContainSubstring("Internal Server Error")) + }) + It("Subnet not added because isci isolation", func() { + FakeStorageManager.AssignSubnetsToAclReturns([]int{}, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Make sure ISCSI Isolation is enabled for this account")) + }) + It("Subnet not added because wrong subnet returned", func() { + FakeStorageManager.AssignSubnetsToAclReturns([]int{999}, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Make sure ISCSI Isolation is enabled for this account")) + }) + It("Subnet not added because API error", func() { + FakeStorageManager.AssignSubnetsToAclReturns([]int{}, errors.New("API ERROR")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to assign subnet id: 5678 to allowed host id: 1234")) + Expect(err.Error()).To(ContainSubstring("API ERROR")) }) }) }) diff --git a/plugin/commands/block/access_list.go b/plugin/commands/block/access_list.go index da4b71c3..54716b28 100644 --- a/plugin/commands/block/access_list.go +++ b/plugin/commands/block/access_list.go @@ -3,6 +3,9 @@ package block import ( "github.com/spf13/cobra" "sort" + "strings" + + "github.com/softlayer/softlayer-go/datatypes" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" @@ -17,26 +20,37 @@ type AccessListCommand struct { StorageManager managers.StorageManager UserColumn []string Sortby string + DefaultColumns []string } func NewAccessListCommand(sl *metadata.SoftlayerStorageCommand) *AccessListCommand { + defaultColumns := []string{ + "id", "name", "type", "private_ip_address", "source_subnet", + "host_iqn", "username", "password","allowed_host_id", + } thisCmd := &AccessListCommand{ SoftlayerStorageCommand: sl, StorageManager: managers.NewStorageManager(sl.Session), + DefaultColumns: defaultColumns, } cobraCmd := &cobra.Command{ Use: "access-list " + T("IDENTIFIER"), Short: T("List hosts that are authorized to access the volume."), - Long: T(`EXAMPLE: + Long: T(`Access Hosts marked 'IN ACL' belong to a parent Access Host with the same allowed_host_id.`) + "\n" + + T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id - This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.`, sl.StorageI18n), + This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.`, + sl.StorageI18n), Args: metadata.OneArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) }, } - cobraCmd.Flags().StringVar(&thisCmd.Sortby, "sortby", "id", T("Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.")) - cobraCmd.Flags().StringSliceVar(&thisCmd.UserColumn, "column", []string{}, T("Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.")) + default_subs := map[string]interface{}{"COLUMNS": strings.Join(defaultColumns, ", ")} + cobraCmd.Flags().StringVar(&thisCmd.Sortby, "sortby", "allowed_host_id", + T("Column to sort by. Options are: {{.COLUMNS}}.", default_subs)) + cobraCmd.Flags().StringSliceVar(&thisCmd.UserColumn, "column", []string{}, + T("Column to display. Options are: {{.COLUMNS}}.", default_subs)) thisCmd.Command = cobraCmd return thisCmd } @@ -52,17 +66,18 @@ func (cmd *AccessListCommand) Run(args []string) error { outputFormat := cmd.GetOutputFlag() - defaultColumns := []string{"id", "name", "type", "private_ip_address", "source_subnet", "host_iqn", "username", "password", "allowed_host_id"} optionalColumns := []string{} - sortColumns := []string{"id", "name", "type", "private_ip_address", "source_subnet", "host_iqn", "username", "password", "allowed_host_id"} - showColumns, err := utils.ValidateColumns2(sortby, cmd.UserColumn, defaultColumns, optionalColumns, sortColumns) + showColumns, err := utils.ValidateColumns2( + sortby, cmd.UserColumn, cmd.DefaultColumns, optionalColumns, cmd.DefaultColumns) if err != nil { return err } volume, err := cmd.StorageManager.GetVolumeAccessList(volumeID) if err != nil { - return slErr.NewAPIError(T("Failed to get access list for volume {{.VolumeID}}.\n", map[string]interface{}{"VolumeID": volumeID}), err.Error(), 2) + return slErr.NewAPIError( + T("Failed to get access list for volume {{.VolumeID}}.\n", map[string]interface{}{"VolumeID": volumeID}), + err.Error(), 2) } accessList := []utils.Access{} @@ -105,6 +120,9 @@ func (cmd *AccessListCommand) Run(args []string) error { } } accessList = append(accessList, access) + if hw.AllowedHost != nil && hw.AllowedHost.SubnetsInAcl != nil { + accessList = append(accessList, SubnetsInAclRows(hw.AllowedHost)...) + } } for _, sn := range volume.AllowedSubnets { @@ -162,7 +180,11 @@ func (cmd *AccessListCommand) Run(args []string) error { access.Password = utils.FormatStringPointer(credentials.Password) } } + if ip.AllowedHost != nil && ip.AllowedHost.SubnetsInAcl != nil { + accessList = append(accessList, SubnetsInAclRows(ip.AllowedHost)...) + } accessList = append(accessList, access) + } if sortby == "id" || sortby == "ID" { @@ -209,3 +231,43 @@ func (cmd *AccessListCommand) Run(args []string) error { table.Print() return nil } + + +func SubnetsInAclRows(allowed_host *datatypes.Network_Storage_Allowed_Host) []utils.Access { + accessList := []utils.Access{} + if allowed_host == nil || allowed_host.SubnetsInAcl == nil { + return nil + } + for _, sn := range allowed_host.SubnetsInAcl { + access := utils.Access{} + + access.ID = utils.FormatIntPointer(sn.Id) + + if utils.FormatStringPointerName(sn.Note) != "" { + access.Name = utils.FormatStringPointerName(sn.NetworkIdentifier) + "/" + utils.FormatIntPointerName(sn.Cidr) + "(" + utils.FormatStringPointerName(sn.Note) + ")" + } else { + access.Name = utils.FormatStringPointerName(sn.NetworkIdentifier) + "/" + utils.FormatIntPointerName(sn.Cidr) + } + + access.Type = T("In ACL") + + if sn.EndPointIpAddress != nil { + access.PrivateIPAddress = utils.FormatStringPointer(sn.EndPointIpAddress.IpAddress) + } else { + access.PrivateIPAddress = utils.EMPTY_VALUE + } + + if allowed_host != nil { + access.SourceSubnet = utils.FormatStringPointer(allowed_host.SourceSubnet) + access.HostIQN = utils.FormatStringPointer(allowed_host.Name) + access.AllowedHostID = utils.FormatIntPointer(allowed_host.Id) + if allowed_host.Credential != nil { + credentials := *allowed_host.Credential + access.UserName = utils.FormatStringPointer(credentials.Username) + access.Password = utils.FormatStringPointer(credentials.Password) + } + } + accessList = append(accessList, access) + } + return accessList +} \ No newline at end of file diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index 6ec06288..01b996aa 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "Anzuzeigende Spalte. Optionen: id, hostname, domain, primary_ip, backend_ip. Diese Option kann mehrmals angegeben werden." }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "Anzuzeigende Spalte. Zulässige Optionen: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. Diese Option kann mehrmals angegeben werden." }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "Spalte, nach der sortiert werden soll. Optionen: id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "Spalte, nach der sortiert werden soll. Zulässige Optionen: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index e21ed64f..463f2c2a 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -1,4 +1,7 @@ { + "\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 5555 --subnet-id 1111\n This command adds subnet with id 1111 to the Allowed Host with id 5555. Use 'access-list' to find this id.\n SoftLayer_Account::iscsiIsolationDisabled must be False for this command to do anything.": { + "other": "\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 5555 --subnet-id 1111\n This command adds subnet with id 1111 to the Allowed Host with id 5555. Use 'access-list' to find this id.\n SoftLayer_Account::iscsiIsolationDisabled must be False for this command to do anything." + }, "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678." }, @@ -395,15 +398,6 @@ "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-modify VOLUME_ID [OPTIONS]\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB." }, - "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a block storage volume, including storage type, volume size, OS type, IOPS, tier level, datacenter, and snapshot size." - }, - "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size.": { - "other": "${COMMAND_NAME} sl {{.storageType}} volume-options\n\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-options\n This command lists all options for creating a file storage volume, including storage type, volume size, IOPS, tier level, datacenter, and snapshot size." - }, - "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { - "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB." - }, "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID.": { "other": "${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\nEXAMPLE:\n\t${COMMAND_NAME} sl {{.storageType}} volume-refresh VOLUME_ID SNAPSHOT_ID\n\tRefresh a duplicate VOLUME_ID with a snapshot from its parent SNAPSHOT_ID." }, @@ -599,6 +593,9 @@ "A Network_Vlan.id value of the desired VLAN or A semantic VLAN identifier of the form ..,\neg. dal13.fcr01.1234 - the router name may optionally contain the 'a' or 'b' redundancy qualifier ": { "other": "A Network_Vlan.id value of the desired VLAN or A semantic VLAN identifier of the form ..,\neg. dal13.fcr01.1234 - the router name may optionally contain the 'a' or 'b' redundancy qualifier " }, + "A Subnet Id. With this option IDENTIFIER should be an 'allowed_host_id' from the access-list command.": { + "other": "A Subnet Id. With this option IDENTIFIER should be an 'allowed_host_id' from the access-list command." + }, "A Virtual_Guest.id or UUID value of the desired server. A value corresponding to a unique\nfully-qualified domain name in the format 'hostname' where < and > are literal, e.g. myhost": { "other": "A Virtual_Guest.id or UUID value of the desired server. A value corresponding to a unique\nfully-qualified domain name in the format 'hostname' where < and > are literal, e.g. myhost" }, @@ -659,6 +656,9 @@ "Aborting AAAA record sync, found {{.Num}} AAAA records exists!": { "other": "Aborting AAAA record sync, found {{.Num}} AAAA records exists!" }, + "Access Hosts marked 'IN ACL' belong to a parent Access Host with the same allowed_host_id.": { + "other": "Access Hosts marked 'IN ACL' belong to a parent Access Host with the same allowed_host_id." + }, "Access Key ID": { "other": "Access Key ID" }, @@ -881,9 +881,6 @@ "Authorize hosts to access a given volume.": { "other": "Authorize hosts to access a given volume." }, - "Available Snapshot Size (GB)": { - "other": "Available Snapshot Size (GB)" - }, "Average": { "other": "Average" }, @@ -1049,6 +1046,9 @@ "Cancel a VLAN": { "other": "Cancel a VLAN" }, + "Cancel a dedicated host server immediately.": { + "other": "Cancel a dedicated host server immediately." + }, "Cancel a global IP": { "other": "Cancel a global IP" }, @@ -1298,15 +1298,15 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times" }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { - "other": "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times." - }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { "other": "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times" }, "Column to display. Options are: id,username,datacenter,storage_type,capacity_gb,bytes_used,IOPs,ip_addr,lunId,created_by,active_transactions,rep_partner_count,notes. This option can be specified multiple times": { "other": "Column to display. Options are: id,username,datacenter,storage_type,capacity_gb,bytes_used,IOPs,ip_addr,lunId,created_by,active_transactions,rep_partner_count,notes. This option can be specified multiple times" }, + "Column to display. Options are: {{.COLUMNS}}.": { + "other": "Column to display. Options are: {{.COLUMNS}}." + }, "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state].": { "other": "Column to display. [Options are: guid, cpu, memory, datacenter, primary_ip, backend_ip, created_by, power_state, tags] [default: id,hostname,domain,primary_ip,backend_ip,power_state]." }, @@ -1331,9 +1331,6 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { - "other": "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." - }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { "other": "Column to sort by. Options are: id,common_name,days_until_expire,note" }, @@ -1358,6 +1355,9 @@ "Column to sort by. Options are: id,virtualServerId,hostname": { "other": "Column to sort by. Options are: id,virtualServerId,hostname" }, + "Column to sort by. Options are: {{.COLUMNS}}.": { + "other": "Column to sort by. Options are: {{.COLUMNS}}." + }, "Comma separated list of tags, enclosed in quotes. 'tag1, tag2'": { "other": "Comma separated list of tags, enclosed in quotes. 'tag1, tag2'" }, @@ -1640,6 +1640,9 @@ "Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.": { "other": "Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host." }, + "Dedicated Host {{.ID}} was cancelled": { + "other": "Dedicated Host {{.ID}} was cancelled" + }, "Dedicated Virtual Host Flavor(s)": { "other": "Dedicated Virtual Host Flavor(s)" }, @@ -1895,6 +1898,9 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation.": { "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-cancel 12345678 --immediate -f \n This command cancels snapshot with ID 12345678 immediately without asking for confirmation." }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type performance --size 1000 --iops 4000 --os-type LINUX -d dal09\n This command orders a performance volume with size is 1000GB, IOPS is 4000, OS type is LINUX, located at dal09.\n ${COMMAND_NAME} sl {{.storageType}} volume-order --storage-type endurance --size 500 --tier 4 --os-type XEN -d dal09 --snapshot-size 500\n This command orders a endurance volume with size is 500GB, tier level is 4 IOPS per GB, OS type is XEN, located at dal09, and additional snapshot space size is 500GB." + }, "EXAMPLE: \n\t${COMMAND_NAME} sl order place CLOUD_SERVER DALLAS13 GUEST_CORES_4 RAM_16_GB REBOOT_REMOTE_CONSOLE 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS BANDWIDTH_0_GB_2 1_IP_ADDRESS GUEST_DISK_100_GB_SAN OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI MONITORING_HOST_PING NOTIFICATION_EMAIL_AND_TICKET AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING --billing hourly --extras '{\"virtualGuests\": [{\"hostname\": \"test\", \"domain\": \"softlayer.com\"}]}' --complex-type SoftLayer_Container_Product_Order_Virtual_Guest\n\tThis command orders an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, Ubuntu 16.04, and 1 Gbps public & private uplink in dal13": { "other": "EXAMPLE: \n\t${COMMAND_NAME} sl order place CLOUD_SERVER DALLAS13 GUEST_CORES_4 RAM_16_GB REBOOT_REMOTE_CONSOLE 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS BANDWIDTH_0_GB_2 1_IP_ADDRESS GUEST_DISK_100_GB_SAN OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI MONITORING_HOST_PING NOTIFICATION_EMAIL_AND_TICKET AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING --billing hourly --extras '{\"virtualGuests\": [{\"hostname\": \"test\", \"domain\": \"softlayer.com\"}]}' --complex-type SoftLayer_Container_Product_Order_Virtual_Guest\n\tThis command orders an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, Ubuntu 16.04, and 1 Gbps public & private uplink in dal13" }, @@ -2600,9 +2606,6 @@ "Failed to get all datacenters.": { "other": "Failed to get all datacenters." }, - "Failed to get all datacenters.\n": { - "other": "Failed to get all datacenters.\n" - }, "Failed to get announcement event ID.": { "other": "Failed to get announcement event ID." }, @@ -3695,6 +3698,9 @@ "Hardware ID should be a number.": { "other": "Hardware ID should be a number." }, + "Hardware Server": { + "other": "Hardware Server" + }, "Hardware server template is exported to: {{.Template}}.": { "other": "Hardware server template is exported to: {{.Template}}." }, @@ -3869,9 +3875,6 @@ "IOPs": { "other": "IOPs" }, - "IOPs limit above 6000 available in select data centers, refer to:http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features": { - "other": "IOPs limit above 6000 available in select data centers, refer to:http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features" - }, "IP": { "other": "IP" }, @@ -3923,6 +3926,9 @@ "If specified, will only show what the quote will order, will NOT place an order [default: False]": { "other": "If specified, will only show what the quote will order, will NOT place an order [default: False]" }, + "If there are any guests on this Dedicated Host, this command will fail until those guests are deleted.\nUse 'sl dedicatedhost cancel-guests [IDENTIFIER]' to remove all guests from a host.\nUse 'sl vs delete' to remove a specific guest.": { + "other": "If there are any guests on this Dedicated Host, this command will fail until those guests are deleted.\nUse 'sl dedicatedhost cancel-guests [IDENTIFIER]' to remove all guests from a host.\nUse 'sl vs delete' to remove a specific guest." + }, "If this option is specified, the public ip will be allocated from a public subnet in this account. Otherwise, it will be allocated form IBM system pool. Only available in PublicToPrivate load balancer type.": { "other": "If this option is specified, the public ip will be allocated from a public subnet in this account. Otherwise, it will be allocated form IBM system pool. Only available in PublicToPrivate load balancer type." }, @@ -3959,6 +3965,9 @@ "Import an image from an object storage": { "other": "Import an image from an object storage" }, + "In ACL": { + "other": "In ACL" + }, "In GB": { "other": "In GB" }, @@ -4262,9 +4271,6 @@ "List all options for ordering a block or file storage volume": { "other": "List all options for ordering a block or file storage volume" }, - "List all options for ordering a file storage": { - "other": "List all options for ordering a file storage" - }, "List all subnets on your account": { "other": "List all subnets on your account" }, @@ -4445,6 +4451,9 @@ "Maintance Window": { "other": "Maintance Window" }, + "Make sure ISCSI Isolation is enabled for this account.": { + "other": "Make sure ISCSI Isolation is enabled for this account." + }, "Manage Classic infrastructure services": { "other": "Manage Classic infrastructure services" }, @@ -4463,9 +4472,6 @@ "Max": { "other": "Max" }, - "Max IOPS": { - "other": "Max IOPS" - }, "Maximum number of connections to allow": { "other": "Maximum number of connections to allow" }, @@ -4511,9 +4517,6 @@ "Min": { "other": "Min" }, - "Min IOPS": { - "other": "Min IOPS" - }, "Minute of the hour when snapshots should be taken, integer between 0 to 59": { "other": "Minute of the hour when snapshots should be taken, integer between 0 to 59" }, @@ -4709,9 +4712,6 @@ "Note value": { "other": "Note value" }, - "Note:": { - "other": "Note:" - }, "Note: IOPS above 6,000 available only in: https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-selectDC": { "other": "Note: IOPS above 6,000 available only in: https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-selectDC" }, @@ -5789,6 +5789,9 @@ "Security group {{.ID}} is updated.": { "other": "Security group {{.ID}} is updated." }, + "See Also:": { + "other": "See Also:" + }, "See https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-getting-started for sizing options.\n${COMMAND_NAME} sl block volume-options' to get available options.\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 \n This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB.": { "other": "See https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-getting-started for sizing options.\n${COMMAND_NAME} sl block volume-options' to get available options.\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} snapshot-order 12345678 -s 1000 -t 4 \n This command orders snapshot space for volume with ID 12345678, the size is 1000GB, the tier level is 4 IOPS per GB." }, @@ -5963,9 +5966,6 @@ "Size": { "other": "Size" }, - "Size (GB)": { - "other": "Size (GB)" - }, "Size On Disk": { "other": "Size On Disk" }, @@ -6107,12 +6107,6 @@ "Storage": { "other": "Storage" }, - "Storage Size (GB)": { - "other": "Storage Size (GB)" - }, - "Storage Type": { - "other": "Storage Type" - }, "Subject": { "other": "Subject" }, @@ -6653,6 +6647,9 @@ "The volume has been cancelled; unable to modify volume.": { "other": "The volume has been cancelled; unable to modify volume." }, + "The {{.SL_Object}} {{.SL_ID}} was authorized to access {{.VolumeId}}.": { + "other": "The {{.SL_Object}} {{.SL_ID}} was authorized to access {{.VolumeId}}." + }, "There are no replication partners for volume {{.VolumeID}}.\n": { "other": "There are no replication partners for volume {{.VolumeID}}.\n" }, @@ -7172,8 +7169,8 @@ "Valid quantities vary by type.\n\t- public IPv4: 4, 8, 16, 32\n\t- private IPv4: 4, 8, 16, 32, 64\n\t- public IPv6: 64\n\nEXAMPLE:\n\t${COMMAND_NAME} sl subnet create public 16 567\n\tThis command creates a public subnet with 16 IPv4 addresses and places it on vlan with ID 567.": { "other": "Valid quantities vary by type.\n\t- public IPv4: 4, 8, 16, 32\n\t- private IPv4: 4, 8, 16, 32, 64\n\t- public IPv6: 64\n\nEXAMPLE:\n\t${COMMAND_NAME} sl subnet create public 16 567\n\tThis command creates a public subnet with 16 IPv4 addresses and places it on vlan with ID 567." }, - "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { - "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB." + "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB.": { + "other": "Valid size and iops options can be found here:\nhttps://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations\nhttps://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-orderingBlockStorage&interface=cli\n\n EXAMPLE:\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 1000 --new-iops 4000 \n\t This command modify a volume 12345678 with size is 1000GB, IOPS is 4000.\n\t ${COMMAND_NAME} sl {{.storageType}} volume-modify 12345678 --new-size 500 --new-tier 4\n\t This command modify a volume 12345678 with size is 500GB, tier level is 4 IOPS per GB." }, "Value": { "other": "Value" @@ -7208,6 +7205,9 @@ "Virtual Guest ID": { "other": "Virtual Guest ID" }, + "Virtual Server": { + "other": "Virtual Server" + }, "Virtual guest ID": { "other": "Virtual guest ID" }, diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index 5c7c30f9..50ebfd5c 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "Columna a visualizar. Las opciones son: id, hostname, domain, primary_ip, backend_ip. Esta opción se puede especificar varias veces" }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "Columna a visualizar. Las opciones son: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. Esta opción se puede especificar varias veces." }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "Columna por la que se debe ordenar. Las opciones son: id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "Columna por la que se debe ordenar. Las opciones son: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 755d0e03..029a340c 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "Colonne à afficher. Les options sont : id, hostname, domain, primary_ip, backend_ip. Cette option peut être indiquée plusieurs fois." }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "Colonne à afficher. Options : id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. Cette option peut être spécifiée plusieurs fois." }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "Colonne de tri. Les options sont : id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "Colonne de tri. Options : id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index ac3cb4dc..694e5e98 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "Colonna da visualizzare. Le opzioni sono: id, hostname, domain, primary_ip, backend_ip. Questa opzione può essere specificata più volte." }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "Colonna da visualizzare. Le opzioni sono: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. Questa opzione può essere specificata più volte." }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "Colonna in base alla quale ordinare. Le opzioni sono: id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "Colonna in base alla quale ordinare. Le opzioni sono: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index 77f57a53..4ce55f0c 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "表示する列。 オプション: id、hostname、domain、primary_ip、backend_ip。 このオプションは複数回指定できます" }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "表示する列。 オプション: id、name、type、private_ip_address、source_subnet、host_iqn、username、password、allowed_host_id。 このオプションは、複数回指定できます。" }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "ソートの基準にする列。 オプション: id、hostname、domain、primary_ip、backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "ソートの基準にする列。 オプション: id、name、type、private_ip_address、source_subnet、host_iqn、username、password、allowed_host_id。" }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index 5011f0c0..a06ceffc 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "표시할 열. 옵션은 id, hostname, domain, primary_ip, backend_ip입니다. 이 옵션은 여러 번 지정할 수 있습니다." }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "표시할 열. 옵션: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. 이 옵션은 여러 번 지정할 수 있습니다." }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "정렬 기준 열. 옵션: id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "정렬 기준 열. 옵션: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index fac9bec2..8d779e8a 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "Coluna a ser exibida. As opções são: id, hostname, domain, primary_ip, backend_ip. Essa opção pode ser especificada várias vezes" }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "Coluna a ser exibida. As opções são: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. Essa opção pode ser especificada múltiplas vezes." }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "Coluna pela qual classificar. As opções são: id, hostname, domain, primary_ip, backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "Coluna pela qual classificar. As opções são: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id." }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index 64f60fd1..68171cf8 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "要显示的列。 选项为:id、hostname、domain、primary_ip 和 backend_ip。 此选项可以多次指定" }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "要显示的列。 选项包括:id、name、type、private_ip_address、source_subnet、host_iqn、username、password 和 allowed_host_id。 可以多次指定此选项。" }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "要作为排序依据的列。 选项为:id、hostname、domain、primary_ip、backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "要作为排序依据的列。 选项包括:id、name、type、private_ip_address、source_subnet、host_iqn、username、password 和 allowed_host_id。" }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 0fef0dfb..2b181180 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -1313,7 +1313,7 @@ "Column to display. Options are: id, hostname, domain, primary_ip, backend_ip. This option can be specified multiple times": { "other": "要顯示的直欄。 選項為:id、hostname、domain、primary_ip、backend_ip。 這個選項可以多次指定" }, - "Column to display. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id. This option can be specified multiple times.": { + "Column to display. Options are: {{.COLUMNS}}. This option can be specified multiple times.": { "other": "要顯示的直欄。 選項包含:id、name、type、private_ip_address、source_subnet、host_iqn、username、password、allowed_host_id。 此選項可以指定多次。" }, "Column to display. Options are: id,hostname,domain,cpu,memory,public_ip,private_ip,datacenter,action,guid,power_state,created_by,tags. This option can be specified multiple times": { @@ -1346,7 +1346,7 @@ "Column to sort by. Options are: id, hostname, domain, primary_ip, backend_ip": { "other": "要排序的直欄。 選項包含:id、hostname、domain、primary_ip、backend_ip" }, - "Column to sort by. Options are: id, name, type, private_ip_address, source_subnet, host_iqn, username, password, allowed_host_id.": { + "Column to sort by. Options are: {{.COLUMNS}}.": { "other": "要排序的直欄。 選項包含:id、name、type、private_ip_address、source_subnet、host_iqn、username、password、allowed_host_id。" }, "Column to sort by. Options are: id,common_name,days_until_expire,note": { diff --git a/plugin/managers/storage.go b/plugin/managers/storage.go index dab25dc8..2445c4be 100644 --- a/plugin/managers/storage.go +++ b/plugin/managers/storage.go @@ -169,10 +169,11 @@ func (s storageManager) GetVolumeSnapshotSchedules(volumeId int) (datatypes.Netw // Returns a list of authorized hosts for a specified volume. // volumeId: ID of volume func (s storageManager) GetVolumeAccessList(volumeId int) (datatypes.Network_Storage, error) { - mask := "id,allowedVirtualGuests.allowedHost.credential," + - "allowedHardware.allowedHost.credential," + - "allowedSubnets.allowedHost.credential," + - "allowedIpAddresses.allowedHost.credential" + mask := `mask[id, +allowedVirtualGuests[id,hostname,domain,primaryBackendIpAddress,allowedHost[credential,subnetsInAcl]], +allowedHardware[id,hostname,domain,primaryBackendIpAddress,allowedHost[credential,subnetsInAcl]], +allowedSubnets[id,note,networkIdentifier,cidr,endPointIpAddress[ipAddress],allowedHost[credential,subnetsInAcl]], +allowedIpAddresses[id,note,ipAddress,allowedHost[credential,subnetsInAcl]]]` return s.StorageService.Id(volumeId).Mask(mask).GetObject() } From aa3354cf6f8c5e954bbea47a9c4219f8d9820615 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 12 Nov 2024 16:35:42 -0600 Subject: [PATCH 35/38] #878 added revoke tests, updated gosec to not be quiet. --- bin/buildAndDeploy.py | 2 +- plugin/commands/block/access_revoke.go | 93 +++++++++++++++++---- plugin/commands/block/access_revoke_test.go | 78 ++++++++--------- plugin/i18n/v2Resources/active.de_DE.json | 4 +- plugin/i18n/v2Resources/active.en-US.json | 12 +++ plugin/i18n/v2Resources/active.es_ES.json | 4 +- plugin/i18n/v2Resources/active.fr_FR.json | 4 +- plugin/i18n/v2Resources/active.it_IT.json | 4 +- plugin/i18n/v2Resources/active.ja_JP.json | 4 +- plugin/i18n/v2Resources/active.ko_KR.json | 4 +- plugin/i18n/v2Resources/active.pt_BR.json | 4 +- plugin/i18n/v2Resources/active.zh_Hans.json | 4 +- plugin/i18n/v2Resources/active.zh_Hant.json | 4 +- 13 files changed, 145 insertions(+), 76 deletions(-) diff --git a/bin/buildAndDeploy.py b/bin/buildAndDeploy.py index 050492c0..c5da176f 100755 --- a/bin/buildAndDeploy.py +++ b/bin/buildAndDeploy.py @@ -94,7 +94,7 @@ def runTests() -> None: except subprocess.CalledProcessError as e: print(f"[red]>>> Go Test failed <<<") sys.exit(e.returncode) - go_sec = ['gosec', '-exclude-dir=fixture', '-exclude-dir=plugin/resources', '-quiet', './...'] + go_sec = ['gosec', '-exclude-dir=fixture', '-exclude-dir=plugin/resources', '-exclude-generated', './...'] # Not using the 'real' command because this is more copy/pasteable. print('[turquoise2]Running: ' + " ".join(go_sec)) try: diff --git a/plugin/commands/block/access_revoke.go b/plugin/commands/block/access_revoke.go index aa0146b4..a3fcfba3 100644 --- a/plugin/commands/block/access_revoke.go +++ b/plugin/commands/block/access_revoke.go @@ -1,12 +1,14 @@ package block import ( + "strconv" "github.com/spf13/cobra" slErr "github.ibm.com/SoftLayer/softlayer-cli/plugin/errors" . "github.ibm.com/SoftLayer/softlayer-cli/plugin/i18n" "github.ibm.com/SoftLayer/softlayer-cli/plugin/managers" "github.ibm.com/SoftLayer/softlayer-cli/plugin/metadata" + "github.ibm.com/SoftLayer/softlayer-cli/plugin/utils" ) type AccessRevokeCommand struct { @@ -18,6 +20,7 @@ type AccessRevokeCommand struct { Virtual_id []int Ip_address_id []int Ip_address []string + Subnet_id int } func NewAccessRevokeCommand(sl *metadata.SoftlayerStorageCommand) *AccessRevokeCommand { @@ -29,27 +32,44 @@ func NewAccessRevokeCommand(sl *metadata.SoftlayerStorageCommand) *AccessRevokeC cobraCmd := &cobra.Command{ Use: "access-revoke " + T("IDENTIFIER"), Short: T("Revoke authorization for hosts that are accessing a specific volume."), - Long: T(`${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS] - -EXAMPLE: + Long: T(`EXAMPLE: ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321 - This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.`, sl.StorageI18n), + This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.`, sl.StorageI18n) + "\n" + + T(` + ${COMMAND_NAME} sl {{.storageType}} access-revoke 5555 --subnet-id 1111 + This command removes subnet with id 1111 from the Allowed Host with id 5555. Use 'access-list' to find this id.`, sl.StorageI18n), Args: metadata.OneArgs, RunE: func(cmd *cobra.Command, args []string) error { return thisCmd.Run(args) }, } - cobraCmd.Flags().IntSliceVarP(&thisCmd.Hardware_id, "hardware-id", "d", []int{}, T("The ID of one hardware server to revoke")) - cobraCmd.Flags().IntSliceVarP(&thisCmd.Virtual_id, "virtual-id", "v", []int{}, T("The ID of one virtual server to revoke")) - cobraCmd.Flags().IntSliceVarP(&thisCmd.Ip_address_id, "ip-address-id", "i", []int{}, T("The ID of one IP address to revoke")) - cobraCmd.Flags().StringSliceVarP(&thisCmd.Ip_address, "ip-address", "p", []string{}, T("An IP address to revoke")) + cobraCmd.Flags().IntSliceVarP(&thisCmd.Hardware_id, "hardware-id", "d", []int{}, + T("The ID of one hardware server to revoke")) + cobraCmd.Flags().IntSliceVarP(&thisCmd.Virtual_id, "virtual-id", "v", []int{}, + T("The ID of one virtual server to revoke")) + cobraCmd.Flags().IntSliceVarP(&thisCmd.Ip_address_id, "ip-address-id", "i", []int{}, + T("The ID of one IP address to revoke")) + cobraCmd.Flags().StringSliceVarP(&thisCmd.Ip_address, "ip-address", "p", []string{}, + T("An IP address to revoke")) + cobraCmd.Flags().IntVarP(&thisCmd.Subnet_id, "subnet-id", "s", 0, + T("A Subnet Id. With this option IDENTIFIER should be an 'allowed_host_id' from the access-list command.")) thisCmd.Command = cobraCmd return thisCmd } func (cmd *AccessRevokeCommand) Run(args []string) error { + + // Subnets have to get added to an existing authorized host. + if cmd.Subnet_id > 0 { + hostId, err := strconv.Atoi(args[0]) + if err != nil { + return slErr.NewInvalidSoftlayerIdInputError("Allowed Host IDENTIFIER") + } + return cmd.RemoveSubnetFromHost(hostId) + } + volumeID, err := cmd.StorageManager.GetVolumeId(args[0], cmd.StorageType) if err != nil { return err @@ -76,17 +96,60 @@ func (cmd *AccessRevokeCommand) Run(args []string) error { } _, err = cmd.StorageManager.DeauthorizeHostToVolume(volumeID, cmd.Hardware_id, cmd.Virtual_id, IPIds, nil) if err != nil { - return slErr.NewAPIError(T("Failed to revoke access to volume {{.VolumeID}}.\n", map[string]interface{}{"VolumeID": volumeID}), err.Error(), 2) + subs := map[string]interface{}{"VolumeID": volumeID} + return slErr.NewAPIError(T("Failed to revoke access to volume {{.VolumeID}}.\n", subs), err.Error(), 2) } cmd.UI.Ok() - for _, vsID := range cmd.Virtual_id { - cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for virtual server {{.VsID}}.", map[string]interface{}{"VolumeId": volumeID, "VsID": vsID})) + subs := map[string]interface{}{ + "VolumeId": volumeID, + "SL_ID": 0, + "SL_Object": "", } - for _, hwID := range cmd.Hardware_id { - cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for hardware server {{.HwID}}.", map[string]interface{}{"VolumeId": volumeID, "HwID": hwID})) + for _, sl_id := range cmd.Virtual_id { + subs["SL_Object"] = T("Virtual Server") + subs["SL_ID"] = sl_id + cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for {{.SL_Object}} {{.SL_ID}}.", subs)) } - for _, ip := range IPIds { - cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for IP address {{.IP}}.", map[string]interface{}{"VolumeId": volumeID, "IP": ip})) + for _, sl_id := range cmd.Hardware_id { + subs["SL_Object"] = T("Hardware Server") + subs["SL_ID"] = sl_id + cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for {{.SL_Object}} {{.SL_ID}}.", subs)) + } + for _, sl_id := range IPIds { + subs["SL_Object"] = T("IP Address") + subs["SL_ID"] = sl_id + cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for {{.SL_Object}} {{.SL_ID}}.", subs)) } return nil } + +func (cmd *AccessRevokeCommand) RemoveSubnetFromHost(host_id int) error { + + outputFormat := cmd.GetOutputFlag() + subnet_ids := []int{cmd.Subnet_id} + resp, err := cmd.StorageManager.RemoveSubnetsFromAcl(host_id, subnet_ids) + if err != nil { + return err + } + // If the API returns an empty array, that means it didn't add the subnet we asked for. + // Likely because ISCSI Isolation is disabled on the account. + // ibmcloud sl call-api SoftLayer_Account getObject --mask="mask[id,iscsiIsolationDisabled]" + if len(resp) == 0 || utils.IntInSlice(cmd.Subnet_id, resp) == -1 { + subs := map[string]interface{}{"subnetID": cmd.Subnet_id, "accessID": host_id} + return slErr.NewAPIError( + T("Failed to remove subnet id: {{.subnetID}} from allowed host id: {{.accessID}}", subs), "", 2, + ) + } + if outputFormat == "JSON" { + return utils.PrintPrettyJSON(cmd.UI, resp) + } + + cmd.UI.Ok() + subs := map[string]interface{}{ + "VolumeId": host_id, + "SL_ID": cmd.Subnet_id, + "SL_Object": T("Subnet"), + } + cmd.UI.Print(T("Access to {{.VolumeId}} was revoked for {{.SL_Object}} {{.SL_ID}}.", subs)) + return nil +} \ No newline at end of file diff --git a/plugin/commands/block/access_revoke_test.go b/plugin/commands/block/access_revoke_test.go index bc2a8251..d2a8504a 100644 --- a/plugin/commands/block/access_revoke_test.go +++ b/plugin/commands/block/access_revoke_test.go @@ -38,85 +38,79 @@ var _ = Describe("Access Revoke", func() { }) Describe("Access Revoke", func() { - Context("Access revoke without volume id", func() { - It("return error", func() { + Context("Syntax Errors", func() { + It("Require One Argument", func() { err := testhelpers.RunCobraCommand(cliCommand.Command) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Incorrect Usage: This command requires one argument")) }) }) - Context("Access revoke with correct volume id and virtual server id", func() { + Context("Successful Revokations", func() { BeforeEach(func() { FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) }) - It("return no error", func() { + It("Virtual Server", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--virtual-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for virtual server 5678")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for Virtual Server 5678")) }) - }) - - Context("Access revoke with correct volume id and hardware server id", func() { - BeforeEach(func() { - FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) - }) - It("return no error", func() { + It("Hardware Server", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--hardware-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for hardware server 5678.")) - }) - }) - - Context("Access revoke with correct volume id and ip address id", func() { - BeforeEach(func() { - FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for Hardware Server 5678.")) }) - It("return no error", func() { + It("Single IP Address ID", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address-id", "5678") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for IP address 5678.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for IP Address 5678.")) }) - }) - - Context("Access revoke with correct volume id and ip address", func() { - BeforeEach(func() { - FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) + It("Single IP address", func() { fakeNetworkManager.IPLookupReturns(datatypes.Network_Subnet_IpAddress{Id: sl.Int(5678)}, nil) - }) - It("return no error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address", "1.2.3.4") Expect(err).NotTo(HaveOccurred()) - Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for IP address 5678.")) + Expect(fakeUI.Outputs()).To(ContainSubstring("Access to 1234 was revoked for IP Address 5678.")) }) }) - Context("Access revoke with correct volume id and wrong ip address", func() { + Context("Error Handling", func() { BeforeEach(func() { - FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) - fakeNetworkManager.IPLookupReturns(datatypes.Network_Subnet_IpAddress{}, errors.New("Not Found")) + FakeStorageManager.DeauthorizeHostToVolumeReturns([]datatypes.Network_Storage_Allowed_Host{}, nil) }) - It("return error", func() { + It("IP Not Found", func() { + fakeNetworkManager.IPLookupReturns(datatypes.Network_Subnet_IpAddress{}, errors.New("Not Found")) err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--ip-address", "1.2.3.4") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("IP address 1.2.3.4 is not found on your account.Please confirm IP and try again.")) + Expect(err.Error()).To(ContainSubstring("IP address 1.2.3.4 is not found on your account.")) Expect(err.Error()).To(ContainSubstring("Not Found")) }) - }) - - Context("Access Authorize with correct volume id but server API call fails", func() { - BeforeEach(func() { + It("API error", func() { FakeStorageManager.DeauthorizeHostToVolumeReturns( - []datatypes.Network_Storage_Allowed_Host{}, - errors.New("Internal Server Error"), + []datatypes.Network_Storage_Allowed_Host{}, errors.New("Internal Server Error"), ) - }) - It("return error", func() { err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--virtual-id", "5678") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("Failed to revoke access to volume 1234.")) Expect(err.Error()).To(ContainSubstring("Internal Server Error")) }) + It("Subnet not removed because isci isolation", func() { + FakeStorageManager.RemoveSubnetsFromAclReturns([]int{}, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to remove subnet id: 5678")) + }) + It("Subnet not removed because wrong subnet returned", func() { + FakeStorageManager.RemoveSubnetsFromAclReturns([]int{999}, nil) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Failed to remove subnet id: 5678")) + }) + It("Subnet not removed because API error", func() { + FakeStorageManager.RemoveSubnetsFromAclReturns([]int{}, errors.New("API ERROR")) + err := testhelpers.RunCobraCommand(cliCommand.Command, "1234", "--subnet-id", "5678") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("API ERROR")) + }) }) }) }) diff --git a/plugin/i18n/v2Resources/active.de_DE.json b/plugin/i18n/v2Resources/active.de_DE.json index 01b996aa..dea9c470 100644 --- a/plugin/i18n/v2Resources/active.de_DE.json +++ b/plugin/i18n/v2Resources/active.de_DE.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID--password PASSWORD\n \n ACCESS_ID ist die zulässige Host-ID (allowed_host_id) aus '${COMMAND_NAME} sl {{.storageType}} access-list'" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke DATENTRÄGER_ID [OPTIONEN]\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Dieser Befehl widerruft den Zugriff des virtuellen Servers mit der ID 87654321 auf den Datenträger mit der ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "BEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Dieser Befehl widerruft den Zugriff des virtuellen Servers mit der ID 87654321 auf den Datenträger mit der ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback DATENTRÄGER_ID\n \nBEISPIEL:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Dieser Befehl führt eine Failback-Operation für einen Datenträger mit der ID 12345678 durch." diff --git a/plugin/i18n/v2Resources/active.en-US.json b/plugin/i18n/v2Resources/active.en-US.json index 463f2c2a..d9029c5a 100644 --- a/plugin/i18n/v2Resources/active.en-US.json +++ b/plugin/i18n/v2Resources/active.en-US.json @@ -2,6 +2,9 @@ "\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 5555 --subnet-id 1111\n This command adds subnet with id 1111 to the Allowed Host with id 5555. Use 'access-list' to find this id.\n SoftLayer_Account::iscsiIsolationDisabled must be False for this command to do anything.": { "other": "\n ${COMMAND_NAME} sl {{.storageType}} access-authorize 5555 --subnet-id 1111\n This command adds subnet with id 1111 to the Allowed Host with id 5555. Use 'access-list' to find this id.\n SoftLayer_Account::iscsiIsolationDisabled must be False for this command to do anything." }, + "\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 5555 --subnet-id 1111\n This command removes subnet with id 1111 from the Allowed Host with id 5555. Use 'access-list' to find this id.": { + "other": "\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 5555 --subnet-id 1111\n This command removes subnet with id 1111 from the Allowed Host with id 5555. Use 'access-list' to find this id." + }, "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678.": { "other": "\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-locations 12345678\n This command lists suitable replication data centers for block volume with ID 12345678." }, @@ -680,6 +683,9 @@ "Access to {{.VolumeId}} was revoked for virtual server {{.VsID}}.": { "other": "Access to {{.VolumeId}} was revoked for virtual server {{.VsID}}." }, + "Access to {{.VolumeId}} was revoked for {{.SL_Object}} {{.SL_ID}}.": { + "other": "Access to {{.VolumeId}} was revoked for {{.SL_Object}} {{.SL_ID}}." + }, "Account": { "other": "Account" }, @@ -1886,6 +1892,9 @@ "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID.": { "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-list 12345678 --sortby id \n This command lists all hosts that are authorized to access volume with ID 12345678 and sorts them by ID." }, + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678." + }, "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321.": { "other": "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failover 12345678 87654321\n This command performs failover operation for volume with ID 12345678 to replica volume with ID 87654321." }, @@ -3122,6 +3131,9 @@ "Failed to remove rule {{.RuleId}} in security group {{.GroupID}}.\n": { "other": "Failed to remove rule {{.RuleId}} in security group {{.GroupID}}.\n" }, + "Failed to remove subnet id: {{.subnetID}} from allowed host id: {{.accessID}}": { + "other": "Failed to remove subnet id: {{.subnetID}} from allowed host id: {{.accessID}}" + }, "Failed to remove subnet id: {{.subnetID}} to allowed host id: {{.accessID}}": { "other": "Failed to remove subnet id: {{.subnetID}} to allowed host id: {{.accessID}}" }, diff --git a/plugin/i18n/v2Resources/active.es_ES.json b/plugin/i18n/v2Resources/active.es_ES.json index 50ebfd5c..93fe6f4a 100644 --- a/plugin/i18n/v2Resources/active.es_ES.json +++ b/plugin/i18n/v2Resources/active.es_ES.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID--password PASSWORD\n\nACCESS_ID es el allowed_host_id de '${COMMAND_NAME} sl {{.storageType}} access-list'" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Este mandato revoca el acceso del servidor virtual con ID 87654321 al volumen con ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "EJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Este mandato revoca el acceso del servidor virtual con ID 87654321 al volumen con ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback ID_VOLUMEN\n\nEJEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Este mandato realiza una operación de restablecimiento para el volumen con ID 12345678." diff --git a/plugin/i18n/v2Resources/active.fr_FR.json b/plugin/i18n/v2Resources/active.fr_FR.json index 029a340c..42ebe11f 100644 --- a/plugin/i18n/v2Resources/active.fr_FR.json +++ b/plugin/i18n/v2Resources/active.fr_FR.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID est l'ID d'hôte autorisé obtenu avec '${COMMAND_NAME} sl {{.storageType}} access-list'" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke ID_VOLUME [OPTIONS]\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Cette commande révoque l'accès du serveur virtuel dont l'ID est 87654321 au volume dont l'ID est 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "EXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Cette commande révoque l'accès du serveur virtuel dont l'ID est 87654321 au volume dont l'ID est 12345678." }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback ID_VOLUME\n\nEXEMPLE :\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Cette commande effectue une opération de reprise par restauration pour le volume dont l'ID 12345678." diff --git a/plugin/i18n/v2Resources/active.it_IT.json b/plugin/i18n/v2Resources/active.it_IT.json index 694e5e98..874318be 100644 --- a/plugin/i18n/v2Resources/active.it_IT.json +++ b/plugin/i18n/v2Resources/active.it_IT.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID è il valore allowed_host_id da '${COMMAND_NAME} sl {{.storageType}} access-list'" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke ID_VOLUME [OPZIONI]\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Questo comando revoca l'accesso del server virtuale con ID 87654321 al volume con ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "ESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Questo comando revoca l'accesso del server virtuale con ID 87654321 al volume con ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback ID_VOLUME\n\nESEMPIO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Questo comando esegue l'operazione failback per il volume con ID 12345678." diff --git a/plugin/i18n/v2Resources/active.ja_JP.json b/plugin/i18n/v2Resources/active.ja_JP.json index 4ce55f0c..8a1f750a 100644 --- a/plugin/i18n/v2Resources/active.ja_JP.json +++ b/plugin/i18n/v2Resources/active.ja_JP.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME}sl {{.storageType}} access-password ACCESS_ID--password PASSWORD \n \n ACCESS_ID は、「${COMMAND_NAME} sl {{.storageType}} access-list」からの allowed_host_id です。" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS] \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 -- virtual-id 87654321\n このコマンドは、ID 87654321 の仮想サーバーから ID 12345678 のボリュームへのアクセスを取り消します。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "例: \n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 -- virtual-id 87654321\n このコマンドは、ID 87654321 の仮想サーバーから ID 12345678 のボリュームへのアクセスを取り消します。" }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID \n \n 例: \n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n このコマンドは、ID 12345678 のボリュームのフェイルバック操作を実行します。" diff --git a/plugin/i18n/v2Resources/active.ko_KR.json b/plugin/i18n/v2Resources/active.ko_KR.json index a06ceffc..6b59ac8c 100644 --- a/plugin/i18n/v2Resources/active.ko_KR.json +++ b/plugin/i18n/v2Resources/active.ko_KR.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID는 '${COMMAND_NAME} sl {{.storageType}} access-list'의 허용된 호스트 id입니다." }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [옵션]\n\n예:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n 이 명령은 볼륨(ID 12345678)에 대한 가상 서버(ID 87654321)의 액세스를 취소합니다." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "예:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n 이 명령은 볼륨(ID 12345678)에 대한 가상 서버(ID 87654321)의 액세스를 취소합니다." }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n 이 명령은 볼륨(ID 12345678)에 대한 장애 복구 조작을 수행합니다." diff --git a/plugin/i18n/v2Resources/active.pt_BR.json b/plugin/i18n/v2Resources/active.pt_BR.json index 8d779e8a..51c3ed20 100644 --- a/plugin/i18n/v2Resources/active.pt_BR.json +++ b/plugin/i18n/v2Resources/active.pt_BR.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID é o allowed_host_id a partir de '${COMMAND_NAME} sl {{.storageType}} access-list'" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Esse comando revoga o acesso de servidor virtual com o ID 87654321 para o volume com o ID 12345678." + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "EXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n Esse comando revoga o acesso de servidor virtual com o ID 87654321 para o volume com o ID 12345678." }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\nEXEMPLO:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n Esse comando executa a operação de failback para o volume com o ID 12345678." diff --git a/plugin/i18n/v2Resources/active.zh_Hans.json b/plugin/i18n/v2Resources/active.zh_Hans.json index 68171cf8..04d100e1 100644 --- a/plugin/i18n/v2Resources/active.zh_Hans.json +++ b/plugin/i18n/v2Resources/active.zh_Hans.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID 是来自“${COMMAND_NAME} sl {{.storageType}} access-list”的 allowed_host_id" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n 此命令撤销标识为 87654321 的虚拟服务器对标识为 12345678 的卷的访问权。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "示例:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n 此命令撤销标识为 87654321 的虚拟服务器对标识为 12345678 的卷的访问权。" }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\n示例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n 此命令对标识为 12345678 的卷执行故障恢复操作。" diff --git a/plugin/i18n/v2Resources/active.zh_Hant.json b/plugin/i18n/v2Resources/active.zh_Hant.json index 2b181180..205e3840 100644 --- a/plugin/i18n/v2Resources/active.zh_Hant.json +++ b/plugin/i18n/v2Resources/active.zh_Hant.json @@ -353,8 +353,8 @@ "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\t\n\tACCESS_ID is the allowed_host_id from '${COMMAND_NAME} sl {{.storageType}} access-list'": { "other": "${COMMAND_NAME} sl {{.storageType}} access-password ACCESS_ID --password PASSWORD\n\nACCESS_ID 是來自 '${COMMAND_NAME} sl {{.storageType}} access-list' 的 allowed_host_id" }, - "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { - "other": "${COMMAND_NAME} sl {{.storageType}} access-revoke VOLUME_ID [OPTIONS]\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n 這個指令會撤銷虛擬伺服器(其 ID 為 87654321)對磁區(其 ID 為 12345678)的存取權。" + "EXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n This command revokes access of virtual server with ID 87654321 to volume with ID 12345678.": { + "other": "範例:\n ${COMMAND_NAME} sl {{.storageType}} access-revoke 12345678 --virtual-id 87654321\n 這個指令會撤銷虛擬伺服器(其 ID 為 87654321)對磁區(其 ID 為 12345678)的存取權。" }, "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n\t\t\nEXAMPLE:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n This command performs failback operation for volume with ID 12345678.": { "other": "${COMMAND_NAME} sl {{.storageType}} replica-failback VOLUME_ID\n \n範例:\n ${COMMAND_NAME} sl {{.storageType}} replica-failback 12345678\n 這個指令會針對磁區(其 ID 為 12345678)執行失效回復作業。" From f03fc096aa712d00a87bc3a868c582a56b1aee91 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 12 Nov 2024 16:56:14 -0600 Subject: [PATCH 36/38] updated readme to include some bits about gpg setup --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 2501e3f3..e3002e26 100644 --- a/README.md +++ b/README.md @@ -611,4 +611,22 @@ detect-secrets scan --update .secrets.baseline If we need to update the excluded files (these are saved in the .secrets.baseline file) do this: ```bash detect-secrets -v scan --update .secrets.baseline --exclude-files "plugin/i18n/v1Resources/|plugin/i18n/v2Resources/|(.*test.*)|(vendor)|(go.sum)|bin/" +``` + + +# Commit Signing +Always a good idea to sign commits, even if it takes a bit to setup. + +1. `git config --global commit.gpgsign true` Enabled signed commits +2. `gpg --full-generate-key` Create a GPG key to sign commits with (use your github email) +3. `git commit --message="THE MESSAGE"` is your normal commit, but should now be signed! You may be prompted for a password +4. `git log --show-signature` confirm its signed. + +## Windows setup + +In windows GPG pops up an annoying window for password prompting. Following [Using Command-Line Passphrase Input for GPG with Git](https://betakuang.medium.com/using-command-line-passphrase-input-for-gpg-with-git-for-windows-f78ae2c7cd2e) to be prompted on the CLI + +``` +$> cat ~/.gnupg/gpg-agent.conf +allow-loopback-pinentry ``` \ No newline at end of file From bfe87d631c97638725d1936402e5a87ec2ea12b8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 12 Nov 2024 17:06:14 -0600 Subject: [PATCH 37/38] Added more gpg signing info to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e3002e26..7d57e5f4 100644 --- a/README.md +++ b/README.md @@ -621,6 +621,8 @@ Always a good idea to sign commits, even if it takes a bit to setup. 2. `gpg --full-generate-key` Create a GPG key to sign commits with (use your github email) 3. `git commit --message="THE MESSAGE"` is your normal commit, but should now be signed! You may be prompted for a password 4. `git log --show-signature` confirm its signed. +5. `gpg --output email.pgp --armor --export email@domain.com` will export your PUBLIC key so you can upload it to github. + ## Windows setup From 40fdb96a3a578bebf1056aaaeb492351e4c7dd44 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 25 Nov 2024 09:46:20 -0600 Subject: [PATCH 38/38] v1.5.4 version bump --- plugin/metadata/sl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/metadata/sl.go b/plugin/metadata/sl.go index ca245365..3442ceb0 100644 --- a/plugin/metadata/sl.go +++ b/plugin/metadata/sl.go @@ -16,7 +16,7 @@ var ( LIMIT = 50 NS_SL_NAME = "sl" OutputFlagName = "output" - PLUGIN_VERSION = "1.5.3" + PLUGIN_VERSION = "1.5.4" PLUGIN_SOFTLAYER = "sl" PLUGIN_SOFTLAYER_USAGE = "Classic Infrastructure" UsageAgentHeader = "ibmcloud sl v" + PLUGIN_VERSION