From fde8c6efd578cb6bd708f31bac395ca391c6efef Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Mon, 10 Nov 2025 13:23:15 +0200 Subject: [PATCH] add sbm ptr methods and power feeds; add ds feature,service,oob creds methods; --- cmd/entities/hosts/features.go | 24 ++ cmd/entities/hosts/features_test.go | 140 +++++++ cmd/entities/hosts/hosts.go | 6 + cmd/entities/hosts/hosts_test.go | 377 ++++++++++++++++-- cmd/entities/hosts/oob.go | 41 ++ cmd/entities/hosts/oob_test.go | 100 +++++ cmd/entities/hosts/power.go | 3 +- cmd/entities/hosts/ptr.go | 105 +++++ cmd/entities/hosts/ptr_test.go | 256 ++++++++++++ cmd/entities/hosts/release.go | 5 +- cmd/entities/hosts/services.go | 24 ++ cmd/entities/hosts/services_test.go | 152 +++++++ cmd/entities/l2_segments/l2_segments_test.go | 11 +- go.mod | 2 +- go.sum | 4 +- internal/mocks/hosts_service.go | 109 ++++- internal/mocks/locations_service.go | 29 ++ internal/output/entities/hosts.go | 48 +++ internal/output/entities/l2_segments.go | 3 +- testdata/entities/hosts/features/list.json | 6 + .../entities/hosts/features/list_all.json | 10 + .../entities/hosts/features/list_pageview.txt | 5 + .../entities/hosts/features/list_template.txt | 2 + ...drive_slots.json => list_drive_slots.json} | 0 ...ots_all.json => list_drive_slots_all.json} | 0 ...view.txt => list_drive_slots_pageview.txt} | 0 ...late.txt => list_drive_slots_template.txt} | 0 .../entities/hosts/list_kbm_power_feeds.json | 10 - testdata/entities/hosts/list_power_feeds.json | 12 + ...m_power_feeds.txt => list_power_feeds.txt} | 0 ...power_feeds.yaml => list_power_feeds.yaml} | 2 + testdata/entities/hosts/oob/get.json | 4 + testdata/entities/hosts/oob/get.txt | 2 + testdata/entities/hosts/oob/get.yaml | 2 + testdata/entities/hosts/release_ds_resp.json | 2 +- .../hosts/release_ds_scheduled_resp.json | 33 ++ testdata/entities/hosts/services/list.json | 18 + .../entities/hosts/services/list_all.json | 34 ++ .../entities/hosts/services/list_pageview.txt | 29 ++ .../entities/hosts/services/list_template.txt | 2 + .../entities/l2-segments/list_groups.json | 4 + .../entities/l2-segments/list_groups_all.json | 8 + .../l2-segments/list_groups_pageview.txt | 32 +- 43 files changed, 1585 insertions(+), 71 deletions(-) create mode 100644 cmd/entities/hosts/features.go create mode 100644 cmd/entities/hosts/features_test.go create mode 100644 cmd/entities/hosts/oob.go create mode 100644 cmd/entities/hosts/oob_test.go create mode 100644 cmd/entities/hosts/services.go create mode 100644 cmd/entities/hosts/services_test.go create mode 100644 testdata/entities/hosts/features/list.json create mode 100644 testdata/entities/hosts/features/list_all.json create mode 100644 testdata/entities/hosts/features/list_pageview.txt create mode 100644 testdata/entities/hosts/features/list_template.txt rename testdata/entities/hosts/{list_kbm_drive_slots.json => list_drive_slots.json} (100%) rename testdata/entities/hosts/{list_kbm_drive_slots_all.json => list_drive_slots_all.json} (100%) rename testdata/entities/hosts/{list_kbm_drive_slots_pageview.txt => list_drive_slots_pageview.txt} (100%) rename testdata/entities/hosts/{list_kbm_drive_slots_template.txt => list_drive_slots_template.txt} (100%) delete mode 100644 testdata/entities/hosts/list_kbm_power_feeds.json create mode 100644 testdata/entities/hosts/list_power_feeds.json rename testdata/entities/hosts/{list_kbm_power_feeds.txt => list_power_feeds.txt} (100%) rename testdata/entities/hosts/{list_kbm_power_feeds.yaml => list_power_feeds.yaml} (69%) create mode 100644 testdata/entities/hosts/oob/get.json create mode 100644 testdata/entities/hosts/oob/get.txt create mode 100644 testdata/entities/hosts/oob/get.yaml create mode 100644 testdata/entities/hosts/release_ds_scheduled_resp.json create mode 100644 testdata/entities/hosts/services/list.json create mode 100644 testdata/entities/hosts/services/list_all.json create mode 100644 testdata/entities/hosts/services/list_pageview.txt create mode 100644 testdata/entities/hosts/services/list_template.txt diff --git a/cmd/entities/hosts/features.go b/cmd/entities/hosts/features.go new file mode 100644 index 0000000..d852a75 --- /dev/null +++ b/cmd/entities/hosts/features.go @@ -0,0 +1,24 @@ +package hosts + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListDSFeaturesCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.DedicatedServerFeature] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + if len(args) == 0 { + log.Fatal("Missing dedicated server ID") + } + id := args[0] + return scClient.Hosts.DedicatedServerFeatures(id) + } + + opts := &base.BaseListOptions[serverscom.DedicatedServerFeature]{} + + return base.NewListCmd("list-features ", "Dedicated server features", factory, cmdContext, opts) +} diff --git a/cmd/entities/hosts/features_test.go b/cmd/entities/hosts/features_test.go new file mode 100644 index 0000000..65bf8cf --- /dev/null +++ b/cmd/entities/hosts/features_test.go @@ -0,0 +1,140 @@ +package hosts + +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 ( + featuresFixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "hosts", "features") +) + +func TestListDSFeaturesCmd(t *testing.T) { + testFeature1 := serverscom.DedicatedServerFeature{ + Name: "disaggregated_public_ports", + Status: "deactivated", + } + testFeature2 := serverscom.DedicatedServerFeature{ + Name: "no_public_network", + Status: "unavailable", + } + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.DedicatedServerFeature]) + }{ + { + name: "list all ds features", + output: "json", + args: []string{"-A", testServerID}, + expectedOutput: testutils.ReadFixture(filepath.Join(featuresFixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerFeature]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.DedicatedServerFeature{ + testFeature1, + testFeature2, + }, nil) + }, + }, + { + name: "list ds features", + output: "json", + args: []string{testServerID}, + expectedOutput: testutils.ReadFixture(filepath.Join(featuresFixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerFeature]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DedicatedServerFeature{ + testFeature1, + }, nil) + }, + }, + { + name: "list ds features with template", + args: []string{testServerID, "--template", "{{range .}}Name: {{.Name}} Status: {{.Status}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(featuresFixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerFeature]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DedicatedServerFeature{ + testFeature1, + testFeature2, + }, nil) + }, + }, + { + name: "list ds features with page-view", + args: []string{testServerID, "--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(featuresFixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerFeature]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DedicatedServerFeature{ + testFeature1, + testFeature2, + }, nil) + }, + }, + { + name: "list ds features with error", + args: []string{testServerID}, + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerFeature]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + hostService := mocks.NewMockHostsService(mockCtrl) + collection := mocks.NewMockCollection[serverscom.DedicatedServerFeature](mockCtrl) + + hostService.EXPECT().DedicatedServerFeatures(gomock.Any()).Return(collection).AnyTimes() + collection.EXPECT().SetParam(gomock.Any(), gomock.Any()).Return(collection).AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostService + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + if tc.configureMock != nil { + tc.configureMock(collection) + } + testCmdContext := testutils.NewTestCmdContext(scClient) + cmd := NewCmd(testCmdContext) + + args := append([]string{"hosts", "ds", "list-features"}, tc.args...) + if tc.output != "" { + args = append(args, "--output", tc.output) + } + builder := testutils.NewTestCommandBuilder(). + WithCommand(cmd). + WithArgs(args) + + c := builder.Build() + err := c.Execute() + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} diff --git a/cmd/entities/hosts/hosts.go b/cmd/entities/hosts/hosts.go index b748096..1c84668 100644 --- a/cmd/entities/hosts/hosts.go +++ b/cmd/entities/hosts/hosts.go @@ -57,6 +57,9 @@ func NewCmd(cmdContext *base.CmdContext) *cobra.Command { newAddDSNetworkCmd, newDeleteDSNetworkCmd, newListDSCmd, + newListDSServicesCmd, + newListDSFeaturesCmd, + newGetDSOOBCredsCmd, }, }, { @@ -90,6 +93,9 @@ func NewCmd(cmdContext *base.CmdContext) *cobra.Command { newUpdateSBMCmd, newSBMReleaseCmd, newListSBMCmd, + newListSBMPTRCmd, + newCreateSBMPTRCmd, + newDeleteSBMPTRCmd, }, }, } diff --git a/cmd/entities/hosts/hosts_test.go b/cmd/entities/hosts/hosts_test.go index ae2b0e5..e6e9923 100644 --- a/cmd/entities/hosts/hosts_test.go +++ b/cmd/entities/hosts/hosts_test.go @@ -113,6 +113,7 @@ var ( testPowerFeed = serverscom.HostPowerFeed{ Name: "testPowerFeed", Status: "on", + Type: "physical", } ) @@ -1373,7 +1374,6 @@ func TestUpdateSBMCmd(t *testing.T) { func TestScheduleReleaseDSCmd(t *testing.T) { releasedServer := testDS - releasedServer.ScheduledRelease = &fixedTime testCases := []struct { name string id string @@ -1381,25 +1381,42 @@ func TestScheduleReleaseDSCmd(t *testing.T) { args []string expectedOutput []byte expectError bool + configureMock func(*mocks.MockHostsService) }{ { name: "release dedicated server", id: testId, output: "json", expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "release_ds_resp.json")), + configureMock: func(mock *mocks.MockHostsService) { + mock.EXPECT(). + ScheduleReleaseForDedicatedServer(gomock.Any(), testId, serverscom.ScheduleReleaseInput{}). + Return(&releasedServer, nil) + }, + }, + { + name: "release dedicated server with --release-after", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "release_ds_scheduled_resp.json")), + args: []string{"--release-after", "2025-01-01T12:34:56+03:00"}, + configureMock: func(mock *mocks.MockHostsService) { + releasedServer.ScheduledRelease = &fixedTime + mock.EXPECT(). + ScheduleReleaseForDedicatedServer(gomock.Any(), testId, serverscom.ScheduleReleaseInput{ReleaseAfter: "2025-01-01T12:34:56+03:00"}). + Return(&releasedServer, nil) + }, }, - // TODO add after implementing release-after in client - // { - // name: "release dedicated server with --release-after", - // id: testId, - // output: "json", - // expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "release_ds_resp.json")), - // args: []string{"--release-after", "2025-01-01T12:34:56+03:00"}, - // }, { name: "release dedicated server with error", id: testId, expectError: true, + configureMock: func(mock *mocks.MockHostsService) { + releasedServer.ScheduledRelease = &fixedTime + mock.EXPECT(). + ScheduleReleaseForDedicatedServer(gomock.Any(), testId, serverscom.ScheduleReleaseInput{}). + Return(nil, errors.New("some error")) + }, }, } @@ -1415,13 +1432,9 @@ func TestScheduleReleaseDSCmd(t *testing.T) { t.Run(tc.name, func(t *testing.T) { g := NewWithT(t) - var err error - if tc.expectError { - err = errors.New("some error") + if tc.configureMock != nil { + tc.configureMock(hostsServiceHandler) } - hostsServiceHandler.EXPECT(). - ScheduleReleaseForDedicatedServer(gomock.Any(), testId). - Return(&releasedServer, err) testCmdContext := testutils.NewTestCmdContext(scClient) hostsCmd := NewCmd(testCmdContext) @@ -1440,7 +1453,7 @@ func TestScheduleReleaseDSCmd(t *testing.T) { cmd := builder.Build() - err = cmd.Execute() + err := cmd.Execute() if tc.expectError { g.Expect(err).To(HaveOccurred()) @@ -2001,19 +2014,19 @@ func TestListKBMPowerFeedsCmd(t *testing.T) { name: "get KBM node power_feeds in default format", id: testId, output: "", - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_power_feeds.txt")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.txt")), }, { name: "get KBM node power_feeds", id: testId, output: "json", - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_power_feeds.json")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.json")), }, { name: "get KBM node power_feeds in YAML format", id: testId, output: "yaml", - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_power_feeds.yaml")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.yaml")), }, { name: "get KBM node power_feeds with error", @@ -2068,6 +2081,180 @@ func TestListKBMPowerFeedsCmd(t *testing.T) { } } +func TestListDSPowerFeedsCmd(t *testing.T) { + testPowerFeed1 := testPowerFeed + testPowerFeed2 := testPowerFeed1 + + testPowerFeed2.Name = "testPowerFeed2" + testPowerFeed2.Status = "off" + + testCases := []struct { + name string + id string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.HostPowerFeed]) + }{ + { + name: "get ds power_feeds in default format", + id: testId, + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.txt")), + }, + { + name: "get ds power_feeds", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.json")), + }, + { + name: "get ds power_feeds in YAML format", + id: testId, + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.yaml")), + }, + { + name: "get ds power_feeds with error", + id: testId, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + hostsServiceHandler := mocks.NewMockHostsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostsServiceHandler + + 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") + } + hostsServiceHandler.EXPECT(). + DedicatedServerPowerFeeds(gomock.Any(), testId). + Return([]serverscom.HostPowerFeed{testPowerFeed1, testPowerFeed2}, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + hostsCmd := NewCmd(testCmdContext) + + args := []string{"hosts", "ds", "list-power-feeds", tc.id} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(hostsCmd). + 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 TestListSBMPowerFeedsCmd(t *testing.T) { + testPowerFeed1 := testPowerFeed + testPowerFeed2 := testPowerFeed1 + + testPowerFeed2.Name = "testPowerFeed2" + testPowerFeed2.Status = "off" + + testCases := []struct { + name string + id string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.HostPowerFeed]) + }{ + { + name: "get sbm power_feeds in default format", + id: testId, + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.txt")), + }, + { + name: "get sbm power_feeds", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.json")), + }, + { + name: "get sbm power_feeds in YAML format", + id: testId, + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_power_feeds.yaml")), + }, + { + name: "get sbm power_feeds with error", + id: testId, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + hostsServiceHandler := mocks.NewMockHostsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostsServiceHandler + + 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") + } + hostsServiceHandler.EXPECT(). + SBMServerPowerFeeds(gomock.Any(), testId). + Return([]serverscom.HostPowerFeed{testPowerFeed1, testPowerFeed2}, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + hostsCmd := NewCmd(testCmdContext) + + args := []string{"hosts", "sbm", "list-power-feeds", tc.id} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(hostsCmd). + 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 TestListKBMNetworksCmd(t *testing.T) { testNetwork1 := testNetwork testNetwork1.ID = testNetworkId @@ -2205,6 +2392,140 @@ func TestListKBMNetworksCmd(t *testing.T) { } } +func TestListDSDriveSlotsCmd(t *testing.T) { + testDriveSlot1 := testDriveSlot + testDriveSlot2 := testDriveSlot1 + testDriveSlot2.Position = 2 + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.HostDriveSlot]) + }{ + { + name: "list ds all drive slots", + output: "json", + args: []string{"testServerId", "-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.HostDriveSlot{ + testDriveSlot1, + testDriveSlot2, + }, nil) + }, + }, + { + name: "list ds drive slots", + output: "json", + args: []string{"testServerId"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.HostDriveSlot{ + testDriveSlot1, + }, nil) + }, + }, + { + name: "list ds drive slots with template", + args: []string{"testServerId", "--template", "{{range .}}Position: {{.Position}} Interface: {{.Interface}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.HostDriveSlot{ + testDriveSlot1, + testDriveSlot2, + }, nil) + }, + }, + { + name: "list ds drive slots with pageView", + args: []string{"testServerId", "--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.HostDriveSlot{ + testDriveSlot1, + testDriveSlot2, + }, nil) + }, + }, + { + name: "list ds drive slots with error", + args: []string{"testServerId"}, + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + hostsServiceHandler := mocks.NewMockHostsService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.HostDriveSlot](mockCtrl) + + hostsServiceHandler.EXPECT(). + DedicatedServerDriveSlots(gomock.Any()). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostsServiceHandler + + 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) + hostsCmd := NewCmd(testCmdContext) + + args := []string{"hosts", "ds", "list-drive-slots"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(hostsCmd). + 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 TestListKBMDriveSlotsCmd(t *testing.T) { testDriveSlot1 := testDriveSlot testDriveSlot2 := testDriveSlot1 @@ -2219,10 +2540,10 @@ func TestListKBMDriveSlotsCmd(t *testing.T) { configureMock func(*mocks.MockCollection[serverscom.HostDriveSlot]) }{ { - name: "list KBM node all slots", + name: "list KBM node all drive slots", output: "json", args: []string{"testServerId", "-A"}, - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_drive_slots_all.json")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots_all.json")), configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { mock.EXPECT(). Collect(gomock.Any()). @@ -2233,10 +2554,10 @@ func TestListKBMDriveSlotsCmd(t *testing.T) { }, }, { - name: "list KBM node slots", + name: "list KBM node drive slots", output: "json", args: []string{"testServerId"}, - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_drive_slots.json")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots.json")), configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { mock.EXPECT(). List(gomock.Any()). @@ -2246,9 +2567,9 @@ func TestListKBMDriveSlotsCmd(t *testing.T) { }, }, { - name: "list KBM node slots with template", + name: "list KBM node drive slots with template", args: []string{"testServerId", "--template", "{{range .}}Position: {{.Position}} Interface: {{.Interface}}\n{{end}}"}, - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_drive_slots_template.txt")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots_template.txt")), configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { mock.EXPECT(). List(gomock.Any()). @@ -2259,9 +2580,9 @@ func TestListKBMDriveSlotsCmd(t *testing.T) { }, }, { - name: "list KBM node slots with pageView", + name: "list KBM node drive slots with pageView", args: []string{"testServerId", "--page-view"}, - expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_kbm_drive_slots_pageview.txt")), + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_drive_slots_pageview.txt")), configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { mock.EXPECT(). List(gomock.Any()). @@ -2272,7 +2593,7 @@ func TestListKBMDriveSlotsCmd(t *testing.T) { }, }, { - name: "list KBM node slots with error", + name: "list KBM node drive slots with error", args: []string{"testServerId"}, expectError: true, configureMock: func(mock *mocks.MockCollection[serverscom.HostDriveSlot]) { diff --git a/cmd/entities/hosts/oob.go b/cmd/entities/hosts/oob.go new file mode 100644 index 0000000..dc0dffc --- /dev/null +++ b/cmd/entities/hosts/oob.go @@ -0,0 +1,41 @@ +package hosts + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetDSOOBCredsCmd(cmdContext *base.CmdContext) *cobra.Command { + var fingerprint string + + cmd := &cobra.Command{ + Use: "get-oob-credentials ", + Short: ("Get a dedicated server oob credentials"), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + id := args[0] + entity, err := scClient.Hosts.GetDedicatedServerOOBCredentials(ctx, id, map[string]string{"fingerprint": fingerprint}) + if err != nil { + return err + } + + if entity != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(entity) + } + return nil + }, + } + + cmd.Flags().StringVar(&fingerprint, "fingerprint", "", "GPG key fingerprint (required)") + _ = cmd.MarkFlagRequired("fingerprint") + + return cmd +} diff --git a/cmd/entities/hosts/oob_test.go b/cmd/entities/hosts/oob_test.go new file mode 100644 index 0000000..07a9ffb --- /dev/null +++ b/cmd/entities/hosts/oob_test.go @@ -0,0 +1,100 @@ +package hosts + +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 ( + oobFixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "hosts", "oob") + testOobCreds = serverscom.DedicatedServerOOBCredentials{ + Login: "test", + Secret: "secret", + } +) + +func TestGetDSOOBCredsCmd(t *testing.T) { + testCases := []struct { + name string + id string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get dedicated server oob creds in default format", + id: testId, + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(oobFixtureBasePath, "get.txt")), + }, + { + name: "get dedicated server oob creds in JSON format", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(oobFixtureBasePath, "get.json")), + }, + { + name: "get dedicated server oob creds in YAML format", + id: testId, + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(oobFixtureBasePath, "get.yaml")), + }, + { + name: "get dedicated server oob creds with error", + id: testId, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + hostsServiceHandler := mocks.NewMockHostsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostsServiceHandler + + 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") + } + hostsServiceHandler.EXPECT(). + GetDedicatedServerOOBCredentials(gomock.Any(), testId, map[string]string{"fingerprint": "test"}). + Return(&testOobCreds, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + hostsCmd := NewCmd(testCmdContext) + + args := []string{"hosts", "ds", "get-oob-credentials", tc.id, "--fingerprint", "test"} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(hostsCmd). + 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))) + } + }) + } +} diff --git a/cmd/entities/hosts/power.go b/cmd/entities/hosts/power.go index f86d4f4..cefe556 100644 --- a/cmd/entities/hosts/power.go +++ b/cmd/entities/hosts/power.go @@ -68,8 +68,7 @@ func (p *SBMPowerMgr) PowerAction(ctx context.Context, client *serverscom.Client } func (p *SBMPowerMgr) ListPowerFeeds(ctx context.Context, client *serverscom.Client, id string) (any, error) { - // return client.Hosts.TODO(ctx, id) - return nil, nil + return client.Hosts.SBMServerPowerFeeds(ctx, id) } func newPowerCmd(cmdContext *base.CmdContext, hostType *HostTypeCmd) *cobra.Command { diff --git a/cmd/entities/hosts/ptr.go b/cmd/entities/hosts/ptr.go index 9598397..59eb456 100644 --- a/cmd/entities/hosts/ptr.go +++ b/cmd/entities/hosts/ptr.go @@ -112,3 +112,108 @@ func newDeleteDSPTRCmd(cmdContext *base.CmdContext) *cobra.Command { return cmd } + +func newListSBMPTRCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.PTRRecord] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + if len(args) == 0 { + log.Fatal("Missing SBM server ID") + } + id := args[0] + return scClient.Hosts.SBMServerPTRRecords(id) + } + + opts := &base.BaseListOptions[serverscom.PTRRecord]{} + + return base.NewListCmd("list-ptr ", "SBM server PTR records", factory, cmdContext, opts) +} + +func newCreateSBMPTRCmd(cmdContext *base.CmdContext) *cobra.Command { + var ( + ip string + domain string + ttl int + priority int + ) + + cmd := &cobra.Command{ + Use: "add-ptr ", + Short: "Create a PTR record", + Long: "Create a PTR record for an SBM server", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + serverID := args[0] + + input := &serverscom.PTRRecordCreateInput{ + IP: ip, + Domain: domain, + } + if cmd.Flags().Changed("ttl") { + input.TTL = &ttl + } + if cmd.Flags().Changed("priority") { + input.Priority = &priority + } + + prtRecord, err := scClient.Hosts.CreatePTRRecordForSBMServer(ctx, serverID, *input) + if err != nil { + return err + } + + if prtRecord != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(prtRecord) + } + return nil + }, + } + + cmd.Flags().StringVar(&ip, "ip", "", "An IP address associated with a PTR record (required)") + _ = cmd.MarkFlagRequired("ip") + + cmd.Flags().StringVar(&domain, "domain", "", "A domain name for a PTR record (required)") + _ = cmd.MarkFlagRequired("domain") + + cmd.Flags().IntVar(&ttl, "ttl", 0, "TTL (time to live) in seconds") + cmd.Flags().IntVar(&priority, "priority", 0, "Priority (lower value means higher priority)") + + return cmd +} + +func newDeleteSBMPTRCmd(cmdContext *base.CmdContext) *cobra.Command { + var recordID string + + cmd := &cobra.Command{ + Use: "delete-ptr ", + Short: "Delete a PTR record", + Long: "Delete a PTR record for an SBM server", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + serverID := args[0] + return scClient.Hosts.DeletePTRRecordForSBMServer(ctx, serverID, recordID) + }, + } + + cmd.Flags().StringVar(&recordID, "ptr-id", "", "Record ID (required)") + _ = cmd.MarkFlagRequired("ptr-id") + + return cmd +} diff --git a/cmd/entities/hosts/ptr_test.go b/cmd/entities/hosts/ptr_test.go index 4b24881..a6cae16 100644 --- a/cmd/entities/hosts/ptr_test.go +++ b/cmd/entities/hosts/ptr_test.go @@ -281,3 +281,259 @@ func TestDeleteDSPTRCmd(t *testing.T) { }) } } + +func TestListSBMPTRCmd(t *testing.T) { + testPTR1 := testPTR + testPTR2 := testPTR + testPTR1.ID += "1" + testPTR2.ID += "2" + testPTR2.Domain = "another.example.com" + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.PTRRecord]) + }{ + { + name: "list all ptr records", + output: "json", + args: []string{"-A", testServerID}, + expectedOutput: testutils.ReadFixture(filepath.Join(ptrFixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.PTRRecord]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.PTRRecord{ + testPTR1, + testPTR2, + }, nil) + }, + }, + { + name: "list ptr records", + output: "json", + args: []string{testServerID}, + expectedOutput: testutils.ReadFixture(filepath.Join(ptrFixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.PTRRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.PTRRecord{ + testPTR1, + }, nil) + }, + }, + { + name: "list ptr records with template", + args: []string{testServerID, "--template", "{{range .}}ID: {{.ID}} PTR: {{.Domain}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(ptrFixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.PTRRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.PTRRecord{ + testPTR1, + testPTR2, + }, nil) + }, + }, + { + name: "list ptr records with page-view", + args: []string{testServerID, "--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(ptrFixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.PTRRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.PTRRecord{ + testPTR1, + testPTR2, + }, nil) + }, + }, + { + name: "list ptr records with error", + args: []string{testServerID}, + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.PTRRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + hostService := mocks.NewMockHostsService(mockCtrl) + collection := mocks.NewMockCollection[serverscom.PTRRecord](mockCtrl) + + hostService.EXPECT().SBMServerPTRRecords(gomock.Any()).Return(collection).AnyTimes() + collection.EXPECT().SetParam(gomock.Any(), gomock.Any()).Return(collection).AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostService + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + if tc.configureMock != nil { + tc.configureMock(collection) + } + testCmdContext := testutils.NewTestCmdContext(scClient) + cmd := NewCmd(testCmdContext) + + args := append([]string{"hosts", "sbm", "list-ptr"}, tc.args...) + if tc.output != "" { + args = append(args, "--output", tc.output) + } + builder := testutils.NewTestCommandBuilder(). + WithCommand(cmd). + WithArgs(args) + + c := builder.Build() + err := c.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 TestCreateSBMPTRCmd(t *testing.T) { + testCases := []struct { + name string + args []string + output string + expectedOutput []byte + configureMock func(*mocks.MockHostsService) + expectError bool + }{ + { + name: "create sbm ptr", + args: []string{testServerID, "--ip", testPTR.IP, "--domain", testPTR.Domain, "--ttl", "300", "--priority", "10"}, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(ptrFixtureBasePath, "get.json")), + configureMock: func(mock *mocks.MockHostsService) { + in := serverscom.PTRRecordCreateInput{ + IP: testPTR.IP, + Domain: testPTR.Domain, + TTL: &testPTR.TTL, + Priority: &testPTR.Priority, + } + mock.EXPECT(). + CreatePTRRecordForSBMServer(gomock.Any(), testServerID, in). + Return(&testPTR, nil) + }, + }, + { + name: "create sbm ptr error", + args: []string{testServerID, "--ip", testPTR.IP, "--domain", testPTR.Domain}, + expectError: true, + configureMock: func(mock *mocks.MockHostsService) { + in := serverscom.PTRRecordCreateInput{ + IP: testPTR.IP, + Domain: testPTR.Domain, + } + mock.EXPECT(). + CreatePTRRecordForSBMServer(gomock.Any(), testServerID, in). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + hostService := mocks.NewMockHostsService(mockCtrl) + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostService + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + if tc.configureMock != nil { + tc.configureMock(hostService) + } + testCmdContext := testutils.NewTestCmdContext(scClient) + cmd := NewCmd(testCmdContext) + + args := []string{"hosts", "sbm", "add-ptr"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + builder := testutils.NewTestCommandBuilder(). + WithCommand(cmd). + WithArgs(args) + + c := builder.Build() + err := c.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 TestDeleteSBMPTRCmd(t *testing.T) { + testCases := []struct { + name string + serverID string + ptrID string + expectError bool + }{ + { + name: "delete ds ptr", + serverID: testServerID, + ptrID: testPTRID, + }, + { + name: "delete ds ptr error", + serverID: testServerID, + ptrID: testPTRID, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + hostService := mocks.NewMockHostsService(mockCtrl) + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostService + + 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") + } + hostService.EXPECT(). + DeletePTRRecordForSBMServer(gomock.Any(), tc.serverID, tc.ptrID). + Return(err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + cmd := NewCmd(testCmdContext) + + args := []string{"hosts", "sbm", "delete-ptr", tc.serverID, "--ptr-id", tc.ptrID} + builder := testutils.NewTestCommandBuilder(). + WithCommand(cmd). + WithArgs(args) + + c := builder.Build() + err = c.Execute() + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + } + }) + } +} diff --git a/cmd/entities/hosts/release.go b/cmd/entities/hosts/release.go index bda96e2..9b6e116 100644 --- a/cmd/entities/hosts/release.go +++ b/cmd/entities/hosts/release.go @@ -1,6 +1,7 @@ package hosts import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" "github.com/serverscom/srvctl/cmd/base" "github.com/spf13/cobra" ) @@ -34,7 +35,6 @@ func newDSAbortReleaseCmd(cmdContext *base.CmdContext) *cobra.Command { return cmd } -// TODO add input in go client to pass releaseAfter func newDSScheduleReleaseCmd(cmdContext *base.CmdContext) *cobra.Command { var releaseAfter string @@ -51,7 +51,8 @@ func newDSScheduleReleaseCmd(cmdContext *base.CmdContext) *cobra.Command { scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() id := args[0] - server, err := scClient.Hosts.ScheduleReleaseForDedicatedServer(ctx, id) + input := serverscom.ScheduleReleaseInput{ReleaseAfter: releaseAfter} + server, err := scClient.Hosts.ScheduleReleaseForDedicatedServer(ctx, id, input) if err != nil { return err } diff --git a/cmd/entities/hosts/services.go b/cmd/entities/hosts/services.go new file mode 100644 index 0000000..2d6468d --- /dev/null +++ b/cmd/entities/hosts/services.go @@ -0,0 +1,24 @@ +package hosts + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListDSServicesCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.DedicatedServerService] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + if len(args) == 0 { + log.Fatal("Missing dedicated server ID") + } + id := args[0] + return scClient.Hosts.DedicatedServerServices(id) + } + + opts := &base.BaseListOptions[serverscom.DedicatedServerService]{} + + return base.NewListCmd("list-services ", "Dedicated server services", factory, cmdContext, opts) +} diff --git a/cmd/entities/hosts/services_test.go b/cmd/entities/hosts/services_test.go new file mode 100644 index 0000000..541652c --- /dev/null +++ b/cmd/entities/hosts/services_test.go @@ -0,0 +1,152 @@ +package hosts + +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 ( + servicesFixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "hosts", "services") + testDSService = serverscom.DedicatedServerService{ + ID: testId, + Name: "Test service", + Type: "server_base", + Currency: "USD", + StartedAt: fixedTime, + FinishedAt: fixedTime, + Total: 100.0, + UsageQuantity: 2.0, + Tax: 10.0, + Subtotal: 100.0, + DiscountRate: 5.0, + DateFrom: "2025-11-01", + DateTo: "2025-11-30", + } +) + +func TestListDSServicesCmd(t *testing.T) { + testService1 := testDSService + testService2 := testDSService + testService2.ID += "2" + testService2.Name = "Test service 2" + testService2.Type = "uplink" + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.DedicatedServerService]) + }{ + { + name: "list all ds services", + output: "json", + args: []string{"-A", testServerID}, + expectedOutput: testutils.ReadFixture(filepath.Join(servicesFixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerService]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.DedicatedServerService{ + testService1, + testService2, + }, nil) + }, + }, + { + name: "list ds services", + output: "json", + args: []string{testServerID}, + expectedOutput: testutils.ReadFixture(filepath.Join(servicesFixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerService]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DedicatedServerService{ + testService1, + }, nil) + }, + }, + { + name: "list ds services with template", + args: []string{testServerID, "--template", "{{range .}}ID: {{.ID}} Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(servicesFixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerService]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DedicatedServerService{ + testService1, + testService2, + }, nil) + }, + }, + { + name: "list ds services with page-view", + args: []string{testServerID, "--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(servicesFixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerService]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DedicatedServerService{ + testService1, + testService2, + }, nil) + }, + }, + { + name: "list ds services with error", + args: []string{testServerID}, + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.DedicatedServerService]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + hostService := mocks.NewMockHostsService(mockCtrl) + collection := mocks.NewMockCollection[serverscom.DedicatedServerService](mockCtrl) + + hostService.EXPECT().DedicatedServerServices(gomock.Any()).Return(collection).AnyTimes() + collection.EXPECT().SetParam(gomock.Any(), gomock.Any()).Return(collection).AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Hosts = hostService + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + if tc.configureMock != nil { + tc.configureMock(collection) + } + testCmdContext := testutils.NewTestCmdContext(scClient) + cmd := NewCmd(testCmdContext) + + args := append([]string{"hosts", "ds", "list-services"}, tc.args...) + if tc.output != "" { + args = append(args, "--output", tc.output) + } + builder := testutils.NewTestCommandBuilder(). + WithCommand(cmd). + WithArgs(args) + + c := builder.Build() + err := c.Execute() + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} diff --git a/cmd/entities/l2_segments/l2_segments_test.go b/cmd/entities/l2_segments/l2_segments_test.go index 9d17bf3..3da5261 100644 --- a/cmd/entities/l2_segments/l2_segments_test.go +++ b/cmd/entities/l2_segments/l2_segments_test.go @@ -35,11 +35,12 @@ var ( } testL2LocationGroup = serverscom.L2LocationGroup{ - ID: 10, - Name: testL2SegmentName, - Code: "testCode", - GroupType: "public", - LocationIDs: []int64{1, 2, 3}, + ID: 10, + Name: testL2SegmentName, + Code: "testCode", + GroupType: "public", + LocationIDs: []int64{1, 2, 3}, + Hyperscalers: []string{"AWS", "Azure"}, } testL2Member = serverscom.L2Member{ diff --git a/go.mod b/go.mod index 872059d..89cf130 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.21 + github.com/serverscom/serverscom-go-client v1.0.25 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 e5b5143..c0e3b48 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.21 h1:7L3Mo56M9yprj33iCxeP/iaxnQfCfiV0OwOqnCqIt1I= -github.com/serverscom/serverscom-go-client v1.0.21/go.mod h1:/Nf+XygKOxm19Sl2gvMzT55O4X+tWDkj/UM4mjzfKgM= +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/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/hosts_service.go b/internal/mocks/hosts_service.go index 8602898..962242f 100644 --- a/internal/mocks/hosts_service.go +++ b/internal/mocks/hosts_service.go @@ -145,6 +145,21 @@ func (mr *MockHostsServiceMockRecorder) CreatePTRRecordForDedicatedServer(ctx, i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePTRRecordForDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).CreatePTRRecordForDedicatedServer), ctx, id, input) } +// CreatePTRRecordForSBMServer mocks base method. +func (m *MockHostsService) CreatePTRRecordForSBMServer(ctx context.Context, id string, input serverscom.PTRRecordCreateInput) (*serverscom.PTRRecord, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePTRRecordForSBMServer", ctx, id, input) + ret0, _ := ret[0].(*serverscom.PTRRecord) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreatePTRRecordForSBMServer indicates an expected call of CreatePTRRecordForSBMServer. +func (mr *MockHostsServiceMockRecorder) CreatePTRRecordForSBMServer(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePTRRecordForSBMServer", reflect.TypeOf((*MockHostsService)(nil).CreatePTRRecordForSBMServer), ctx, id, input) +} + // CreateSBMServers mocks base method. func (m *MockHostsService) CreateSBMServers(ctx context.Context, input serverscom.SBMServerCreateInput) ([]serverscom.SBMServer, error) { m.ctrl.T.Helper() @@ -188,6 +203,20 @@ func (mr *MockHostsServiceMockRecorder) DedicatedServerDriveSlots(id any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DedicatedServerDriveSlots", reflect.TypeOf((*MockHostsService)(nil).DedicatedServerDriveSlots), id) } +// DedicatedServerFeatures mocks base method. +func (m *MockHostsService) DedicatedServerFeatures(id string) serverscom.Collection[serverscom.DedicatedServerFeature] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DedicatedServerFeatures", id) + ret0, _ := ret[0].(serverscom.Collection[serverscom.DedicatedServerFeature]) + return ret0 +} + +// DedicatedServerFeatures indicates an expected call of DedicatedServerFeatures. +func (mr *MockHostsServiceMockRecorder) DedicatedServerFeatures(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DedicatedServerFeatures", reflect.TypeOf((*MockHostsService)(nil).DedicatedServerFeatures), id) +} + // DedicatedServerNetworks mocks base method. func (m *MockHostsService) DedicatedServerNetworks(id string) serverscom.Collection[serverscom.Network] { m.ctrl.T.Helper() @@ -231,6 +260,20 @@ func (mr *MockHostsServiceMockRecorder) DedicatedServerPowerFeeds(ctx, id any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DedicatedServerPowerFeeds", reflect.TypeOf((*MockHostsService)(nil).DedicatedServerPowerFeeds), ctx, id) } +// DedicatedServerServices mocks base method. +func (m *MockHostsService) DedicatedServerServices(id string) serverscom.Collection[serverscom.DedicatedServerService] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DedicatedServerServices", id) + ret0, _ := ret[0].(serverscom.Collection[serverscom.DedicatedServerService]) + return ret0 +} + +// DedicatedServerServices indicates an expected call of DedicatedServerServices. +func (mr *MockHostsServiceMockRecorder) DedicatedServerServices(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DedicatedServerServices", reflect.TypeOf((*MockHostsService)(nil).DedicatedServerServices), id) +} + // DeleteDedicatedServerNetwork mocks base method. func (m *MockHostsService) DeleteDedicatedServerNetwork(ctx context.Context, serverID, networkID string) (*serverscom.Network, error) { m.ctrl.T.Helper() @@ -260,6 +303,20 @@ func (mr *MockHostsServiceMockRecorder) DeletePTRRecordForDedicatedServer(ctx, s return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePTRRecordForDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).DeletePTRRecordForDedicatedServer), ctx, serverID, ptrRecordID) } +// DeletePTRRecordForSBMServer mocks base method. +func (m *MockHostsService) DeletePTRRecordForSBMServer(ctx context.Context, serverID, ptrRecordID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePTRRecordForSBMServer", ctx, serverID, ptrRecordID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePTRRecordForSBMServer indicates an expected call of DeletePTRRecordForSBMServer. +func (mr *MockHostsServiceMockRecorder) DeletePTRRecordForSBMServer(ctx, serverID, ptrRecordID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePTRRecordForSBMServer", reflect.TypeOf((*MockHostsService)(nil).DeletePTRRecordForSBMServer), ctx, serverID, ptrRecordID) +} + // GetDedicatedServer mocks base method. func (m *MockHostsService) GetDedicatedServer(ctx context.Context, id string) (*serverscom.DedicatedServer, error) { m.ctrl.T.Helper() @@ -305,6 +362,21 @@ func (mr *MockHostsServiceMockRecorder) GetDedicatedServerNetworkUsage(ctx, id a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDedicatedServerNetworkUsage", reflect.TypeOf((*MockHostsService)(nil).GetDedicatedServerNetworkUsage), ctx, id) } +// GetDedicatedServerOOBCredentials mocks base method. +func (m *MockHostsService) GetDedicatedServerOOBCredentials(ctx context.Context, id string, params map[string]string) (*serverscom.DedicatedServerOOBCredentials, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDedicatedServerOOBCredentials", ctx, id, params) + ret0, _ := ret[0].(*serverscom.DedicatedServerOOBCredentials) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDedicatedServerOOBCredentials indicates an expected call of GetDedicatedServerOOBCredentials. +func (mr *MockHostsServiceMockRecorder) GetDedicatedServerOOBCredentials(ctx, id, params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDedicatedServerOOBCredentials", reflect.TypeOf((*MockHostsService)(nil).GetDedicatedServerOOBCredentials), ctx, id, params) +} + // GetKubernetesBaremetalNode mocks base method. func (m *MockHostsService) GetKubernetesBaremetalNode(ctx context.Context, id string) (*serverscom.KubernetesBaremetalNode, error) { m.ctrl.T.Helper() @@ -600,19 +672,48 @@ func (mr *MockHostsServiceMockRecorder) ReleaseSBMServer(ctx, id any) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseSBMServer", reflect.TypeOf((*MockHostsService)(nil).ReleaseSBMServer), ctx, id) } +// SBMServerPTRRecords mocks base method. +func (m *MockHostsService) SBMServerPTRRecords(id string) serverscom.Collection[serverscom.PTRRecord] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SBMServerPTRRecords", id) + ret0, _ := ret[0].(serverscom.Collection[serverscom.PTRRecord]) + return ret0 +} + +// SBMServerPTRRecords indicates an expected call of SBMServerPTRRecords. +func (mr *MockHostsServiceMockRecorder) SBMServerPTRRecords(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SBMServerPTRRecords", reflect.TypeOf((*MockHostsService)(nil).SBMServerPTRRecords), id) +} + +// SBMServerPowerFeeds mocks base method. +func (m *MockHostsService) SBMServerPowerFeeds(ctx context.Context, id string) ([]serverscom.HostPowerFeed, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SBMServerPowerFeeds", ctx, id) + ret0, _ := ret[0].([]serverscom.HostPowerFeed) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SBMServerPowerFeeds indicates an expected call of SBMServerPowerFeeds. +func (mr *MockHostsServiceMockRecorder) SBMServerPowerFeeds(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SBMServerPowerFeeds", reflect.TypeOf((*MockHostsService)(nil).SBMServerPowerFeeds), ctx, id) +} + // ScheduleReleaseForDedicatedServer mocks base method. -func (m *MockHostsService) ScheduleReleaseForDedicatedServer(ctx context.Context, id string) (*serverscom.DedicatedServer, error) { +func (m *MockHostsService) ScheduleReleaseForDedicatedServer(ctx context.Context, id string, input serverscom.ScheduleReleaseInput) (*serverscom.DedicatedServer, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ScheduleReleaseForDedicatedServer", ctx, id) + ret := m.ctrl.Call(m, "ScheduleReleaseForDedicatedServer", ctx, id, input) ret0, _ := ret[0].(*serverscom.DedicatedServer) ret1, _ := ret[1].(error) return ret0, ret1 } // ScheduleReleaseForDedicatedServer indicates an expected call of ScheduleReleaseForDedicatedServer. -func (mr *MockHostsServiceMockRecorder) ScheduleReleaseForDedicatedServer(ctx, id any) *gomock.Call { +func (mr *MockHostsServiceMockRecorder) ScheduleReleaseForDedicatedServer(ctx, id, input any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScheduleReleaseForDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).ScheduleReleaseForDedicatedServer), ctx, id) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScheduleReleaseForDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).ScheduleReleaseForDedicatedServer), ctx, id, input) } // UpdateDedicatedServer mocks base method. diff --git a/internal/mocks/locations_service.go b/internal/mocks/locations_service.go index e504469..08c70b8 100644 --- a/internal/mocks/locations_service.go +++ b/internal/mocks/locations_service.go @@ -143,6 +143,21 @@ func (mr *MockLocationsServiceMockRecorder) GetOperatingSystemOption(ctx, locati return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperatingSystemOption", reflect.TypeOf((*MockLocationsService)(nil).GetOperatingSystemOption), ctx, locationID, serverModelID, operatingSystemID) } +// GetRemoteBlockStorageFlavor mocks base method. +func (m *MockLocationsService) GetRemoteBlockStorageFlavor(ctx context.Context, locationID, flavorID int64) (*serverscom.RemoteBlockStorageFlavor, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRemoteBlockStorageFlavor", ctx, locationID, flavorID) + ret0, _ := ret[0].(*serverscom.RemoteBlockStorageFlavor) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRemoteBlockStorageFlavor indicates an expected call of GetRemoteBlockStorageFlavor. +func (mr *MockLocationsServiceMockRecorder) GetRemoteBlockStorageFlavor(ctx, locationID, flavorID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemoteBlockStorageFlavor", reflect.TypeOf((*MockLocationsService)(nil).GetRemoteBlockStorageFlavor), ctx, locationID, flavorID) +} + // GetSBMFlavorOption mocks base method. func (m *MockLocationsService) GetSBMFlavorOption(ctx context.Context, locationID, sbmFlavorModelID int64) (*serverscom.SBMFlavor, error) { m.ctrl.T.Helper() @@ -231,6 +246,20 @@ func (mr *MockLocationsServiceMockRecorder) RAMOptions(locationID, serverModelID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RAMOptions", reflect.TypeOf((*MockLocationsService)(nil).RAMOptions), locationID, serverModelID) } +// RemoteBlockStorageFlavors mocks base method. +func (m *MockLocationsService) RemoteBlockStorageFlavors(locationID int64) serverscom.Collection[serverscom.RemoteBlockStorageFlavor] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoteBlockStorageFlavors", locationID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.RemoteBlockStorageFlavor]) + return ret0 +} + +// RemoteBlockStorageFlavors indicates an expected call of RemoteBlockStorageFlavors. +func (mr *MockLocationsServiceMockRecorder) RemoteBlockStorageFlavors(locationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteBlockStorageFlavors", reflect.TypeOf((*MockLocationsService)(nil).RemoteBlockStorageFlavors), locationID) +} + // SBMFlavorOptions mocks base method. func (m *MockLocationsService) SBMFlavorOptions(locationID int64) serverscom.Collection[serverscom.SBMFlavor] { m.ctrl.T.Helper() diff --git a/internal/output/entities/hosts.go b/internal/output/entities/hosts.go index be3f903..c66af91 100644 --- a/internal/output/entities/hosts.go +++ b/internal/output/entities/hosts.go @@ -17,6 +17,9 @@ var ( HostDriveSlotType = reflect.TypeOf(serverscom.HostDriveSlot{}) HostPTRRecordType = reflect.TypeOf(serverscom.PTRRecord{}) HostNetworkType = reflect.TypeOf(serverscom.Network{}) + HostFeatureType = reflect.TypeOf(serverscom.DedicatedServerFeature{}) + HostServiceType = reflect.TypeOf(serverscom.DedicatedServerService{}) + HostOOBCredsType = reflect.TypeOf(serverscom.DedicatedServerOOBCredentials{}) HostListDefaultFields = []string{"ID", "Type", "Title", "LocationCode", "Status", "PublicIPv4Address"} DedicatedServerListDefaultFields = []string{"ID", "Title", "RackID", "LocationCode", "Status", "PublicIPv4Address"} KubernetesBaremetalNodeListDefaultFields = []string{"ID", "KubernetesClusterNodeNumber", "Title", "LocationCode", "Status", "PublicIPv4Address"} @@ -275,4 +278,49 @@ func RegisterHostsSubDefinitions() { if err := Registry.Register(hostNetworkEntity); err != nil { log.Fatal(err) } + + hostFeatureEntity := &Entity{ + fields: []Field{ + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Status", Name: "Status", Path: "Status", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: HostFeatureType, + } + if err := Registry.Register(hostFeatureEntity); err != nil { + log.Fatal(err) + } + + hostServiceEntity := &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: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Currency", Name: "Currency", Path: "Currency", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Label", Name: "Label", Path: "Label", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "StartedAt", Name: "StartedAt", Path: "StartedAt", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + {ID: "FinishedAt", Name: "FinishedAt", Path: "FinishedAt", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + {ID: "DateFrom", Name: "DateFrom", Path: "DateFrom", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "DateTo", Name: "DateTo", Path: "DateTo", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "UsageQuantity", Name: "UsageQuantity", Path: "UsageQuantity", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "Tax", Name: "Tax", Path: "Tax", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "Total", Name: "Total", Path: "Total", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Subtotal", Name: "Subtotal", Path: "Subtotal", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "DiscountRate", Name: "DiscountRate", Path: "DiscountRate", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + }, + eType: HostServiceType, + } + if err := Registry.Register(hostServiceEntity); err != nil { + log.Fatal(err) + } + + hostOOBCredsEntity := &Entity{ + fields: []Field{ + {ID: "Login", Name: "Login", Path: "Login", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Secret", Name: "Secret", Path: "Secret", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: HostOOBCredsType, + } + if err := Registry.Register(hostOOBCredsEntity); err != nil { + log.Fatal(err) + } } diff --git a/internal/output/entities/l2_segments.go b/internal/output/entities/l2_segments.go index 82a1372..53be7b7 100644 --- a/internal/output/entities/l2_segments.go +++ b/internal/output/entities/l2_segments.go @@ -46,8 +46,7 @@ func RegisterL2SegmentDefinitions() { {ID: "Code", Name: "Code", Path: "Code", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "GroupType", Name: "GroupType", Path: "GroupType", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "LocationIDs", Name: "LocationIDs", Path: "LocationIDs", PageViewHandlerFunc: slicePvHandler}, - // TODO field missing in go client - // {ID: "Hyperscalers", Name: "Hyperscalers", Path: "Hyperscalers", PageViewHandlerFunc: slicePvHandler}, + {ID: "Hyperscalers", Name: "Hyperscalers", Path: "Hyperscalers", PageViewHandlerFunc: slicePvHandler}, }, eType: L2SegmentLocationGroupType, } diff --git a/testdata/entities/hosts/features/list.json b/testdata/entities/hosts/features/list.json new file mode 100644 index 0000000..ee68a6f --- /dev/null +++ b/testdata/entities/hosts/features/list.json @@ -0,0 +1,6 @@ +[ + { + "name": "disaggregated_public_ports", + "status": "deactivated" + } +] \ No newline at end of file diff --git a/testdata/entities/hosts/features/list_all.json b/testdata/entities/hosts/features/list_all.json new file mode 100644 index 0000000..ab87de0 --- /dev/null +++ b/testdata/entities/hosts/features/list_all.json @@ -0,0 +1,10 @@ +[ + { + "name": "disaggregated_public_ports", + "status": "deactivated" + }, + { + "name": "no_public_network", + "status": "unavailable" + } +] \ No newline at end of file diff --git a/testdata/entities/hosts/features/list_pageview.txt b/testdata/entities/hosts/features/list_pageview.txt new file mode 100644 index 0000000..373f181 --- /dev/null +++ b/testdata/entities/hosts/features/list_pageview.txt @@ -0,0 +1,5 @@ +Name: disaggregated_public_ports +Status: deactivated +--- +Name: no_public_network +Status: unavailable diff --git a/testdata/entities/hosts/features/list_template.txt b/testdata/entities/hosts/features/list_template.txt new file mode 100644 index 0000000..e367719 --- /dev/null +++ b/testdata/entities/hosts/features/list_template.txt @@ -0,0 +1,2 @@ +Name: disaggregated_public_ports Status: deactivated +Name: no_public_network Status: unavailable diff --git a/testdata/entities/hosts/list_kbm_drive_slots.json b/testdata/entities/hosts/list_drive_slots.json similarity index 100% rename from testdata/entities/hosts/list_kbm_drive_slots.json rename to testdata/entities/hosts/list_drive_slots.json diff --git a/testdata/entities/hosts/list_kbm_drive_slots_all.json b/testdata/entities/hosts/list_drive_slots_all.json similarity index 100% rename from testdata/entities/hosts/list_kbm_drive_slots_all.json rename to testdata/entities/hosts/list_drive_slots_all.json diff --git a/testdata/entities/hosts/list_kbm_drive_slots_pageview.txt b/testdata/entities/hosts/list_drive_slots_pageview.txt similarity index 100% rename from testdata/entities/hosts/list_kbm_drive_slots_pageview.txt rename to testdata/entities/hosts/list_drive_slots_pageview.txt diff --git a/testdata/entities/hosts/list_kbm_drive_slots_template.txt b/testdata/entities/hosts/list_drive_slots_template.txt similarity index 100% rename from testdata/entities/hosts/list_kbm_drive_slots_template.txt rename to testdata/entities/hosts/list_drive_slots_template.txt diff --git a/testdata/entities/hosts/list_kbm_power_feeds.json b/testdata/entities/hosts/list_kbm_power_feeds.json deleted file mode 100644 index 13fcc72..0000000 --- a/testdata/entities/hosts/list_kbm_power_feeds.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "name": "testPowerFeed", - "status": "on" - }, - { - "name": "testPowerFeed2", - "status": "off" - } -] \ No newline at end of file diff --git a/testdata/entities/hosts/list_power_feeds.json b/testdata/entities/hosts/list_power_feeds.json new file mode 100644 index 0000000..fff0c03 --- /dev/null +++ b/testdata/entities/hosts/list_power_feeds.json @@ -0,0 +1,12 @@ +[ + { + "name": "testPowerFeed", + "status": "on", + "type": "physical" + }, + { + "name": "testPowerFeed2", + "status": "off", + "type": "physical" + } +] \ No newline at end of file diff --git a/testdata/entities/hosts/list_kbm_power_feeds.txt b/testdata/entities/hosts/list_power_feeds.txt similarity index 100% rename from testdata/entities/hosts/list_kbm_power_feeds.txt rename to testdata/entities/hosts/list_power_feeds.txt diff --git a/testdata/entities/hosts/list_kbm_power_feeds.yaml b/testdata/entities/hosts/list_power_feeds.yaml similarity index 69% rename from testdata/entities/hosts/list_kbm_power_feeds.yaml rename to testdata/entities/hosts/list_power_feeds.yaml index 6956d1b..2a0a171 100644 --- a/testdata/entities/hosts/list_kbm_power_feeds.yaml +++ b/testdata/entities/hosts/list_power_feeds.yaml @@ -1,4 +1,6 @@ - name: testPowerFeed status: "on" + type: physical - name: testPowerFeed2 status: "off" + type: physical diff --git a/testdata/entities/hosts/oob/get.json b/testdata/entities/hosts/oob/get.json new file mode 100644 index 0000000..25e185b --- /dev/null +++ b/testdata/entities/hosts/oob/get.json @@ -0,0 +1,4 @@ +{ + "login": "test", + "secret": "secret" +} \ No newline at end of file diff --git a/testdata/entities/hosts/oob/get.txt b/testdata/entities/hosts/oob/get.txt new file mode 100644 index 0000000..c3512da --- /dev/null +++ b/testdata/entities/hosts/oob/get.txt @@ -0,0 +1,2 @@ +Login Secret +test secret diff --git a/testdata/entities/hosts/oob/get.yaml b/testdata/entities/hosts/oob/get.yaml new file mode 100644 index 0000000..e1f1cf4 --- /dev/null +++ b/testdata/entities/hosts/oob/get.yaml @@ -0,0 +1,2 @@ +login: test +secret: secret diff --git a/testdata/entities/hosts/release_ds_resp.json b/testdata/entities/hosts/release_ds_resp.json index c10f62b..ab3d509 100644 --- a/testdata/entities/hosts/release_ds_resp.json +++ b/testdata/entities/hosts/release_ds_resp.json @@ -12,7 +12,7 @@ "private_ipv4_address": null, "public_ipv4_address": "1.2.3.4", "lease_start_at": "", - "scheduled_release_at": "2025-01-01T12:00:00Z", + "scheduled_release_at": null, "oob_ipv4_address": "", "configuration_details": { "ram_size": 2, diff --git a/testdata/entities/hosts/release_ds_scheduled_resp.json b/testdata/entities/hosts/release_ds_scheduled_resp.json new file mode 100644 index 0000000..c10f62b --- /dev/null +++ b/testdata/entities/hosts/release_ds_scheduled_resp.json @@ -0,0 +1,33 @@ +{ + "id": "testId", + "rack_id": "testId", + "type": "dedicated_server", + "title": "example.aa", + "location_id": 0, + "location_code": "test", + "status": "active", + "operational_status": "", + "power_status": "", + "configuration": "", + "private_ipv4_address": null, + "public_ipv4_address": "1.2.3.4", + "lease_start_at": "", + "scheduled_release_at": "2025-01-01T12:00:00Z", + "oob_ipv4_address": "", + "configuration_details": { + "ram_size": 2, + "server_model_id": 1, + "server_model_name": "server-model-123", + "public_uplink_id": 2, + "public_uplink_name": "Public 1 Gbps without redundancy", + "private_uplink_id": 3, + "private_uplink_name": "Private 1 Gbps without redundancy", + "bandwidth_id": 4, + "bandwidth_name": "20000 GB", + "operating_system_id": 5, + "operating_system_full_name": "CentOS 7 x86_64" + }, + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} \ No newline at end of file diff --git a/testdata/entities/hosts/services/list.json b/testdata/entities/hosts/services/list.json new file mode 100644 index 0000000..9609b8d --- /dev/null +++ b/testdata/entities/hosts/services/list.json @@ -0,0 +1,18 @@ +[ + { + "id": "testId", + "name": "Test service", + "type": "server_base", + "currency": "USD", + "label": null, + "started_at": "2025-01-01T12:00:00Z", + "finished_at": "2025-01-01T12:00:00Z", + "date_from": "2025-11-01", + "date_to": "2025-11-30", + "usage_quantity": 2, + "tax": 10, + "total": 100, + "subtotal": 100, + "discount_rate": 5 + } +] \ No newline at end of file diff --git a/testdata/entities/hosts/services/list_all.json b/testdata/entities/hosts/services/list_all.json new file mode 100644 index 0000000..e15e374 --- /dev/null +++ b/testdata/entities/hosts/services/list_all.json @@ -0,0 +1,34 @@ +[ + { + "id": "testId", + "name": "Test service", + "type": "server_base", + "currency": "USD", + "label": null, + "started_at": "2025-01-01T12:00:00Z", + "finished_at": "2025-01-01T12:00:00Z", + "date_from": "2025-11-01", + "date_to": "2025-11-30", + "usage_quantity": 2, + "tax": 10, + "total": 100, + "subtotal": 100, + "discount_rate": 5 + }, + { + "id": "testId2", + "name": "Test service 2", + "type": "uplink", + "currency": "USD", + "label": null, + "started_at": "2025-01-01T12:00:00Z", + "finished_at": "2025-01-01T12:00:00Z", + "date_from": "2025-11-01", + "date_to": "2025-11-30", + "usage_quantity": 2, + "tax": 10, + "total": 100, + "subtotal": 100, + "discount_rate": 5 + } +] \ No newline at end of file diff --git a/testdata/entities/hosts/services/list_pageview.txt b/testdata/entities/hosts/services/list_pageview.txt new file mode 100644 index 0000000..cb9c742 --- /dev/null +++ b/testdata/entities/hosts/services/list_pageview.txt @@ -0,0 +1,29 @@ +ID: testId +Name: Test service +Type: server_base +Currency: USD +Label: +StartedAt: 2025-01-01T12:00:00Z +FinishedAt: 2025-01-01T12:00:00Z +DateFrom: 2025-11-01 +DateTo: 2025-11-30 +UsageQuantity: 2 +Tax: 10 +Total: 100 +Subtotal: 100 +DiscountRate: 5 +--- +ID: testId2 +Name: Test service 2 +Type: uplink +Currency: USD +Label: +StartedAt: 2025-01-01T12:00:00Z +FinishedAt: 2025-01-01T12:00:00Z +DateFrom: 2025-11-01 +DateTo: 2025-11-30 +UsageQuantity: 2 +Tax: 10 +Total: 100 +Subtotal: 100 +DiscountRate: 5 diff --git a/testdata/entities/hosts/services/list_template.txt b/testdata/entities/hosts/services/list_template.txt new file mode 100644 index 0000000..2cd2c5f --- /dev/null +++ b/testdata/entities/hosts/services/list_template.txt @@ -0,0 +1,2 @@ +ID: testId Name: Test service +ID: testId2 Name: Test service 2 diff --git a/testdata/entities/l2-segments/list_groups.json b/testdata/entities/l2-segments/list_groups.json index 3b3394b..7411b6d 100644 --- a/testdata/entities/l2-segments/list_groups.json +++ b/testdata/entities/l2-segments/list_groups.json @@ -8,6 +8,10 @@ 1, 2, 3 + ], + "hyperscalers": [ + "AWS", + "Azure" ] } ] \ No newline at end of file diff --git a/testdata/entities/l2-segments/list_groups_all.json b/testdata/entities/l2-segments/list_groups_all.json index 228cbb5..141d539 100644 --- a/testdata/entities/l2-segments/list_groups_all.json +++ b/testdata/entities/l2-segments/list_groups_all.json @@ -8,6 +8,10 @@ 1, 2, 3 + ], + "hyperscalers": [ + "AWS", + "Azure" ] }, { @@ -19,6 +23,10 @@ 1, 2, 3 + ], + "hyperscalers": [ + "AWS", + "Azure" ] } ] \ No newline at end of file diff --git a/testdata/entities/l2-segments/list_groups_pageview.txt b/testdata/entities/l2-segments/list_groups_pageview.txt index 3c302e2..a4e9b23 100644 --- a/testdata/entities/l2-segments/list_groups_pageview.txt +++ b/testdata/entities/l2-segments/list_groups_pageview.txt @@ -1,15 +1,19 @@ -ID: 10 -Name: testName -Code: testCode -GroupType: public -LocationIDs: 1 - 2 - 3 +ID: 10 +Name: testName +Code: testCode +GroupType: public +LocationIDs: 1 + 2 + 3 +Hyperscalers: AWS + Azure --- -ID: 11 -Name: testName2 -Code: testCode -GroupType: public -LocationIDs: 1 - 2 - 3 +ID: 11 +Name: testName2 +Code: testCode +GroupType: public +LocationIDs: 1 + 2 + 3 +Hyperscalers: AWS + Azure