Skip to content

Commit

Permalink
feat: reverse dns (#448)
Browse files Browse the repository at this point in the history
* dep: update dns sdk

* feat: add quota

* feat: add dnsec

* feat: dnssec

* fix: cols for creation

* doc: regen docs

* fix: wiki dns link

* feat: add reverse records

* list reverse records

* create reverse records

* ip completions

* delete

* doc: regen docs:

* fix: enabled correct jsonpath

* feat: update

* feat: reverse record get

* doc: regen docs

* doc: update changelog
  • Loading branch information
avirtopeanu-ionos committed Jun 27, 2024
1 parent f5d987f commit cd79ec3
Show file tree
Hide file tree
Showing 18 changed files with 818 additions and 6 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

### Added
- Added support for DNS resources:
- `ionosctl dns keys` commands which allows you to enable/disable DNSSEC and manage DNSKEY records
- `ionosctl dns quota` commands which allows you to get the DNS quota for your account
- `ionosctl dns keys` commands which allows you to enable/disable DNSSEC and manage DNSKEY records.
- `ionosctl dns quota` commands which allows you to get the DNS quota for your account.
- `ionosctl dns reverse-record` commands which allows you to manage reverse DNS records.

### Fixed
- Fixed the column path mapping for 'server' resource to display the actual server's type ('CUBE'/'ENTERPRISE'),
Expand Down
2 changes: 2 additions & 0 deletions commands/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/ionos-cloud/ionosctl/v6/commands/dns/dnssec"
"github.com/ionos-cloud/ionosctl/v6/commands/dns/quota"
"github.com/ionos-cloud/ionosctl/v6/commands/dns/record"
reverse_record "github.com/ionos-cloud/ionosctl/v6/commands/dns/reverse-record"
"github.com/ionos-cloud/ionosctl/v6/commands/dns/zone"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/spf13/cobra"
Expand All @@ -19,6 +20,7 @@ func DNSCommand() *core.Command {
}
cmd.AddCommand(zone.ZoneCommand())
cmd.AddCommand(record.RecordCommand())
cmd.AddCommand(reverse_record.Root())
cmd.AddCommand(quota.Root())
cmd.AddCommand(dnssec.Root())

Expand Down
2 changes: 1 addition & 1 deletion commands/dns/record/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func ZonesRecordsPostCmd() *core.Command {
Resource: "record",
Verb: "create",
Aliases: []string{"c", "post"},
ShortDesc: "Create a record. Wiki: https://docs.ionos.com/dns-as-a-service/readme/api-how-tos/create-a-new-dns-record",
ShortDesc: "Create a record. Wiki: https://docs.ionos.com/cloud/network-services/cloud-dns/api-how-tos/create-dns-record",
Example: "ionosctl dns r create --zone foo-bar.com --type A --content 1.2.3.4 --name \\*",
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlags(c.Command, c.NS, constants.FlagName, constants.FlagZone, constants.FlagContent, constants.FlagType); err != nil {
Expand Down
81 changes: 81 additions & 0 deletions commands/dns/reverse-record/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package reverse_record

import (
"context"
"fmt"

"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
"github.com/ionos-cloud/ionosctl/v6/pkg/pointer"
"github.com/ionos-cloud/ionosctl/v6/pkg/uuidgen"
dns "github.com/ionos-cloud/sdk-go-dns"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func Create() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "dns",
Resource: "reverse-record",
Verb: "create",
Aliases: []string{"c", "post"},
ShortDesc: "Create a record. Wiki: https://docs.ionos.com/cloud/network-services/cloud-dns/api-how-tos/create-and-manage-reverse-dns",
Example: "ionosctl dns rr create --name mail.example.com --ip 5.6.7.8",
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlags(c.Command, c.NS, constants.FlagName, constants.FlagIp); err != nil {
return err
}

return nil
},
CmdRun: func(c *core.CommandConfig) error {
rec, _, err := client.Must().DnsClient.ReverseRecordsApi.ReverserecordsPut(context.Background(), uuidgen.Must()).
ReverseRecordEnsure(dns.ReverseRecordEnsure{
Properties: &dns.ReverseRecord{
Name: pointer.From(viper.GetString(core.GetFlagName(c.NS, constants.FlagName))),
Ip: pointer.From(viper.GetString(core.GetFlagName(c.NS, constants.FlagIp))),
Description: pointer.From(viper.GetString(core.GetFlagName(c.NS, constants.FlagDescription))),
},
}).Execute()
if err != nil {
return err
}

cols, _ := c.Command.Command.Flags().GetStringSlice(constants.ArgCols)
out, err := jsontabwriter.GenerateOutput("", jsonpaths.DnsReverseRecord, rec,
tabheaders.GetHeadersAllDefault(allCols, cols))
if err != nil {
return err
}

fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
return nil
},
InitClient: true,
})

cmd.AddStringFlag(constants.FlagName, constants.FlagNameShort, "", "The name of the DNS Reverse Record.", core.RequiredFlagOption())
cmd.AddStringFlag(constants.FlagIp, "", "", "[IPv4/IPv6] Specifies for which IP address the reverse record should be created. The IP addresses needs to be owned by the contract", core.RequiredFlagOption())
cmd.Command.RegisterFlagCompletionFunc(constants.FlagIp, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ipblocks, _, err := client.Must().CloudClient.IPBlocksApi.IpblocksGet(context.Background()).Execute()
if err != nil || ipblocks.Items == nil || len(*ipblocks.Items) == 0 {
return nil, cobra.ShellCompDirectiveError
}
var ips []string
for _, ipblock := range *ipblocks.Items {
if ipblock.Properties.Ips != nil {
ips = append(ips, *ipblock.Properties.Ips...)
}
}
return ips, cobra.ShellCompDirectiveNoFileComp
})

cmd.AddStringFlag(constants.FlagDescription, "", "", "Description stored along with the reverse DNS record to describe its usage")
cmd.Command.SilenceUsage = true

return cmd
}
93 changes: 93 additions & 0 deletions commands/dns/reverse-record/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package reverse_record

import (
"context"
"fmt"

"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/pkg/confirm"
"github.com/ionos-cloud/ionosctl/v6/pkg/functional"
ionoscloud "github.com/ionos-cloud/sdk-go-dns"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func Delete() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "dns",
Resource: "reverse-record",
Verb: "delete",
Aliases: []string{"d", "del", "rm"},
ShortDesc: "Delete a record",
Example: "ionosctl dns rr delete -af\n" +
"ionosctl dns rr delete --record RECORD_IP\n" +
"ionosctl dns rr delete --record RECORD_ID",
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlagsSets(c.Command, c.NS,
[]string{constants.FlagRecord}, []string{constants.ArgAll}); err != nil {
return err
}

return nil
},
CmdRun: func(c *core.CommandConfig) error {
if all := viper.GetBool(core.GetFlagName(c.NS, constants.ArgAll)); all {
return deleteAll(c)
}

return deleteSingle(c, viper.GetString(core.GetFlagName(c.NS, constants.FlagRecord)))
},
InitClient: true,
})

cmd.AddStringFlag(constants.FlagRecord, "", "", "The record ID or IP which you want to delete", core.RequiredFlagOption())
// Completions: all current IPs
cmd.Command.RegisterFlagCompletionFunc(constants.FlagRecord, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ips := RecordsProperty(func(read ionoscloud.ReverseRecordRead) string {
return *read.Properties.Ip
})
return ips, cobra.ShellCompDirectiveNoFileComp
})
cmd.AddBoolFlag(constants.ArgAll, constants.ArgAllShort, false, "Delete all records if set", core.RequiredFlagOption())

cmd.Command.SilenceUsage = true
return cmd
}

func deleteAll(c *core.CommandConfig) error {
records, err := Records()
if err != nil {
return fmt.Errorf("failed getting all records: %w", err)
}

return functional.ApplyAndAggregateErrors(*records.GetItems(), func(r ionoscloud.ReverseRecordRead) error {
return deleteSingle(c, *r.Id)
})
}

func deleteSingle(c *core.CommandConfig, ipOrIdOfRecord string) error {
id, err := Resolve(ipOrIdOfRecord)
if err != nil {
return fmt.Errorf("can't resolve IP %s to a record ID: %s", ipOrIdOfRecord, err)
}

r, _, err := client.Must().DnsClient.ReverseRecordsApi.ReverserecordsFindById(context.Background(), id).Execute()
if err != nil {
return fmt.Errorf("failed querying for reverse record ID %s: %s", id, err)
}
yes := confirm.FAsk(c.Command.Command.InOrStdin(), fmt.Sprintf(
"Are you sure you want to delete record %s (IP: '%s'; description: '%s'; ID: '%s')",
*r.Properties.Name, *r.Properties.Ip, *r.Properties.Description, *r.Id),
viper.GetBool(constants.ArgForce))
if !yes {
return fmt.Errorf("user cancelled deletion")
}

_, _, err = client.Must().DnsClient.ReverseRecordsApi.ReverserecordsDelete(context.Background(),
*r.Id,
).Execute()

return err
}
69 changes: 69 additions & 0 deletions commands/dns/reverse-record/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package reverse_record

import (
"context"
"fmt"

"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
ionoscloud "github.com/ionos-cloud/sdk-go-dns"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func Get() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "dns",
Resource: "reverse-record",
Verb: "get",
Aliases: []string{"g"},
ShortDesc: "Find a record by IP or ID",
Example: "ionosctl dns rr get --record RECORD_IP\n" +
"ionosctl dns rr get --record RECORD_ID",
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlagsSets(c.Command, c.NS,
[]string{constants.FlagRecord}, []string{constants.ArgAll}); err != nil {
return err
}

return nil
},
CmdRun: func(c *core.CommandConfig) error {
id, err := Resolve(viper.GetString(core.GetFlagName(c.NS, constants.FlagRecord)))
if err != nil {
return fmt.Errorf("can't resolve IP to a record ID: %s", err)
}

rec, _, err := client.Must().DnsClient.ReverseRecordsApi.ReverserecordsFindById(context.Background(), id).Execute()
if err != nil {
return fmt.Errorf("failed querying for reverse record ID %s: %s", id, err)
}

cols, _ := c.Command.Command.Flags().GetStringSlice(constants.ArgCols)
out, err := jsontabwriter.GenerateOutput("", jsonpaths.DnsReverseRecord, rec,
tabheaders.GetHeadersAllDefault(allCols, cols))
if err != nil {
return err
}

fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
return nil
},
InitClient: true,
})

cmd.AddStringFlag(constants.FlagRecord, "", "", "The record ID or IP, for identifying which record you want to update", core.RequiredFlagOption())
cmd.Command.RegisterFlagCompletionFunc(constants.FlagRecord, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ips := RecordsProperty(func(read ionoscloud.ReverseRecordRead) string {
return *read.Properties.Ip
})
return ips, cobra.ShellCompDirectiveNoFileComp
})

cmd.Command.SilenceUsage = true
return cmd
}
59 changes: 59 additions & 0 deletions commands/dns/reverse-record/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package reverse_record

import (
"context"
"fmt"

"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
dns "github.com/ionos-cloud/sdk-go-dns"
"github.com/spf13/cobra"

"github.com/ionos-cloud/ionosctl/v6/internal/core"
)

func List() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "dns",
Resource: "record",
Verb: "list",
Aliases: []string{"ls", "l"},
ShortDesc: "Retrieve all reverse records",
Example: "ionosctl dns rr list",
CmdRun: func(c *core.CommandConfig) error {
ls, err := Records(FilterLimitOffset(c.NS), FilterRecordsByIp(c.NS))
if err != nil {
return fmt.Errorf("failed listing records: %w", err)
}

items, ok := ls.GetItemsOk()
if !ok || items == nil {
return fmt.Errorf("could not retrieve Record items")
}

cols, _ := c.Command.Command.Flags().GetStringSlice(constants.ArgCols)
out, err := jsontabwriter.GenerateOutput("items", jsonpaths.DnsReverseRecord,
ls, tabheaders.GetHeadersAllDefault(allCols, cols))
if err != nil {
return err
}

fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
return nil
},
InitClient: true,
})

cmd.AddStringFlag(constants.FlagIps, "i", "", "Optional filter for the IP address of the reverse record")
_ = cmd.Command.RegisterFlagCompletionFunc(constants.FlagZone, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return RecordsProperty(func(r dns.ReverseRecordRead) string {
return *r.Properties.Ip
}), cobra.ShellCompDirectiveNoFileComp
})
cmd.AddInt32Flag(constants.FlagOffset, "", 0, "The first element (of the total list of elements) to include in the response. Use together with limit for pagination")
cmd.AddInt32Flag(constants.FlagMaxResults, "", 0, constants.DescMaxResults)

return cmd
}
Loading

0 comments on commit cd79ec3

Please sign in to comment.