Skip to content

Commit 94c5eea

Browse files
committed
Merge upstream/narsupport into nonadmin-restore
2 parents b5a75f0 + 413812a commit 94c5eea

11 files changed

Lines changed: 1749 additions & 319 deletions

File tree

cmd/non-admin/backup/describe.go

Lines changed: 318 additions & 316 deletions
Large diffs are not rendered by default.

cmd/non-admin/nonadmin.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package nonadmin
1919
import (
2020
"github.com/migtools/oadp-cli/cmd/non-admin/backup"
2121
"github.com/migtools/oadp-cli/cmd/non-admin/bsl"
22+
"github.com/migtools/oadp-cli/cmd/non-admin/restore"
2223
"github.com/spf13/cobra"
2324
"github.com/vmware-tanzu/velero/pkg/client"
2425
)
@@ -28,13 +29,15 @@ func NewNonAdminCommand(f client.Factory) *cobra.Command {
2829
c := &cobra.Command{
2930
Use: "nonadmin",
3031
Short: "Work with non-admin resources",
31-
Long: "Work with non-admin resources like backups and backup storage locations",
32+
Long: "Work with non-admin resources like backups, restores, and backup storage locations",
3233
Aliases: []string{"na"},
3334
}
3435

3536
// Add backup subcommand
3637
c.AddCommand(backup.NewBackupCommand(f))
3738

39+
// Add restore subcommand
40+
c.AddCommand(restore.NewRestoreCommand(f))
3841
// Add backup storage location subcommand
3942
c.AddCommand(bsl.NewBSLCommand(f))
4043

cmd/non-admin/restore/create.go

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
package restore
2+
3+
/*
4+
Copyright The Velero Contributors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
"github.com/spf13/cobra"
25+
"github.com/spf13/pflag"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
"github.com/migtools/oadp-cli/cmd/shared"
30+
nacv1alpha1 "github.com/migtools/oadp-non-admin/api/v1alpha1"
31+
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
32+
"github.com/vmware-tanzu/velero/pkg/client"
33+
"github.com/vmware-tanzu/velero/pkg/cmd"
34+
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
35+
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
36+
)
37+
38+
func NewCreateCommand(f client.Factory, use string) *cobra.Command {
39+
o := NewCreateOptions()
40+
41+
c := &cobra.Command{
42+
Use: use + " NAME --from-backup BACKUP_NAME",
43+
Short: "Create a non-admin restore",
44+
Args: cobra.MaximumNArgs(1),
45+
Run: func(c *cobra.Command, args []string) {
46+
cmd.CheckError(o.Complete(args, f))
47+
cmd.CheckError(o.Validate(c, args, f))
48+
cmd.CheckError(o.Run(c, f))
49+
},
50+
Example: ` # Create a non-admin restore from a backup in the current namespace.
51+
kubectl oadp nonadmin restore create restore1 --from-backup backup1
52+
53+
# Create a non-admin restore with specific resource types.
54+
kubectl oadp nonadmin restore create restore2 --from-backup backup1 --include-resources deployments,services
55+
56+
# Create a non-admin restore excluding certain resources.
57+
kubectl oadp nonadmin restore create restore3 --from-backup backup1 --exclude-resources secrets
58+
59+
# View the YAML for a non-admin restore without sending it to the server.
60+
kubectl oadp nonadmin restore create restore4 --from-backup backup1 -o yaml
61+
62+
# Wait for a non-admin restore to complete before returning from the command.
63+
kubectl oadp nonadmin restore create restore5 --from-backup backup1 --wait`,
64+
}
65+
66+
o.BindFlags(c.Flags())
67+
o.BindWait(c.Flags())
68+
output.BindFlags(c.Flags())
69+
output.ClearOutputFlagDefault(c)
70+
71+
return c
72+
}
73+
74+
type CreateOptions struct {
75+
Name string
76+
FromBackup string
77+
IncludeResources flag.StringArray
78+
ExcludeResources flag.StringArray
79+
Labels flag.Map
80+
Annotations flag.Map
81+
Selector flag.LabelSelector
82+
OrSelector flag.OrLabelSelector
83+
IncludeClusterResources flag.OptionalBool
84+
Wait bool
85+
RestorePVs flag.OptionalBool
86+
PreserveNodePorts flag.OptionalBool
87+
ItemOperationTimeout time.Duration
88+
ExistingResourcePolicy string
89+
UploaderConfig flag.Map
90+
client kbclient.WithWatch
91+
currentNamespace string
92+
}
93+
94+
func NewCreateOptions() *CreateOptions {
95+
return &CreateOptions{
96+
IncludeResources: flag.NewStringArray("*"),
97+
Labels: flag.NewMap(),
98+
Annotations: flag.NewMap(),
99+
UploaderConfig: flag.NewMap(),
100+
IncludeClusterResources: flag.NewOptionalBool(nil),
101+
RestorePVs: flag.NewOptionalBool(nil),
102+
PreserveNodePorts: flag.NewOptionalBool(nil),
103+
}
104+
}
105+
106+
func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
107+
flags.StringVar(&o.FromBackup, "from-backup", o.FromBackup, "Backup to restore from (required).")
108+
flags.Var(&o.IncludeResources, "include-resources", "Resources to include in the restore, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources).")
109+
flags.Var(&o.ExcludeResources, "exclude-resources", "Resources to exclude from the restore, formatted as resource.group, such as storageclasses.storage.k8s.io.")
110+
flags.Var(&o.Labels, "labels", "Labels to apply to the restore.")
111+
flags.Var(&o.Annotations, "annotations", "Annotations to apply to the restore.")
112+
flags.VarP(&o.Selector, "selector", "l", "Only restore resources matching this label selector.")
113+
flags.Var(&o.OrSelector, "or-selector", "Restore resources matching at least one of the label selector from the list. Label selectors should be separated by ' or '. For example, foo=bar or app=nginx")
114+
flags.DurationVar(&o.ItemOperationTimeout, "item-operation-timeout", o.ItemOperationTimeout, "How long to wait for async plugin operations before timeout.")
115+
flags.StringVar(&o.ExistingResourcePolicy, "existing-resource-policy", "", "Policy to handle restore collisions (none, update)")
116+
flags.Var(&o.UploaderConfig, "uploader-config", "Configuration for the uploader in form key1=value1,key2=value2")
117+
118+
f := flags.VarPF(&o.IncludeClusterResources, "include-cluster-resources", "", "Include cluster-scoped resources.")
119+
f.NoOptDefVal = cmd.TRUE
120+
121+
f = flags.VarPF(&o.RestorePVs, "restore-volumes", "", "Whether to restore volumes from snapshots.")
122+
f.NoOptDefVal = cmd.TRUE
123+
124+
f = flags.VarPF(&o.PreserveNodePorts, "preserve-nodeports", "", "Whether to restore NodePort services as NodePort.")
125+
f.NoOptDefVal = cmd.TRUE
126+
}
127+
128+
func (o *CreateOptions) BindWait(flags *pflag.FlagSet) {
129+
flags.BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait for the operation to complete.")
130+
}
131+
132+
func (o *CreateOptions) Complete(args []string, f client.Factory) error {
133+
// If an explicit name is specified, use that name
134+
if len(args) > 0 {
135+
o.Name = args[0]
136+
}
137+
138+
// Create client with NonAdmin scheme
139+
client, err := shared.NewClientWithScheme(f, shared.ClientOptions{
140+
IncludeNonAdminTypes: true,
141+
})
142+
if err != nil {
143+
return err
144+
}
145+
146+
// Get the current namespace from kubeconfig instead of using factory namespace
147+
currentNS, err := shared.GetCurrentNamespace()
148+
if err != nil {
149+
return fmt.Errorf("failed to determine current namespace: %w", err)
150+
}
151+
152+
o.client = client
153+
o.currentNamespace = currentNS
154+
return nil
155+
}
156+
157+
func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Factory) error {
158+
if len(args) < 1 {
159+
return fmt.Errorf("restore name is required")
160+
}
161+
162+
if o.FromBackup == "" {
163+
return fmt.Errorf("--from-backup is required")
164+
}
165+
166+
if o.Name == "" {
167+
o.Name = args[0]
168+
}
169+
170+
return nil
171+
}
172+
173+
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
174+
if printed, err := output.PrintWithFormat(c, o.buildRestore()); printed || err != nil {
175+
return err
176+
}
177+
178+
restore := o.buildRestore()
179+
180+
if err := o.client.Create(context.Background(), restore); err != nil {
181+
return err
182+
}
183+
184+
fmt.Printf("NonAdminRestore %q created successfully.\n", restore.Name)
185+
186+
if o.Wait {
187+
return o.waitForRestore(restore)
188+
}
189+
190+
return nil
191+
}
192+
193+
func (o *CreateOptions) buildRestore() *nacv1alpha1.NonAdminRestore {
194+
// Create a Velero RestoreSpec
195+
restoreSpec := &velerov1api.RestoreSpec{
196+
BackupName: o.FromBackup,
197+
}
198+
199+
// Add resource filters
200+
if len(o.IncludeResources) > 0 {
201+
restoreSpec.IncludedResources = o.IncludeResources
202+
}
203+
if len(o.ExcludeResources) > 0 {
204+
restoreSpec.ExcludedResources = o.ExcludeResources
205+
}
206+
207+
// Note: The namespace-scoped and cluster-scoped resource filters are only available
208+
// in backup operations, not restore operations in Velero RestoreSpec.
209+
// For restores, use IncludedResources/ExcludedResources with specific resource types.
210+
211+
// Note: Namespace mappings are restricted for non-admin users and therefore not processed
212+
213+
// Add selectors
214+
if o.Selector.LabelSelector != nil {
215+
restoreSpec.LabelSelector = o.Selector.LabelSelector
216+
}
217+
if len(o.OrSelector.OrLabelSelectors) > 0 {
218+
restoreSpec.OrLabelSelectors = o.OrSelector.OrLabelSelectors
219+
}
220+
221+
// Add optional settings
222+
if o.IncludeClusterResources.Value != nil {
223+
restoreSpec.IncludeClusterResources = o.IncludeClusterResources.Value
224+
}
225+
if o.RestorePVs.Value != nil {
226+
restoreSpec.RestorePVs = o.RestorePVs.Value
227+
}
228+
if o.PreserveNodePorts.Value != nil {
229+
restoreSpec.PreserveNodePorts = o.PreserveNodePorts.Value
230+
}
231+
if o.ItemOperationTimeout > 0 {
232+
restoreSpec.ItemOperationTimeout = metav1.Duration{Duration: o.ItemOperationTimeout}
233+
}
234+
if o.ExistingResourcePolicy != "" {
235+
policy := velerov1api.PolicyType(o.ExistingResourcePolicy)
236+
restoreSpec.ExistingResourcePolicy = policy
237+
}
238+
if o.UploaderConfig.Data() != nil && len(o.UploaderConfig.Data()) > 0 {
239+
restoreSpec.UploaderConfig = &velerov1api.UploaderConfigForRestore{}
240+
// Note: UploaderConfigForRestore fields would be set here based on the map values
241+
// The exact field structure depends on the Velero version being used
242+
}
243+
244+
// Create NonAdminRestore using the builder
245+
restore := ForNonAdminRestore(o.currentNamespace, o.Name).
246+
ObjectMeta(
247+
WithLabelsMap(o.Labels.Data()),
248+
WithAnnotationsMap(o.Annotations.Data()),
249+
).
250+
RestoreSpec(nacv1alpha1.NonAdminRestoreSpec{
251+
RestoreSpec: restoreSpec,
252+
}).
253+
Result()
254+
255+
return restore
256+
}
257+
258+
func (o *CreateOptions) waitForRestore(restore *nacv1alpha1.NonAdminRestore) error {
259+
fmt.Printf("Waiting for restore %s to complete...\n", restore.Name)
260+
261+
// TODO: Implement proper wait functionality
262+
// For now, just poll the restore status periodically
263+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
264+
defer cancel()
265+
266+
ticker := time.NewTicker(5 * time.Second)
267+
defer ticker.Stop()
268+
269+
for {
270+
select {
271+
case <-ctx.Done():
272+
return fmt.Errorf("timeout waiting for restore to complete")
273+
case <-ticker.C:
274+
// Get current restore status
275+
currentRestore := &nacv1alpha1.NonAdminRestore{}
276+
err := o.client.Get(ctx, kbclient.ObjectKey{
277+
Namespace: restore.Namespace,
278+
Name: restore.Name,
279+
}, currentRestore)
280+
if err != nil {
281+
return fmt.Errorf("failed to get restore status: %w", err)
282+
}
283+
284+
phase := currentRestore.Status.Phase
285+
fmt.Printf("Restore %s status: %s\n", restore.Name, phase)
286+
287+
// Check if completed (using generic NonAdminPhase constants)
288+
if phase == nacv1alpha1.NonAdminPhaseCreated {
289+
fmt.Printf("Restore %s completed successfully.\n", restore.Name)
290+
return nil
291+
}
292+
// Add other phase checks as needed
293+
}
294+
}
295+
}

0 commit comments

Comments
 (0)