/
refs_restore.go
112 lines (99 loc) · 3.83 KB
/
refs_restore.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/spf13/cobra"
"github.com/treeverse/lakefs/pkg/api/apigen"
"github.com/treeverse/lakefs/pkg/api/helpers"
"github.com/treeverse/lakefs/pkg/logging"
)
const refsRestoreSuccess = `
{{ "All references restored successfully!" | green }}
`
var refsRestoreCmd = &cobra.Command{
Use: "refs-restore <repository URI>",
Short: "Restores refs (branches, commits, tags) from the underlying object store to a bare repository",
Long: `restores refs (branches, commits, tags) from the underlying object store to a bare repository.
This command is expected to run on a bare repository (i.e. one created with 'lakectl repo create-bare').
Since a bare repo is expected, in case of transient failure, delete the repository and recreate it as bare and retry.`,
Example: "aws s3 cp s3://bucket/_lakefs/refs_manifest.json - | lakectl refs-restore lakefs://my-bare-repository --manifest -",
Hidden: true,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
repoURI := MustParseRepoURI("repository URI", args[0])
pollInterval := Must(cmd.Flags().GetDuration("poll-interval"))
if pollInterval < minimumPollInterval {
DieFmt("Poll interval must be at least %s", minimumPollInterval)
}
timeoutDuration := Must(cmd.Flags().GetDuration("timeout"))
fmt.Println("Repository:", repoURI)
manifestFileName := Must(cmd.Flags().GetString("manifest"))
fp := Must(OpenByPath(manifestFileName))
defer func() {
_ = fp.Close()
}()
// read and parse the JSON
data, err := io.ReadAll(fp)
if err != nil {
DieErr(err)
}
var manifest apigen.RefsRestore
err = json.Unmarshal(data, &manifest)
if err != nil {
DieErr(err)
}
// execute the restore operation
client := getClient()
ctx := cmd.Context()
resp, err := client.RestoreSubmitWithResponse(ctx, repoURI.Repository, apigen.RestoreSubmitJSONRequestBody(manifest))
DieOnErrorOrUnexpectedStatusCode(resp, err, http.StatusAccepted)
if resp.JSON202 == nil {
Die("Bad response from server", 1)
}
taskID := resp.JSON202.Id
logging.FromContext(ctx).WithField("task_id", taskID).Debug("Submitted refs restore")
var bo backoff.BackOff = backoff.NewConstantBackOff(pollInterval)
bo = backoff.WithContext(bo, ctx)
restoreStatus, err := backoff.RetryWithData(func() (*apigen.RepositoryRestoreStatus, error) {
logging.FromContext(ctx).
WithFields(logging.Fields{"task_id": taskID}).
Debug("Checking status of refs restore")
resp, err := client.RestoreStatusWithResponse(ctx, repoURI.Repository, &apigen.RestoreStatusParams{
TaskId: taskID,
})
DieOnErrorOrUnexpectedStatusCode(resp, err, http.StatusOK)
if resp.JSON200 == nil {
err := fmt.Errorf("restore status %w: %s", helpers.ErrRequestFailed, resp.Status())
return nil, backoff.Permanent(err)
}
if resp.JSON200.Done {
return resp.JSON200, nil
}
if timeoutDuration >= 0 && time.Since(resp.JSON200.UpdateTime) > timeoutDuration {
return nil, backoff.Permanent(ErrTaskNotCompleted)
}
return nil, ErrTaskNotCompleted
}, bo)
switch {
case err != nil:
DieErr(err)
case restoreStatus == nil:
Die("Refs restore failed: no status returned", 1)
case restoreStatus.Error != nil:
DieFmt("Refs restore failed: %s", *restoreStatus.Error)
}
Write(refsRestoreSuccess, nil)
},
}
//nolint:gochecknoinits
func init() {
rootCmd.AddCommand(refsRestoreCmd)
refsRestoreCmd.Flags().String("manifest", "", "path to a refs manifest json file (as generated by `refs-dump`). Alternatively, use \"-\" to read from stdin")
refsRestoreCmd.Flags().Duration("poll-interval", defaultPollInterval, "poll status check interval")
refsRestoreCmd.Flags().Duration("timeout", defaultPollTimeout, "timeout for polling status checks")
_ = refsRestoreCmd.MarkFlagRequired("manifest")
}