Skip to content

Commit

Permalink
feat: implement snapshot browsing
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed Nov 17, 2023
1 parent 8488d46 commit 8ffffa0
Show file tree
Hide file tree
Showing 12 changed files with 882 additions and 97 deletions.
466 changes: 389 additions & 77 deletions gen/go/v1/service.pb.go

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions gen/go/v1/service.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions gen/go/v1/service_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,23 @@ func (s *Server) ListSnapshots(ctx context.Context, query *v1.ListSnapshotsReque
}, nil
}

func (s *Server) ListSnapshotFiles(ctx context.Context, query *v1.ListSnapshotFilesRequest) (*v1.ListSnapshotFilesResponse, error) {
repo, err := s.orchestrator.GetRepo(query.RepoId)
if err != nil {
return nil, fmt.Errorf("failed to get repo: %w", err)
}

entries, err := repo.ListSnapshotFiles(ctx, query.SnapshotId, query.Path)
if err != nil {
return nil, fmt.Errorf("failed to list snapshot files: %w", err)
}

return &v1.ListSnapshotFilesResponse{
Path: query.Path,
Entries: entries,
}, nil
}

// GetOperationEvents implements GET /v1/events/operations
func (s *Server) GetOperationEvents(_ *emptypb.Empty, stream v1.ResticUI_GetOperationEventsServer) error {
errorChan := make(chan error)
Expand Down
19 changes: 18 additions & 1 deletion internal/orchestrator/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrat
}

func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) {
snapshots, err := r.repo.Snapshots(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...), restic.WithFlags("--latest", "1000"))
snapshots, err := r.repo.Snapshots(ctx, restic.WithFlags("--latest", "1000"))
if err != nil {
return nil, fmt.Errorf("restic.Snapshots: %w", err)
}
Expand Down Expand Up @@ -83,6 +83,23 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa
return summary, nil
}

func (r *RepoOrchestrator) ListSnapshotFiles(ctx context.Context, snapshotId string, path string) ([]*v1.LsEntry, error) {
r.mu.Lock()
defer r.mu.Unlock()

_, entries, err := r.repo.ListDirectory(ctx, snapshotId, path)
if err != nil {
return nil, fmt.Errorf("failed to list snapshot files: %w", err)
}

lsEnts := make([]*v1.LsEntry, 0, len(entries))
for _, entry := range entries {
lsEnts = append(lsEnts, entry.ToProto())
}

return lsEnts, nil
}

func filterSnapshotsForPlan(snapshots []*restic.Snapshot, plan *v1.Plan) []*restic.Snapshot {
wantTag := tagForPlan(plan)
var filtered []*restic.Snapshot
Expand Down
3 changes: 1 addition & 2 deletions internal/orchestrator/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type ScheduledBackupTask struct {
var _ Task = &ScheduledBackupTask{}

func NewScheduledBackupTask(orchestrator *Orchestrator, plan *v1.Plan) (*ScheduledBackupTask, error) {
sched, err := cronexpr.Parse(plan.Cron)
sched, err := cronexpr.ParseInLocation(plan.Cron, time.Now().Location().String())
if err != nil {
return nil, fmt.Errorf("failed to parse schedule %q: %w", plan.Cron, err)
}
Expand Down Expand Up @@ -160,7 +160,6 @@ func indexSnapshotsHelper(ctx context.Context, orchestrator *Orchestrator, plan
opTime := curTimeMillis()
var indexOps []*v1.Operation
for _, snapshot := range snapshots {
zap.L().Debug("checking if snapshot has been indexed", zap.String("snapshot", snapshot.Id))
opid, err := orchestrator.oplog.HasIndexedSnapshot(snapshot.Id)
if err != nil {
return fmt.Errorf("HasIndexSnapshot for snapshot %q: %w", snapshot.Id, err)
Expand Down
15 changes: 15 additions & 0 deletions pkg/restic/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ type LsEntry struct {
Ctime string `json:"ctime"`
}

func (e *LsEntry) ToProto() *v1.LsEntry {
return &v1.LsEntry{
Name: e.Name,
Type: e.Type,
Path: e.Path,
Uid: int64(e.Uid),
Gid: int64(e.Gid),
Size: int64(e.Size),
Mode: int64(e.Mode),
Mtime: e.Mtime,
Atime: e.Atime,
Ctime: e.Ctime,
}
}

type Snapshot struct {
Id string `json:"id"`
Time string `json:"time"`
Expand Down
1 change: 0 additions & 1 deletion pkg/restic/restic.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ func (r *Repo) ListDirectory(ctx context.Context, snapshot string, path string,
return nil, nil, NewCmdError(cmd, output, err)
}


snapshots, entries, err := readLs(bytes.NewBuffer(output))
if err != nil {
return nil, nil, NewCmdError(cmd, output, err)
Expand Down
31 changes: 31 additions & 0 deletions proto/v1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ service ResticUI {
body: "*"
};
}

rpc ListSnapshotFiles(ListSnapshotFilesRequest) returns (ListSnapshotFilesResponse) {
option (google.api.http) = {
post: "/v1/snapshots/files"
body: "*"
};
}

// Backup schedules a backup operation. It accepts a plan id and returns empty if the task is enqueued.
rpc Backup(types.StringValue) returns (google.protobuf.Empty) {
Expand Down Expand Up @@ -79,3 +86,27 @@ message GetOperationsRequest {
string plan_id = 2;
int64 last_n = 3; // limit to the last n operations
}

message ListSnapshotFilesRequest {
string repo_id = 1;
string snapshot_id = 2;
string path = 3;
}

message ListSnapshotFilesResponse {
string path = 1;
repeated LsEntry entries = 2;
}

message LsEntry {
string name = 1;
string type = 2;
string path = 3;
int64 uid = 4;
int64 gid = 5;
int64 size = 6;
int64 mode = 7;
string mtime = 8;
string atime = 9;
string ctime = 10;
}
27 changes: 27 additions & 0 deletions webui/gen/ts/v1/service.pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ export type GetOperationsRequest = {
lastN?: string
}

export type ListSnapshotFilesRequest = {
repoId?: string
snapshotId?: string
path?: string
}

export type ListSnapshotFilesResponse = {
path?: string
entries?: LsEntry[]
}

export type LsEntry = {
name?: string
type?: string
path?: string
uid?: string
gid?: string
size?: string
mode?: string
mtime?: string
atime?: string
ctime?: string
}

export class ResticUI {
static GetConfig(req: GoogleProtobufEmpty.Empty, initReq?: fm.InitReq): Promise<V1Config.Config> {
return fm.fetchReq<GoogleProtobufEmpty.Empty, V1Config.Config>(`/v1/config?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
Expand All @@ -40,6 +64,9 @@ export class ResticUI {
static ListSnapshots(req: ListSnapshotsRequest, initReq?: fm.InitReq): Promise<V1Restic.ResticSnapshotList> {
return fm.fetchReq<ListSnapshotsRequest, V1Restic.ResticSnapshotList>(`/v1/snapshots`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
}
static ListSnapshotFiles(req: ListSnapshotFilesRequest, initReq?: fm.InitReq): Promise<ListSnapshotFilesResponse> {
return fm.fetchReq<ListSnapshotFilesRequest, ListSnapshotFilesResponse>(`/v1/snapshots/files`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
}
static Backup(req: TypesValue.StringValue, initReq?: fm.InitReq): Promise<GoogleProtobufEmpty.Empty> {
return fm.fetchReq<TypesValue.StringValue, GoogleProtobufEmpty.Empty>(`/v1/cmd/backup`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
}
Expand Down
Loading

0 comments on commit 8ffffa0

Please sign in to comment.