Skip to content

Commit

Permalink
Adding cmd for creating postgres addon (#378)
Browse files Browse the repository at this point in the history
* Adding cmd for creating postgres addon

* fix cluster addon ls for postgres

* Include postgres addon data

* fix descriptions

* version can default to latest

* use default error

* removing username and password

* unhide addons
  • Loading branch information
jdewinne authored Apr 17, 2024
1 parent a81be10 commit 03c7449
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 6 deletions.
5 changes: 2 additions & 3 deletions cli/cmd/cluster_addon.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import (

func (r *runners) InitClusterAddon(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "addon",
Short: "Manage cluster add-ons",
Hidden: true, // this feature is not fully implemented and controlled behind a feature toggle in the api until ready
Use: "addon",
Short: "Manage cluster add-ons",
}
parent.AddCommand(cmd)

Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/cluster_addon_create_objectstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (r *runners) createAndWaitForClusterAddonCreateObjectStore(opts kotsclient.
return addon, nil
}

// if the wait flag was provided, we poll the api until the cluster is ready, or a timeout
// if the wait flag was provided, we poll the api until the addon is ready, or a timeout
if waitDuration > 0 {
return waitForAddon(r.kotsAPI, opts.ClusterID, addon.ID, waitDuration)
}
Expand Down
103 changes: 103 additions & 0 deletions cli/cmd/cluster_addon_create_postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cmd

import (
"fmt"
"os"
"time"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/cli/print"
"github.com/replicatedhq/replicated/pkg/kotsclient"
"github.com/replicatedhq/replicated/pkg/platformclient"
"github.com/replicatedhq/replicated/pkg/types"
"github.com/spf13/cobra"
)

type clusterAddonCreatePostgresArgs struct {
version string
diskGiB int64
instanceType string

clusterID string
waitDuration time.Duration
dryRun bool
outputFormat string
}

func (r *runners) InitClusterAddonCreatePostgres(parent *cobra.Command) *cobra.Command {
args := clusterAddonCreatePostgresArgs{}

cmd := &cobra.Command{
Use: "postgres CLUSTER_ID",
Short: "Create a Postgres database for a cluster",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, cmdArgs []string) error {
args.clusterID = cmdArgs[0]
return r.clusterAddonCreatePostgresCreateRun(args)
},
}
parent.AddCommand(cmd)

_ = clusterAddonCreatePostgresFlags(cmd, &args)

return cmd
}

func clusterAddonCreatePostgresFlags(cmd *cobra.Command, args *clusterAddonCreatePostgresArgs) error {
cmd.Flags().StringVar(&args.version, "version", "", "The Postgres version to create")
cmd.Flags().Int64Var(&args.diskGiB, "disk", 200, "Disk Size (GiB) for the Postgres database")
cmd.Flags().StringVar(&args.instanceType, "instance-type", "db.t3.micro", "The type of instance to use for the Postgres database")

cmd.Flags().DurationVar(&args.waitDuration, "wait", 0, "Wait duration for add-on to be ready before exiting (leave empty to not wait)")
cmd.Flags().BoolVar(&args.dryRun, "dry-run", false, "Simulate creation to verify that your inputs are valid without actually creating an add-on")
cmd.Flags().StringVar(&args.outputFormat, "output", "table", "The output format to use. One of: json|table|wide (default: table)")
return nil
}

func (r *runners) clusterAddonCreatePostgresCreateRun(args clusterAddonCreatePostgresArgs) error {
opts := kotsclient.CreateClusterAddonPostgresOpts{
ClusterID: args.clusterID,
Version: args.version,
DiskGiB: args.diskGiB,
InstanceType: args.instanceType,
DryRun: args.dryRun,
}

addon, err := r.createAndWaitForClusterAddonCreatePostgres(opts, args.waitDuration)
if err != nil {
if errors.Cause(err) == ErrWaitDurationExceeded {
defer func() {
os.Exit(124)
}()
} else {
return err
}
}

if opts.DryRun {
_, err := fmt.Fprintln(r.w, "Dry run succeeded.")
return err
}

return print.Addon(args.outputFormat, r.w, addon)
}

func (r *runners) createAndWaitForClusterAddonCreatePostgres(opts kotsclient.CreateClusterAddonPostgresOpts, waitDuration time.Duration) (*types.ClusterAddon, error) {
addon, err := r.kotsAPI.CreateClusterAddonPostgres(opts)
if errors.Cause(err) == platformclient.ErrForbidden {
return nil, ErrCompatibilityMatrixTermsNotAccepted
} else if err != nil {
return nil, errors.Wrap(err, "create cluster add-on postgres")
}

if opts.DryRun {
return addon, nil
}

// if the wait flag was provided, we poll the api until the add-on is ready, or a timeout
if waitDuration > 0 {
return waitForAddon(r.kotsAPI, opts.ClusterID, addon.ID, waitDuration)
}

return addon, nil
}
1 change: 1 addition & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
runCmds.InitClusterAddonRm(clusterAddonCmd)
clusterAddonCreateCmd := runCmds.InitClusterAddonCreate(clusterAddonCmd)
runCmds.InitClusterAddonCreateObjectStore(clusterAddonCreateCmd)
runCmds.InitClusterAddonCreatePostgres(clusterAddonCreateCmd)

clusterPortCmd := runCmds.InitClusterPort(clusterCmd)
runCmds.InitClusterPortLs(clusterPortCmd)
Expand Down
10 changes: 10 additions & 0 deletions cli/print/cluster_addons.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func addonData(addon *types.ClusterAddon) string {
switch {
case addon.ObjectStore != nil:
return addonObjectStoreData(*addon.ObjectStore)
case addon.Postgres != nil:
return addonPostgresData(*addon.Postgres)
default:
return ""
}
Expand All @@ -93,3 +95,11 @@ func addonObjectStoreData(data types.ClusterAddonObjectStore) string {
}
return string(b)
}

func addonPostgresData(data types.ClusterAddonPostgres) string {
b, err := json.Marshal(data)
if err != nil {
log.Printf("failed to marshal postgres data: %v", err)
}
return string(b)
}
4 changes: 2 additions & 2 deletions pkg/kotsclient/cluster_addon_objectstore_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type CreateClusterAddonObjectStoreResponse struct {
}

type CreateClusterAddonErrorResponse struct {
Error string `json:"error"`
Message string `json:"message"`
}

func (c *VendorV3Client) CreateClusterAddonObjectStore(opts CreateClusterAddonObjectStoreOpts) (*types.ClusterAddon, error) {
Expand All @@ -51,7 +51,7 @@ func (c *VendorV3Client) doCreateClusterAddonObjectStoreRequest(clusterID string
if jsonErr := json.Unmarshal(apiErr.Body, errResp); jsonErr != nil {
return nil, fmt.Errorf("unmarshal error response: %w", err)
}
return nil, errors.New(errResp.Error)
return nil, errors.New(errResp.Message)
}
}

Expand Down
60 changes: 60 additions & 0 deletions pkg/kotsclient/cluster_addon_postgres_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package kotsclient

import (
"encoding/json"
"fmt"
"net/http"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/pkg/platformclient"
"github.com/replicatedhq/replicated/pkg/types"
)

type CreateClusterAddonPostgresOpts struct {
ClusterID string
Version string
DiskGiB int64
InstanceType string
DryRun bool
}

type CreateClusterAddonPostgresRequest struct {
Version string `json:"version"`
DiskGiB int64 `json:"disk_gib"`
InstanceType string `json:"instance_type"`
}

func (c *VendorV3Client) CreateClusterAddonPostgres(opts CreateClusterAddonPostgresOpts) (*types.ClusterAddon, error) {
req := CreateClusterAddonPostgresRequest{
Version: opts.Version,
DiskGiB: opts.DiskGiB,
InstanceType: opts.InstanceType,
}
return c.doCreateClusterAddonPostgresRequest(opts.ClusterID, req, opts.DryRun)
}

func (c *VendorV3Client) doCreateClusterAddonPostgresRequest(clusterID string, req CreateClusterAddonPostgresRequest, dryRun bool) (*types.ClusterAddon, error) {
resp := CreateClusterAddonObjectStoreResponse{}
endpoint := fmt.Sprintf("/v3/cluster/%s/addons/postgres", clusterID)
if dryRun {
endpoint = fmt.Sprintf("%s?dry-run=true", endpoint)
}
err := c.DoJSON("POST", endpoint, http.StatusCreated, req, &resp)
if err != nil {
// if err is APIError and the status code is 400, then we have a validation error
// and we can return the validation error
if apiErr, ok := errors.Cause(err).(platformclient.APIError); ok {
if apiErr.StatusCode == http.StatusBadRequest {
errResp := &CreateClusterAddonErrorResponse{}
if jsonErr := json.Unmarshal(apiErr.Body, errResp); jsonErr != nil {
return nil, fmt.Errorf("unmarshal error response: %w", err)
}
return nil, errors.New(errResp.Message)
}
}

return nil, err
}

return resp.Addon, nil
}
11 changes: 11 additions & 0 deletions pkg/types/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type ClusterAddon struct {
CreatedAt time.Time `json:"created_at"`

ObjectStore *ClusterAddonObjectStore `json:"object_store,omitempty"`
Postgres *ClusterAddonPostgres `json:"postgres,omitempty"`
}

type ClusterAddonObjectStore struct {
Expand All @@ -92,10 +93,20 @@ type ClusterAddonObjectStore struct {
ServiceAccountNameReadOnly string `json:"service_account_name_read_only,omitempty"`
}

type ClusterAddonPostgres struct {
Version string `json:"version"`
DiskGiB int64 `json:"disk_gib"`
InstanceType string `json:"instance_type"`

URI string `json:"uri,omitempty"`
}

func (addon *ClusterAddon) TypeName() string {
switch {
case addon.ObjectStore != nil:
return "Object Store"
case addon.Postgres != nil:
return "Postgres"
default:
return "Unknown"
}
Expand Down

0 comments on commit 03c7449

Please sign in to comment.