Skip to content

Commit c9d6cf0

Browse files
committed
Implement snapshot subcommand
1 parent ce30395 commit c9d6cf0

9 files changed

+495
-0
lines changed

pkg/koyeb/idmapper/idmapper.go

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Mapper struct {
1717
organization *OrganizationMapper
1818
database *DatabaseMapper
1919
volume *VolumeMapper
20+
snapshot *SnapshotMapper
2021
}
2122

2223
func NewMapper(ctx context.Context, client *koyeb.APIClient) *Mapper {
@@ -30,6 +31,7 @@ func NewMapper(ctx context.Context, client *koyeb.APIClient) *Mapper {
3031
organizationMapper := NewOrganizationMapper(ctx, client)
3132
databaseMapper := NewDatabaseMapper(ctx, client, appMapper)
3233
volumeMapper := NewVolumeMapper(ctx, client)
34+
snapshotMapper := NewSnapshotMapper(ctx, client)
3335

3436
return &Mapper{
3537
app: appMapper,
@@ -42,6 +44,7 @@ func NewMapper(ctx context.Context, client *koyeb.APIClient) *Mapper {
4244
organization: organizationMapper,
4345
database: databaseMapper,
4446
volume: volumeMapper,
47+
snapshot: snapshotMapper,
4548
}
4649
}
4750

@@ -84,3 +87,7 @@ func (mapper *Mapper) Database() *DatabaseMapper {
8487
func (mapper *Mapper) Volume() *VolumeMapper {
8588
return mapper.volume
8689
}
90+
91+
func (mapper *Mapper) Snapshot() *SnapshotMapper {
92+
return mapper.snapshot
93+
}

pkg/koyeb/idmapper/snapshot.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package idmapper
2+
3+
import (
4+
"context"
5+
"strconv"
6+
7+
"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
8+
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
9+
)
10+
11+
type SnapshotMapper struct {
12+
ctx context.Context
13+
client *koyeb.APIClient
14+
fetched bool
15+
sidMap *IDMap
16+
nameMap *IDMap
17+
}
18+
19+
func NewSnapshotMapper(ctx context.Context, client *koyeb.APIClient) *SnapshotMapper {
20+
return &SnapshotMapper{
21+
ctx: ctx,
22+
client: client,
23+
fetched: false,
24+
sidMap: NewIDMap(),
25+
nameMap: NewIDMap(),
26+
}
27+
}
28+
29+
func (mapper *SnapshotMapper) ResolveID(val string) (string, error) {
30+
if IsUUIDv4(val) {
31+
return val, nil
32+
}
33+
34+
if !mapper.fetched {
35+
err := mapper.fetch()
36+
if err != nil {
37+
return "", err
38+
}
39+
}
40+
41+
id, ok := mapper.sidMap.GetID(val)
42+
if ok {
43+
return id, nil
44+
}
45+
46+
id, ok = mapper.nameMap.GetID(val)
47+
if ok {
48+
return id, nil
49+
}
50+
51+
return "", errors.NewCLIErrorForMapperResolve(
52+
"snapshot",
53+
val,
54+
[]string{"snapshot full UUID", "snapshot short ID (8 characters)", "snapshot name"},
55+
)
56+
}
57+
58+
func (mapper *SnapshotMapper) fetch() error {
59+
radix := NewRadixTree()
60+
61+
page := int64(0)
62+
offset := int64(0)
63+
limit := int64(100)
64+
for {
65+
res, resp, err := mapper.client.SnapshotsApi.ListSnapshots(mapper.ctx).
66+
Limit(strconv.FormatInt(limit, 10)).
67+
Offset(strconv.FormatInt(offset, 10)).
68+
Execute()
69+
if err != nil {
70+
return errors.NewCLIErrorFromAPIError(
71+
"Error listing snapshots to resolve the provided identifier to an object ID",
72+
err,
73+
resp,
74+
)
75+
}
76+
77+
snapshots := res.GetSnapshots()
78+
79+
if len(snapshots) == 0 {
80+
break
81+
}
82+
83+
for i := range snapshots {
84+
snapshot := &snapshots[i]
85+
radix.Insert(getKey(snapshot.GetId()), snapshot)
86+
}
87+
88+
page++
89+
offset = page * limit
90+
}
91+
92+
minLength := radix.MinimalLength(8)
93+
err := radix.ForEach(func(key Key, value Value) error {
94+
snapshot := value.(*koyeb.Snapshot)
95+
id := snapshot.GetId()
96+
name := snapshot.GetName()
97+
sid := getShortID(id, minLength)
98+
99+
mapper.sidMap.Set(id, sid)
100+
mapper.nameMap.Set(id, name)
101+
102+
return nil
103+
})
104+
if err != nil {
105+
return err
106+
}
107+
108+
mapper.fetched = true
109+
110+
return nil
111+
}

pkg/koyeb/koyeb.go

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ func GetRootCommand() *cobra.Command {
115115
rootCmd.AddCommand(NewArchiveCmd())
116116
rootCmd.AddCommand(NewDeployCmd())
117117
rootCmd.AddCommand(NewVolumeCmd())
118+
rootCmd.AddCommand(NewSnapshotCmd())
118119
return rootCmd
119120
}
120121

pkg/koyeb/snapshots.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package koyeb
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
7+
)
8+
9+
func NewSnapshotCmd() *cobra.Command {
10+
h := NewSnapshotHandler()
11+
_ = h
12+
13+
snapshotCmd := &cobra.Command{
14+
Use: "snapshots ACTION",
15+
Aliases: []string{"vol", "snapshot"},
16+
Short: "Manage snapshots",
17+
}
18+
19+
createSnapshotCmd := &cobra.Command{
20+
Use: "create NAME",
21+
Short: "Create a new snapshot",
22+
Args: cobra.ExactArgs(2),
23+
RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error {
24+
req := koyeb.NewCreateSnapshotRequestWithDefaults()
25+
26+
parentVolumeID, err := ctx.Mapper.Volume().ResolveID(args[1])
27+
if err != nil {
28+
return err
29+
}
30+
31+
req.SetName(args[0])
32+
req.SetParentVolumeId(parentVolumeID)
33+
34+
return h.Create(ctx, cmd, args, req)
35+
}),
36+
}
37+
createSnapshotCmd.Flags().String("region", "was", "Region of the snapshot")
38+
createSnapshotCmd.Flags().Int64("size", 10, "Size of the snapshot in GB")
39+
createSnapshotCmd.Flags().Bool("read-only", false, "Force the snapshot to be read-only")
40+
snapshotCmd.AddCommand(createSnapshotCmd)
41+
42+
getSnapshotCmd := &cobra.Command{
43+
Use: "get NAME",
44+
Short: "Get a snapshot",
45+
Args: cobra.ExactArgs(1),
46+
RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error {
47+
return h.Get(ctx, cmd, args)
48+
}),
49+
}
50+
snapshotCmd.AddCommand(getSnapshotCmd)
51+
52+
listSnapshotCmd := &cobra.Command{
53+
Use: "list",
54+
Short: "List snapshots",
55+
RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error {
56+
return h.List(ctx, cmd, args)
57+
}),
58+
}
59+
snapshotCmd.AddCommand(listSnapshotCmd)
60+
61+
updateSnapshotCmd := &cobra.Command{
62+
Use: "update NAME",
63+
Short: "Update a snapshot",
64+
Args: cobra.ExactArgs(1),
65+
RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error {
66+
req := koyeb.NewUpdateSnapshotRequestWithDefaults()
67+
68+
name, _ := cmd.Flags().GetString("name")
69+
if name != "" {
70+
req.SetName(name)
71+
}
72+
73+
return h.Update(ctx, cmd, args, req)
74+
}),
75+
}
76+
updateSnapshotCmd.Flags().String("name", "", "Change the snapshot name")
77+
updateSnapshotCmd.Flags().Int64("size", -1, "Increase the snapshot size")
78+
snapshotCmd.AddCommand(updateSnapshotCmd)
79+
80+
deleteSnapshotCmd := &cobra.Command{
81+
Use: "delete NAME",
82+
Short: "Delete a snapshot",
83+
Args: cobra.ExactArgs(1),
84+
RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error {
85+
return h.Delete(ctx, cmd, args)
86+
}),
87+
}
88+
snapshotCmd.AddCommand(deleteSnapshotCmd)
89+
90+
return snapshotCmd
91+
}
92+
93+
func NewSnapshotHandler() *SnapshotHandler {
94+
return &SnapshotHandler{}
95+
}
96+
97+
type SnapshotHandler struct {
98+
}
99+
100+
func ResolveSnapshotArgs(ctx *CLIContext, val string) (string, error) {
101+
snapshotMapper := ctx.Mapper.Snapshot()
102+
id, err := snapshotMapper.ResolveID(val)
103+
if err != nil {
104+
return "", err
105+
}
106+
return id, nil
107+
}

pkg/koyeb/snapshots_create.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package koyeb
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
7+
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func (h *SnapshotHandler) Create(ctx *CLIContext, cmd *cobra.Command, args []string, createSnapshot *koyeb.CreateSnapshotRequest) error {
12+
res, resp, err := ctx.Client.SnapshotsApi.CreateSnapshot(ctx.Context).Body(*createSnapshot).Execute()
13+
if err != nil {
14+
return errors.NewCLIErrorFromAPIError(
15+
fmt.Sprintf("Error while creating the snapshot `%s`", args[0]),
16+
err,
17+
resp,
18+
)
19+
}
20+
21+
full := GetBoolFlags(cmd, "full")
22+
getSnapshotReply := NewGetSnapshotReply(ctx.Mapper, &koyeb.GetSnapshotReply{Snapshot: res.Snapshot}, full)
23+
ctx.Renderer.Render(getSnapshotReply)
24+
return nil
25+
}

pkg/koyeb/snapshots_delete.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package koyeb
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
7+
log "github.com/sirupsen/logrus"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func (h *SnapshotHandler) Delete(ctx *CLIContext, cmd *cobra.Command, args []string) error {
12+
snapshot, err := ResolveSnapshotArgs(ctx, args[0])
13+
if err != nil {
14+
return err
15+
}
16+
17+
_, resp, err := ctx.Client.SnapshotsApi.DeleteSnapshot(ctx.Context, snapshot).Execute()
18+
if err != nil {
19+
return errors.NewCLIErrorFromAPIError(
20+
fmt.Sprintf("Error while deleting the snapshot `%s`", args[0]),
21+
err,
22+
resp,
23+
)
24+
}
25+
log.Infof("Snapshot %s deleted.", args[0])
26+
return nil
27+
}

0 commit comments

Comments
 (0)