From 736f78e0b78991651255356b8f4194a1beadf06b Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Wed, 20 Aug 2025 17:35:30 +0300 Subject: [PATCH] add missing ptr cmds for ds --- cmd/entities/hosts/hosts.go | 2 + cmd/entities/hosts/ptr.go | 90 +++++++- cmd/entities/hosts/ptr_test.go | 283 ++++++++++++++++++++++++ testdata/entities/ptr/get.json | 7 + testdata/entities/ptr/list.json | 9 + testdata/entities/ptr/list_all.json | 16 ++ testdata/entities/ptr/list_pageview.txt | 11 + testdata/entities/ptr/list_template.txt | 2 + 8 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 cmd/entities/hosts/ptr_test.go create mode 100644 testdata/entities/ptr/get.json create mode 100644 testdata/entities/ptr/list.json create mode 100644 testdata/entities/ptr/list_all.json create mode 100644 testdata/entities/ptr/list_pageview.txt create mode 100644 testdata/entities/ptr/list_template.txt diff --git a/cmd/entities/hosts/hosts.go b/cmd/entities/hosts/hosts.go index 249f888..a769294 100644 --- a/cmd/entities/hosts/hosts.go +++ b/cmd/entities/hosts/hosts.go @@ -48,6 +48,8 @@ func NewCmd(cmdContext *base.CmdContext) *cobra.Command { newListDSDriveSlotsCmd, newListDSConnectionsCmd, newListDSPTRCmd, + newCreateDSPTRCmd, + newDeleteDSPTRCmd, newDSAbortReleaseCmd, newDSScheduleReleaseCmd, newListDSNetworksCmd, diff --git a/cmd/entities/hosts/ptr.go b/cmd/entities/hosts/ptr.go index 9088cd2..9598397 100644 --- a/cmd/entities/hosts/ptr.go +++ b/cmd/entities/hosts/ptr.go @@ -23,4 +23,92 @@ func newListDSPTRCmd(cmdContext *base.CmdContext) *cobra.Command { return base.NewListCmd("list-ptr ", "Dedicated server PTR records", factory, cmdContext, opts) } -// TODO add other PTR methods +func newCreateDSPTRCmd(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 a dedicated 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.CreatePTRRecordForDedicatedServer(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 newDeleteDSPTRCmd(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 a dedicated 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.DeletePTRRecordForDedicatedServer(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 new file mode 100644 index 0000000..4b24881 --- /dev/null +++ b/cmd/entities/hosts/ptr_test.go @@ -0,0 +1,283 @@ +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 ( + ptrFixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "ptr") + testServerID = "serverId" + testPTRID = "ptrId" + + testPTR = serverscom.PTRRecord{ + ID: testPTRID, + IP: "192.0.2.5", + Domain: "ptr-test.example.com", + Priority: 10, + TTL: 300, + } +) + +func TestListDSPTRCmd(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().DedicatedServerPTRRecords(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-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 TestCreateDSPTRCmd(t *testing.T) { + testCases := []struct { + name string + args []string + output string + expectedOutput []byte + configureMock func(*mocks.MockHostsService) + expectError bool + }{ + { + name: "create ds 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(). + CreatePTRRecordForDedicatedServer(gomock.Any(), testServerID, in). + Return(&testPTR, nil) + }, + }, + { + name: "create ds 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(). + CreatePTRRecordForDedicatedServer(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", "ds", "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 TestDeleteDSPTRCmd(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(). + DeletePTRRecordForDedicatedServer(gomock.Any(), tc.serverID, tc.ptrID). + Return(err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + cmd := NewCmd(testCmdContext) + + args := []string{"hosts", "ds", "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/testdata/entities/ptr/get.json b/testdata/entities/ptr/get.json new file mode 100644 index 0000000..899243a --- /dev/null +++ b/testdata/entities/ptr/get.json @@ -0,0 +1,7 @@ +{ + "id": "ptrId", + "ip": "192.0.2.5", + "domain": "ptr-test.example.com", + "priority": 10, + "ttl": 300 +} \ No newline at end of file diff --git a/testdata/entities/ptr/list.json b/testdata/entities/ptr/list.json new file mode 100644 index 0000000..01b67cd --- /dev/null +++ b/testdata/entities/ptr/list.json @@ -0,0 +1,9 @@ +[ + { + "id": "ptrId1", + "ip": "192.0.2.5", + "domain": "ptr-test.example.com", + "priority": 10, + "ttl": 300 + } +] \ No newline at end of file diff --git a/testdata/entities/ptr/list_all.json b/testdata/entities/ptr/list_all.json new file mode 100644 index 0000000..a91e7da --- /dev/null +++ b/testdata/entities/ptr/list_all.json @@ -0,0 +1,16 @@ +[ + { + "id": "ptrId1", + "ip": "192.0.2.5", + "domain": "ptr-test.example.com", + "priority": 10, + "ttl": 300 + }, + { + "id": "ptrId2", + "ip": "192.0.2.5", + "domain": "another.example.com", + "priority": 10, + "ttl": 300 + } +] \ No newline at end of file diff --git a/testdata/entities/ptr/list_pageview.txt b/testdata/entities/ptr/list_pageview.txt new file mode 100644 index 0000000..fa8146d --- /dev/null +++ b/testdata/entities/ptr/list_pageview.txt @@ -0,0 +1,11 @@ +ID: ptrId1 +IP: 192.0.2.5 +Domain: ptr-test.example.com +Priority: 10 +TTL: 300 +--- +ID: ptrId2 +IP: 192.0.2.5 +Domain: another.example.com +Priority: 10 +TTL: 300 diff --git a/testdata/entities/ptr/list_template.txt b/testdata/entities/ptr/list_template.txt new file mode 100644 index 0000000..fedddab --- /dev/null +++ b/testdata/entities/ptr/list_template.txt @@ -0,0 +1,2 @@ +ID: ptrId1 PTR: ptr-test.example.com +ID: ptrId2 PTR: another.example.com