Skip to content

Commit 8e3e7b5

Browse files
authored
STAC-23603: Restoring Settings (#6)
1 parent 6d11f50 commit 8e3e7b5

File tree

14 files changed

+881
-16
lines changed

14 files changed

+881
-16
lines changed

cmd/elasticsearch/list_snapshots_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@ victoriaMetrics:
6969
requests:
7070
cpu: "500m"
7171
memory: "1Gi"
72+
settings:
73+
bucket: sts-settings-backup
74+
s3Prefix: ""
75+
restore:
76+
scaleDownLabelSelector: "app=settings"
77+
loggingConfigConfigMap: logging-config
78+
baseUrl: "http://server:7070"
79+
receiverBaseUrl: "http://receiver:7077"
80+
platformVersion: "5.2.0"
81+
zookeeperQuorum: "zookeeper:2181"
82+
job:
83+
image: settings-backup:latest
84+
waitImage: wait:latest
85+
resources:
86+
limits:
87+
cpu: "1"
88+
memory: "2Gi"
89+
requests:
90+
cpu: "500m"
91+
memory: "1Gi"
7292
`
7393

7494
// mockESClient is a simple mock for testing commands

cmd/root.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/spf13/cobra"
77
"github.com/stackvista/stackstate-backup-cli/cmd/elasticsearch"
8+
"github.com/stackvista/stackstate-backup-cli/cmd/settings"
89
"github.com/stackvista/stackstate-backup-cli/cmd/stackgraph"
910
"github.com/stackvista/stackstate-backup-cli/cmd/version"
1011
"github.com/stackvista/stackstate-backup-cli/cmd/victoriametrics"
@@ -40,6 +41,10 @@ func init() {
4041
addBackupConfigFlags(stackgraphCmd)
4142
rootCmd.AddCommand(stackgraphCmd)
4243

44+
settingsCmd := settings.Cmd(flags)
45+
addBackupConfigFlags(settingsCmd)
46+
rootCmd.AddCommand(settingsCmd)
47+
4348
victoriaMetricsCmd := victoriametrics.Cmd(flags)
4449
addBackupConfigFlags(victoriaMetricsCmd)
4550
rootCmd.AddCommand(victoriaMetricsCmd)

cmd/settings/check_and_finalize.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package settings
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/stackvista/stackstate-backup-cli/internal/app"
9+
"github.com/stackvista/stackstate-backup-cli/internal/foundation/config"
10+
"github.com/stackvista/stackstate-backup-cli/internal/orchestration/restore"
11+
)
12+
13+
// Check and finalize command flags
14+
var (
15+
checkJobName string
16+
waitForJob bool
17+
)
18+
19+
func checkAndFinalizeCmd(globalFlags *config.CLIGlobalFlags) *cobra.Command {
20+
cmd := &cobra.Command{
21+
Use: "check-and-finalize",
22+
Short: "Check and finalize a Settings restore job",
23+
Long: `Check the status of a background Settings restore job and clean up resources.
24+
25+
This command is useful when a restore job was started with --background flag or was interrupted (Ctrl+C).
26+
It will check the job status, print logs if it failed, and clean up the job and PVC resources.
27+
28+
Examples:
29+
# Check job status without waiting
30+
sts-backup settings check-and-finalize --job settings-restore-20250128t143000 -n my-namespace
31+
32+
# Wait for job completion and cleanup
33+
sts-backup settings check-and-finalize --job settings-restore-20250128t143000 --wait -n my-namespace`,
34+
Run: func(_ *cobra.Command, _ []string) {
35+
appCtx, err := app.NewContext(globalFlags)
36+
if err != nil {
37+
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
38+
os.Exit(1)
39+
}
40+
if err := runCheckAndFinalize(appCtx); err != nil {
41+
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
42+
os.Exit(1)
43+
}
44+
},
45+
}
46+
47+
cmd.Flags().StringVarP(&checkJobName, "job", "j", "", "Settings restore job name (required)")
48+
cmd.Flags().BoolVarP(&waitForJob, "wait", "w", false, "Wait for job to complete before cleanup")
49+
_ = cmd.MarkFlagRequired("job")
50+
51+
return cmd
52+
}
53+
54+
func runCheckAndFinalize(appCtx *app.Context) error {
55+
return restore.CheckAndFinalize(restore.CheckAndFinalizeParams{
56+
K8sClient: appCtx.K8sClient,
57+
Namespace: appCtx.Namespace,
58+
JobName: checkJobName,
59+
ServiceName: "settings",
60+
ScaleSelector: appCtx.Config.Settings.Restore.ScaleDownLabelSelector,
61+
CleanupPVC: true,
62+
WaitForJob: waitForJob,
63+
Log: appCtx.Logger,
64+
})
65+
}

cmd/settings/list.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package settings
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"sort"
8+
9+
"github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/service/s3"
11+
"github.com/spf13/cobra"
12+
"github.com/stackvista/stackstate-backup-cli/internal/app"
13+
s3client "github.com/stackvista/stackstate-backup-cli/internal/clients/s3"
14+
"github.com/stackvista/stackstate-backup-cli/internal/foundation/config"
15+
"github.com/stackvista/stackstate-backup-cli/internal/foundation/output"
16+
"github.com/stackvista/stackstate-backup-cli/internal/orchestration/portforward"
17+
)
18+
19+
const (
20+
isMultiPartArchive = false
21+
)
22+
23+
func listCmd(globalFlags *config.CLIGlobalFlags) *cobra.Command {
24+
return &cobra.Command{
25+
Use: "list",
26+
Short: "List available Settings backups from S3/Minio",
27+
Run: func(_ *cobra.Command, _ []string) {
28+
appCtx, err := app.NewContext(globalFlags)
29+
if err != nil {
30+
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
31+
os.Exit(1)
32+
}
33+
if err := runList(appCtx); err != nil {
34+
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
35+
os.Exit(1)
36+
}
37+
},
38+
}
39+
}
40+
41+
func runList(appCtx *app.Context) error {
42+
// Setup port-forward to Minio
43+
serviceName := appCtx.Config.Minio.Service.Name
44+
localPort := appCtx.Config.Minio.Service.LocalPortForwardPort
45+
remotePort := appCtx.Config.Minio.Service.Port
46+
47+
pf, err := portforward.SetupPortForward(appCtx.K8sClient, appCtx.Namespace, serviceName, localPort, remotePort, appCtx.Logger)
48+
if err != nil {
49+
return err
50+
}
51+
defer close(pf.StopChan)
52+
53+
// List objects in bucket
54+
bucket := appCtx.Config.Settings.Bucket
55+
prefix := appCtx.Config.Settings.S3Prefix
56+
57+
appCtx.Logger.Infof("Listing Settings backups in bucket '%s'...", bucket)
58+
59+
input := &s3.ListObjectsV2Input{
60+
Bucket: aws.String(bucket),
61+
Prefix: aws.String(prefix),
62+
}
63+
64+
result, err := appCtx.S3Client.ListObjectsV2(context.Background(), input)
65+
if err != nil {
66+
return fmt.Errorf("failed to list S3 objects: %w", err)
67+
}
68+
69+
// Filter objects based on whether the archive is split or not
70+
filteredObjects := s3client.FilterBackupObjects(result.Contents, isMultiPartArchive)
71+
72+
// Sort by LastModified time (most recent first)
73+
sort.Slice(filteredObjects, func(i, j int) bool {
74+
return filteredObjects[i].LastModified.After(filteredObjects[j].LastModified)
75+
})
76+
77+
if len(filteredObjects) == 0 {
78+
appCtx.Formatter.PrintMessage("No backups found")
79+
return nil
80+
}
81+
82+
table := output.Table{
83+
Headers: []string{"NAME", "LAST MODIFIED", "SIZE"},
84+
Rows: make([][]string, 0, len(filteredObjects)),
85+
}
86+
87+
for _, obj := range filteredObjects {
88+
row := []string{
89+
obj.Key,
90+
obj.LastModified.Format("2006-01-02 15:04:05 MST"),
91+
output.FormatBytes(obj.Size),
92+
}
93+
table.Rows = append(table.Rows, row)
94+
}
95+
96+
return appCtx.Formatter.PrintTable(table)
97+
}

0 commit comments

Comments
 (0)