Skip to content

Commit 76606ad

Browse files
GokceGKDiogoFerrao
andauthored
Onboard dns zone clone command (#419)
* onboard dns zone clone command * Update examples Co-authored-by: Diogo Ferrão <[email protected]> --------- Co-authored-by: Diogo Ferrão <[email protected]>
1 parent 37584fc commit 76606ad

File tree

5 files changed

+507
-0
lines changed

5 files changed

+507
-0
lines changed

docs/stackit_dns_zone.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ stackit dns zone [flags]
2929
### SEE ALSO
3030

3131
* [stackit dns](./stackit_dns.md) - Provides functionality for DNS
32+
* [stackit dns zone clone](./stackit_dns_zone_clone.md) - Clones a DNS zone
3233
* [stackit dns zone create](./stackit_dns_zone_create.md) - Creates a DNS zone
3334
* [stackit dns zone delete](./stackit_dns_zone_delete.md) - Deletes a DNS zone
3435
* [stackit dns zone describe](./stackit_dns_zone_describe.md) - Shows details of a DNS zone

docs/stackit_dns_zone_clone.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
## stackit dns zone clone
2+
3+
Clones a DNS zone
4+
5+
### Synopsis
6+
7+
Clones an existing DNS zone with all record sets to a new zone with a different name.
8+
9+
```
10+
stackit dns zone clone [flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
Clones a DNS zone with ID "xxx" to a new zone with DNS name "www.my-zone.com"
17+
$ stackit dns zone clone xxx --dns-name www.my-zone.com
18+
19+
Clones a DNS zone with ID "xxx" to a new zone with DNS name "www.my-zone.com" and adjust records "true"
20+
$ stackit dns zone clone xxx --dns-name www.my-zone.com --adjust-records
21+
22+
Clones a DNS zone with ID "xxx" to a new zone with DNS name "www.my-zone.com" and display name "new-zone"
23+
$ stackit dns zone clone xxx --dns-name www.my-zone.com --name new-zone
24+
```
25+
26+
### Options
27+
28+
```
29+
--adjust-records Sets content and replaces the DNS name of the original zone with the new DNS name of the cloned zone
30+
--description string New description for the cloned zone
31+
--dns-name string Fully qualified domain name of the new DNS zone to clone
32+
-h, --help Help for "stackit dns zone clone"
33+
--name string User given new name for the cloned zone
34+
```
35+
36+
### Options inherited from parent commands
37+
38+
```
39+
-y, --assume-yes If set, skips all confirmation prompts
40+
--async If set, runs the command asynchronously
41+
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
42+
-p, --project-id string Project ID
43+
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
44+
```
45+
46+
### SEE ALSO
47+
48+
* [stackit dns zone](./stackit_dns_zone.md) - Provides functionality for DNS zones
49+

internal/cmd/dns/zone/clone/clone.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package clone
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/goccy/go-yaml"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client"
16+
dnsUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/utils"
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
19+
20+
"github.com/spf13/cobra"
21+
"github.com/stackitcloud/stackit-sdk-go/services/dns"
22+
"github.com/stackitcloud/stackit-sdk-go/services/dns/wait"
23+
)
24+
25+
const (
26+
nameFlag = "name"
27+
dnsNameFlag = "dns-name"
28+
descriptionFlag = "description"
29+
adjustRecordsFlag = "adjust-records"
30+
zoneIdArg = "ZONE_ID"
31+
)
32+
33+
type inputModel struct {
34+
*globalflags.GlobalFlagModel
35+
Name *string
36+
DnsName *string
37+
Description *string
38+
AdjustRecords *bool
39+
ZoneId string
40+
}
41+
42+
func NewCmd(p *print.Printer) *cobra.Command {
43+
cmd := &cobra.Command{
44+
Use: "clone",
45+
Short: "Clones a DNS zone",
46+
Long: "Clones an existing DNS zone with all record sets to a new zone with a different name.",
47+
Args: args.SingleArg(zoneIdArg, utils.ValidateUUID),
48+
Example: examples.Build(
49+
examples.NewExample(
50+
`Clones a DNS zone with ID "xxx" to a new zone with DNS name "www.my-zone.com"`,
51+
"$ stackit dns zone clone xxx --dns-name www.my-zone.com"),
52+
examples.NewExample(
53+
`Clones a DNS zone with ID "xxx" to a new zone with DNS name "www.my-zone.com" and display name "new-zone"`,
54+
"$ stackit dns zone clone xxx --dns-name www.my-zone.com --name new-zone"),
55+
examples.NewExample(
56+
`Clones a DNS zone with ID "xxx" to a new zone with DNS name "www.my-zone.com" and adjust records "true"`,
57+
"$ stackit dns zone clone xxx --dns-name www.my-zone.com --adjust-records"),
58+
),
59+
RunE: func(cmd *cobra.Command, args []string) error {
60+
ctx := context.Background()
61+
model, err := parseInput(p, cmd, args)
62+
if err != nil {
63+
return err
64+
}
65+
66+
// Configure API client
67+
apiClient, err := client.ConfigureClient(p)
68+
if err != nil {
69+
return err
70+
}
71+
72+
zoneLabel, err := dnsUtils.GetZoneName(ctx, apiClient, model.ProjectId, model.ZoneId)
73+
if err != nil {
74+
p.Debug(print.ErrorLevel, "get zone name: %v", err)
75+
zoneLabel = model.ZoneId
76+
}
77+
78+
if !model.AssumeYes {
79+
prompt := fmt.Sprintf("Are you sure you want to clone the zone %s?", zoneLabel)
80+
err = p.PromptForConfirmation(prompt)
81+
if err != nil {
82+
return err
83+
}
84+
}
85+
86+
// Call API
87+
req := buildRequest(ctx, model, apiClient)
88+
resp, err := req.Execute()
89+
if err != nil {
90+
return fmt.Errorf("clone DNS zone: %w", err)
91+
}
92+
zoneId := *resp.Zone.Id
93+
94+
// Wait for async operation, if async mode not enabled
95+
if !model.Async {
96+
s := spinner.New(p)
97+
s.Start("Cloning zone")
98+
_, err = wait.CreateZoneWaitHandler(ctx, apiClient, model.ProjectId, zoneId).WaitWithContext(ctx)
99+
if err != nil {
100+
return fmt.Errorf("wait for DNS zone cloning: %w", err)
101+
}
102+
s.Stop()
103+
}
104+
105+
return outputResult(p, model, zoneLabel, resp)
106+
},
107+
}
108+
configureFlags(cmd)
109+
return cmd
110+
}
111+
112+
func configureFlags(cmd *cobra.Command) {
113+
cmd.Flags().String(nameFlag, "", "User given new name for the cloned zone")
114+
cmd.Flags().String(dnsNameFlag, "", "Fully qualified domain name of the new DNS zone to clone")
115+
cmd.Flags().String(descriptionFlag, "", "New description for the cloned zone")
116+
cmd.Flags().Bool(adjustRecordsFlag, false, "Sets content and replaces the DNS name of the original zone with the new DNS name of the cloned zone")
117+
118+
err := flags.MarkFlagsRequired(cmd, dnsNameFlag)
119+
cobra.CheckErr(err)
120+
}
121+
122+
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
123+
zoneId := inputArgs[0]
124+
125+
globalFlags := globalflags.Parse(p, cmd)
126+
if globalFlags.ProjectId == "" {
127+
return nil, &errors.ProjectIdError{}
128+
}
129+
130+
model := inputModel{
131+
GlobalFlagModel: globalFlags,
132+
Name: flags.FlagToStringPointer(p, cmd, nameFlag),
133+
DnsName: flags.FlagToStringPointer(p, cmd, dnsNameFlag),
134+
Description: flags.FlagToStringPointer(p, cmd, descriptionFlag),
135+
AdjustRecords: flags.FlagToBoolPointer(p, cmd, adjustRecordsFlag),
136+
ZoneId: zoneId,
137+
}
138+
139+
if p.IsVerbosityDebug() {
140+
modelStr, err := print.BuildDebugStrFromInputModel(model)
141+
if err != nil {
142+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
143+
} else {
144+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
145+
}
146+
}
147+
148+
return &model, nil
149+
}
150+
151+
func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClient) dns.ApiCloneZoneRequest {
152+
req := apiClient.CloneZone(ctx, model.ProjectId, model.ZoneId)
153+
req = req.CloneZonePayload(dns.CloneZonePayload{
154+
Name: model.Name,
155+
DnsName: model.DnsName,
156+
Description: model.Description,
157+
AdjustRecords: model.AdjustRecords,
158+
})
159+
return req
160+
}
161+
162+
func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *dns.ZoneResponse) error {
163+
switch model.OutputFormat {
164+
case print.JSONOutputFormat:
165+
details, err := json.MarshalIndent(resp, "", " ")
166+
if err != nil {
167+
return fmt.Errorf("marshal DNS zone: %w", err)
168+
}
169+
p.Outputln(string(details))
170+
171+
return nil
172+
case print.YAMLOutputFormat:
173+
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true))
174+
if err != nil {
175+
return fmt.Errorf("marshal DNS zone: %w", err)
176+
}
177+
p.Outputln(string(details))
178+
179+
return nil
180+
default:
181+
operationState := "Cloned"
182+
if model.Async {
183+
operationState = "Triggered cloning of"
184+
}
185+
p.Outputf("%s zone for project %q. Zone ID: %s\n", operationState, projectLabel, *resp.Zone.Id)
186+
return nil
187+
}
188+
}

0 commit comments

Comments
 (0)