From 60d2293ffc70fd561b142d4e512318dd55e3a6b1 Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Thu, 22 Jan 2026 19:11:35 +0200 Subject: [PATCH 1/3] add cloud-regions cmd and tests --- Makefile | 5 +- cmd/base/list_options.go | 28 + cmd/entities/cloud-regions/add_snapshot.go | 59 ++ cmd/entities/cloud-regions/cloud_regions.go | 44 + .../cloud-regions/cloud_regions_test.go | 821 ++++++++++++++++++ cmd/entities/cloud-regions/delete_snapshot.go | 48 + cmd/entities/cloud-regions/get_credentials.go | 39 + cmd/entities/cloud-regions/list.go | 22 + cmd/entities/cloud-regions/list_flavors.go | 29 + cmd/entities/cloud-regions/list_images.go | 29 + cmd/entities/cloud-regions/list_snapshots.go | 31 + cmd/root.go | 2 + go.mod | 2 +- go.sum | 4 +- .../mocks/cloud_computing_regions_service.go | 142 +++ internal/output/entities/cloud_regions.go | 102 +++ internal/output/entities/init.go | 1 + .../entities/cloud-regions/add_snapshot.json | 9 + .../entities/cloud-regions/add_snapshot.txt | 7 + .../entities/cloud-regions/add_snapshot.yaml | 7 + .../cloud-regions/get_credentials.json | 6 + .../cloud-regions/get_credentials.txt | 2 + .../cloud-regions/get_credentials.yaml | 4 + testdata/entities/cloud-regions/list.json | 7 + testdata/entities/cloud-regions/list_all.json | 12 + .../entities/cloud-regions/list_flavors.json | 6 + .../cloud-regions/list_flavors_all.json | 10 + .../cloud-regions/list_flavors_pageview.txt | 5 + .../cloud-regions/list_flavors_template.txt | 2 + .../entities/cloud-regions/list_images.json | 6 + .../cloud-regions/list_images_all.json | 10 + .../cloud-regions/list_images_pageview.txt | 5 + .../cloud-regions/list_images_template.txt | 2 + .../entities/cloud-regions/list_pageview.txt | 7 + .../cloud-regions/list_snapshots.json | 11 + .../cloud-regions/list_snapshots_all.json | 20 + .../cloud-regions/list_snapshots_pageview.txt | 15 + .../cloud-regions/list_snapshots_template.txt | 2 + .../entities/cloud-regions/list_template.txt | 2 + 39 files changed, 1561 insertions(+), 4 deletions(-) create mode 100644 cmd/entities/cloud-regions/add_snapshot.go create mode 100644 cmd/entities/cloud-regions/cloud_regions.go create mode 100644 cmd/entities/cloud-regions/cloud_regions_test.go create mode 100644 cmd/entities/cloud-regions/delete_snapshot.go create mode 100644 cmd/entities/cloud-regions/get_credentials.go create mode 100644 cmd/entities/cloud-regions/list.go create mode 100644 cmd/entities/cloud-regions/list_flavors.go create mode 100644 cmd/entities/cloud-regions/list_images.go create mode 100644 cmd/entities/cloud-regions/list_snapshots.go create mode 100644 internal/mocks/cloud_computing_regions_service.go create mode 100644 internal/output/entities/cloud_regions.go create mode 100644 testdata/entities/cloud-regions/add_snapshot.json create mode 100644 testdata/entities/cloud-regions/add_snapshot.txt create mode 100644 testdata/entities/cloud-regions/add_snapshot.yaml create mode 100644 testdata/entities/cloud-regions/get_credentials.json create mode 100644 testdata/entities/cloud-regions/get_credentials.txt create mode 100644 testdata/entities/cloud-regions/get_credentials.yaml create mode 100644 testdata/entities/cloud-regions/list.json create mode 100644 testdata/entities/cloud-regions/list_all.json create mode 100644 testdata/entities/cloud-regions/list_flavors.json create mode 100644 testdata/entities/cloud-regions/list_flavors_all.json create mode 100644 testdata/entities/cloud-regions/list_flavors_pageview.txt create mode 100644 testdata/entities/cloud-regions/list_flavors_template.txt create mode 100644 testdata/entities/cloud-regions/list_images.json create mode 100644 testdata/entities/cloud-regions/list_images_all.json create mode 100644 testdata/entities/cloud-regions/list_images_pageview.txt create mode 100644 testdata/entities/cloud-regions/list_images_template.txt create mode 100644 testdata/entities/cloud-regions/list_pageview.txt create mode 100644 testdata/entities/cloud-regions/list_snapshots.json create mode 100644 testdata/entities/cloud-regions/list_snapshots_all.json create mode 100644 testdata/entities/cloud-regions/list_snapshots_pageview.txt create mode 100644 testdata/entities/cloud-regions/list_snapshots_template.txt create mode 100644 testdata/entities/cloud-regions/list_template.txt diff --git a/Makefile b/Makefile index aa98965..b31cba0 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ generate: deps mockgen --destination ./internal/mocks/kubernetes_clusters_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/kubernetes_clusters.go mockgen --destination ./internal/mocks/l2_segment_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/l2_segments.go mockgen --destination ./internal/mocks/network_pool_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/network_pools.go + mockgen --destination ./internal/mocks/cloud_computing_regions_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/cloud_computing_regions.go sed -i '' 's|github.com/serverscom/srvctl/vendor/github.com/serverscom/serverscom-go-client/pkg|github.com/serverscom/serverscom-go-client/pkg|g' \ ./internal/mocks/ssh_service.go \ ./internal/mocks/hosts_service.go \ @@ -33,4 +34,6 @@ generate: deps ./internal/mocks/locations_service.go \ ./internal/mocks/kubernetes_clusters_service.go \ ./internal/mocks/l2_segment_service.go \ - ./internal/mocks/network_pool_service.go \ No newline at end of file + ./internal/mocks/network_pool_service.go + ./internal/mocks/network_pool_service.go \ + ./internal/mocks/cloud_computing_regions_service.go diff --git a/cmd/base/list_options.go b/cmd/base/list_options.go index 6602048..3e24454 100644 --- a/cmd/base/list_options.go +++ b/cmd/base/list_options.go @@ -459,3 +459,31 @@ func (o *RackIDOption[T]) ApplyToCollection(collection serverscom.Collection[T]) collection.SetParam("rack_id", o.rackID) } } + +type InstanceIDOption[T any] struct { + instanceID string +} + +func (o *InstanceIDOption[T]) AddFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&o.instanceID, "instance-id", "", "Filter results by instance ID") +} + +func (o *InstanceIDOption[T]) ApplyToCollection(collection serverscom.Collection[T]) { + if o.instanceID != "" { + collection.SetParam("instance_id", o.instanceID) + } +} + +type IsBackupOption[T any] struct { + isBackup bool +} + +func (o *IsBackupOption[T]) AddFlags(cmd *cobra.Command) { + cmd.Flags().BoolVar(&o.isBackup, "is-backup", false, "Filter results by backup status (true, false)") +} + +func (o *IsBackupOption[T]) ApplyToCollection(collection serverscom.Collection[T]) { + if o.isBackup { + collection.SetParam("is_backup", "true") + } +} diff --git a/cmd/entities/cloud-regions/add_snapshot.go b/cmd/entities/cloud-regions/add_snapshot.go new file mode 100644 index 0000000..3725490 --- /dev/null +++ b/cmd/entities/cloud-regions/add_snapshot.go @@ -0,0 +1,59 @@ +package cloudregions + +import ( + "log" + "strconv" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newAddSnapshotCmd(cmdContext *base.CmdContext) *cobra.Command { + var name string + var instanceID string + + cmd := &cobra.Command{ + Use: "add-snapshot ", + Short: "Add snapshot for cloud region by Region ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + regionID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + input := serverscom.CloudSnapshotCreateInput{ + Name: name, + InstanceID: instanceID, + } + + snapshot, err := scClient.CloudComputingRegions.CreateSnapshot(ctx, regionID, input) + if err != nil { + return err + } + + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(snapshot) + }, + } + + cmd.Flags().StringVar(&name, "name", "", "Snapshot name") + cmd.Flags().StringVar(&instanceID, "instance-id", "", "Instance ID") + + if err := cmd.MarkFlagRequired("name"); err != nil { + log.Fatal(err) + } + if err := cmd.MarkFlagRequired("instance-id"); err != nil { + log.Fatal(err) + } + + return cmd +} diff --git a/cmd/entities/cloud-regions/cloud_regions.go b/cmd/entities/cloud-regions/cloud_regions.go new file mode 100644 index 0000000..eadc528 --- /dev/null +++ b/cmd/entities/cloud-regions/cloud_regions.go @@ -0,0 +1,44 @@ +package cloudregions + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/serverscom/srvctl/internal/output/entities" + "github.com/spf13/cobra" +) + +func NewCmd(cmdContext *base.CmdContext) *cobra.Command { + cloudEntity, err := entities.Registry.GetEntityFromValue(serverscom.CloudComputingRegion{}) + if err != nil { + log.Fatal(err) + } + entitiesMap := make(map[string]entities.EntityInterface) + entitiesMap["cloud-regions"] = cloudEntity + + cmd := &cobra.Command{ + Use: "cloud-regions", + Short: "Manage cloud regions", + PersistentPreRunE: base.CombinePreRunE( + base.CheckFormatterFlags(cmdContext, entitiesMap), + base.CheckEmptyContexts(cmdContext), + ), + Args: base.NoArgs, + Run: base.UsageRun, + } + + cmd.AddCommand( + newListCmd(cmdContext), + newGetCredentialsCmd(cmdContext), + newListImagesCmd(cmdContext), + newListFlavorsCmd(cmdContext), + newListSnapshotsCmd(cmdContext), + newAddSnapshotCmd(cmdContext), + newDeleteSnapshotCmd(cmdContext), + ) + + base.AddFormatFlags(cmd) + + return cmd +} diff --git a/cmd/entities/cloud-regions/cloud_regions_test.go b/cmd/entities/cloud-regions/cloud_regions_test.go new file mode 100644 index 0000000..41069a5 --- /dev/null +++ b/cmd/entities/cloud-regions/cloud_regions_test.go @@ -0,0 +1,821 @@ +package cloudregions + +import ( + "errors" + "path/filepath" + "testing" + + . "github.com/onsi/gomega" + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/testutils" + "github.com/serverscom/srvctl/internal/mocks" + "go.uber.org/mock/gomock" +) + +var ( + fixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "cloud-regions") + testRegionID = int64(1) + testRegion = serverscom.CloudComputingRegion{ + ID: 1, + Name: "Test Region", + Code: "TEST", + } + testRegion2 = serverscom.CloudComputingRegion{ + ID: 2, + Name: "Test Region 2", + Code: "TEST2", + } + testCredentials = serverscom.CloudComputingRegionCredentials{ + Password: "test-password", + TenantName: 12345, + URL: "https://test.example.com", + Username: 67890, + } + testImage = serverscom.CloudComputingImage{ + ID: "img-123", + Name: "Test Image", + } + testImage2 = serverscom.CloudComputingImage{ + ID: "img-456", + Name: "Test Image 2", + } + testFlavor = serverscom.CloudComputingFlavor{ + ID: "flavor-123", + Name: "Test Flavor", + } + testFlavor2 = serverscom.CloudComputingFlavor{ + ID: "flavor-456", + Name: "Test Flavor 2", + } + testSnapshot = serverscom.CloudSnapshot{ + ID: "snap-123", + Name: "Test Snapshot", + ImageSize: 1024, + MinDisk: 10, + Status: "available", + IsBackup: false, + FileURL: "https://test.example.com/snap-123", + } + testSnapshot2 = serverscom.CloudSnapshot{ + ID: "snap-456", + Name: "Test Snapshot 2", + ImageSize: 1024, + MinDisk: 10, + Status: "available", + IsBackup: false, + FileURL: "https://test.example.com/snap-123", + } +) + +func TestListCloudRegionsCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.CloudComputingRegion]) + }{ + { + name: "list all cloud regions", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingRegion]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.CloudComputingRegion{ + testRegion, + testRegion2, + }, nil) + }, + }, + { + name: "list cloud regions", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingRegion]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingRegion{ + testRegion, + }, nil) + }, + }, + { + name: "list cloud regions with template", + args: []string{"--template", "{{range .}}ID: {{.ID}} Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingRegion]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingRegion{ + testRegion, + testRegion2, + }, nil) + }, + }, + { + name: "list cloud regions with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingRegion]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingRegion{ + testRegion, + testRegion2, + }, nil) + }, + }, + { + name: "list cloud regions with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingRegion]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.CloudComputingRegion](mockCtrl) + + cloudRegionsServiceHandler.EXPECT(). + Collection(). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "list"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestGetCredentialsCmd(t *testing.T) { + testCases := []struct { + name string + regionID string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get credentials in default format", + regionID: "1", + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_credentials.txt")), + }, + { + name: "get credentials in JSON format", + regionID: "1", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_credentials.json")), + }, + { + name: "get credentials in YAML format", + regionID: "1", + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_credentials.yaml")), + }, + { + name: "get credentials with error", + regionID: "1", + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + var err error + if tc.expectError { + err = errors.New("some error") + } + cloudRegionsServiceHandler.EXPECT(). + Credentials(gomock.Any(), testRegionID). + Return(&testCredentials, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "get-credentials", tc.regionID} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err = cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestListImagesCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.CloudComputingImage]) + }{ + { + name: "list all images", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_images_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingImage]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.CloudComputingImage{ + testImage, + testImage2, + }, nil) + }, + }, + { + name: "list images", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_images.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingImage]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingImage{ + testImage, + }, nil) + }, + }, + { + name: "list images with template", + args: []string{"--template", "{{range .}}ID: {{.ID}} Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_images_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingImage]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingImage{ + testImage, + testImage2, + }, nil) + }, + }, + { + name: "list images with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_images_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingImage]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingImage{ + testImage, + testImage2, + }, nil) + }, + }, + { + name: "list images with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingImage]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.CloudComputingImage](mockCtrl) + + cloudRegionsServiceHandler.EXPECT(). + Images(testRegionID). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "list-images", "1"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestListFlavorsCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.CloudComputingFlavor]) + }{ + { + name: "list all flavors", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_flavors_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingFlavor]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.CloudComputingFlavor{ + testFlavor, + testFlavor2, + }, nil) + }, + }, + { + name: "list flavors", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_flavors.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingFlavor]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingFlavor{ + testFlavor, + }, nil) + }, + }, + { + name: "list flavors with template", + args: []string{"--template", "{{range .}}ID: {{.ID}} Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_flavors_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingFlavor]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingFlavor{ + testFlavor, + testFlavor2, + }, nil) + }, + }, + { + name: "list flavors with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_flavors_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingFlavor]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudComputingFlavor{ + testFlavor, + testFlavor2, + }, nil) + }, + }, + { + name: "list flavors with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.CloudComputingFlavor]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.CloudComputingFlavor](mockCtrl) + + cloudRegionsServiceHandler.EXPECT(). + Flavors(testRegionID). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "list-flavors", "1"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestListSnapshotsCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.CloudSnapshot]) + }{ + { + name: "list all snapshots", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_snapshots_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudSnapshot]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.CloudSnapshot{ + testSnapshot, + testSnapshot2, + }, nil) + }, + }, + { + name: "list snapshots", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_snapshots.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudSnapshot]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudSnapshot{ + testSnapshot, + }, nil) + }, + }, + { + name: "list snapshots with template", + args: []string{"--template", "{{range .}}ID: {{.ID}} Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_snapshots_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudSnapshot]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudSnapshot{ + testSnapshot, + testSnapshot2, + }, nil) + }, + }, + { + name: "list snapshots with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_snapshots_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.CloudSnapshot]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.CloudSnapshot{ + testSnapshot, + testSnapshot2, + }, nil) + }, + }, + { + name: "list snapshots with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.CloudSnapshot]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.CloudSnapshot](mockCtrl) + + cloudRegionsServiceHandler.EXPECT(). + Snapshots(testRegionID). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "list-snapshots", "1"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestAddSnapshotCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + configureMock func(*mocks.MockCloudComputingRegionsService) + expectedOutput []byte + expectError bool + }{ + { + name: "add snapshot", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "add_snapshot.json")), + args: []string{ + "--name", "Test Snapshot", + "--instance-id", "instance-123", + }, + configureMock: func(mock *mocks.MockCloudComputingRegionsService) { + mock.EXPECT(). + CreateSnapshot(gomock.Any(), testRegionID, serverscom.CloudSnapshotCreateInput{ + Name: "Test Snapshot", + InstanceID: "instance-123", + }). + Return(&testSnapshot, nil) + }, + }, + { + name: "add snapshot with error", + expectError: true, + args: []string{ + "--name", "Test Snapshot", + "--instance-id", "instance-123", + }, + configureMock: func(mock *mocks.MockCloudComputingRegionsService) { + mock.EXPECT(). + CreateSnapshot(gomock.Any(), testRegionID, serverscom.CloudSnapshotCreateInput{ + Name: "Test Snapshot", + InstanceID: "instance-123", + }). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(cloudRegionsServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "add-snapshot", "1"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestDeleteSnapshotCmd(t *testing.T) { + testCases := []struct { + name string + args []string + configureMock func(*mocks.MockCloudComputingRegionsService) + expectError bool + }{ + { + name: "delete snapshot", + args: []string{ + "--snapshot-id", "snap-123", + }, + configureMock: func(mock *mocks.MockCloudComputingRegionsService) { + mock.EXPECT(). + DeleteSnapshot(gomock.Any(), testRegionID, "snap-123"). + Return(nil) + }, + }, + { + name: "delete snapshot with error", + args: []string{ + "--snapshot-id", "snap-123", + }, + expectError: true, + configureMock: func(mock *mocks.MockCloudComputingRegionsService) { + mock.EXPECT(). + DeleteSnapshot(gomock.Any(), testRegionID, "snap-123"). + Return(errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + cloudRegionsServiceHandler := mocks.NewMockCloudComputingRegionsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.CloudComputingRegions = cloudRegionsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(cloudRegionsServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + cloudRegionsCmd := NewCmd(testCmdContext) + + args := []string{"cloud-regions", "delete-snapshot", "1"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(cloudRegionsCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + } + }) + } +} diff --git a/cmd/entities/cloud-regions/delete_snapshot.go b/cmd/entities/cloud-regions/delete_snapshot.go new file mode 100644 index 0000000..e4ea8bb --- /dev/null +++ b/cmd/entities/cloud-regions/delete_snapshot.go @@ -0,0 +1,48 @@ +package cloudregions + +import ( + "fmt" + "log" + "strconv" + + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newDeleteSnapshotCmd(cmdContext *base.CmdContext) *cobra.Command { + var snapshotID string + + cmd := &cobra.Command{ + Use: "delete-snapshot ", + Short: "Delete snapshot for cloud region by Region ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + regionID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + err = scClient.CloudComputingRegions.DeleteSnapshot(ctx, regionID, snapshotID) + if err != nil { + return err + } + + fmt.Printf("Snapshot %s deleted successfully\n", snapshotID) + return nil + }, + } + + cmd.Flags().StringVar(&snapshotID, "snapshot-id", "", "Snapshot ID") + if err := cmd.MarkFlagRequired("snapshot-id"); err != nil { + log.Fatal(err) + } + + return cmd +} diff --git a/cmd/entities/cloud-regions/get_credentials.go b/cmd/entities/cloud-regions/get_credentials.go new file mode 100644 index 0000000..3af065f --- /dev/null +++ b/cmd/entities/cloud-regions/get_credentials.go @@ -0,0 +1,39 @@ +package cloudregions + +import ( + "strconv" + + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetCredentialsCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "get-credentials ", + Short: "Get credentials for cloud region by Region ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + regionID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + credentials, err := scClient.CloudComputingRegions.Credentials(ctx, regionID) + if err != nil { + return err + } + + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(credentials) + }, + } + + return cmd +} diff --git a/cmd/entities/cloud-regions/list.go b/cmd/entities/cloud-regions/list.go new file mode 100644 index 0000000..d0d4f0a --- /dev/null +++ b/cmd/entities/cloud-regions/list.go @@ -0,0 +1,22 @@ +package cloudregions + +import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.CloudComputingRegion] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + collection := scClient.CloudComputingRegions.Collection() + + return collection + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.CloudComputingRegion]{}, + ) + + return base.NewListCmd("list", "cloud regions", factory, cmdContext, opts...) +} diff --git a/cmd/entities/cloud-regions/list_flavors.go b/cmd/entities/cloud-regions/list_flavors.go new file mode 100644 index 0000000..cb8efbd --- /dev/null +++ b/cmd/entities/cloud-regions/list_flavors.go @@ -0,0 +1,29 @@ +package cloudregions + +import ( + "strconv" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListFlavorsCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.CloudComputingFlavor] { + regionID, _ := strconv.ParseInt(args[0], 10, 64) + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + collection := scClient.CloudComputingRegions.Flavors(regionID) + + return collection + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.CloudComputingFlavor]{}, + ) + + cmd := base.NewListCmd("list-flavors", "cloud region flavors", factory, cmdContext, opts...) + cmd.Use = "list-flavors " + cmd.Args = cobra.ExactArgs(1) + + return cmd +} diff --git a/cmd/entities/cloud-regions/list_images.go b/cmd/entities/cloud-regions/list_images.go new file mode 100644 index 0000000..eb4358f --- /dev/null +++ b/cmd/entities/cloud-regions/list_images.go @@ -0,0 +1,29 @@ +package cloudregions + +import ( + "strconv" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListImagesCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.CloudComputingImage] { + regionID, _ := strconv.ParseInt(args[0], 10, 64) + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + collection := scClient.CloudComputingRegions.Images(regionID) + + return collection + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.CloudComputingImage]{}, + ) + + cmd := base.NewListCmd("list-images", "cloud region images", factory, cmdContext, opts...) + cmd.Use = "list-images " + cmd.Args = cobra.ExactArgs(1) + + return cmd +} diff --git a/cmd/entities/cloud-regions/list_snapshots.go b/cmd/entities/cloud-regions/list_snapshots.go new file mode 100644 index 0000000..eb24ba4 --- /dev/null +++ b/cmd/entities/cloud-regions/list_snapshots.go @@ -0,0 +1,31 @@ +package cloudregions + +import ( + "strconv" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListSnapshotsCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.CloudSnapshot] { + regionID, _ := strconv.ParseInt(args[0], 10, 64) + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + collection := scClient.CloudComputingRegions.Snapshots(regionID) + + return collection + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.CloudSnapshot]{}, + &base.InstanceIDOption[serverscom.CloudSnapshot]{}, + &base.IsBackupOption[serverscom.CloudSnapshot]{}, + ) + + cmd := base.NewListCmd("list-snapshots", "cloud region snapshots", factory, cmdContext, opts...) + cmd.Use = "list-snapshots " + cmd.Args = cobra.ExactArgs(1) + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index 7c7c08f..e8f0e56 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "github.com/serverscom/srvctl/cmd/config" "github.com/serverscom/srvctl/cmd/context" "github.com/serverscom/srvctl/cmd/entities/account" + cloudregions "github.com/serverscom/srvctl/cmd/entities/cloud-regions" "github.com/serverscom/srvctl/cmd/entities/drivemodels" "github.com/serverscom/srvctl/cmd/entities/hosts" "github.com/serverscom/srvctl/cmd/entities/invoices" @@ -74,6 +75,7 @@ func NewRootCmd(version string) *cobra.Command { cmd.AddCommand(sbmmodels.NewCmd(cmdContext)) cmd.AddCommand(l2segments.NewCmd(cmdContext)) cmd.AddCommand(networkpools.NewCmd(cmdContext)) + cmd.AddCommand(cloudregions.NewCmd(cmdContext)) return cmd } diff --git a/go.mod b/go.mod index 89cf130..58c1033 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/creack/pty v1.1.24 github.com/jmespath/go-jmespath v0.4.0 github.com/onsi/gomega v1.38.0 - github.com/serverscom/serverscom-go-client v1.0.25 + github.com/serverscom/serverscom-go-client v1.0.28 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.7 github.com/spf13/viper v1.20.1 diff --git a/go.sum b/go.sum index c0e3b48..64a8f63 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,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/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc= github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw= -github.com/serverscom/serverscom-go-client v1.0.25 h1:W0onVxNRAFDTZNrJyQx/kMNx+ZeICouLWgkdaE3FMnk= -github.com/serverscom/serverscom-go-client v1.0.25/go.mod h1:/Nf+XygKOxm19Sl2gvMzT55O4X+tWDkj/UM4mjzfKgM= +github.com/serverscom/serverscom-go-client v1.0.28 h1:gmUsdIzPlqCEZKc0qwC4SFgpHmsTLymcXGzQ3zNRS5M= +github.com/serverscom/serverscom-go-client v1.0.28/go.mod h1:/Nf+XygKOxm19Sl2gvMzT55O4X+tWDkj/UM4mjzfKgM= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= diff --git a/internal/mocks/cloud_computing_regions_service.go b/internal/mocks/cloud_computing_regions_service.go new file mode 100644 index 0000000..39ccd7d --- /dev/null +++ b/internal/mocks/cloud_computing_regions_service.go @@ -0,0 +1,142 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./vendor/github.com/serverscom/serverscom-go-client/pkg/cloud_computing_regions.go +// +// Generated by this command: +// +// mockgen --destination ./internal/mocks/cloud_computing_regions_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/cloud_computing_regions.go +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + gomock "go.uber.org/mock/gomock" +) + +// MockCloudComputingRegionsService is a mock of CloudComputingRegionsService interface. +type MockCloudComputingRegionsService struct { + ctrl *gomock.Controller + recorder *MockCloudComputingRegionsServiceMockRecorder + isgomock struct{} +} + +// MockCloudComputingRegionsServiceMockRecorder is the mock recorder for MockCloudComputingRegionsService. +type MockCloudComputingRegionsServiceMockRecorder struct { + mock *MockCloudComputingRegionsService +} + +// NewMockCloudComputingRegionsService creates a new mock instance. +func NewMockCloudComputingRegionsService(ctrl *gomock.Controller) *MockCloudComputingRegionsService { + mock := &MockCloudComputingRegionsService{ctrl: ctrl} + mock.recorder = &MockCloudComputingRegionsServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCloudComputingRegionsService) EXPECT() *MockCloudComputingRegionsServiceMockRecorder { + return m.recorder +} + +// Collection mocks base method. +func (m *MockCloudComputingRegionsService) Collection() serverscom.Collection[serverscom.CloudComputingRegion] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Collection") + ret0, _ := ret[0].(serverscom.Collection[serverscom.CloudComputingRegion]) + return ret0 +} + +// Collection indicates an expected call of Collection. +func (mr *MockCloudComputingRegionsServiceMockRecorder) Collection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collection", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).Collection)) +} + +// CreateSnapshot mocks base method. +func (m *MockCloudComputingRegionsService) CreateSnapshot(ctx context.Context, regionID int64, input serverscom.CloudSnapshotCreateInput) (*serverscom.CloudSnapshot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSnapshot", ctx, regionID, input) + ret0, _ := ret[0].(*serverscom.CloudSnapshot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSnapshot indicates an expected call of CreateSnapshot. +func (mr *MockCloudComputingRegionsServiceMockRecorder) CreateSnapshot(ctx, regionID, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSnapshot", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).CreateSnapshot), ctx, regionID, input) +} + +// Credentials mocks base method. +func (m *MockCloudComputingRegionsService) Credentials(ctx context.Context, regionID int64) (*serverscom.CloudComputingRegionCredentials, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Credentials", ctx, regionID) + ret0, _ := ret[0].(*serverscom.CloudComputingRegionCredentials) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Credentials indicates an expected call of Credentials. +func (mr *MockCloudComputingRegionsServiceMockRecorder) Credentials(ctx, regionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Credentials", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).Credentials), ctx, regionID) +} + +// DeleteSnapshot mocks base method. +func (m *MockCloudComputingRegionsService) DeleteSnapshot(ctx context.Context, regionID int64, snapshotID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSnapshot", ctx, regionID, snapshotID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSnapshot indicates an expected call of DeleteSnapshot. +func (mr *MockCloudComputingRegionsServiceMockRecorder) DeleteSnapshot(ctx, regionID, snapshotID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSnapshot", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).DeleteSnapshot), ctx, regionID, snapshotID) +} + +// Flavors mocks base method. +func (m *MockCloudComputingRegionsService) Flavors(regionID int64) serverscom.Collection[serverscom.CloudComputingFlavor] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Flavors", regionID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.CloudComputingFlavor]) + return ret0 +} + +// Flavors indicates an expected call of Flavors. +func (mr *MockCloudComputingRegionsServiceMockRecorder) Flavors(regionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Flavors", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).Flavors), regionID) +} + +// Images mocks base method. +func (m *MockCloudComputingRegionsService) Images(regionID int64) serverscom.Collection[serverscom.CloudComputingImage] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Images", regionID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.CloudComputingImage]) + return ret0 +} + +// Images indicates an expected call of Images. +func (mr *MockCloudComputingRegionsServiceMockRecorder) Images(regionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Images", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).Images), regionID) +} + +// Snapshots mocks base method. +func (m *MockCloudComputingRegionsService) Snapshots(regionID int64) serverscom.Collection[serverscom.CloudSnapshot] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Snapshots", regionID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.CloudSnapshot]) + return ret0 +} + +// Snapshots indicates an expected call of Snapshots. +func (mr *MockCloudComputingRegionsServiceMockRecorder) Snapshots(regionID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Snapshots", reflect.TypeOf((*MockCloudComputingRegionsService)(nil).Snapshots), regionID) +} diff --git a/internal/output/entities/cloud_regions.go b/internal/output/entities/cloud_regions.go new file mode 100644 index 0000000..5a522ba --- /dev/null +++ b/internal/output/entities/cloud_regions.go @@ -0,0 +1,102 @@ +package entities + +import ( + "log" + "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" +) + +var ( + CloudComputingRegionType = reflect.TypeOf(serverscom.CloudComputingRegion{}) + CloudComputingRegionListDefaultFields = []string{"ID", "Name", "Code"} + CloudComputingImageType = reflect.TypeOf(serverscom.CloudComputingImage{}) + CloudComputingImageListDefaultFields = []string{"ID", "Name"} + CloudComputingFlavorType = reflect.TypeOf(serverscom.CloudComputingFlavor{}) + CloudComputingFlavorListDefaultFields = []string{"ID", "Name"} + CloudSnapshotType = reflect.TypeOf(serverscom.CloudSnapshot{}) + CloudSnapshotListDefaultFields = []string{"ID", "Name", "ImageSize", "MinDisk", "Status", "IsBackup", "FileURL"} + CloudComputingRegionCredentialsType = reflect.TypeOf(serverscom.CloudComputingRegionCredentials{}) +) + +func RegisterCloudComputingRegionDefinitions() { + // CloudComputingRegion + cloudRegionEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Code", Name: "Code", Path: "Code", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list": CloudComputingRegionListDefaultFields, + }, + eType: CloudComputingRegionType, + } + if err := Registry.Register(cloudRegionEntity); err != nil { + log.Fatal(err) + } + + // CloudComputingImage + cloudImageEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list": CloudComputingImageListDefaultFields, + }, + eType: CloudComputingImageType, + } + if err := Registry.Register(cloudImageEntity); err != nil { + log.Fatal(err) + } + + // CloudComputingFlavor + cloudFlavorEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list": CloudComputingFlavorListDefaultFields, + }, + eType: CloudComputingFlavorType, + } + if err := Registry.Register(cloudFlavorEntity); err != nil { + log.Fatal(err) + } + + // CloudSnapshot + cloudSnapshotEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "ImageSize", Name: "Image Size", Path: "ImageSize", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "MinDisk", Name: "Min Disk", Path: "MinDisk", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Status", Name: "Status", Path: "Status", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "IsBackup", Name: "Is Backup", Path: "IsBackup", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "FileURL", Name: "File URL", Path: "FileURL", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list": CloudSnapshotListDefaultFields, + }, + eType: CloudSnapshotType, + } + if err := Registry.Register(cloudSnapshotEntity); err != nil { + log.Fatal(err) + } + + // CloudComputingRegionCredentials + cloudCredentialsEntity := &Entity{ + fields: []Field{ + {ID: "Password", Name: "Password", Path: "Password", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "TenantName", Name: "Tenant Name", Path: "TenantName", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "URL", Name: "URL", Path: "URL", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Username", Name: "Username", Path: "Username", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: CloudComputingRegionCredentialsType, + } + if err := Registry.Register(cloudCredentialsEntity); err != nil { + log.Fatal(err) + } +} diff --git a/internal/output/entities/init.go b/internal/output/entities/init.go index a5244a8..8788674 100644 --- a/internal/output/entities/init.go +++ b/internal/output/entities/init.go @@ -32,4 +32,5 @@ func init() { RegisterRAMOptionDefinition() RegisterL2SegmentDefinitions() RegisterNetworkPoolDefinitions() + RegisterCloudComputingRegionDefinitions() } diff --git a/testdata/entities/cloud-regions/add_snapshot.json b/testdata/entities/cloud-regions/add_snapshot.json new file mode 100644 index 0000000..32b7dd6 --- /dev/null +++ b/testdata/entities/cloud-regions/add_snapshot.json @@ -0,0 +1,9 @@ +{ + "id": "snap-123", + "name": "Test Snapshot", + "image_size": 1024, + "min_disk": 10, + "status": "available", + "is_backup": false, + "file_url": "https://test.example.com/snap-123" +} \ No newline at end of file diff --git a/testdata/entities/cloud-regions/add_snapshot.txt b/testdata/entities/cloud-regions/add_snapshot.txt new file mode 100644 index 0000000..44b8bf5 --- /dev/null +++ b/testdata/entities/cloud-regions/add_snapshot.txt @@ -0,0 +1,7 @@ +ID snap-123 +Name Test Snapshot +Image Size 1024 +Min Disk 10 +Status available +Is Backup false +File URL https://test.example.com/snap-123 diff --git a/testdata/entities/cloud-regions/add_snapshot.yaml b/testdata/entities/cloud-regions/add_snapshot.yaml new file mode 100644 index 0000000..12a7187 --- /dev/null +++ b/testdata/entities/cloud-regions/add_snapshot.yaml @@ -0,0 +1,7 @@ +id: snap-123 +name: Test Snapshot +image_size: 1024 +min_disk: 10 +status: available +is_backup: false +file_url: https://test.example.com/snap-123 diff --git a/testdata/entities/cloud-regions/get_credentials.json b/testdata/entities/cloud-regions/get_credentials.json new file mode 100644 index 0000000..b837f81 --- /dev/null +++ b/testdata/entities/cloud-regions/get_credentials.json @@ -0,0 +1,6 @@ +{ + "password": "test-password", + "tenant_name": 12345, + "url": "https://test.example.com", + "username": 67890 +} \ No newline at end of file diff --git a/testdata/entities/cloud-regions/get_credentials.txt b/testdata/entities/cloud-regions/get_credentials.txt new file mode 100644 index 0000000..4bbdd26 --- /dev/null +++ b/testdata/entities/cloud-regions/get_credentials.txt @@ -0,0 +1,2 @@ +Password Tenant Name URL Username +test-password 12345 https://test.example.com 67890 diff --git a/testdata/entities/cloud-regions/get_credentials.yaml b/testdata/entities/cloud-regions/get_credentials.yaml new file mode 100644 index 0000000..26463ec --- /dev/null +++ b/testdata/entities/cloud-regions/get_credentials.yaml @@ -0,0 +1,4 @@ +password: test-password +tenantname: 12345 +url: https://test.example.com +username: 67890 diff --git a/testdata/entities/cloud-regions/list.json b/testdata/entities/cloud-regions/list.json new file mode 100644 index 0000000..f0742e0 --- /dev/null +++ b/testdata/entities/cloud-regions/list.json @@ -0,0 +1,7 @@ +[ + { + "id": 1, + "name": "Test Region", + "code": "TEST" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_all.json b/testdata/entities/cloud-regions/list_all.json new file mode 100644 index 0000000..ca1435e --- /dev/null +++ b/testdata/entities/cloud-regions/list_all.json @@ -0,0 +1,12 @@ +[ + { + "id": 1, + "name": "Test Region", + "code": "TEST" + }, + { + "id": 2, + "name": "Test Region 2", + "code": "TEST2" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_flavors.json b/testdata/entities/cloud-regions/list_flavors.json new file mode 100644 index 0000000..9de9f81 --- /dev/null +++ b/testdata/entities/cloud-regions/list_flavors.json @@ -0,0 +1,6 @@ +[ + { + "id": "flavor-123", + "name": "Test Flavor" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_flavors_all.json b/testdata/entities/cloud-regions/list_flavors_all.json new file mode 100644 index 0000000..22cf1f7 --- /dev/null +++ b/testdata/entities/cloud-regions/list_flavors_all.json @@ -0,0 +1,10 @@ +[ + { + "id": "flavor-123", + "name": "Test Flavor" + }, + { + "id": "flavor-456", + "name": "Test Flavor 2" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_flavors_pageview.txt b/testdata/entities/cloud-regions/list_flavors_pageview.txt new file mode 100644 index 0000000..fe33b2c --- /dev/null +++ b/testdata/entities/cloud-regions/list_flavors_pageview.txt @@ -0,0 +1,5 @@ +ID: flavor-123 +Name: Test Flavor +--- +ID: flavor-456 +Name: Test Flavor 2 diff --git a/testdata/entities/cloud-regions/list_flavors_template.txt b/testdata/entities/cloud-regions/list_flavors_template.txt new file mode 100644 index 0000000..7e23c20 --- /dev/null +++ b/testdata/entities/cloud-regions/list_flavors_template.txt @@ -0,0 +1,2 @@ +ID: flavor-123 Name: Test Flavor +ID: flavor-456 Name: Test Flavor 2 diff --git a/testdata/entities/cloud-regions/list_images.json b/testdata/entities/cloud-regions/list_images.json new file mode 100644 index 0000000..8111878 --- /dev/null +++ b/testdata/entities/cloud-regions/list_images.json @@ -0,0 +1,6 @@ +[ + { + "id": "img-123", + "name": "Test Image" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_images_all.json b/testdata/entities/cloud-regions/list_images_all.json new file mode 100644 index 0000000..8a6f983 --- /dev/null +++ b/testdata/entities/cloud-regions/list_images_all.json @@ -0,0 +1,10 @@ +[ + { + "id": "img-123", + "name": "Test Image" + }, + { + "id": "img-456", + "name": "Test Image 2" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_images_pageview.txt b/testdata/entities/cloud-regions/list_images_pageview.txt new file mode 100644 index 0000000..134815f --- /dev/null +++ b/testdata/entities/cloud-regions/list_images_pageview.txt @@ -0,0 +1,5 @@ +ID: img-123 +Name: Test Image +--- +ID: img-456 +Name: Test Image 2 diff --git a/testdata/entities/cloud-regions/list_images_template.txt b/testdata/entities/cloud-regions/list_images_template.txt new file mode 100644 index 0000000..cba2f10 --- /dev/null +++ b/testdata/entities/cloud-regions/list_images_template.txt @@ -0,0 +1,2 @@ +ID: img-123 Name: Test Image +ID: img-456 Name: Test Image 2 diff --git a/testdata/entities/cloud-regions/list_pageview.txt b/testdata/entities/cloud-regions/list_pageview.txt new file mode 100644 index 0000000..82ad537 --- /dev/null +++ b/testdata/entities/cloud-regions/list_pageview.txt @@ -0,0 +1,7 @@ +ID: 1 +Name: Test Region +Code: TEST +--- +ID: 2 +Name: Test Region 2 +Code: TEST2 diff --git a/testdata/entities/cloud-regions/list_snapshots.json b/testdata/entities/cloud-regions/list_snapshots.json new file mode 100644 index 0000000..e13d6f1 --- /dev/null +++ b/testdata/entities/cloud-regions/list_snapshots.json @@ -0,0 +1,11 @@ +[ + { + "id": "snap-123", + "name": "Test Snapshot", + "image_size": 1024, + "min_disk": 10, + "status": "available", + "is_backup": false, + "file_url": "https://test.example.com/snap-123" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_snapshots_all.json b/testdata/entities/cloud-regions/list_snapshots_all.json new file mode 100644 index 0000000..80ad961 --- /dev/null +++ b/testdata/entities/cloud-regions/list_snapshots_all.json @@ -0,0 +1,20 @@ +[ + { + "id": "snap-123", + "name": "Test Snapshot", + "image_size": 1024, + "min_disk": 10, + "status": "available", + "is_backup": false, + "file_url": "https://test.example.com/snap-123" + }, + { + "id": "snap-456", + "name": "Test Snapshot 2", + "image_size": 1024, + "min_disk": 10, + "status": "available", + "is_backup": false, + "file_url": "https://test.example.com/snap-123" + } +] \ No newline at end of file diff --git a/testdata/entities/cloud-regions/list_snapshots_pageview.txt b/testdata/entities/cloud-regions/list_snapshots_pageview.txt new file mode 100644 index 0000000..422d045 --- /dev/null +++ b/testdata/entities/cloud-regions/list_snapshots_pageview.txt @@ -0,0 +1,15 @@ +ID: snap-123 +Name: Test Snapshot +Image Size: 1024 +Min Disk: 10 +Status: available +Is Backup: false +File URL: https://test.example.com/snap-123 +--- +ID: snap-456 +Name: Test Snapshot 2 +Image Size: 1024 +Min Disk: 10 +Status: available +Is Backup: false +File URL: https://test.example.com/snap-123 diff --git a/testdata/entities/cloud-regions/list_snapshots_template.txt b/testdata/entities/cloud-regions/list_snapshots_template.txt new file mode 100644 index 0000000..c2f0037 --- /dev/null +++ b/testdata/entities/cloud-regions/list_snapshots_template.txt @@ -0,0 +1,2 @@ +ID: snap-123 Name: Test Snapshot +ID: snap-456 Name: Test Snapshot 2 diff --git a/testdata/entities/cloud-regions/list_template.txt b/testdata/entities/cloud-regions/list_template.txt new file mode 100644 index 0000000..923e309 --- /dev/null +++ b/testdata/entities/cloud-regions/list_template.txt @@ -0,0 +1,2 @@ +ID: 1 Name: Test Region +ID: 2 Name: Test Region 2 From 7aeee24e1ebb385b1ee8eeb63b81c9e40d29d29d Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Thu, 22 Jan 2026 19:15:00 +0200 Subject: [PATCH 2/3] fix make generate --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index b31cba0..857db5d 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,5 @@ generate: deps ./internal/mocks/locations_service.go \ ./internal/mocks/kubernetes_clusters_service.go \ ./internal/mocks/l2_segment_service.go \ - ./internal/mocks/network_pool_service.go ./internal/mocks/network_pool_service.go \ ./internal/mocks/cloud_computing_regions_service.go From 514f80bb64d22288dd8bc11a4ede5325b4951317 Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Tue, 27 Jan 2026 14:36:07 +0200 Subject: [PATCH 3/3] use structure for input params for cloud-regions add cmd --- cmd/entities/cloud-regions/add_snapshot.go | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cmd/entities/cloud-regions/add_snapshot.go b/cmd/entities/cloud-regions/add_snapshot.go index 3725490..6ef2949 100644 --- a/cmd/entities/cloud-regions/add_snapshot.go +++ b/cmd/entities/cloud-regions/add_snapshot.go @@ -1,7 +1,6 @@ package cloudregions import ( - "log" "strconv" serverscom "github.com/serverscom/serverscom-go-client/pkg" @@ -9,9 +8,13 @@ import ( "github.com/spf13/cobra" ) +type AddedFlags struct { + Name string + InstanceID string +} + func newAddSnapshotCmd(cmdContext *base.CmdContext) *cobra.Command { - var name string - var instanceID string + flags := &AddedFlags{} cmd := &cobra.Command{ Use: "add-snapshot ", @@ -31,8 +34,8 @@ func newAddSnapshotCmd(cmdContext *base.CmdContext) *cobra.Command { scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() input := serverscom.CloudSnapshotCreateInput{ - Name: name, - InstanceID: instanceID, + Name: flags.Name, + InstanceID: flags.InstanceID, } snapshot, err := scClient.CloudComputingRegions.CreateSnapshot(ctx, regionID, input) @@ -45,15 +48,11 @@ func newAddSnapshotCmd(cmdContext *base.CmdContext) *cobra.Command { }, } - cmd.Flags().StringVar(&name, "name", "", "Snapshot name") - cmd.Flags().StringVar(&instanceID, "instance-id", "", "Instance ID") + cmd.Flags().StringVar(&flags.Name, "name", "", "Snapshot name") + cmd.Flags().StringVar(&flags.InstanceID, "instance-id", "", "Instance ID") - if err := cmd.MarkFlagRequired("name"); err != nil { - log.Fatal(err) - } - if err := cmd.MarkFlagRequired("instance-id"); err != nil { - log.Fatal(err) - } + _ = cmd.MarkFlagRequired("name") + _ = cmd.MarkFlagRequired("instance-id") return cmd }