diff --git a/cli/cmd/cluster_addon.go b/cli/cmd/cluster_addon.go index 3ef407703..168a92d6b 100644 --- a/cli/cmd/cluster_addon.go +++ b/cli/cmd/cluster_addon.go @@ -1,15 +1,44 @@ package cmd import ( + "time" + + "github.com/pkg/errors" + "github.com/replicatedhq/replicated/pkg/kotsclient" + "github.com/replicatedhq/replicated/pkg/types" "github.com/spf13/cobra" ) -func (r *runners) InitClusterAddOn(parent *cobra.Command) *cobra.Command { +func (r *runners) InitClusterAddon(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "addon", + Short: "Manage cluster addons", Hidden: true, // this feature is not fully implemented and controlled behind a feature toggle in the api until ready } parent.AddCommand(cmd) return cmd } + +func waitForAddon(kotsRestClient *kotsclient.VendorV3Client, clusterID, id string, duration time.Duration) (*types.ClusterAddon, error) { + start := time.Now() + for { + addon, err := kotsRestClient.GetClusterAddon(clusterID, id) + if err != nil { + return nil, errors.Wrap(err, "get cluster addon") + } + + if addon.Status == types.ClusterAddonStatusRunning { + return addon, nil + } else if addon.Status == types.ClusterAddonStatusError { + return nil, errors.New("cluster addon failed to provision") + } else { + if time.Now().After(start.Add(duration)) { + // In case of timeout, return the cluster and a WaitDurationExceeded error + return addon, ErrWaitDurationExceeded + } + } + + time.Sleep(time.Second * 5) + } +} diff --git a/cli/cmd/cluster_addon_create.go b/cli/cmd/cluster_addon_create.go new file mode 100644 index 000000000..6bffd9ae6 --- /dev/null +++ b/cli/cmd/cluster_addon_create.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func (r *runners) InitClusterAddonCreate(parent *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Create cluster addons", + } + parent.AddCommand(cmd) + + return cmd +} diff --git a/cli/cmd/cluster_addon_create_objectstore.go b/cli/cmd/cluster_addon_create_objectstore.go new file mode 100644 index 000000000..bc26a49e2 --- /dev/null +++ b/cli/cmd/cluster_addon_create_objectstore.go @@ -0,0 +1,99 @@ +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 clusterAddonCreateObjectStoreArgs struct { + objectStoreBucket string + clusterID string + waitDuration time.Duration + dryRun bool + outputFormat string +} + +func (r *runners) InitClusterAddonCreateObjectStore(parent *cobra.Command) *cobra.Command { + args := clusterAddonCreateObjectStoreArgs{} + + cmd := &cobra.Command{ + Use: "object-store CLUSTER_ID --bucket BUCKET_NAME", + Short: "Create an object store for a cluster", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, cmdArgs []string) error { + args.clusterID = cmdArgs[0] + return r.clusterAddonCreateObjectStoreCreateRun(args) + }, + } + parent.AddCommand(cmd) + + _ = clusterAddonCreateObjectStoreFlags(cmd, &args) + + return cmd +} + +func clusterAddonCreateObjectStoreFlags(cmd *cobra.Command, args *clusterAddonCreateObjectStoreArgs) error { + cmd.Flags().StringVar(&args.objectStoreBucket, "bucket", "", "The object store bucket name to create (required)") + err := cmd.MarkFlagRequired("bucket") + if err != nil { + return err + } + cmd.Flags().DurationVar(&args.waitDuration, "wait", 0, "Wait duration for addon to be ready (leave empty to not wait)") + cmd.Flags().BoolVar(&args.dryRun, "dry-run", false, "Dry run") + cmd.Flags().StringVar(&args.outputFormat, "output", "table", "The output format to use. One of: json|table|wide (default: table)") + return nil +} + +func (r *runners) clusterAddonCreateObjectStoreCreateRun(args clusterAddonCreateObjectStoreArgs) error { + opts := kotsclient.CreateClusterAddonObjectStoreOpts{ + ClusterID: args.clusterID, + Bucket: args.objectStoreBucket, + DryRun: args.dryRun, + } + + addon, err := r.createAndWaitForClusterAddonCreateObjectStore(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) createAndWaitForClusterAddonCreateObjectStore(opts kotsclient.CreateClusterAddonObjectStoreOpts, waitDuration time.Duration) (*types.ClusterAddon, error) { + addon, err := r.kotsAPI.CreateClusterAddonObjectStore(opts) + if errors.Cause(err) == platformclient.ErrForbidden { + return nil, ErrCompatibilityMatrixTermsNotAccepted + } else if err != nil { + return nil, errors.Wrap(err, "create cluster addon object store") + } + + if opts.DryRun { + return addon, nil + } + + // if the wait flag was provided, we poll the api until the cluster is ready, or a timeout + if waitDuration > 0 { + return waitForAddon(r.kotsAPI, opts.ClusterID, addon.ID, waitDuration) + } + + return addon, nil +} diff --git a/cli/cmd/cluster_addon_ingress.go b/cli/cmd/cluster_addon_ingress.go deleted file mode 100644 index b56cf3f41..000000000 --- a/cli/cmd/cluster_addon_ingress.go +++ /dev/null @@ -1,14 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -func (r *runners) InitClusterAddOnIngress(parent *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "ingress", - } - parent.AddCommand(cmd) - - return cmd -} diff --git a/cli/cmd/cluster_addon_ingress_create.go b/cli/cmd/cluster_addon_ingress_create.go deleted file mode 100644 index 998d58d44..000000000 --- a/cli/cmd/cluster_addon_ingress_create.go +++ /dev/null @@ -1,73 +0,0 @@ -package cmd - -import ( - "os" - - "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" -) - -func (r *runners) InitClusterAddOnIngressCreate(parent *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "create --target svc/my-service --port ", - RunE: r.ingressClusterCreate, - Args: cobra.ExactArgs(1), - } - parent.AddCommand(cmd) - - cmd.Flags().StringVar(&r.args.clusterCreateIngressTarget, "target", "", "The target service name the ingress should route to") - cmd.MarkFlagRequired("target") - - cmd.Flags().IntVar(&r.args.clusterCreateIngressPort, "port", 0, "The target port number that the ingress should route to") - cmd.MarkFlagRequired("port") - - cmd.Flags().StringVar(&r.args.clusterCreateIngressNamespace, "namespace", "default", "The namespace the target service is in") - - cmd.Flags().StringVar(&r.outputFormat, "output", "table", "The output format to use. One of: json|table (default: table)") - - return cmd -} - -func (r *runners) ingressClusterCreate(_ *cobra.Command, args []string) error { - clusterID := args[0] - - namespace := r.args.clusterCreateIngressNamespace - if namespace == "" { - namespace = "default" // avoiding the entire k8s dep list - } - - opts := kotsclient.CreateClusterIngressOpts{ - ClusterID: clusterID, - Target: r.args.clusterCreateIngressTarget, - Port: r.args.clusterCreateIngressPort, - Namespace: namespace, - } - - ing, err := r.createAndWaitForIngress(opts) - if err != nil { - if errors.Cause(err) == ErrWaitDurationExceeded { - defer func() { - os.Exit(124) - }() - } else { - return err - } - } - - return print.AddOn(r.outputFormat, r.w, ing) -} - -func (r *runners) createAndWaitForIngress(opts kotsclient.CreateClusterIngressOpts) (*types.ClusterAddOn, error) { - ing, err := r.kotsAPI.CreateClusterIngress(opts) - if errors.Cause(err) == platformclient.ErrForbidden { - return nil, ErrCompatibilityMatrixTermsNotAccepted - } else if err != nil { - return nil, errors.Wrap(err, "create cluster ingress") - } - - return ing, nil -} diff --git a/cli/cmd/cluster_addon_ls.go b/cli/cmd/cluster_addon_ls.go index 78c9515e4..67109cf3d 100644 --- a/cli/cmd/cluster_addon_ls.go +++ b/cli/cmd/cluster_addon_ls.go @@ -5,21 +5,40 @@ import ( "github.com/spf13/cobra" ) -func (r *runners) InitClusterAddOnLs(parent *cobra.Command) *cobra.Command { +type clusterAddonLsArgs struct { + clusterID string + outputFormat string +} + +func (r *runners) InitClusterAddonLs(parent *cobra.Command) *cobra.Command { + args := clusterAddonLsArgs{} + cmd := &cobra.Command{ - Use: "ls", - RunE: r.addOnClusterLs, + Use: "ls CLUSTER_ID", + Short: "List cluster addons for a cluster", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, cmdArgs []string) error { + args.clusterID = cmdArgs[0] + return r.addonClusterLsRun(args) + }, } parent.AddCommand(cmd) + _ = clusterAddonLsFlags(cmd, &args) + return cmd } -func (r *runners) addOnClusterLs(_ *cobra.Command, args []string) error { - addons, err := r.kotsAPI.ListClusterAddOns() +func clusterAddonLsFlags(cmd *cobra.Command, args *clusterAddonLsArgs) error { + cmd.Flags().StringVar(&args.outputFormat, "output", "table", "The output format to use. One of: json|table|wide (default: table)") + return nil +} + +func (r *runners) addonClusterLsRun(args clusterAddonLsArgs) error { + addons, err := r.kotsAPI.ListClusterAddons(args.clusterID) if err != nil { return err } - return print.AddOns(r.outputFormat, r.w, addons, true) + return print.Addons(r.outputFormat, r.w, addons, true) } diff --git a/cli/cmd/cluster_addon_rm.go b/cli/cmd/cluster_addon_rm.go index d8f6064ce..a2d4fcfbd 100644 --- a/cli/cmd/cluster_addon_rm.go +++ b/cli/cmd/cluster_addon_rm.go @@ -1,25 +1,50 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" ) -func (r *runners) InitClusterAddOnRm(parent *cobra.Command) *cobra.Command { +type clusterAddonRmArgs struct { + id string + clusterID string +} + +func (r *runners) InitClusterAddonRm(parent *cobra.Command) *cobra.Command { + args := clusterAddonRmArgs{} + cmd := &cobra.Command{ - Use: "rm", - RunE: r.addOnClusterRm, - Args: cobra.ExactArgs(1), + Use: "rm CLUSTER_ID --id ADDON_ID", + Short: "Remove cluster addon by ID", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, cmdArgs []string) error { + args.clusterID = cmdArgs[0] + return r.clusterAddonRmRun(args) + }, } parent.AddCommand(cmd) + _ = clusterAddonRmFlags(cmd, &args) + return cmd } -func (r *runners) addOnClusterRm(_ *cobra.Command, args []string) error { - err := r.kotsAPI.DeleteClusterAddOn(args[0]) +func clusterAddonRmFlags(cmd *cobra.Command, args *clusterAddonRmArgs) error { + cmd.Flags().StringVar(&args.id, "id", "", "The ID of the cluster addon to remove (required)") + err := cmd.MarkFlagRequired("id") if err != nil { return err } - return nil } + +func (r *runners) clusterAddonRmRun(args clusterAddonRmArgs) error { + err := r.kotsAPI.DeleteClusterAddon(args.clusterID, args.id) + if err != nil { + return err + } + + _, err = fmt.Fprintf(r.w, "Addon %s has been deleted\n", args.id) + return err +} diff --git a/cli/cmd/cluster_port_expose.go b/cli/cmd/cluster_port_expose.go index eeace2a46..c601dd4fc 100644 --- a/cli/cmd/cluster_port_expose.go +++ b/cli/cmd/cluster_port_expose.go @@ -8,7 +8,7 @@ import ( func (r *runners) InitClusterPortExpose(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ - Use: "expose --port --protocol ", + Use: "expose CLUSTER_ID --port PORT --protocol PROTOCOL", RunE: r.clusterPortExpose, Args: cobra.ExactArgs(1), } diff --git a/cli/cmd/cluster_port_ls.go b/cli/cmd/cluster_port_ls.go index 75199b76f..4db9a680d 100644 --- a/cli/cmd/cluster_port_ls.go +++ b/cli/cmd/cluster_port_ls.go @@ -7,7 +7,7 @@ import ( func (r *runners) InitClusterPortLs(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ - Use: "ls", + Use: "ls CLUSTER_ID", RunE: r.clusterPortList, Args: cobra.ExactArgs(1), } diff --git a/cli/cmd/cluster_port_rm.go b/cli/cmd/cluster_port_rm.go index 815c55fd3..00aa90959 100644 --- a/cli/cmd/cluster_port_rm.go +++ b/cli/cmd/cluster_port_rm.go @@ -8,7 +8,7 @@ import ( func (r *runners) InitClusterPortRm(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ - Use: "rm", + Use: "rm CLUSTER_ID", RunE: r.clusterPortRemove, Args: cobra.ExactArgs(1), } diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 464fa0b2b..a050b0a3d 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -224,12 +224,11 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i clusterNodeGroupCmd := runCmds.InitClusterNodeGroup(clusterCmd) runCmds.InitClusterNodeGroupList(clusterNodeGroupCmd) - clusterAddOnCmd := runCmds.InitClusterAddOn(clusterCmd) - runCmds.InitClusterAddOnRm(clusterAddOnCmd) - runCmds.InitClusterAddOnLs(clusterAddOnCmd) - - clusterAddOnIngressCmd := runCmds.InitClusterAddOnIngress(clusterAddOnCmd) - runCmds.InitClusterAddOnIngressCreate(clusterAddOnIngressCmd) + clusterAddonCmd := runCmds.InitClusterAddon(clusterCmd) + runCmds.InitClusterAddonLs(clusterAddonCmd) + runCmds.InitClusterAddonRm(clusterAddonCmd) + clusterAddonCreateCmd := runCmds.InitClusterAddonCreate(clusterAddonCmd) + runCmds.InitClusterAddonCreateObjectStore(clusterAddonCreateCmd) clusterPortCmd := runCmds.InitClusterPort(clusterCmd) runCmds.InitClusterPortLs(clusterPortCmd) diff --git a/cli/cmd/runner.go b/cli/cmd/runner.go index f29a3a468..86407df79 100644 --- a/cli/cmd/runner.go +++ b/cli/cmd/runner.go @@ -231,10 +231,6 @@ type runnerArgs struct { shellClusterName string shellClusterID string - clusterCreateIngressTarget string - clusterCreateIngressPort int - clusterCreateIngressNamespace string - clusterExposePortPort int clusterExposePortProtocols []string diff --git a/cli/print/cluster_addons.go b/cli/print/cluster_addons.go index 10d7a9832..85c29087d 100644 --- a/cli/print/cluster_addons.go +++ b/cli/print/cluster_addons.go @@ -9,28 +9,48 @@ import ( "github.com/replicatedhq/replicated/pkg/types" ) -var addOnsTmplHeaderSrc = `ID TYPE STATUS NOTES` -var addOnsTmplRowSrc = `{{ range . -}} -{{ .ID }} {{ if .Ingress }}Ingress{{ else }}Other{{ end }} {{ printf "%-12s" .State }} {{ if .Ingress }}http[s]://{{ .Ingress.Hostname }}{{ end }} +var addonsTmplHeaderSrc = `ID TYPE STATUS DATA` +var addonsTmplRowSrc = `{{ range . -}} +{{ .ID }} {{ Type . }} {{ printf "%-12s" .Status }} {{ Data . }} {{ end }}` -var addOnsTmplSrc = fmt.Sprintln(addOnsTmplHeaderSrc) + addOnsTmplRowSrc -var addOnsTmpl = template.Must(template.New("ingresses").Funcs(funcs).Parse(addOnsTmplSrc)) -var addOnsTmplNoHeader = template.Must(template.New("ingresses").Funcs(funcs).Parse(addOnsTmplRowSrc)) +var addonsTmplSrc = fmt.Sprintln(addonsTmplHeaderSrc) + addonsTmplRowSrc +var addonsTmpl = template.Must(template.New("addons").Funcs(addonsFuncs).Parse(addonsTmplSrc)) +var addonsTmplNoHeader = template.Must(template.New("addons").Funcs(addonsFuncs).Parse(addonsTmplRowSrc)) -func AddOns(outputFormat string, w *tabwriter.Writer, addOns []*types.ClusterAddOn, header bool) error { +var addonsFuncs = template.FuncMap{ + "Type": func(addon *types.ClusterAddon) string { + return addon.TypeName() + }, + "Data": func(addon *types.ClusterAddon) string { + switch { + case addon.ObjectStore != nil: + return fmt.Sprintf("Bucket: %s", addon.ObjectStore.Bucket) + default: + return "" + } + }, +} + +func init() { + for k, v := range funcs { + addonsFuncs[k] = v + } +} + +func Addons(outputFormat string, w *tabwriter.Writer, addons []*types.ClusterAddon, header bool) error { switch outputFormat { - case "table": + case "table", "wide": if header { - if err := addOnsTmpl.Execute(w, addOns); err != nil { + if err := addonsTmpl.Execute(w, addons); err != nil { return err } } else { - if err := addOnsTmplNoHeader.Execute(w, addOns); err != nil { + if err := addonsTmplNoHeader.Execute(w, addons); err != nil { return err } } case "json": - cAsByte, err := json.MarshalIndent(addOns, "", " ") + cAsByte, err := json.MarshalIndent(addons, "", " ") if err != nil { return err } @@ -43,14 +63,14 @@ func AddOns(outputFormat string, w *tabwriter.Writer, addOns []*types.ClusterAdd return w.Flush() } -func AddOn(outputFormat string, w *tabwriter.Writer, addOn *types.ClusterAddOn) error { +func Addon(outputFormat string, w *tabwriter.Writer, addon *types.ClusterAddon) error { switch outputFormat { - case "table": - if err := addOnsTmpl.Execute(w, []*types.ClusterAddOn{addOn}); err != nil { + case "table", "wide": + if err := addonsTmpl.Execute(w, []*types.ClusterAddon{addon}); err != nil { return err } case "json": - cAsByte, err := json.MarshalIndent(addOn, "", " ") + cAsByte, err := json.MarshalIndent(addon, "", " ") if err != nil { return err } diff --git a/cli/print/cluster_nodegroups.go b/cli/print/cluster_nodegroups.go index cb15a358b..814bca675 100644 --- a/cli/print/cluster_nodegroups.go +++ b/cli/print/cluster_nodegroups.go @@ -16,14 +16,14 @@ var nodeGroupsTmplSrc = `ID NAME DEFAULT INSTANCE TYPE NODES DISK var nodeGroupsTmpl = template.Must(template.New("nodegroups").Parse(nodeGroupsTmplSrc)) -func NodeGroups(outputFormat string, w *tabwriter.Writer, addOns []*types.NodeGroup) error { +func NodeGroups(outputFormat string, w *tabwriter.Writer, nodeGroups []*types.NodeGroup) error { switch outputFormat { case "table", "wide": - if err := nodeGroupsTmpl.Execute(w, addOns); err != nil { + if err := nodeGroupsTmpl.Execute(w, nodeGroups); err != nil { return err } case "json": - cAsByte, err := json.MarshalIndent(addOns, "", " ") + cAsByte, err := json.MarshalIndent(nodeGroups, "", " ") if err != nil { return err } diff --git a/pkg/kotsclient/cluster_addon_get.go b/pkg/kotsclient/cluster_addon_get.go new file mode 100644 index 000000000..ca706b88b --- /dev/null +++ b/pkg/kotsclient/cluster_addon_get.go @@ -0,0 +1,34 @@ +package kotsclient + +import ( + "fmt" + + "github.com/replicatedhq/replicated/pkg/platformclient" + "github.com/replicatedhq/replicated/pkg/types" +) + +type GetClusterAddonResponse struct { + Addon *types.ClusterAddon `json:"addon"` +} + +func (c *VendorV3Client) GetClusterAddon(clusterID, id string) (*types.ClusterAddon, error) { + resp := GetClusterAddonResponse{} + + addons, err := c.ListClusterAddons(clusterID) + if err != nil { + return nil, fmt.Errorf("list cluster addons: %v", err) + } + + for _, addon := range addons { + if addon.ID == id { + resp.Addon = addon + break + } + } + + if resp.Addon == nil { + return nil, platformclient.ErrNotFound + } + + return resp.Addon, nil +} diff --git a/pkg/kotsclient/cluster_addon_objectstore_create.go b/pkg/kotsclient/cluster_addon_objectstore_create.go new file mode 100644 index 000000000..f9f81f3f1 --- /dev/null +++ b/pkg/kotsclient/cluster_addon_objectstore_create.go @@ -0,0 +1,62 @@ +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 CreateClusterAddonObjectStoreOpts struct { + ClusterID string + Bucket string + DryRun bool +} + +type CreateClusterAddonObjectStoreRequest struct { + Bucket string `json:"bucket"` +} + +type CreateClusterAddonObjectStoreResponse struct { + Addon *types.ClusterAddon `json:"addon"` +} + +type CreateClusterAddonErrorResponse struct { + Error string `json:"error"` +} + +func (c *VendorV3Client) CreateClusterAddonObjectStore(opts CreateClusterAddonObjectStoreOpts) (*types.ClusterAddon, error) { + req := CreateClusterAddonObjectStoreRequest{ + Bucket: opts.Bucket, + } + return c.doCreateClusterAddonObjectStoreRequest(opts.ClusterID, req, opts.DryRun) +} + +func (c *VendorV3Client) doCreateClusterAddonObjectStoreRequest(clusterID string, req CreateClusterAddonObjectStoreRequest, dryRun bool) (*types.ClusterAddon, error) { + resp := CreateClusterAddonObjectStoreResponse{} + endpoint := fmt.Sprintf("/v3/cluster/%s/addons/objectstore", 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.Error) + } + } + + return nil, err + } + + return resp.Addon, nil +} diff --git a/pkg/kotsclient/cluster_addon_rm.go b/pkg/kotsclient/cluster_addon_rm.go index 892619138..a763d4892 100644 --- a/pkg/kotsclient/cluster_addon_rm.go +++ b/pkg/kotsclient/cluster_addon_rm.go @@ -5,19 +5,13 @@ import ( "net/http" ) -type DeleteClusterAddOnRequest struct { +type DeleteClusterAddonRequest struct { ID string `json:"id"` } -type DeleteClusterAddOnResponse struct { - Error string `json:"error"` -} - -func (c *VendorV3Client) DeleteClusterAddOn(id string) error { - resp := DeleteClusterAddOnResponse{} - - url := fmt.Sprintf("/v3/cluster/addons/%s", id) - err := c.DoJSON("DELETE", url, http.StatusOK, nil, &resp) +func (c *VendorV3Client) DeleteClusterAddon(clusterID, addonID string) error { + endpoint := fmt.Sprintf("/v3/cluster/%s/addons/%s", clusterID, addonID) + err := c.DoJSON("DELETE", endpoint, http.StatusNoContent, nil, nil) if err != nil { return err } diff --git a/pkg/kotsclient/cluster_addons_list.go b/pkg/kotsclient/cluster_addons_list.go index 7f3590e81..75b6b82cf 100644 --- a/pkg/kotsclient/cluster_addons_list.go +++ b/pkg/kotsclient/cluster_addons_list.go @@ -1,21 +1,24 @@ package kotsclient import ( + "fmt" "net/http" "github.com/replicatedhq/replicated/pkg/types" ) -type ListClusterAddOnsResponse struct { - AddOns []*types.ClusterAddOn `json:"addons"` +type ListClusterAddonsResponse struct { + Addons []*types.ClusterAddon `json:"addons"` } -func (c *VendorV3Client) ListClusterAddOns() ([]*types.ClusterAddOn, error) { - resp := ListClusterAddOnsResponse{} - err := c.DoJSON("GET", "/v3/cluster/addons", http.StatusOK, nil, &resp) +func (c *VendorV3Client) ListClusterAddons(clusterID string) ([]*types.ClusterAddon, error) { + resp := ListClusterAddonsResponse{} + + endpoint := fmt.Sprintf("/v3/cluster/%s/addons", clusterID) + err := c.DoJSON("GET", endpoint, http.StatusOK, nil, &resp) if err != nil { return nil, err } - return resp.AddOns, nil + return resp.Addons, nil } diff --git a/pkg/kotsclient/cluster_ingress_create.go b/pkg/kotsclient/cluster_ingress_create.go deleted file mode 100644 index 31f583e23..000000000 --- a/pkg/kotsclient/cluster_ingress_create.go +++ /dev/null @@ -1,45 +0,0 @@ -package kotsclient - -import ( - "net/http" - - "github.com/replicatedhq/replicated/pkg/types" -) - -type CreateClusterIngressOpts struct { - ClusterID string - Target string - Port int - Namespace string -} - -type CreateClusterIngressRequest struct { - Target string `json:"target"` - Port int `json:"port"` - Namespace string `json:"namespace"` -} - -type CreateClusterIngressResponse struct { - Ingress *types.ClusterAddOn `json:"ingress"` -} - -func (c *VendorV3Client) CreateClusterIngress(opts CreateClusterIngressOpts) (*types.ClusterAddOn, error) { - req := CreateClusterIngressRequest{ - Target: opts.Target, - Port: opts.Port, - Namespace: opts.Namespace, - } - - return c.doCreateClusterIngressRequest(opts.ClusterID, req) -} - -func (c *VendorV3Client) doCreateClusterIngressRequest(clusterID string, req CreateClusterIngressRequest) (*types.ClusterAddOn, error) { - resp := CreateClusterIngressResponse{} - endpoint := "/v3/cluster/" + clusterID + "/addons/ingress" - err := c.DoJSON("POST", endpoint, http.StatusCreated, req, &resp) - if err != nil { - return nil, err - } - - return resp.Ingress, nil -} diff --git a/pkg/types/cluster.go b/pkg/types/cluster.go index 41674ae71..0cfa04017 100644 --- a/pkg/types/cluster.go +++ b/pkg/types/cluster.go @@ -64,30 +64,48 @@ type ClusterVersion struct { Status *ClusterDistributionStatus `json:"status,omitempty"` } -type AddOnState string +type ClusterAddonStatus string const ( - AddOnStatePending AddOnState = "pending" // No attempts to install this ingress yet - AddOnStateApplied AddOnState = "applied" // The ingress has been applied to the cluster - AddOnStateRunning AddOnState = "ready" // The ingress is ready to be used - AddOnStateError AddOnState = "error" // The ingress has an error - AddOnStateRemoving AddOnState = "removing" // The ingress is being removed - AddOnStateRemoved AddOnState = "removed" // The ingress has been removed + ClusterAddonStatusPending ClusterAddonStatus = "pending" // No attempts to install this addon + ClusterAddonStatusApplied ClusterAddonStatus = "applied" // The addon has been applied to the cluster + ClusterAddonStatusRunning ClusterAddonStatus = "ready" // The addon is ready to be used + ClusterAddonStatusError ClusterAddonStatus = "error" // The addon has an error + ClusterAddonStatusRemoving ClusterAddonStatus = "removing" // The addon is being removed + ClusterAddonStatusRemoved ClusterAddonStatus = "removed" // The addon has been removed ) -type ClusterAddOn struct { - ID string `json:"id"` - State AddOnState `json:"state"` +type ClusterAddon struct { + ID string `json:"id"` + ClusterID string `json:"cluster_id"` + Status ClusterAddonStatus `json:"status"` + CreatedAt time.Time `json:"created_at"` - Ingress *ClusterIngressAddOn `json:"ingress,omitempty"` + ObjectStore *ClusterAddonObjectStore `json:"object_store,omitempty"` } -type ClusterIngressAddOn struct { - Hostname string `json:"hostname"` - Target string `json:"target"` - Namespace string `json:"namespace"` +type ClusterAddonObjectStore struct { + Bucket string `json:"bucket"` } +func (addon *ClusterAddon) TypeName() string { + switch { + case addon.ObjectStore != nil: + return "Object Store" + default: + return "Unknown" + } +} + +type ClusterPortState string + +const ( + ClusterPortStatePending ClusterPortState = "pending" + ClusterPortStateReady ClusterPortState = "ready" + ClusterPortStateError ClusterPortState = "error" + ClusterPortStateRemoved ClusterPortState = "removed" +) + type ClusterExposedPort struct { Protocol string `json:"protocol"` ExposedPort int `json:"exposed_port"` @@ -98,5 +116,5 @@ type ClusterPort struct { ExposedPorts []ClusterExposedPort `json:"exposed_ports"` CreatedAt time.Time `json:"created_at"` Hostname string `json:"hostname"` - State AddOnState `json:"state"` + State ClusterPortState `json:"state"` }