diff --git a/cli/safescale/commands/cluster.go b/cli/safescale/commands/cluster.go index 2a4321a5b..e797b02df 100644 --- a/cli/safescale/commands/cluster.go +++ b/cli/safescale/commands/cluster.go @@ -204,7 +204,7 @@ func formatClusterConfig(config map[string]interface{}, detailed bool) (map[stri return nil, fail.InconsistentError("'nodes' should be a map[string][]*protocol.Host") } } else { - config["remote_desktop"] = fmt.Sprintf("no remote desktop available; to install on all masters, run 'safescale cluster feature add %s remotedesktop'", config["name"].(string)) + config["remote_desktop"] = fmt.Sprintf("no remote desktop available") } } return config, nil @@ -1487,7 +1487,7 @@ var clusterNodeStateCommand = cli.Command{ const clusterMasterCmdLabel = "master" -// clusterMasterCommands handles 'safescale cluster master ... +// clusterMasterCommands handles 'safescale cluster master ...' var clusterMasterCommands = cli.Command{ Name: clusterMasterCmdLabel, Usage: "manage cluster masters", @@ -1596,7 +1596,7 @@ var clusterMasterInspectCommand = cli.Command{ }() } - host, err := ClientSession.Cluster.InspectNode(clusterName, hostName, 0) + host, err := ClientSession.Cluster.InspectMaster(clusterName, hostName, 0) if err != nil { err = fail.FromGRPCStatus(err) return clitools.FailureResponse(clitools.ExitOnRPC(err.Error())) @@ -1724,7 +1724,6 @@ var clusterFeatureCommands = cli.Command{ ArgsUsage: "COMMAND", Subcommands: cli.Commands{ clusterFeatureListCommand, - clusterFeatureExportCommand, clusterFeatureCheckCommand, clusterFeatureAddCommand, clusterFeatureRemoveCommand, @@ -1787,77 +1786,6 @@ func clusterFeatureListAction(c *cli.Context) (ferr error) { return clitools.SuccessResponse(features) } -// clusterFeatureExportCommand handles 'safescale cluster feature export ' -var clusterFeatureExportCommand = cli.Command{ - Name: "list", - Aliases: []string{"ls"}, - Usage: "List features installed on the cluster", - ArgsUsage: "", - - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "embedded", - // Value: false, - Usage: "if used, tells to export embedded feature (if it exists)", - }, - cli.BoolFlag{ - Name: "raw", - // Value: false, - Usage: "outputs only the feature content, without json", - }, - }, - - Action: clusterFeatureExportAction, -} - -func clusterFeatureExportAction(c *cli.Context) (ferr error) { - defer fail.OnPanic(&ferr) - logrus.Tracef("SafeScale command: %s %s with args '%s'", clusterCmdLabel, c.Command.Name, c.Args()) - - if err := extractClusterName(c); err != nil { - return clitools.FailureResponse(err) - } - - featureName := c.Args().Get(1) - if featureName == "" { - _ = cli.ShowSubcommandHelp(c) - return clitools.ExitOnInvalidArgument("Invalid argument FEATURENAME.") - } - - if beta := os.Getenv("SAFESCALE_BETA"); beta != "" { - description := "Exporting feature" - pb := progressbar.NewOptions(-1, progressbar.OptionFullWidth(), progressbar.OptionClearOnFinish(), progressbar.OptionSetDescription(description)) - go func() { - for { - if pb.IsFinished() { - return - } - err := pb.Add(1) - if err != nil { - return - } - time.Sleep(100 * time.Millisecond) - } - }() - - defer func() { - _ = pb.Finish() - }() - } - - export, err := ClientSession.Cluster.ExportFeature(clusterName, featureName, c.Bool("embedded"), 0) // FIXME: set timeout - if err != nil { - err = fail.FromGRPCStatus(err) - return clitools.FailureResponse(clitools.ExitOnRPC(err.Error())) - } - - if c.Bool("raw") { - return clitools.SuccessResponse(export.Export) - } - - return clitools.SuccessResponse(export) -} - // clusterFeatureAddCommand handles 'safescale cluster feature add CLUSTERNAME FEATURENAME' var clusterFeatureAddCommand = cli.Command{ Name: "add", diff --git a/cli/safescale/commands/host.go b/cli/safescale/commands/host.go index e32d04d48..36e642fbe 100644 --- a/cli/safescale/commands/host.go +++ b/cli/safescale/commands/host.go @@ -846,7 +846,6 @@ var hostFeatureCommands = cli.Command{ Usage: hostFeatureCmdLabel + " COMMAND", Subcommands: cli.Commands{ hostFeatureCheckCommand, - hostFeatureExportCommand, hostFeatureAddCommand, hostFeatureRemoveCommand, hostFeatureListCommand, @@ -909,75 +908,6 @@ func hostFeatureListAction(c *cli.Context) (ferr error) { return clitools.SuccessResponse(list) } -// hostFeatureExportCommand handles 'safescale cluster feature export ' -var hostFeatureExportCommand = cli.Command{ - Name: "export", - Aliases: []string{"dump"}, - Usage: "Export feature file content", - ArgsUsage: "", - - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "embedded", - Usage: "if used, tells to export embedded feature (if it exists)", - }, - cli.BoolFlag{ - Name: "raw", - Usage: "outputs only the feature content, without json", - }, - }, - - Action: hostFeatureExportAction, -} - -func hostFeatureExportAction(c *cli.Context) (ferr error) { - defer fail.OnPanic(&ferr) - logrus.Tracef("SafeScale command: %s %s with args '%s'", hostCmdLabel, c.Command.Name, c.Args()) - - hostName, _, err := extractHostArgument(c, 0, DoNotInstanciate) - if err != nil { - return clitools.FailureResponse(err) - } - - featureName, err := extractFeatureArgument(c) - if err != nil { - return clitools.FailureResponse(err) - } - - if beta := os.Getenv("SAFESCALE_BETA"); beta != "" { - description := "Exporting host features" - pb := progressbar.NewOptions(-1, progressbar.OptionFullWidth(), progressbar.OptionClearOnFinish(), progressbar.OptionSetDescription(description)) - go func() { - for { - if pb.IsFinished() { - return - } - err := pb.Add(1) - if err != nil { - return - } - time.Sleep(100 * time.Millisecond) - } - }() - - defer func() { - _ = pb.Finish() - }() - } - - export, err := ClientSession.Host.ExportFeature(hostName, featureName, c.Bool("embedded"), 0) // FIXME: set timeout - if err != nil { - err = fail.FromGRPCStatus(err) - return clitools.FailureResponse(clitools.ExitOnRPC(err.Error())) - } - - if c.Bool("raw") { - return clitools.SuccessResponse(export.Export) - } - - return clitools.SuccessResponse(export) -} - // hostAddFeatureCommand handles 'deploy host package add' var hostFeatureAddCommand = cli.Command{ Name: "add", diff --git a/cli/safescale/commands/tenant.go b/cli/safescale/commands/tenant.go index 354b737ba..dd141b7b4 100644 --- a/cli/safescale/commands/tenant.go +++ b/cli/safescale/commands/tenant.go @@ -42,7 +42,6 @@ var TenantCommand = cli.Command{ tenantSetCommand, tenantInspectCommand, tenantScanCommand, - tenantMetadataCommands, }, } @@ -221,44 +220,3 @@ var tenantScanCommand = cli.Command{ return clitools.SuccessResponse(results.GetResults()) }, } - -const tenantMetadataCmdLabel = "metadata" - -// tenantMetadataCommands handles 'safescale tenant metadata' commands -var tenantMetadataCommands = cli.Command{ - Name: tenantMetadataCmdLabel, - Usage: "manage tenant metadata", - ArgsUsage: "COMMAND", - - Subcommands: cli.Commands{ - // tenantMetadataUpgradeCommand, - // tenantMetadataBackupCommand, - // tenantMetadataRestoreCommand, - tenantMetadataDeleteCommand, - }, -} - -const tenantMetadataDeleteCmdLabel = "delete" - -var tenantMetadataDeleteCommand = cli.Command{ - Name: tenantMetadataDeleteCmdLabel, - Aliases: []string{"remove", "rm", "destroy", "cleanup"}, - Usage: "Remove SafeScale metadata (making SafeScale unable to manage resources anymore); use with caution", - Action: func(c *cli.Context) (ferr error) { - defer fail.OnPanic(&ferr) - if c.NArg() != 1 { - _ = cli.ShowSubcommandHelp(c) - return clitools.FailureResponse(clitools.ExitOnInvalidArgument("Missing mandatory argument .")) - } - - logrus.Tracef("SafeScale command: %s %s with args '%s'", tenantCmdLabel, c.Command.Name, c.Args()) - - err := ClientSession.Tenant.Cleanup(c.Args().First(), 0) - if err != nil { - err = fail.FromGRPCStatus(err) - return clitools.FailureResponse(clitools.ExitOnRPC(strprocess.Capitalize(client.DecorateTimeoutError(err, "set tenant", false).Error()))) - } - - return clitools.SuccessResponse(nil) - }, -} diff --git a/doc/USAGE.md b/doc/USAGE.md index e5559503e..12676cfc7 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -1170,7 +1170,7 @@ The following actions are proposed: safescale network security group rule add [command_options] <network_name_or_id> <security_group_name_or_id> - REVIEW_ME: Add a Security Group rule

+ Add a Security Group rule

command_options:
  • --direction ingress|egresscode> Defines the direction of the rule (optional, default: ingress)
  • @@ -1205,7 +1205,7 @@ The following actions are proposed: safescale network security group rule delete [command_options] <network_name_or_id> <security_group_name_or_id> - REVIEW_ME: Delete a rule from a Security Group

    + Delete a rule from a Security Group

    command_options:
    • --direction ingress|egresscode> Defines the direction of the rule (optional, default: ingress)
    • @@ -1250,7 +1250,7 @@ Note: if <subnet_name_or_id> or <security_group_name This command family deals with host management: creation, list, connection, deletion... The following actions are proposed: -REVIEW_ME: + @@ -1302,14 +1302,14 @@ REVIEW_ME:
    • Create a Host inside a specific Subnet of a Network: -
      $ safescale host create --network example_network --subnet example_subnet --sizing "cpu=4,ram=[7-14],disk>=100" myhost
      +
      $ safescale host create --network example_network --subnet example_subnet --sizing "cpu=2,ram=[2-14],disk>=100" myhost
      response on success:
      -{"result":{
      +{"result":{"cpu":1,"creation_date":"2023-01-30T16:12:53Z","disk":10,"id":"ddac95cf-340f-4e89-8b60-ce3b62a43e79","kvs":[{"key":"Revision","value":"c4b987e257ff31e2610d57d94cb1eff9cb88c78d"},{"key":"Template","value":"2eedae16-a86c-4caa-a3d2-14be03293ba8"},{"key":"Image","value":"75483863-4aee-4e37-a93e-5cb49ea13d1b"},{"key":"DeclaredInBucket","value":"0.safescale-f63b10c1f1d99a0014bfd076b894219f.afraid"},{"key":"CreationDate","value":"2023-01-30T16:12:53Z"},{"key":"ManagedBy","value":"safescale"}],"name":"myhost","password":".....","private_ip":"192.168.38.22","private_key":"-----BEGIN RSA PRIVATE KEY-----......\n-----END RSA PRIVATE KEY-----","ram":2,"state":2,"state_label":"Started","template":"s1-2"},"status":"success"}
                 
      response on failure:
      -{"error": {
      +{"error":{"exitcode":6,"message":"Cannot create host: 'myhost' already exists [ctx:198e1825-861d-4d83-b355-2ff77ced26f5]"},"result":null,"status":"failure"}
                 
    • @@ -1456,7 +1456,7 @@ REVIEW_ME: "status": "success" } - response on failure: REVIEW_ME + response on failure:
       {
         "error": {
      @@ -1471,61 +1471,61 @@ REVIEW_ME:
       
       
      - - - - @@ -1539,7 +1539,7 @@ REVIEW_ME: example:
      $ safescale host check-feature myhost docker
      - response if feature is present: REVIEW_ME + response if feature is present
       {
         "result": null,
      @@ -1595,51 +1595,56 @@ REVIEW_ME:
         "status": "success"
       }
             
      - response on failure may vary. - - - @@ -1667,7 +1672,7 @@ The following actions are proposed: example:
      $ safescale volume create myvolume
      - response on success:REVIEW_ME + response on success
       {
         "result": {
      @@ -1720,7 +1725,7 @@ The following actions are proposed:
           Get info about a volume.

      example:
      $ safescale volume inspect myvolume
      - response on success: REVIEW_ME + response on success
       {
         "result": {
      @@ -1922,7 +1927,7 @@ The following actions are proposed:
           Get detailed information about the share.

      example:
      $ safescale share inspect myshare
      - response on success: REVIEW_ME + response on success
       {
         "result": {
      @@ -1951,7 +1956,7 @@ The following actions are proposed:
         "status": "success"
       }
           
      - response on failure:REVIEW_ME + response on failure
       {
         "error": {
      @@ -1988,11 +1993,11 @@ The following actions are proposed:
           
       {"result":null,"status":"success"}
           
      - response on failure (Share not found): REVIEW_ME + response on failure (Share not found)
       {"error":{"exitcode":6,"message":"cannot unmount share 'myshare' [caused by {failed to find share 'myshare'}]"},"result":null,"status":"failure"}
           
      - response on failure (hHost not found): REVIEW_ME + response on failure (Host not found)
       {"error":{"exitcode":6,"message":"cannot unmount share 'myshare' [caused by {failed to find host 'myclient'}]"},"result":null,"status":"failure"}
           
      @@ -2008,11 +2013,11 @@ The following actions are proposed:
       {"result":null,"status":"success"}
           
      - response on failure (Host not found): REVIEW_ME + response on failure (Host not found)
       {"error":{"exitcode":6,"message":"cannot unmount share 'myshare' [caused by {failed to find host 'myclient'}]"},"result":null,"status":"failure"}
           
      - response on failure (Share not found): REVIEW_ME + response on failure (Share not found)
       {"error":{"exitcode":6,"message":"cannot unmount share 'myshare' [caused by {failed to find share 'myshare'}]"},"result":null,"status":"failure"}
           
      @@ -2058,11 +2063,11 @@ The following actions are proposed: Create a bucket

      example:
      $ safescale bucket create mybucket
      - response on success: REVIEW_ME + response on success
       {"result":null,"status":"success"}
           
      - response on failure: REVIEW_ME + response on failure
       {"error":{"exitcode":6,"message":"Cannot create bucket [caused by {bucket 'mybucket' already exists}]"},"result":null,"status":"failure"}
           
      @@ -2074,7 +2079,7 @@ The following actions are proposed: List buckets

      example:
      $ safescale bucket list
      - response: REVIEW_ME + response
       {"result":{"buckets":[{"name":"0.safescale-96d245d7cf98171f14f4bc0abd8f8019"},{"name":"mybucket"}]},"status":"success"}
           
      @@ -2090,7 +2095,7 @@ The following actions are proposed:
       {"result":{"bucket":"mybucket","host":{}},"status":"success"}
           
      - response on failure: REVIEW_ME + response on failure
       {"error":{"exitcode":6,"message":"Cannot inspect bucket [caused by {failed to find bucket 'mybucket'}]"},"result":null,"status":"failure"}
           
      @@ -2110,11 +2115,11 @@ The following actions are proposed:
       {"result":null,"status":"success"}
           
      - response on failure (Host not found): REVIEW_ME + response on failure (Host not found)
       {"error":{"exitcode":6,"message":"No host found with name or id 'myhost2'"},"result":null,"status":"failure"}
           
      - response on failure (Bucket not found): REVIEW_ME + response on failure (Bucket not found)
       {"error":{"exitcode":6,"message":"Not found"},"result":null,"status":"failure"}
           
      @@ -2130,11 +2135,11 @@ The following actions are proposed:
       {"result":null,"status":"success"}
           
      - response on failure (Bucket not found): REVIEW_ME + response on failure (Bucket not found)
       {"error":{"exitcode":6,"message":"Failed to find bucket 'mybucket'"},"result":null,"status":"failure"}
           
      - response on failure (Host not found): REVIEW_ME + response on failure (Host not found)
       {"error":{"exitcode":6,"message":"Failed to find host 'myhost'"},"result":null,"status":"failure"}
           
      @@ -2150,11 +2155,11 @@ The following actions are proposed:
       {"result":null,"status":"success"}
           
      - response on failure (Bucket not found): REVIEW_ME + response on failure (Bucket not found)
       {"error":{"exitcode":6,"message":"cannot delete bucket [caused by {Container Not Found}]"},"result":null,"status":"failure"}
           
      - response on failure (Bucket mounted on Host(s)): REVIEW_ME + response on failure (Bucket mounted on Host(s))
       {"error":{"exitcode":6,"message":"cannot delete bucket [caused by {Container Not Empty}]"},"result":null,"status":"failure"}
           
      @@ -2177,7 +2182,7 @@ The following actions are proposed:
      - - - - @@ -2519,12 +2525,12 @@ mycluster-node-1 Ready <none> 10m v1.18.5
      Action
      description
      safescale [global_options] host start <host_name_or_id>REVIEW_ME: Starts a Host.

      +
      Starts a Host.

      example:
      $ safescale host start example_host
      response on success:
      -{
      +{"result":null,"status":"success"}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot start host: timeout waiting Host 'myhost' to be started: retries timed out after 00h02m30.378s (timeout defined at 00h02m30.000s) (timeout: 2m30s): myhost not started yet: Stopped [ctx: 57df83c7-c2b4-4873-87cc-26eeafda5d17]"},"result":null,"status":"failure"}
             
      safescale [global_options] host stop <host_name_or_id>REVIEW_ME: Stop a Host.

      +
      Stop a Host.

      example:
      $ safescale host stop example_host
      response on success:
      -{
      +{"result":null,"status":"success"}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot stop host: timeout waiting Host 'myhost' to be stopped: retries timed out after 00h02m30.326s (timeout defined at 00h02m30.000s) (timeout: 2m30s): myhost not stopped yet: Started [ctx: df57ffb8-71f5-404c-a554-a258c6a43c5e]"},"result":null,"status":"failure"}
             
      safescale [global_options] host reboot <host_name_or_id>REVIEW_ME: Reboots an Host.

      +
      Reboots an Host.

      example:
      $ safescale host reboot example_host
      response on success:
      -{
      +{"result":null,"status":"success"}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot reboot host: stopping retries: Cannot 'stop' instance ddac95cf-340f-4e89-8b60-ce3b62a43e79 while it is in vm_state stopped [ctx: 2b8c0ad9-dfd8-498f-9736-27c2f80233ec]"},"result":null,"status":"failure"}
             
      safescale [global_options] host status <host_name_or_id>REVIEW_ME: Displays the current status of an Host.

      +
      Displays the current status of an Host.

      example:
      $ safescale host status example_host
      response on success:
      -{
      +{"result":{"name":"myhost","status_code":0,"status_label":"Stopped"},"status":"success"}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot get host status: host 'boom' not found [ctx: 92290112-acf3-43c8-9257-59bf96d2e95f]"},"result":null,"status":"failure"}
             
      safescale [global_options] host security group list <host_name_or_id>REVIEW_ME: Lists the Security Groups bound to an Host.

      +
      Lists the Security Groups bound to an Host.

      example: -
      $ safescale host security group list example_host
      +
      $ safescale host security group list carved
      response on success:
      -{
      +{"result":[{"id":"79ea1d7a-a813-46f0-931e-e77b34056be3","name":"safescale-sg_subnet_internals.carved.carved"}],"status":"success"}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot list security group on host: host 'riot' not found [ctx: af2a9c7c-ca56-48f6-9c88-de47bc7cdd8c]"},"result":null,"status":"failure"}
             
      safescale [global_options] host security group bind <host_name_or_id> <securitygroup_name_or_id>REVIEW_ME: Links a Security Group to an Host.

      +
      Links a Security Group to an Host.

      example:
      $ safescale host security group bind example_host sg-for-some-hosts
      response on success:
       {
      +  "result": null,
      +  "status": "success"
      +}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot bind Security Group to Host: neither security-groups/byName/default nor security-groups/byID/default were found in the bucket [ctx: f086fba0-4268-422d-84f0-aab92f62310e]"},"result":null,"status":"failure"}
             
      safescale [global_options] host security group unbind <host_name_or_id> <securitygroup_name_or_id>REVIEW_ME: Unlinks a Security Group from an Host.

      +
      Unlinks a Security Group from an Host.

      example:
      $ safescale host security group bind example_host sg-for-some-hosts
      response on success:
       {
      +  "result": null,
      +  "status": "success"
      +}
             
      response on failure:
      -{
      +{"error":{"exitcode":6,"message":"Cannot unbind Security Group from Host: neither security-groups/byName/default nor security-groups/byID/default were found in the bucket [ctx: f086fba0-4268-422d-84f0-aab92f62310e]"},"result":null,"status":"failure"}
             
      safescale [global_options] ssh run -c "<command>" <host_name_or_id> Run a command on the host

      - command is the command to execute remotely.

      REVIEW_ME + command is the command to execute remotely.

      example:
      $ safescale ssh run -c "ls -la ~" example_host
      response on success: @@ -2199,14 +2204,14 @@ drwx------ 2 safescale safescale 4096 Jun 5 13:00 .ssh
      safescale [global_options] ssh copy <src> <dest> Copy a local file/directory to an Host or copy from an Host to local

      - example: REVIEW_ME + example
      $ safescale ssh copy /my/local/file example_host:/remote/path
      safescale [global_options] ssh connect <host_name_or_id> - REVIEW_ME: Connect to an Host with interactive shell

      + Connect to an Host with interactive shell

      example:
      $ safescale ssh connect example_host
      response on success: @@ -2322,12 +2327,12 @@ The following actions are proposed:
      safescale [global_options] cluster state <cluster_name>REVIEW_ME: Get current state of a Cluster

      +
      Get current state of a Cluster

      example:
      $ safescale cluster state mycluster
      response on success:
      -{"result":{
      +{"result":{"Name":"mycluster","State":1,"StateLabel":"Nominal"},"status":"success"}
             
      response on failure:
      @@ -2408,7 +2413,7 @@ The following actions are proposed:
       
      safescale [global_options] cluster expand [command_options] <cluster_name>REVIEW_ME:Creates new Cluster nodes and add them to Cluster for duty

      +
      Creates new Cluster nodes and add them to Cluster for duty

      command_options:
      @@ -2416,7 +2421,7 @@ The following actions are proposed:
      $ safescale cluster expand mycluster
      response on success:
      -{"result":{
      +{"result":{"nodes":[{"creation_date":"2023-01-30T17:52:41Z","id":"551e2db3-eb78-4e22-a067-29d7899749c8","kvs":[{"key":"Revision","value":"dce4ee77859e1e249a39ec84d18b41f4ee4de319"},{"key":"Template","value":"906e8259-0340-4856-95b5-4ea2d26fe377"},{"key":"clusterID","value":"ed7fde77-4c6b-495e-afc5-0edcd8b67b67"},{"key":"type","value":"node"},{"key":"CreationDate","value":"2023-01-30T17:52:41Z"},{"key":"DeclaredInBucket","value":"0.safescale-f63b10c1f1d99a0014bfd076b894219f.afraid"},{"key":"Image","value":"75483863-4aee-4e37-a93e-5cb49ea13d1b"},{"key":"ManagedBy","value":"safescale"}],"name":"corvo-node-7","password":"...","private_ip":"192.168.37.9","private_key":"-----BEGIN RSA PRIVATE KEY-----\n.....\n-----END RSA PRIVATE KEY-----","state":2,"state_label":"Started","template":"b2-7"}]},"status":"success"}
             
      response on failure:
      @@ -2426,12 +2431,12 @@ The following actions are proposed:
       
      safescale [global_options] cluster shrink [command_options] <cluster_name>REVIEW_ME: Reduce the numbers of Cluster nodes and deletes the chosen ones

      +
      Reduce the numbers of Cluster nodes and deletes the chosen ones

      example:
      $ safescale cluster shrink mycluster
      response on success:
      -{"result":{
      +{"result":null,"status":"success"}
             
      response on failure:
      @@ -2489,16 +2494,17 @@ mycluster-node-1     Ready    <none>   10m   v1.18.5
       
      safescale [global_options] cluster helm [command_options] <cluster_name> -- <helm_parameters>REVIEW_ME: Executes helm command on Cluster

      +
      Executes helm command on Cluster

      example:
      $ safescale cluster helm mycluster -- install nginx
      response on success:
      -{"result":{
      +{"result":null,"status":"success"}
             
      response on failure:
      -{"error":{"exitcode":4,"message":"Cluster 'mycluster' not found.\n"},"result":null,"status":"failure"}
      +-bash: helm: command not found
      +{"error":{"exitcode":1,"message":""},"result":null,"status":"failure"}