From b98862c5f1f61ddcfbc5c54931be90ef308d3d4f Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Wed, 23 Sep 2026 13:20:01 +0300 Subject: [PATCH] add dns cmds --- Makefile | 4 +- cmd/base/list_options.go | 30 + cmd/entities/dns/add.go | 100 ++ cmd/entities/dns/add_record.go | 98 ++ cmd/entities/dns/delegation_data.go | 38 + cmd/entities/dns/delete.go | 30 + cmd/entities/dns/delete_record.go | 38 + cmd/entities/dns/dns.go | 62 + cmd/entities/dns/dns_test.go | 1007 +++++++++++++++++ cmd/entities/dns/get.go | 39 + cmd/entities/dns/get_record.go | 47 + cmd/entities/dns/list.go | 41 + cmd/entities/dns/update.go | 54 + cmd/entities/dns/update_record.go | 78 ++ cmd/root.go | 2 + internal/mocks/dns_service.go | 203 ++++ internal/output/entities/dns.go | 71 ++ internal/output/entities/init.go | 1 + .../skeletons/templates/dns/add-record.json | 7 + .../output/skeletons/templates/dns/add.json | 8 + testdata/entities/dns/create.json | 8 + testdata/entities/dns/create_record.json | 6 + testdata/entities/dns/delegation_data.json | 7 + testdata/entities/dns/delegation_data.txt | 2 + testdata/entities/dns/delegation_data.yaml | 4 + testdata/entities/dns/get.json | 13 + testdata/entities/dns/get.txt | 2 + testdata/entities/dns/get.yaml | 10 + testdata/entities/dns/get_record.json | 11 + testdata/entities/dns/get_record.txt | 2 + testdata/entities/dns/get_record.yaml | 9 + testdata/entities/dns/list.json | 15 + testdata/entities/dns/list_all.json | 28 + testdata/entities/dns/list_pageview.txt | 19 + testdata/entities/dns/list_records.json | 13 + testdata/entities/dns/list_records_all.json | 24 + .../entities/dns/list_records_pageview.txt | 19 + .../entities/dns/list_records_template.txt | 2 + testdata/entities/dns/list_template.txt | 2 + testdata/entities/dns/update.json | 13 + testdata/entities/dns/update_record.json | 11 + 41 files changed, 2177 insertions(+), 1 deletion(-) create mode 100644 cmd/entities/dns/add.go create mode 100644 cmd/entities/dns/add_record.go create mode 100644 cmd/entities/dns/delegation_data.go create mode 100644 cmd/entities/dns/delete.go create mode 100644 cmd/entities/dns/delete_record.go create mode 100644 cmd/entities/dns/dns.go create mode 100644 cmd/entities/dns/dns_test.go create mode 100644 cmd/entities/dns/get.go create mode 100644 cmd/entities/dns/get_record.go create mode 100644 cmd/entities/dns/list.go create mode 100644 cmd/entities/dns/update.go create mode 100644 cmd/entities/dns/update_record.go create mode 100644 internal/mocks/dns_service.go create mode 100644 internal/output/entities/dns.go create mode 100644 internal/output/skeletons/templates/dns/add-record.json create mode 100644 internal/output/skeletons/templates/dns/add.json create mode 100644 testdata/entities/dns/create.json create mode 100644 testdata/entities/dns/create_record.json create mode 100644 testdata/entities/dns/delegation_data.json create mode 100644 testdata/entities/dns/delegation_data.txt create mode 100644 testdata/entities/dns/delegation_data.yaml create mode 100644 testdata/entities/dns/get.json create mode 100644 testdata/entities/dns/get.txt create mode 100644 testdata/entities/dns/get.yaml create mode 100644 testdata/entities/dns/get_record.json create mode 100644 testdata/entities/dns/get_record.txt create mode 100644 testdata/entities/dns/get_record.yaml create mode 100644 testdata/entities/dns/list.json create mode 100644 testdata/entities/dns/list_all.json create mode 100644 testdata/entities/dns/list_pageview.txt create mode 100644 testdata/entities/dns/list_records.json create mode 100644 testdata/entities/dns/list_records_all.json create mode 100644 testdata/entities/dns/list_records_pageview.txt create mode 100644 testdata/entities/dns/list_records_template.txt create mode 100644 testdata/entities/dns/list_template.txt create mode 100644 testdata/entities/dns/update.json create mode 100644 testdata/entities/dns/update_record.json diff --git a/Makefile b/Makefile index 4f856c8..3c27219 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ generate: deps mockgen --destination ./internal/mocks/cloud_block_storage_volumes_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/cloud_block_storage_volumes.go mockgen --destination ./internal/mocks/cloud_block_storage_backups_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/cloud_block_storage_backups.go mockgen --destination ./internal/mocks/metrics_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/metrics.go + mockgen --destination ./internal/mocks/dns_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/dns.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 \ @@ -45,7 +46,8 @@ generate: deps ./internal/mocks/cloud_computing_regions_service.go \ ./internal/mocks/cloud_block_storage_volumes_service.go \ ./internal/mocks/cloud_block_storage_backups_service.go \ - ./internal/mocks/metrics_service.go + ./internal/mocks/metrics_service.go \ + ./internal/mocks/dns_service.go docs: go run cmd/gendoc/main.go diff --git a/cmd/base/list_options.go b/cmd/base/list_options.go index 26cf026..d6ed1e4 100644 --- a/cmd/base/list_options.go +++ b/cmd/base/list_options.go @@ -530,3 +530,33 @@ func (o *IsBackupOption[T]) ApplyToCollection(collection serverscom.Collection[T collection.SetParam("is_backup", "true") } } + +// dns domain delegation status option +type DelegationStatusOption[T any] struct { + delegationStatus string +} + +func (o *DelegationStatusOption[T]) AddFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&o.delegationStatus, "delegation-status", "", "Filter results by delegation status (delegated, verified, undelegated)") +} + +func (o *DelegationStatusOption[T]) ApplyToCollection(collection serverscom.Collection[T]) { + if o.delegationStatus != "" { + collection.SetParam("delegation_status", o.delegationStatus) + } +} + +// dns record type option +type DNSRecordTypeOption[T any] struct { + recordType string +} + +func (o *DNSRecordTypeOption[T]) AddFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&o.recordType, "type", "", "Filter results by record type (A, AAAA, CNAME, MX, TXT, NS, SRV, CAA, ALIAS, PTR, SOA)") +} + +func (o *DNSRecordTypeOption[T]) ApplyToCollection(collection serverscom.Collection[T]) { + if o.recordType != "" { + collection.SetParam("type", strings.ToUpper(o.recordType)) + } +} diff --git a/cmd/entities/dns/add.go b/cmd/entities/dns/add.go new file mode 100644 index 0000000..1d9c2bc --- /dev/null +++ b/cmd/entities/dns/add.go @@ -0,0 +1,100 @@ +package dns + +import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +type AddedFlags struct { + Skeleton bool + InputPath string + Name string + Email string + TTL int + Labels []string +} + +func newAddCmd(cmdContext *base.CmdContext) *cobra.Command { + flags := &AddedFlags{} + + cmd := &cobra.Command{ + Use: "add", + Short: "Add a DNS domain", + Long: "Create/Register a new DNS domain/zone", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + formatter := cmdContext.GetOrCreateFormatter(cmd) + + if flags.Skeleton { + return formatter.FormatSkeleton("dns/add.json") + } + + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + input := &serverscom.DNSDomainCreateInput{} + + if flags.InputPath != "" { + if err := base.ReadInputJSON(flags.InputPath, cmd.InOrStdin(), input); err != nil { + return err + } + } else { + required := []string{"name", "email"} + if err := base.ValidateFlags(cmd, required); err != nil { + return err + } + } + + if err := flags.FillInput(cmd, input); err != nil { + return err + } + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + domain, err := scClient.DNS.CreateDomain(ctx, *input) + if err != nil { + return err + } + + if domain != nil { + return formatter.Format(domain) + } + return nil + }, + } + + cmd.Flags().StringVarP(&flags.InputPath, "input", "i", "", "path to input file or '-' to read from stdin") + cmd.Flags().BoolVarP(&flags.Skeleton, "skeleton", "s", false, "JSON object with structure that is required to be passed") + + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "A name of a DNS domain") + cmd.Flags().StringVarP(&flags.Email, "email", "", "", "An email used for the zone's SOA record") + cmd.Flags().IntVarP(&flags.TTL, "ttl", "", 0, "A default TTL in seconds (0-604800)") + cmd.Flags().StringArrayVarP(&flags.Labels, "label", "l", []string{}, "string in key=value format") + + return cmd +} + +func (f *AddedFlags) FillInput(cmd *cobra.Command, input *serverscom.DNSDomainCreateInput) error { + if cmd.Flags().Changed("name") { + input.Name = f.Name + } + if cmd.Flags().Changed("email") { + input.Email = f.Email + } + if cmd.Flags().Changed("ttl") { + input.TTL = f.TTL + } + if cmd.Flags().Changed("label") { + labelsMap, err := base.ParseLabels(f.Labels) + if err != nil { + return err + } + + input.Labels = labelsMap + } + + return nil +} diff --git a/cmd/entities/dns/add_record.go b/cmd/entities/dns/add_record.go new file mode 100644 index 0000000..03530b1 --- /dev/null +++ b/cmd/entities/dns/add_record.go @@ -0,0 +1,98 @@ +package dns + +import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +type AddRecordFlags struct { + Skeleton bool + InputPath string + Type string + Name string + Data string + TTL int + Priority int +} + +func newAddRecordCmd(cmdContext *base.CmdContext) *cobra.Command { + flags := &AddRecordFlags{} + + cmd := &cobra.Command{ + Use: "add-record ", + Short: "Add a DNS record", + Long: "Add a new DNS record to a domain", + Args: base.SkeletonOrExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + formatter := cmdContext.GetOrCreateFormatter(cmd) + + if flags.Skeleton { + return formatter.FormatSkeleton("dns/add-record.json") + } + + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + input := &serverscom.DNSRecordCreateInput{} + + if flags.InputPath != "" { + if err := base.ReadInputJSON(flags.InputPath, cmd.InOrStdin(), input); err != nil { + return err + } + } else { + required := []string{"type", "name", "data"} + if err := base.ValidateFlags(cmd, required); err != nil { + return err + } + } + + flags.FillInput(cmd, input) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + domainID := args[0] + record, err := scClient.DNS.CreateRecord(ctx, domainID, *input) + if err != nil { + return err + } + + if record != nil { + return formatter.Format(record) + } + return nil + }, + } + + cmd.Flags().StringVarP(&flags.InputPath, "input", "i", "", "path to input file or '-' to read from stdin") + cmd.Flags().BoolVarP(&flags.Skeleton, "skeleton", "s", false, "JSON object with structure that is required to be passed") + + cmd.Flags().StringVarP(&flags.Type, "type", "", "", "A type of a DNS record (A, AAAA, CNAME, MX, TXT, NS, SRV, CAA, ALIAS)") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "A name of a DNS record") + cmd.Flags().StringVarP(&flags.Data, "data", "", "", "A data of a DNS record") + cmd.Flags().IntVarP(&flags.TTL, "ttl", "", 0, "A TTL of a DNS record in seconds") + cmd.Flags().IntVarP(&flags.Priority, "priority", "", 0, "A priority of a DNS record (required by convention for MX/SRV, ignored for other types)") + + return cmd +} + +func (f *AddRecordFlags) FillInput(cmd *cobra.Command, input *serverscom.DNSRecordCreateInput) { + if cmd.Flags().Changed("type") { + input.Type = serverscom.DNSRecordType(f.Type) + } + if cmd.Flags().Changed("name") { + input.Name = f.Name + } + if cmd.Flags().Changed("data") { + input.Data = f.Data + } + if cmd.Flags().Changed("ttl") { + input.TTL = &f.TTL + } + if cmd.Flags().Changed("priority") { + input.Priority = &f.Priority + } +} diff --git a/cmd/entities/dns/delegation_data.go b/cmd/entities/dns/delegation_data.go new file mode 100644 index 0000000..c892274 --- /dev/null +++ b/cmd/entities/dns/delegation_data.go @@ -0,0 +1,38 @@ +package dns + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newDelegationDataCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegation-data", + Short: "Get DNS delegation data", + Long: "Get the nameservers and required TXT record to configure at the domain registrar to delegate/verify a DNS zone", + Args: cobra.ExactArgs(0), + 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() + + data, err := scClient.DNS.GetDelegationData(ctx) + if err != nil { + return err + } + + if data != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(data) + } + return nil + }, + } + + return cmd +} diff --git a/cmd/entities/dns/delete.go b/cmd/entities/dns/delete.go new file mode 100644 index 0000000..a113ef4 --- /dev/null +++ b/cmd/entities/dns/delete.go @@ -0,0 +1,30 @@ +package dns + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newDeleteCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete ", + Short: "Delete a DNS domain", + Long: "Delete a DNS domain by id", + 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() + + domainID := args[0] + return scClient.DNS.DeleteDomain(ctx, domainID) + }, + } + + return cmd +} diff --git a/cmd/entities/dns/delete_record.go b/cmd/entities/dns/delete_record.go new file mode 100644 index 0000000..f2f547e --- /dev/null +++ b/cmd/entities/dns/delete_record.go @@ -0,0 +1,38 @@ +package dns + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newDeleteRecordCmd(cmdContext *base.CmdContext) *cobra.Command { + var recordID string + + cmd := &cobra.Command{ + Use: "delete-record --record-id ", + Short: "Delete a DNS record", + Long: "Delete a DNS record by id within a domain", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if err := base.ValidateFlags(cmd, []string{"record-id"}); 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() + + domainID := args[0] + return scClient.DNS.DeleteRecord(ctx, domainID, recordID) + }, + } + + cmd.Flags().StringVarP(&recordID, "record-id", "", "", "A DNS record id") + + return cmd +} diff --git a/cmd/entities/dns/dns.go b/cmd/entities/dns/dns.go new file mode 100644 index 0000000..56f2921 --- /dev/null +++ b/cmd/entities/dns/dns.go @@ -0,0 +1,62 @@ +package dns + +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 { + dnsDomainEntity, err := entities.Registry.GetEntityFromValue(serverscom.DNSDomain{}) + if err != nil { + log.Fatal(err) + } + dnsRecordEntity, err := entities.Registry.GetEntityFromValue(serverscom.DNSRecord{}) + if err != nil { + log.Fatal(err) + } + dnsDelegationEntity, err := entities.Registry.GetEntityFromValue(serverscom.DNSDomainDelegationData{}) + if err != nil { + log.Fatal(err) + } + + entitiesMap := make(map[string]entities.EntityInterface) + entitiesMap["dns"] = dnsDomainEntity + entitiesMap["list-records"] = dnsRecordEntity + entitiesMap["get-record"] = dnsRecordEntity + entitiesMap["add-record"] = dnsRecordEntity + entitiesMap["update-record"] = dnsRecordEntity + entitiesMap["delegation-data"] = dnsDelegationEntity + + cmd := &cobra.Command{ + Use: "dns", + Short: "Manage DNS domains and records", + PersistentPreRunE: base.CombinePreRunE( + base.CheckFormatterFlags(cmdContext, entitiesMap), + base.CheckEmptyContexts(cmdContext), + ), + Args: base.NoArgs, + Run: base.UsageRun, + } + + cmd.AddCommand( + newListCmd(cmdContext), + newGetCmd(cmdContext), + newAddCmd(cmdContext), + newUpdateCmd(cmdContext), + newDeleteCmd(cmdContext), + newDelegationDataCmd(cmdContext), + newListRecordsCmd(cmdContext), + newGetRecordCmd(cmdContext), + newAddRecordCmd(cmdContext), + newUpdateRecordCmd(cmdContext), + newDeleteRecordCmd(cmdContext), + ) + + base.AddFormatFlags(cmd) + + return cmd +} diff --git a/cmd/entities/dns/dns_test.go b/cmd/entities/dns/dns_test.go new file mode 100644 index 0000000..99a0c99 --- /dev/null +++ b/cmd/entities/dns/dns_test.go @@ -0,0 +1,1007 @@ +package dns + +import ( + "errors" + "path/filepath" + "testing" + "time" + + . "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", "dns") + skeletonTemplatePath = filepath.Join("..", "..", "..", "internal", "output", "skeletons", "templates", "dns") + fixedTime = time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + + testDomainID = "abc123" + testRecordID = "rec1" + + testDomain = serverscom.DNSDomain{ + ID: testDomainID, + Name: "example.com", + Email: "admin@example.com", + TTL: 3600, + DelegationStatus: serverscom.DNSDomainVerified, + Labels: map[string]string{"foo": "bar"}, + Created: fixedTime, + Updated: fixedTime, + } + + testRecord = serverscom.DNSRecord{ + ID: testRecordID, + DomainID: testDomainID, + Name: "www", + Type: serverscom.DNSRecordTypeA, + Data: strPtr("1.2.3.4"), + TTL: intPtr(3600), + Created: fixedTime, + Updated: fixedTime, + } + + testDelegation = serverscom.DNSDomainDelegationData{ + Nameservers: []string{"ns1.example.com", "ns2.example.com"}, + RequiredTxt: "sc-verification=abc123", + } +) + +func strPtr(s string) *string { return &s } +func intPtr(i int) *int { return &i } + +func TestAddDNSDomainCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + configureMock func(*mocks.MockDNSService) + expectedOutput []byte + expectError bool + }{ + { + name: "create domain with input", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), + args: []string{"--input", filepath.Join(fixtureBasePath, "create.json")}, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + CreateDomain(gomock.Any(), serverscom.DNSDomainCreateInput{ + Name: "example.com", + Email: "admin@example.com", + TTL: 3600, + Labels: map[string]string{"foo": "bar"}, + }). + Return(&testDomain, nil) + }, + }, + { + name: "create domain with flags", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), + args: []string{ + "--name", "example.com", + "--email", "admin@example.com", + "--ttl", "3600", + "--label", "foo=bar", + }, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + CreateDomain(gomock.Any(), serverscom.DNSDomainCreateInput{ + Name: "example.com", + Email: "admin@example.com", + TTL: 3600, + Labels: map[string]string{"foo": "bar"}, + }). + Return(&testDomain, nil) + }, + }, + { + name: "skeleton for domain input", + output: "json", + args: []string{"--skeleton"}, + expectedOutput: testutils.ReadFixture(filepath.Join(skeletonTemplatePath, "add.json")), + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT().CreateDomain(gomock.Any(), gomock.Any()).Times(0) + }, + }, + { + name: "create domain with error", + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(dnsServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "add"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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(MatchJSON(tc.expectedOutput)) + } + }) + } +} + +func TestGetDNSDomainCmd(t *testing.T) { + testCases := []struct { + name string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get domain in default format", + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.txt")), + }, + { + name: "get domain in JSON format", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), + }, + { + name: "get domain in YAML format", + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.yaml")), + }, + { + name: "get domain with error", + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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") + } + dnsServiceHandler.EXPECT(). + GetDomain(gomock.Any(), testDomainID). + Return(&testDomain, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "get", testDomainID} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestListDNSDomainsCmd(t *testing.T) { + domain1 := testDomain + domain2 := testDomain + domain2.ID = "def456" + domain2.Name = "example2.com" + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.DNSDomain]) + }{ + { + name: "list all domains", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSDomain]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.DNSDomain{domain1, domain2}, nil) + }, + }, + { + name: "list domains", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSDomain]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DNSDomain{domain1}, nil) + }, + }, + { + name: "list domains with template", + args: []string{"--template", "{{range .}}Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSDomain]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DNSDomain{domain1, domain2}, nil) + }, + }, + { + name: "list domains with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSDomain]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DNSDomain{domain1, domain2}, nil) + }, + }, + { + name: "list domains with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.DNSDomain]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.DNSDomain](mockCtrl) + + dnsServiceHandler.EXPECT(). + Collection(). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "list"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestUpdateDNSDomainCmd(t *testing.T) { + newDomain := testDomain + newDomain.Labels = map[string]string{"new": "label"} + + testCases := []struct { + name string + output string + args []string + configureMock func(*mocks.MockDNSService) + expectedOutput []byte + expectError bool + }{ + { + name: "update domain", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "update.json")), + args: []string{"--label", "new=label"}, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + UpdateDomain(gomock.Any(), testDomainID, serverscom.DNSDomainUpdateInput{ + Labels: map[string]string{"new": "label"}, + }). + Return(&newDomain, nil) + }, + }, + { + name: "update domain with error", + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + UpdateDomain(gomock.Any(), testDomainID, serverscom.DNSDomainUpdateInput{ + Labels: make(map[string]string), + }). + Return(nil, errors.New("some error")) + }, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(dnsServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "update", testDomainID} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestDeleteDNSDomainCmd(t *testing.T) { + testCases := []struct { + name string + expectError bool + }{ + {name: "delete domain"}, + {name: "delete domain with error", expectError: true}, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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") + } + dnsServiceHandler.EXPECT(). + DeleteDomain(gomock.Any(), testDomainID). + Return(err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "delete", testDomainID} + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + WithArgs(args) + + cmd := builder.Build() + + err = cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + } + }) + } +} + +func TestDelegationDataCmd(t *testing.T) { + testCases := []struct { + name string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "delegation data in default format", + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "delegation_data.txt")), + }, + { + name: "delegation data in JSON format", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "delegation_data.json")), + }, + { + name: "delegation data in YAML format", + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "delegation_data.yaml")), + }, + { + name: "delegation data with error", + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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") + } + dnsServiceHandler.EXPECT(). + GetDelegationData(gomock.Any()). + Return(&testDelegation, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "delegation-data"} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestListDNSRecordsCmd(t *testing.T) { + record1 := testRecord + record2 := testRecord + record2.ID = "rec2" + record2.Name = "mail" + record2.Type = serverscom.DNSRecordTypeMX + record2.Data = strPtr("mail.example.com") + record2.Priority = intPtr(10) + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.DNSRecord]) + }{ + { + name: "list all records", + output: "json", + args: []string{testDomainID, "-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_records_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSRecord]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.DNSRecord{record1, record2}, nil) + }, + }, + { + name: "list records", + output: "json", + args: []string{testDomainID}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_records.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DNSRecord{record1}, nil) + }, + }, + { + name: "list records with template", + args: []string{testDomainID, "--template", "{{range .}}Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_records_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DNSRecord{record1, record2}, nil) + }, + }, + { + name: "list records with pageView", + args: []string{testDomainID, "--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_records_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.DNSRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.DNSRecord{record1, record2}, nil) + }, + }, + { + name: "list records with error", + args: []string{testDomainID}, + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.DNSRecord]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.DNSRecord](mockCtrl) + + dnsServiceHandler.EXPECT(). + Records(testDomainID). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "list-records"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestGetDNSRecordCmd(t *testing.T) { + testCases := []struct { + name string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get record in default format", + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_record.txt")), + }, + { + name: "get record in JSON format", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_record.json")), + }, + { + name: "get record in YAML format", + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_record.yaml")), + }, + { + name: "get record with error", + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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") + } + dnsServiceHandler.EXPECT(). + GetRecord(gomock.Any(), testDomainID, testRecordID). + Return(&testRecord, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "get-record", testDomainID, "--record-id", testRecordID} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestAddDNSRecordCmd(t *testing.T) { + testCases := []struct { + name string + output string + args []string + configureMock func(*mocks.MockDNSService) + expectedOutput []byte + expectError bool + }{ + { + name: "create record with input", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_record.json")), + args: []string{testDomainID, "--input", filepath.Join(fixtureBasePath, "create_record.json")}, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + CreateRecord(gomock.Any(), testDomainID, serverscom.DNSRecordCreateInput{ + Type: serverscom.DNSRecordTypeA, + Name: "www", + Data: "1.2.3.4", + TTL: intPtr(3600), + }). + Return(&testRecord, nil) + }, + }, + { + name: "create record with flags", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_record.json")), + args: []string{ + testDomainID, + "--type", "A", + "--name", "www", + "--data", "1.2.3.4", + "--ttl", "3600", + }, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + CreateRecord(gomock.Any(), testDomainID, serverscom.DNSRecordCreateInput{ + Type: serverscom.DNSRecordTypeA, + Name: "www", + Data: "1.2.3.4", + TTL: intPtr(3600), + }). + Return(&testRecord, nil) + }, + }, + { + name: "skeleton for record input", + output: "json", + args: []string{"--skeleton"}, + expectedOutput: testutils.ReadFixture(filepath.Join(skeletonTemplatePath, "add-record.json")), + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT().CreateRecord(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + }, + { + name: "create record with error", + args: []string{testDomainID}, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(dnsServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "add-record"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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(MatchJSON(tc.expectedOutput)) + } + }) + } +} + +func TestUpdateDNSRecordCmd(t *testing.T) { + updatedRecord := testRecord + updatedRecord.Data = strPtr("5.6.7.8") + updatedRecord.TTL = intPtr(7200) + + testCases := []struct { + name string + output string + args []string + configureMock func(*mocks.MockDNSService) + expectedOutput []byte + expectError bool + }{ + { + name: "update record", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "update_record.json")), + args: []string{testDomainID, "--record-id", testRecordID, "--data", "5.6.7.8", "--ttl", "7200"}, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + UpdateRecord(gomock.Any(), testDomainID, testRecordID, serverscom.DNSRecordUpdateInput{ + Data: "5.6.7.8", + TTL: intPtr(7200), + }). + Return(&updatedRecord, nil) + }, + }, + { + name: "update record with error", + args: []string{testDomainID, "--record-id", testRecordID}, + configureMock: func(mock *mocks.MockDNSService) { + mock.EXPECT(). + UpdateRecord(gomock.Any(), testDomainID, testRecordID, serverscom.DNSRecordUpdateInput{}). + Return(nil, errors.New("some error")) + }, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(dnsServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "update-record"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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 TestDeleteDNSRecordCmd(t *testing.T) { + testCases := []struct { + name string + expectError bool + }{ + {name: "delete record"}, + {name: "delete record with error", expectError: true}, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + dnsServiceHandler := mocks.NewMockDNSService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.DNS = dnsServiceHandler + + 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") + } + dnsServiceHandler.EXPECT(). + DeleteRecord(gomock.Any(), testDomainID, testRecordID). + Return(err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + dnsCmd := NewCmd(testCmdContext) + + args := []string{"dns", "delete-record", testDomainID, "--record-id", testRecordID} + + builder := testutils.NewTestCommandBuilder(). + WithCommand(dnsCmd). + 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/dns/get.go b/cmd/entities/dns/get.go new file mode 100644 index 0000000..0cd8124 --- /dev/null +++ b/cmd/entities/dns/get.go @@ -0,0 +1,39 @@ +package dns + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "get ", + Short: "Get a DNS domain", + Long: "Get a DNS domain by id", + 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() + + domainID := args[0] + domain, err := scClient.DNS.GetDomain(ctx, domainID) + if err != nil { + return err + } + + if domain != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(domain) + } + return nil + }, + } + + return cmd +} diff --git a/cmd/entities/dns/get_record.go b/cmd/entities/dns/get_record.go new file mode 100644 index 0000000..51b64de --- /dev/null +++ b/cmd/entities/dns/get_record.go @@ -0,0 +1,47 @@ +package dns + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetRecordCmd(cmdContext *base.CmdContext) *cobra.Command { + var recordID string + + cmd := &cobra.Command{ + Use: "get-record --record-id ", + Short: "Get a DNS record", + Long: "Get a DNS record by id within a domain", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if err := base.ValidateFlags(cmd, []string{"record-id"}); 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() + + domainID := args[0] + record, err := scClient.DNS.GetRecord(ctx, domainID, recordID) + if err != nil { + return err + } + + if record != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(record) + } + return nil + }, + } + + cmd.Flags().StringVarP(&recordID, "record-id", "", "", "A DNS record id") + + return cmd +} diff --git a/cmd/entities/dns/list.go b/cmd/entities/dns/list.go new file mode 100644 index 0000000..64d57fc --- /dev/null +++ b/cmd/entities/dns/list.go @@ -0,0 +1,41 @@ +package dns + +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.DNSDomain] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + return scClient.DNS.Collection() + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.DNSDomain]{}, + &base.SearchPatternOption[serverscom.DNSDomain]{}, + &base.DelegationStatusOption[serverscom.DNSDomain]{}, + &base.LabelSelectorOption[serverscom.DNSDomain]{}, + ) + + return base.NewListCmd("list", "DNS domains", factory, cmdContext, opts...) +} + +func newListRecordsCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.DNSRecord] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + return scClient.DNS.Records(args[0]) + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.DNSRecord]{}, + &base.DNSRecordTypeOption[serverscom.DNSRecord]{}, + ) + + cmd := base.NewListCmd("list-records", "DNS records", factory, cmdContext, opts...) + cmd.Use = "list-records " + cmd.Args = cobra.ExactArgs(1) + + return cmd +} diff --git a/cmd/entities/dns/update.go b/cmd/entities/dns/update.go new file mode 100644 index 0000000..d7d979d --- /dev/null +++ b/cmd/entities/dns/update.go @@ -0,0 +1,54 @@ +package dns + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newUpdateCmd(cmdContext *base.CmdContext) *cobra.Command { + var labels []string + + cmd := &cobra.Command{ + Use: "update ", + Short: "Update a DNS domain", + Long: "Update a DNS domain by id (labels only)", + 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) + + labelsMap, err := base.ParseLabels(labels) + if err != nil { + log.Fatal(err) + } + input := serverscom.DNSDomainUpdateInput{ + Labels: labelsMap, + } + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + domainID := args[0] + domain, err := scClient.DNS.UpdateDomain(ctx, domainID, input) + if err != nil { + return err + } + + if domain != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(domain) + } + return nil + }, + } + + cmd.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "string in key=value format") + + return cmd +} diff --git a/cmd/entities/dns/update_record.go b/cmd/entities/dns/update_record.go new file mode 100644 index 0000000..490b5d9 --- /dev/null +++ b/cmd/entities/dns/update_record.go @@ -0,0 +1,78 @@ +package dns + +import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +type UpdateRecordFlags struct { + RecordID string + Name string + Data string + TTL int + Priority int +} + +func newUpdateRecordCmd(cmdContext *base.CmdContext) *cobra.Command { + flags := &UpdateRecordFlags{} + + cmd := &cobra.Command{ + Use: "update-record --record-id ", + Short: "Update a DNS record", + Long: "Update a DNS record by id within a domain. Record type cannot be changed; SOA/PTR records are not editable", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if err := base.ValidateFlags(cmd, []string{"record-id"}); err != nil { + return err + } + + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + input := serverscom.DNSRecordUpdateInput{} + flags.FillInput(cmd, &input) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + domainID := args[0] + record, err := scClient.DNS.UpdateRecord(ctx, domainID, flags.RecordID, input) + if err != nil { + return err + } + + if record != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(record) + } + return nil + }, + } + + cmd.Flags().StringVarP(&flags.RecordID, "record-id", "", "", "A DNS record id") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "A name of a DNS record") + cmd.Flags().StringVarP(&flags.Data, "data", "", "", "A data of a DNS record") + cmd.Flags().IntVarP(&flags.TTL, "ttl", "", 0, "A TTL of a DNS record in seconds") + cmd.Flags().IntVarP(&flags.Priority, "priority", "", 0, "A priority of a DNS record (required by convention for MX/SRV, ignored for other types)") + + return cmd +} + +func (f *UpdateRecordFlags) FillInput(cmd *cobra.Command, input *serverscom.DNSRecordUpdateInput) { + if cmd.Flags().Changed("name") { + input.Name = f.Name + } + if cmd.Flags().Changed("data") { + input.Data = f.Data + } + if cmd.Flags().Changed("ttl") { + input.TTL = &f.TTL + } + if cmd.Flags().Changed("priority") { + input.Priority = &f.Priority + } +} diff --git a/cmd/root.go b/cmd/root.go index 7ad2013..4e2c9d2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,7 @@ import ( cloudinstances "github.com/serverscom/srvctl/cmd/entities/cloud-instances" cloudregions "github.com/serverscom/srvctl/cmd/entities/cloud-regions" cloudvolumes "github.com/serverscom/srvctl/cmd/entities/cloud-volumes" + "github.com/serverscom/srvctl/cmd/entities/dns" "github.com/serverscom/srvctl/cmd/entities/drivemodels" "github.com/serverscom/srvctl/cmd/entities/hosts" "github.com/serverscom/srvctl/cmd/entities/invoices" @@ -110,6 +111,7 @@ func NewRootCmd(version string) *cobra.Command { cloudbackups.NewCmd(cmdContext), rbsvolumes.NewCmd(cmdContext), metrics.NewCmd(cmdContext), + dns.NewCmd(cmdContext), ) cmd.SetHelpCommandGroupID(groupOther) diff --git a/internal/mocks/dns_service.go b/internal/mocks/dns_service.go new file mode 100644 index 0000000..2f60b12 --- /dev/null +++ b/internal/mocks/dns_service.go @@ -0,0 +1,203 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./vendor/github.com/serverscom/serverscom-go-client/pkg/dns.go +// +// Generated by this command: +// +// mockgen --destination ./internal/mocks/dns_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/dns.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" +) + +// MockDNSService is a mock of DNSService interface. +type MockDNSService struct { + ctrl *gomock.Controller + recorder *MockDNSServiceMockRecorder + isgomock struct{} +} + +// MockDNSServiceMockRecorder is the mock recorder for MockDNSService. +type MockDNSServiceMockRecorder struct { + mock *MockDNSService +} + +// NewMockDNSService creates a new mock instance. +func NewMockDNSService(ctrl *gomock.Controller) *MockDNSService { + mock := &MockDNSService{ctrl: ctrl} + mock.recorder = &MockDNSServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDNSService) EXPECT() *MockDNSServiceMockRecorder { + return m.recorder +} + +// Collection mocks base method. +func (m *MockDNSService) Collection() serverscom.Collection[serverscom.DNSDomain] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Collection") + ret0, _ := ret[0].(serverscom.Collection[serverscom.DNSDomain]) + return ret0 +} + +// Collection indicates an expected call of Collection. +func (mr *MockDNSServiceMockRecorder) Collection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collection", reflect.TypeOf((*MockDNSService)(nil).Collection)) +} + +// CreateDomain mocks base method. +func (m *MockDNSService) CreateDomain(ctx context.Context, input serverscom.DNSDomainCreateInput) (*serverscom.DNSDomain, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateDomain", ctx, input) + ret0, _ := ret[0].(*serverscom.DNSDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateDomain indicates an expected call of CreateDomain. +func (mr *MockDNSServiceMockRecorder) CreateDomain(ctx, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDomain", reflect.TypeOf((*MockDNSService)(nil).CreateDomain), ctx, input) +} + +// CreateRecord mocks base method. +func (m *MockDNSService) CreateRecord(ctx context.Context, domainID string, input serverscom.DNSRecordCreateInput) (*serverscom.DNSRecord, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRecord", ctx, domainID, input) + ret0, _ := ret[0].(*serverscom.DNSRecord) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateRecord indicates an expected call of CreateRecord. +func (mr *MockDNSServiceMockRecorder) CreateRecord(ctx, domainID, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRecord", reflect.TypeOf((*MockDNSService)(nil).CreateRecord), ctx, domainID, input) +} + +// DeleteDomain mocks base method. +func (m *MockDNSService) DeleteDomain(ctx context.Context, id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDomain", ctx, id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteDomain indicates an expected call of DeleteDomain. +func (mr *MockDNSServiceMockRecorder) DeleteDomain(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDomain", reflect.TypeOf((*MockDNSService)(nil).DeleteDomain), ctx, id) +} + +// DeleteRecord mocks base method. +func (m *MockDNSService) DeleteRecord(ctx context.Context, domainID, recordID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRecord", ctx, domainID, recordID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRecord indicates an expected call of DeleteRecord. +func (mr *MockDNSServiceMockRecorder) DeleteRecord(ctx, domainID, recordID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRecord", reflect.TypeOf((*MockDNSService)(nil).DeleteRecord), ctx, domainID, recordID) +} + +// GetDelegationData mocks base method. +func (m *MockDNSService) GetDelegationData(ctx context.Context) (*serverscom.DNSDomainDelegationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDelegationData", ctx) + ret0, _ := ret[0].(*serverscom.DNSDomainDelegationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDelegationData indicates an expected call of GetDelegationData. +func (mr *MockDNSServiceMockRecorder) GetDelegationData(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegationData", reflect.TypeOf((*MockDNSService)(nil).GetDelegationData), ctx) +} + +// GetDomain mocks base method. +func (m *MockDNSService) GetDomain(ctx context.Context, id string) (*serverscom.DNSDomain, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDomain", ctx, id) + ret0, _ := ret[0].(*serverscom.DNSDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDomain indicates an expected call of GetDomain. +func (mr *MockDNSServiceMockRecorder) GetDomain(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDomain", reflect.TypeOf((*MockDNSService)(nil).GetDomain), ctx, id) +} + +// GetRecord mocks base method. +func (m *MockDNSService) GetRecord(ctx context.Context, domainID, recordID string) (*serverscom.DNSRecord, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRecord", ctx, domainID, recordID) + ret0, _ := ret[0].(*serverscom.DNSRecord) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRecord indicates an expected call of GetRecord. +func (mr *MockDNSServiceMockRecorder) GetRecord(ctx, domainID, recordID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRecord", reflect.TypeOf((*MockDNSService)(nil).GetRecord), ctx, domainID, recordID) +} + +// Records mocks base method. +func (m *MockDNSService) Records(domainID string) serverscom.Collection[serverscom.DNSRecord] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Records", domainID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.DNSRecord]) + return ret0 +} + +// Records indicates an expected call of Records. +func (mr *MockDNSServiceMockRecorder) Records(domainID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockDNSService)(nil).Records), domainID) +} + +// UpdateDomain mocks base method. +func (m *MockDNSService) UpdateDomain(ctx context.Context, id string, input serverscom.DNSDomainUpdateInput) (*serverscom.DNSDomain, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDomain", ctx, id, input) + ret0, _ := ret[0].(*serverscom.DNSDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateDomain indicates an expected call of UpdateDomain. +func (mr *MockDNSServiceMockRecorder) UpdateDomain(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDomain", reflect.TypeOf((*MockDNSService)(nil).UpdateDomain), ctx, id, input) +} + +// UpdateRecord mocks base method. +func (m *MockDNSService) UpdateRecord(ctx context.Context, domainID, recordID string, input serverscom.DNSRecordUpdateInput) (*serverscom.DNSRecord, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRecord", ctx, domainID, recordID, input) + ret0, _ := ret[0].(*serverscom.DNSRecord) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateRecord indicates an expected call of UpdateRecord. +func (mr *MockDNSServiceMockRecorder) UpdateRecord(ctx, domainID, recordID, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRecord", reflect.TypeOf((*MockDNSService)(nil).UpdateRecord), ctx, domainID, recordID, input) +} diff --git a/internal/output/entities/dns.go b/internal/output/entities/dns.go new file mode 100644 index 0000000..3b1e0d7 --- /dev/null +++ b/internal/output/entities/dns.go @@ -0,0 +1,71 @@ +package entities + +import ( + "log" + "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" +) + +var ( + DNSDomainType = reflect.TypeFor[serverscom.DNSDomain]() + DNSRecordType = reflect.TypeFor[serverscom.DNSRecord]() + DNSDelegationDataType = reflect.TypeFor[serverscom.DNSDomainDelegationData]() + DNSDomainListDefaultFields = []string{"ID", "Name", "DelegationStatus", "TTL"} + DNSRecordListDefaultFields = []string{"ID", "Type", "Name", "Data", "TTL", "Priority"} +) + +func RegisterDNSDefinitions() { + dnsDomainEntity := &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: "Email", Name: "Email", Path: "Email", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "TTL", Name: "TTL", Path: "TTL", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "DelegationStatus", Name: "DelegationStatus", Path: "DelegationStatus", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Labels", Name: "Labels", Path: "Labels", PageViewHandlerFunc: mapPvHandler}, + {ID: "UnpublishDate", Name: "UnpublishDate", Path: "UnpublishDate", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler}, + {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list": DNSDomainListDefaultFields, + }, + eType: DNSDomainType, + } + if err := Registry.Register(dnsDomainEntity); err != nil { + log.Fatal(err) + } + + dnsRecordEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "DomainID", Name: "DomainID", Path: "DomainID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Data", Name: "Data", Path: "Data", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "TTL", Name: "TTL", Path: "TTL", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Priority", Name: "Priority", Path: "Priority", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list-records": DNSRecordListDefaultFields, + }, + eType: DNSRecordType, + } + if err := Registry.Register(dnsRecordEntity); err != nil { + log.Fatal(err) + } + + dnsDelegationDataEntity := &Entity{ + fields: []Field{ + {ID: "Nameservers", Name: "Nameservers", Path: "Nameservers", ListHandlerFunc: stringHandler, PageViewHandlerFunc: slicePvHandler, Default: true}, + {ID: "RequiredTxt", Name: "RequiredTxt", Path: "RequiredTxt", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: DNSDelegationDataType, + } + if err := Registry.Register(dnsDelegationDataEntity); err != nil { + log.Fatal(err) + } +} diff --git a/internal/output/entities/init.go b/internal/output/entities/init.go index c82fa62..1420b24 100644 --- a/internal/output/entities/init.go +++ b/internal/output/entities/init.go @@ -40,4 +40,5 @@ func init() { RegisterRbsVolumeCredentialsDefinition() RegisterHostMetricDefinition() RegisterRackMetricDefinition() + RegisterDNSDefinitions() } diff --git a/internal/output/skeletons/templates/dns/add-record.json b/internal/output/skeletons/templates/dns/add-record.json new file mode 100644 index 0000000..cbbc203 --- /dev/null +++ b/internal/output/skeletons/templates/dns/add-record.json @@ -0,0 +1,7 @@ +{ + "type": "", + "name": "", + "data": "", + "ttl": 0, + "priority": 0 +} diff --git a/internal/output/skeletons/templates/dns/add.json b/internal/output/skeletons/templates/dns/add.json new file mode 100644 index 0000000..16c7201 --- /dev/null +++ b/internal/output/skeletons/templates/dns/add.json @@ -0,0 +1,8 @@ +{ + "name": "", + "email": "", + "ttl": 0, + "labels": { + "key": "value" + } +} diff --git a/testdata/entities/dns/create.json b/testdata/entities/dns/create.json new file mode 100644 index 0000000..369bac5 --- /dev/null +++ b/testdata/entities/dns/create.json @@ -0,0 +1,8 @@ +{ + "name": "example.com", + "email": "admin@example.com", + "ttl": 3600, + "labels": { + "foo": "bar" + } +} diff --git a/testdata/entities/dns/create_record.json b/testdata/entities/dns/create_record.json new file mode 100644 index 0000000..df1e672 --- /dev/null +++ b/testdata/entities/dns/create_record.json @@ -0,0 +1,6 @@ +{ + "type": "A", + "name": "www", + "data": "1.2.3.4", + "ttl": 3600 +} diff --git a/testdata/entities/dns/delegation_data.json b/testdata/entities/dns/delegation_data.json new file mode 100644 index 0000000..34f974b --- /dev/null +++ b/testdata/entities/dns/delegation_data.json @@ -0,0 +1,7 @@ +{ + "nameservers": [ + "ns1.example.com", + "ns2.example.com" + ], + "required_txt": "sc-verification=abc123" +} diff --git a/testdata/entities/dns/delegation_data.txt b/testdata/entities/dns/delegation_data.txt new file mode 100644 index 0000000..b1c55ba --- /dev/null +++ b/testdata/entities/dns/delegation_data.txt @@ -0,0 +1,2 @@ +Nameservers RequiredTxt +[ns1.example.com ns2.example.com] sc-verification=abc123 diff --git a/testdata/entities/dns/delegation_data.yaml b/testdata/entities/dns/delegation_data.yaml new file mode 100644 index 0000000..cb530b2 --- /dev/null +++ b/testdata/entities/dns/delegation_data.yaml @@ -0,0 +1,4 @@ +nameservers: + - ns1.example.com + - ns2.example.com +requiredtxt: sc-verification=abc123 diff --git a/testdata/entities/dns/get.json b/testdata/entities/dns/get.json new file mode 100644 index 0000000..d5d2bc4 --- /dev/null +++ b/testdata/entities/dns/get.json @@ -0,0 +1,13 @@ +{ + "id": "abc123", + "name": "example.com", + "email": "admin@example.com", + "ttl": 3600, + "delegation_status": "verified", + "labels": { + "foo": "bar" + }, + "unpublish_date": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} diff --git a/testdata/entities/dns/get.txt b/testdata/entities/dns/get.txt new file mode 100644 index 0000000..87b4f77 --- /dev/null +++ b/testdata/entities/dns/get.txt @@ -0,0 +1,2 @@ +ID Name Email TTL DelegationStatus Created Updated +abc123 example.com admin@example.com 3600 verified 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/dns/get.yaml b/testdata/entities/dns/get.yaml new file mode 100644 index 0000000..e64600d --- /dev/null +++ b/testdata/entities/dns/get.yaml @@ -0,0 +1,10 @@ +id: abc123 +name: example.com +email: admin@example.com +ttl: 3600 +delegationstatus: verified +labels: + foo: bar +unpublishdate: null +created: 2025-01-01T12:00:00Z +updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/dns/get_record.json b/testdata/entities/dns/get_record.json new file mode 100644 index 0000000..10e7c37 --- /dev/null +++ b/testdata/entities/dns/get_record.json @@ -0,0 +1,11 @@ +{ + "id": "rec1", + "domain_id": "abc123", + "name": "www", + "type": "A", + "data": "1.2.3.4", + "ttl": 3600, + "priority": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} diff --git a/testdata/entities/dns/get_record.txt b/testdata/entities/dns/get_record.txt new file mode 100644 index 0000000..3d0097e --- /dev/null +++ b/testdata/entities/dns/get_record.txt @@ -0,0 +1,2 @@ +ID DomainID Type Name Data TTL Priority Created Updated +rec1 abc123 A www 1.2.3.4 3600 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/dns/get_record.yaml b/testdata/entities/dns/get_record.yaml new file mode 100644 index 0000000..c0d8082 --- /dev/null +++ b/testdata/entities/dns/get_record.yaml @@ -0,0 +1,9 @@ +id: rec1 +domainid: abc123 +name: www +type: A +data: 1.2.3.4 +ttl: 3600 +priority: null +created: 2025-01-01T12:00:00Z +updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/dns/list.json b/testdata/entities/dns/list.json new file mode 100644 index 0000000..dba936f --- /dev/null +++ b/testdata/entities/dns/list.json @@ -0,0 +1,15 @@ +[ + { + "id": "abc123", + "name": "example.com", + "email": "admin@example.com", + "ttl": 3600, + "delegation_status": "verified", + "labels": { + "foo": "bar" + }, + "unpublish_date": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] diff --git a/testdata/entities/dns/list_all.json b/testdata/entities/dns/list_all.json new file mode 100644 index 0000000..72bfaeb --- /dev/null +++ b/testdata/entities/dns/list_all.json @@ -0,0 +1,28 @@ +[ + { + "id": "abc123", + "name": "example.com", + "email": "admin@example.com", + "ttl": 3600, + "delegation_status": "verified", + "labels": { + "foo": "bar" + }, + "unpublish_date": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + }, + { + "id": "def456", + "name": "example2.com", + "email": "admin@example.com", + "ttl": 3600, + "delegation_status": "verified", + "labels": { + "foo": "bar" + }, + "unpublish_date": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] diff --git a/testdata/entities/dns/list_pageview.txt b/testdata/entities/dns/list_pageview.txt new file mode 100644 index 0000000..1336931 --- /dev/null +++ b/testdata/entities/dns/list_pageview.txt @@ -0,0 +1,19 @@ +ID: abc123 +Name: example.com +Email: admin@example.com +TTL: 3600 +DelegationStatus: verified +Labels: foo=bar +UnpublishDate: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z +--- +ID: def456 +Name: example2.com +Email: admin@example.com +TTL: 3600 +DelegationStatus: verified +Labels: foo=bar +UnpublishDate: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/dns/list_records.json b/testdata/entities/dns/list_records.json new file mode 100644 index 0000000..083496a --- /dev/null +++ b/testdata/entities/dns/list_records.json @@ -0,0 +1,13 @@ +[ + { + "id": "rec1", + "domain_id": "abc123", + "name": "www", + "type": "A", + "data": "1.2.3.4", + "ttl": 3600, + "priority": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] diff --git a/testdata/entities/dns/list_records_all.json b/testdata/entities/dns/list_records_all.json new file mode 100644 index 0000000..997e293 --- /dev/null +++ b/testdata/entities/dns/list_records_all.json @@ -0,0 +1,24 @@ +[ + { + "id": "rec1", + "domain_id": "abc123", + "name": "www", + "type": "A", + "data": "1.2.3.4", + "ttl": 3600, + "priority": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + }, + { + "id": "rec2", + "domain_id": "abc123", + "name": "mail", + "type": "MX", + "data": "mail.example.com", + "ttl": 3600, + "priority": 10, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] diff --git a/testdata/entities/dns/list_records_pageview.txt b/testdata/entities/dns/list_records_pageview.txt new file mode 100644 index 0000000..2f6ad08 --- /dev/null +++ b/testdata/entities/dns/list_records_pageview.txt @@ -0,0 +1,19 @@ +ID: rec1 +DomainID: abc123 +Type: A +Name: www +Data: 1.2.3.4 +TTL: 3600 +Priority: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z +--- +ID: rec2 +DomainID: abc123 +Type: MX +Name: mail +Data: mail.example.com +TTL: 3600 +Priority: 10 +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/dns/list_records_template.txt b/testdata/entities/dns/list_records_template.txt new file mode 100644 index 0000000..f902a9b --- /dev/null +++ b/testdata/entities/dns/list_records_template.txt @@ -0,0 +1,2 @@ +Name: www +Name: mail diff --git a/testdata/entities/dns/list_template.txt b/testdata/entities/dns/list_template.txt new file mode 100644 index 0000000..6f5254e --- /dev/null +++ b/testdata/entities/dns/list_template.txt @@ -0,0 +1,2 @@ +Name: example.com +Name: example2.com diff --git a/testdata/entities/dns/update.json b/testdata/entities/dns/update.json new file mode 100644 index 0000000..4b0d233 --- /dev/null +++ b/testdata/entities/dns/update.json @@ -0,0 +1,13 @@ +{ + "id": "abc123", + "name": "example.com", + "email": "admin@example.com", + "ttl": 3600, + "delegation_status": "verified", + "labels": { + "new": "label" + }, + "unpublish_date": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} diff --git a/testdata/entities/dns/update_record.json b/testdata/entities/dns/update_record.json new file mode 100644 index 0000000..fad9c93 --- /dev/null +++ b/testdata/entities/dns/update_record.json @@ -0,0 +1,11 @@ +{ + "id": "rec1", + "domain_id": "abc123", + "name": "www", + "type": "A", + "data": "5.6.7.8", + "ttl": 7200, + "priority": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +}