Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/stackit_dns.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ stackit dns [flags]
### SEE ALSO

* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit dns lookup](./stackit_dns_lookup.md) - Looks up a domain on the STACKIT DNS resolvers
* [stackit dns record-set](./stackit_dns_record-set.md) - Provides functionality for DNS record set
* [stackit dns zone](./stackit_dns_zone.md) - Provides functionality for DNS zones

49 changes: 49 additions & 0 deletions docs/stackit_dns_lookup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## stackit dns lookup

Looks up a domain on the STACKIT DNS resolvers

### Synopsis

Looks up a domain on the STACKIT DNS resolvers.

```
stackit dns lookup DOMAIN [flags]
```

### Examples

```
Look up domain "www.example.com"
$ stackit dns lookup www.example.com

Look up the AAAA records of domain "www.example.com" on all STACKIT DNS resolvers
$ stackit dns lookup www.example.com --record-type AAAA --all-resolvers

Look up domain "www.example.com" including the raw DNS response in JSON format
$ stackit dns lookup www.example.com --include-raw-response --output-format json
```

### Options

```
--all-resolvers[=true|false] If set, all STACKIT DNS resolvers are queried instead of a single one
-h, --help[=true|false] Help for "stackit dns lookup"
--include-raw-response[=true|false] If set, the raw DNS response is included in the output. Only shown in JSON and YAML output
--record-type string Record type to look up. If not set, defaults to A (one of: [A, AAAA, SOA, CNAME, NS, MX, TXT, SRV, PTR, ALIAS, DNAME, CAA, DNSKEY, DS, LOC, NAPTR, SSHFP, TLSA, URI, CERT, SVCB, TYPE, CSYNC, HINFO, HTTPS, ANY])
```

### Options inherited from parent commands

```
-y, --assume-yes[=true|false] If set, skips all confirmation prompts
--async[=true|false] If set, runs the command asynchronously
-o, --output-format string Output format, (one of: [json, pretty, none, yaml])
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, (one of: [debug, info, warning, error]) (default "info")
```

### SEE ALSO

* [stackit dns](./stackit_dns.md) - Provides functionality for DNS

2 changes: 2 additions & 0 deletions internal/cmd/dns/dns.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dns

import (
"github.com/stackitcloud/stackit-cli/internal/cmd/dns/lookup"
recordset "github.com/stackitcloud/stackit-cli/internal/cmd/dns/record-set"
"github.com/stackitcloud/stackit-cli/internal/cmd/dns/zone"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
Expand All @@ -25,4 +26,5 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
cmd.AddCommand(zone.NewCmd(params))
cmd.AddCommand(recordset.NewCmd(params))
cmd.AddCommand(lookup.NewCmd(params))
}
172 changes: 172 additions & 0 deletions internal/cmd/dns/lookup/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package lookup

import (
"context"
"fmt"

"github.com/stackitcloud/stackit-cli/internal/pkg/types"

"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"

"github.com/spf13/cobra"
dns "github.com/stackitcloud/stackit-sdk-go/services/dns/v1api"
)

const (
domainArg = "DOMAIN"

allResolversFlag = "all-resolvers"
includeRawResponseFlag = "include-raw-response"
)

var recordTypeFlag = flags.StringEnumFlag(
"record-type",
dns.AllowedLookupDomainPayloadRecordTypeEnumValues,
"Record type to look up. If not set, defaults to A",
)

type inputModel struct {
*globalflags.GlobalFlagModel
Domain string
RecordType *dns.LookupDomainPayloadRecordType
AllResolvers bool
IncludeRawResponse bool
}

func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("lookup %s", domainArg),
Short: "Looks up a domain on the STACKIT DNS resolvers",
Long: "Looks up a domain on the STACKIT DNS resolvers.",
Args: args.SingleArg(domainArg, nil),
Example: examples.Build(
examples.NewExample(
`Look up domain "www.example.com"`,
"$ stackit dns lookup www.example.com"),
examples.NewExample(
`Look up the AAAA records of domain "www.example.com" on all STACKIT DNS resolvers`,
"$ stackit dns lookup www.example.com --record-type AAAA --all-resolvers"),
examples.NewExample(
`Look up domain "www.example.com" including the raw DNS response in JSON format`,
"$ stackit dns lookup www.example.com --include-raw-response --output-format json"),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}

// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}

// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("look up DNS domain: %w", err)
}

return outputResult(params.Printer, model, resp)
},
}
configureFlags(cmd)
return cmd
}

func configureFlags(cmd *cobra.Command) {
recordTypeFlag.Register(cmd.Flags())
cmd.Flags().Bool(allResolversFlag, false, "If set, all STACKIT DNS resolvers are queried instead of a single one")
cmd.Flags().Bool(includeRawResponseFlag, false, "If set, the raw DNS response is included in the output. Only shown in JSON and YAML output")
}

func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
domain := inputArgs[0]
globalFlags := globalflags.Parse(p, cmd)

model := inputModel{
GlobalFlagModel: globalFlags,
Domain: domain,
RecordType: recordTypeFlag.Ptr(),
AllResolvers: flags.FlagToBoolValue(p, cmd, allResolversFlag),
IncludeRawResponse: flags.FlagToBoolValue(p, cmd, includeRawResponseFlag),
}

if model.IncludeRawResponse && model.OutputFormat != print.JSONOutputFormat && model.OutputFormat != print.YAMLOutputFormat {
p.Warn("--%s is only shown in JSON or YAML output, set --output-format json or --output-format yaml to see the raw DNS response\n", includeRawResponseFlag)
}

p.DebugInputModel(model)
return &model, nil
}

func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClient) dns.ApiLookupDomainRequest {
payload := dns.LookupDomainPayload{
Address: utils.Ptr(model.Domain),
RecordType: model.RecordType,
}
if model.AllResolvers {
payload.Type = utils.Ptr(dns.LOOKUPDOMAINPAYLOADTYPE_ALL)
}
if model.IncludeRawResponse {
payload.IncludeRawResponse = utils.Ptr(true)
}
if model.Region != "" {
payload.Region = utils.Ptr(model.Region)
}

req := apiClient.DefaultAPI.LookupDomain(ctx)
req = req.LookupDomainPayload(payload)
return req
}

func outputResult(p *print.Printer, model *inputModel, resp *dns.LookupDomainResponse) error {
return p.OutputResult(model.OutputFormat, resp, func() error {
if resp == nil {
return fmt.Errorf("look up DNS domain response is empty")
}
table := tables.NewTable()
table.SetHeader("RESOLVER IP", "NAME", "TYPE", "TTL", "DATA")
rows := 0
for _, result := range resp.State {
// When querying all resolvers, also list the ones without records,
// so it's visible which resolvers answered
if model.AllResolvers && len(result.Records) == 0 {
table.AddRow(utils.PtrString(result.ResolverIP), "", "", "", "No records found")
rows++
continue
}
for _, record := range result.Records {
table.AddRow(
utils.PtrString(result.ResolverIP),
utils.PtrString(record.Name),
utils.PtrString(record.Type),
utils.PtrString(record.Ttl),
utils.PtrString(record.Data),
)
rows++
}
}
if rows == 0 {
p.Outputf("No records found for domain %q\n", model.Domain)
return nil
}

table.EnableAutoMergeOnColumns(1)
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
})
}
Loading
Loading