Skip to content

Commit

Permalink
Implement koyeb db get
Browse files Browse the repository at this point in the history
For now, db get has the same output as db list. We will display more
info in future commits.
  • Loading branch information
brmzkw committed Nov 14, 2023
1 parent df6d6d5 commit 492d39f
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 7 deletions.
15 changes: 14 additions & 1 deletion pkg/koyeb/databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ func NewDatabaseCmd() *cobra.Command {
}
databaseCmd.AddCommand(listDbCmd)

getDbCmd := &cobra.Command{
Use: "get NAME",
Short: "Get database",
Args: cobra.ExactArgs(1),
RunE: WithCLIContext(h.Get),
}
databaseCmd.AddCommand(getDbCmd)

return databaseCmd
}

Expand All @@ -29,5 +37,10 @@ type DatabaseHandler struct {
}

func (h *DatabaseHandler) ResolveDatabaseArgs(ctx *CLIContext, val string) (string, error) {
return "", nil
databaseMapper := ctx.Mapper.Database()
id, err := databaseMapper.ResolveID(val)
if err != nil {
return "", err
}
return id, nil
}
112 changes: 112 additions & 0 deletions pkg/koyeb/databases_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package koyeb

import (
"fmt"
"strconv"

"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
"github.com/koyeb/koyeb-cli/pkg/koyeb/idmapper"
"github.com/koyeb/koyeb-cli/pkg/koyeb/renderer"
"github.com/spf13/cobra"
)

// DatabaseListItemInfo wraps a service returned by the services API and it's latest deployment.
type DatabaseInfo struct {
Service koyeb.Service `json:"service"`
Deployment koyeb.Deployment `json:"deployment"`
}

func (h *DatabaseHandler) Get(ctx *CLIContext, cmd *cobra.Command, args []string) error {
database, err := h.ResolveDatabaseArgs(ctx, args[0])
if err != nil {
return err
}

resService, resp, err := ctx.Client.ServicesApi.GetService(ctx.Context, database).Execute()
if err != nil {
return errors.NewCLIErrorFromAPIError(
fmt.Sprintf("Error while retrieving the database `%s`", args[0]),
err,
resp,
)
}

resDeployment, resp, err := ctx.Client.DeploymentsApi.GetDeployment(ctx.Context, resService.Service.GetLatestDeploymentId()).Execute()
if err != nil {
return errors.NewCLIErrorFromAPIError(
fmt.Sprintf("Error while fetching the deployment for the database service `%s`", resService.Service.GetId()),
err,
resp,
)
}

full := GetBoolFlags(cmd, "full")
getDatabaseReply := NewGetDatabaseReply(
ctx.Mapper,
DatabaseInfo{Service: resService.GetService(), Deployment: resDeployment.GetDeployment()},
full,
)
ctx.Renderer.Render(getDatabaseReply)
return nil
}

type GetDatabaseReply struct {
mapper *idmapper.Mapper
value DatabaseInfo
full bool
}

func NewGetDatabaseReply(mapper *idmapper.Mapper, value DatabaseInfo, full bool) *GetDatabaseReply {
return &GetDatabaseReply{
mapper: mapper,
value: value,
full: full,
}
}

func (GetDatabaseReply) Title() string {
return "Database"
}

func (r *GetDatabaseReply) MarshalBinary() ([]byte, error) {
return r.value.Service.MarshalJSON()
}

func (r *GetDatabaseReply) Headers() []string {
return []string{"id", "name", "region", "engine", "status", "active_time", "used_storage", "created_at"}
}

func (r *GetDatabaseReply) Fields() []map[string]string {
var region, engine, activeTime, usedStorage string

// At the moment, we only support neon postgres so the if statement is
// always true. If we add support for other providers in the future, the statement
// will prevent a nil pointer dereference.
if r.value.Deployment.DatabaseInfo.HasNeonPostgres() {
region = r.value.Deployment.Definition.GetDatabase().NeonPostgres.GetRegion()
engine = fmt.Sprintf("Postgres %d", r.value.Deployment.Definition.GetDatabase().NeonPostgres.GetPgVersion())

size, _ := strconv.Atoi(r.value.Deployment.DatabaseInfo.NeonPostgres.GetDefaultBranchLogicalSize())
// Convert to MB
size = size / 1024 / 1024
// The maximum size is 3GB and is not configurable yet.
maxSize := 3 * 1024
activeTimeValue, _ := strconv.ParseFloat(r.value.Deployment.DatabaseInfo.NeonPostgres.GetActiveTimeSeconds(), 32)
// The maximum active time is 100h and is not configurable yet.
activeTime = fmt.Sprintf("%.1fh/100h", activeTimeValue/60/60)
usedStorage = fmt.Sprintf("%dMB/%dMB (%d%%)", size, maxSize, size*100/maxSize)
}

fields := map[string]string{
"id": renderer.FormatID(r.value.Service.GetId(), r.full),
"name": r.value.Service.GetName(),
"region": region,
"engine": engine,
"status": formatServiceStatus(r.value.Service.GetStatus()),
"active_time": activeTime,
"used_storage": usedStorage,
"created_at": renderer.FormatTime(r.value.Service.GetCreatedAt()),
}
return []map[string]string{fields}
}
12 changes: 6 additions & 6 deletions pkg/koyeb/databases_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import (
"github.com/spf13/cobra"
)

// DatabaseInfo wraps a service returned by the services API and it's latest deployment.
type DatabaseInfo struct {
// DatabaseListItemInfo wraps a service returned by the services API and it's latest deployment.
type DatabaseListItemInfo struct {
Service koyeb.ServiceListItem `json:"service"`
Deployment koyeb.Deployment `json:"deployment"`
}

func (h *DatabaseHandler) List(ctx *CLIContext, cmd *cobra.Command, args []string) error {
list := []DatabaseInfo{}
list := []DatabaseListItemInfo{}

page := int64(0)
offset := int64(0)
Expand Down Expand Up @@ -48,7 +48,7 @@ func (h *DatabaseHandler) List(ctx *CLIContext, cmd *cobra.Command, args []strin
)
}

list = append(list, DatabaseInfo{
list = append(list, DatabaseListItemInfo{
Service: svc,
Deployment: *res.Deployment,
})
Expand All @@ -69,11 +69,11 @@ func (h *DatabaseHandler) List(ctx *CLIContext, cmd *cobra.Command, args []strin

type ListDatabasesReply struct {
mapper *idmapper.Mapper
databases []DatabaseInfo
databases []DatabaseListItemInfo
full bool
}

func NewListDatabasesReply(mapper *idmapper.Mapper, databases []DatabaseInfo, full bool) *ListDatabasesReply {
func NewListDatabasesReply(mapper *idmapper.Mapper, databases []DatabaseListItemInfo, full bool) *ListDatabasesReply {
return &ListDatabasesReply{
mapper: mapper,
databases: databases,
Expand Down
90 changes: 90 additions & 0 deletions pkg/koyeb/idmapper/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package idmapper

import (
"context"
"strconv"

"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
"github.com/koyeb/koyeb-cli/pkg/koyeb/errors"
)

type DatabaseMapper struct {
ctx context.Context
client *koyeb.APIClient
fetched bool
sidMap *IDMap
nameMap *IDMap
}

func NewDatabaseMapper(ctx context.Context, client *koyeb.APIClient) *DatabaseMapper {
return &DatabaseMapper{
ctx: ctx,
client: client,
fetched: false,
sidMap: NewIDMap(),
nameMap: NewIDMap(),
}
}

func (mapper *DatabaseMapper) ResolveID(val string) (string, error) {
if IsUUIDv4(val) {
return val, nil
}

if !mapper.fetched {
err := mapper.fetch()
if err != nil {
return "", err
}
}

id, ok := mapper.sidMap.GetID(val)
if ok {
return id, nil
}

id, ok = mapper.nameMap.GetID(val)
if ok {
return id, nil
}

return "", errors.NewCLIErrorForMapperResolve(
"database",
val,
[]string{"database full UUID", "database short ID (8 characters)", "database name"},
)
}

func (mapper *DatabaseMapper) fetch() error {
page := int64(0)
offset := int64(0)
limit := int64(100)
for {
res, resp, err := mapper.client.ServicesApi.ListServices(mapper.ctx).
Types([]string{"DATABASE"}).
Limit(strconv.FormatInt(limit, 10)).
Offset(strconv.FormatInt(offset, 10)).
Execute()
if err != nil {
return errors.NewCLIErrorFromAPIError(
"Error listing databases to resolve the provided identifier to an object ID",
err,
resp,
)
}

for _, service := range res.GetServices() {
mapper.sidMap.Set(service.GetId(), getShortID(service.GetId(), 8))
mapper.nameMap.Set(service.GetId(), service.GetName())
}

page++
offset = page * limit
if offset >= res.GetCount() {
break
}
}

mapper.fetched = true
return nil
}
7 changes: 7 additions & 0 deletions pkg/koyeb/idmapper/idmapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Mapper struct {
instance *InstanceMapper
secret *SecretMapper
organization *OrganizationMapper
database *DatabaseMapper
}

func NewMapper(ctx context.Context, client *koyeb.APIClient) *Mapper {
Expand All @@ -26,6 +27,7 @@ func NewMapper(ctx context.Context, client *koyeb.APIClient) *Mapper {
instanceMapper := NewInstanceMapper(ctx, client)
secretMapper := NewSecretMapper(ctx, client)
organizationMapper := NewOrganizationMapper(ctx, client)
databaseMapper := NewDatabaseMapper(ctx, client)

return &Mapper{
app: appMapper,
Expand All @@ -36,6 +38,7 @@ func NewMapper(ctx context.Context, client *koyeb.APIClient) *Mapper {
instance: instanceMapper,
secret: secretMapper,
organization: organizationMapper,
database: databaseMapper,
}
}

Expand Down Expand Up @@ -70,3 +73,7 @@ func (mapper *Mapper) Secret() *SecretMapper {
func (mapper *Mapper) Organization() *OrganizationMapper {
return mapper.organization
}

func (mapper *Mapper) Database() *DatabaseMapper {
return mapper.database
}

0 comments on commit 492d39f

Please sign in to comment.