morpheus-cli 2.10.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/morpheus +27 -32
- data/lib/morpheus/api/accounts_interface.rb +36 -47
- data/lib/morpheus/api/api_client.rb +141 -110
- data/lib/morpheus/api/app_templates_interface.rb +56 -72
- data/lib/morpheus/api/apps_interface.rb +111 -132
- data/lib/morpheus/api/auth_interface.rb +30 -0
- data/lib/morpheus/api/clouds_interface.rb +71 -76
- data/lib/morpheus/api/custom_instance_types_interface.rb +21 -46
- data/lib/morpheus/api/dashboard_interface.rb +10 -17
- data/lib/morpheus/api/deploy_interface.rb +60 -72
- data/lib/morpheus/api/deployments_interface.rb +53 -71
- data/lib/morpheus/api/groups_interface.rb +55 -45
- data/lib/morpheus/api/instance_types_interface.rb +19 -23
- data/lib/morpheus/api/instances_interface.rb +179 -177
- data/lib/morpheus/api/key_pairs_interface.rb +11 -17
- data/lib/morpheus/api/license_interface.rb +18 -23
- data/lib/morpheus/api/load_balancers_interface.rb +54 -69
- data/lib/morpheus/api/logs_interface.rb +25 -29
- data/lib/morpheus/api/options_interface.rb +13 -17
- data/lib/morpheus/api/provision_types_interface.rb +19 -22
- data/lib/morpheus/api/roles_interface.rb +75 -94
- data/lib/morpheus/api/security_group_rules_interface.rb +28 -37
- data/lib/morpheus/api/security_groups_interface.rb +39 -51
- data/lib/morpheus/api/servers_interface.rb +113 -115
- data/lib/morpheus/api/setup_interface.rb +31 -0
- data/lib/morpheus/api/task_sets_interface.rb +36 -38
- data/lib/morpheus/api/tasks_interface.rb +56 -69
- data/lib/morpheus/api/users_interface.rb +67 -76
- data/lib/morpheus/api/virtual_images_interface.rb +61 -61
- data/lib/morpheus/api/whoami_interface.rb +12 -15
- data/lib/morpheus/cli.rb +71 -60
- data/lib/morpheus/cli/accounts.rb +254 -315
- data/lib/morpheus/cli/alias_command.rb +219 -0
- data/lib/morpheus/cli/app_templates.rb +264 -272
- data/lib/morpheus/cli/apps.rb +608 -671
- data/lib/morpheus/cli/cli_command.rb +259 -21
- data/lib/morpheus/cli/cli_registry.rb +99 -14
- data/lib/morpheus/cli/clouds.rb +599 -372
- data/lib/morpheus/cli/config_file.rb +126 -0
- data/lib/morpheus/cli/credentials.rb +141 -117
- data/lib/morpheus/cli/dashboard_command.rb +48 -56
- data/lib/morpheus/cli/deployments.rb +254 -268
- data/lib/morpheus/cli/deploys.rb +150 -142
- data/lib/morpheus/cli/error_handler.rb +38 -0
- data/lib/morpheus/cli/groups.rb +551 -179
- data/lib/morpheus/cli/hosts.rb +862 -617
- data/lib/morpheus/cli/instance_types.rb +103 -95
- data/lib/morpheus/cli/instances.rb +1335 -1009
- data/lib/morpheus/cli/key_pairs.rb +82 -90
- data/lib/morpheus/cli/library.rb +498 -499
- data/lib/morpheus/cli/license.rb +83 -101
- data/lib/morpheus/cli/load_balancers.rb +314 -300
- data/lib/morpheus/cli/login.rb +66 -44
- data/lib/morpheus/cli/logout.rb +47 -46
- data/lib/morpheus/cli/mixins/accounts_helper.rb +69 -31
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +106 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +181 -17
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +535 -458
- data/lib/morpheus/cli/mixins/whoami_helper.rb +2 -2
- data/lib/morpheus/cli/option_parser.rb +35 -0
- data/lib/morpheus/cli/option_types.rb +232 -192
- data/lib/morpheus/cli/recent_activity_command.rb +61 -65
- data/lib/morpheus/cli/remote.rb +446 -199
- data/lib/morpheus/cli/roles.rb +884 -906
- data/lib/morpheus/cli/security_group_rules.rb +213 -203
- data/lib/morpheus/cli/security_groups.rb +237 -192
- data/lib/morpheus/cli/shell.rb +338 -231
- data/lib/morpheus/cli/tasks.rb +326 -308
- data/lib/morpheus/cli/users.rb +457 -462
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/version_command.rb +16 -18
- data/lib/morpheus/cli/virtual_images.rb +526 -345
- data/lib/morpheus/cli/whoami.rb +125 -111
- data/lib/morpheus/cli/workflows.rb +338 -185
- data/lib/morpheus/formatters.rb +8 -1
- data/lib/morpheus/logging.rb +1 -1
- data/lib/morpheus/rest_client.rb +17 -8
- metadata +9 -3
- data/lib/morpheus/api/custom_instance_types.rb +0 -55
    
        data/lib/morpheus/cli/whoami.rb
    CHANGED
    
    | @@ -11,128 +11,142 @@ class Morpheus::Cli::Whoami | |
| 11 11 | 
             
              include Morpheus::Cli::CliCommand
         | 
| 12 12 | 
             
              include Morpheus::Cli::WhoamiHelper
         | 
| 13 13 | 
             
              include Morpheus::Cli::AccountsHelper
         | 
| 14 | 
            -
              
         | 
| 15 | 
            -
            	def initialize() 
         | 
| 16 | 
            -
            		@appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
         | 
| 17 | 
            -
            	end
         | 
| 14 | 
            +
              include Morpheus::Cli::InfrastructureHelper
         | 
| 18 15 |  | 
| 19 | 
            -
             | 
| 20 | 
            -
            		@access_token = Morpheus::Cli::Credentials.new(@appliance_name,@appliance_url).load_saved_credentials()
         | 
| 21 | 
            -
            		# always try this.. it will 401
         | 
| 22 | 
            -
            		# if @access_token.empty?
         | 
| 23 | 
            -
            		# 	print_red_alert "Invalid Credentials. Unable to acquire access token. Please verify your credentials and try again."
         | 
| 24 | 
            -
            		# 	exit 1
         | 
| 25 | 
            -
            		# end
         | 
| 26 | 
            -
            		@api_client = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url)
         | 
| 27 | 
            -
            		
         | 
| 16 | 
            +
              set_command_name :whoami
         | 
| 28 17 |  | 
| 29 | 
            -
             | 
| 18 | 
            +
              # no subcommands, just show()
         | 
| 30 19 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 20 | 
            +
              def initialize()
         | 
| 21 | 
            +
                # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
         | 
| 22 | 
            +
              end
         | 
| 34 23 |  | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
            		options = {}
         | 
| 41 | 
            -
            		optparse = OptionParser.new do|opts|
         | 
| 42 | 
            -
            			opts.banner = usage
         | 
| 43 | 
            -
            			opts.on(nil,'--feature-access', "Display Feature Access") do |val|
         | 
| 44 | 
            -
            				options[:include_feature_access] = true
         | 
| 45 | 
            -
            			end
         | 
| 46 | 
            -
            			# opts.on(nil,'--group-access', "Display Group Access") do
         | 
| 47 | 
            -
            			# 	options[:include_group_access] = true
         | 
| 48 | 
            -
            			# end
         | 
| 49 | 
            -
            			# opts.on(nil,'--cloud-access', "Display Cloud Access") do
         | 
| 50 | 
            -
            			# 	options[:include_cloud_access] = true
         | 
| 51 | 
            -
            			# end
         | 
| 52 | 
            -
            			# opts.on(nil,'--instance-type-access', "Display Instance Type Access") do
         | 
| 53 | 
            -
            			# 	options[:include_instance_type_access] = true
         | 
| 54 | 
            -
            			# end
         | 
| 55 | 
            -
            			opts.on(nil,'--all-access', "Display All Access Lists") do
         | 
| 56 | 
            -
            				options[:include_feature_access] = true
         | 
| 57 | 
            -
            				options[:include_group_access] = true
         | 
| 58 | 
            -
            				options[:include_cloud_access] = true
         | 
| 59 | 
            -
            				options[:include_instance_type_access] = true
         | 
| 60 | 
            -
            			end
         | 
| 61 | 
            -
            			build_common_options(opts, options, [:json]) # todo: support :remote too
         | 
| 62 | 
            -
            		end
         | 
| 63 | 
            -
            		optparse.parse(args)
         | 
| 24 | 
            +
              def connect(opts)
         | 
| 25 | 
            +
                @api_client = establish_remote_appliance_connection(opts)
         | 
| 26 | 
            +
                @groups_interface = @api_client.groups
         | 
| 27 | 
            +
                @active_group_id = Morpheus::Cli::Groups.active_group
         | 
| 28 | 
            +
              end
         | 
| 64 29 |  | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
            			json_response = load_whoami()
         | 
| 69 | 
            -
            			
         | 
| 70 | 
            -
            			if options[:json]
         | 
| 71 | 
            -
            				print JSON.pretty_generate(json_response)
         | 
| 72 | 
            -
            				print "\n"
         | 
| 73 | 
            -
            			else
         | 
| 74 | 
            -
            				user = @current_user
         | 
| 75 | 
            -
            				if !user
         | 
| 76 | 
            -
            					puts yellow,"No active session. Please login",reset
         | 
| 77 | 
            -
            					exit 1
         | 
| 78 | 
            -
            				end
         | 
| 30 | 
            +
              def handle(args)
         | 
| 31 | 
            +
                show(args)
         | 
| 32 | 
            +
              end
         | 
| 79 33 |  | 
| 80 | 
            -
             | 
| 34 | 
            +
              def show(args)
         | 
| 35 | 
            +
                options = {}
         | 
| 36 | 
            +
                optparse = OptionParser.new do|opts|
         | 
| 37 | 
            +
                  opts.banner = usage
         | 
| 38 | 
            +
                  opts.on(nil,'--feature-access', "Display Feature Access") do |val|
         | 
| 39 | 
            +
                    options[:include_feature_access] = true
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                  # opts.on(nil,'--group-access', "Display Group Access") do
         | 
| 42 | 
            +
                  #   options[:include_group_access] = true
         | 
| 43 | 
            +
                  # end
         | 
| 44 | 
            +
                  # opts.on(nil,'--cloud-access', "Display Cloud Access") do
         | 
| 45 | 
            +
                  #   options[:include_cloud_access] = true
         | 
| 46 | 
            +
                  # end
         | 
| 47 | 
            +
                  # opts.on(nil,'--instance-type-access', "Display Instance Type Access") do
         | 
| 48 | 
            +
                  #   options[:include_instance_type_access] = true
         | 
| 49 | 
            +
                  # end
         | 
| 50 | 
            +
                  opts.on(nil,'--all-access', "Display All Access Lists") do
         | 
| 51 | 
            +
                    options[:include_feature_access] = true
         | 
| 52 | 
            +
                    options[:include_group_access] = true
         | 
| 53 | 
            +
                    options[:include_cloud_access] = true
         | 
| 54 | 
            +
                    options[:include_instance_type_access] = true
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                  build_common_options(opts, options, [:json, :remote]) # todo: support :remote too
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                optparse.parse!(args)
         | 
| 59 | 
            +
                # todo: check to see if they have credentials instead of just trying to connect (and prompting)
         | 
| 60 | 
            +
                connect(options)
         | 
| 61 | 
            +
                begin
         | 
| 62 | 
            +
                  json_response = load_whoami()
         | 
| 63 | 
            +
                  group = nil
         | 
| 64 | 
            +
                  begin
         | 
| 65 | 
            +
                    group = @active_group_id ? find_group_by_name_or_id(@active_group_id) : nil # via InfrastructureHelper mixin
         | 
| 66 | 
            +
                  rescue => err
         | 
| 67 | 
            +
                    if options[:debug]
         | 
| 68 | 
            +
                      print red,"Unable to determine active group: #{err}\n",reset
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                  if options[:json]
         | 
| 72 | 
            +
                    print JSON.pretty_generate(json_response)
         | 
| 73 | 
            +
                    print "\n"
         | 
| 74 | 
            +
                  else
         | 
| 75 | 
            +
                    user = @current_user
         | 
| 76 | 
            +
                    if !user
         | 
| 77 | 
            +
                      puts yellow,"No active session. Please login",reset
         | 
| 78 | 
            +
                      exit 1
         | 
| 79 | 
            +
                    end
         | 
| 81 80 |  | 
| 82 | 
            -
             | 
| 83 | 
            -
            				print cyan
         | 
| 84 | 
            -
            				# if @is_master_account
         | 
| 85 | 
            -
            					puts "ID: #{user['id']}"
         | 
| 86 | 
            -
            					puts "Account: #{user['account'] ? user['account']['name'] : nil}" + (@is_master_account ? " (Master Account)" : "")
         | 
| 87 | 
            -
            				# end
         | 
| 88 | 
            -
            				puts "First Name: #{user['firstName']}"
         | 
| 89 | 
            -
            				puts "Last Name: #{user['lastName']}"
         | 
| 90 | 
            -
            				puts "Username: #{user['username']}"
         | 
| 91 | 
            -
            				puts "Email: #{user['email']}"
         | 
| 92 | 
            -
            				puts "Role: #{format_user_role_names(user)}"
         | 
| 93 | 
            -
            				# puts "Date Created: #{format_local_dt(user['dateCreated'])}"
         | 
| 94 | 
            -
            				# puts "Last Updated: #{format_local_dt(user['lastUpdated'])}"
         | 
| 95 | 
            -
            				# print "\n" ,cyan, bold, "User Instance Limits\n","==================", reset, "\n\n"
         | 
| 96 | 
            -
            				# print cyan
         | 
| 97 | 
            -
            				# puts "Max Storage (bytes): #{user['instanceLimits'] ? user['instanceLimits']['maxStorage'] : 0}"
         | 
| 98 | 
            -
            				# puts "Max Memory (bytes): #{user['instanceLimits'] ? user['instanceLimits']['maxMemory'] : 0}"
         | 
| 99 | 
            -
            				# puts "CPU Count: #{user['instanceLimits'] ? user['instanceLimits']['maxCpu'] : 0}"
         | 
| 81 | 
            +
                    # todo: impersonate command and show that info here
         | 
| 100 82 |  | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 83 | 
            +
                    print "\n" ,cyan, bold, "Current User\n","==================", reset, "\n\n"
         | 
| 84 | 
            +
                    # print cyan
         | 
| 85 | 
            +
                    # if @is_master_account
         | 
| 86 | 
            +
                    puts "ID: #{user['id']}"
         | 
| 87 | 
            +
                    puts "Account: #{user['account'] ? user['account']['name'] : nil}" + (@is_master_account ? " (Master Account)" : "")
         | 
| 88 | 
            +
                    # end
         | 
| 89 | 
            +
                    puts "First Name: #{user['firstName']}"
         | 
| 90 | 
            +
                    puts "Last Name: #{user['lastName']}"
         | 
| 91 | 
            +
                    puts "Username: #{user['username']}"
         | 
| 92 | 
            +
                    puts "Email: #{user['email']}"
         | 
| 93 | 
            +
                    puts "Role: #{format_user_role_names(user)}"
         | 
| 94 | 
            +
                    # puts "Date Created: #{format_local_dt(user['dateCreated'])}"
         | 
| 95 | 
            +
                    # puts "Last Updated: #{format_local_dt(user['lastUpdated'])}"
         | 
| 96 | 
            +
                    # print "\n" ,cyan, bold, "User Instance Limits\n","==================", reset, "\n\n"
         | 
| 97 | 
            +
                    # print cyan
         | 
| 98 | 
            +
                    # puts "Max Storage (bytes): #{user['instanceLimits'] ? user['instanceLimits']['maxStorage'] : 0}"
         | 
| 99 | 
            +
                    # puts "Max Memory (bytes): #{user['instanceLimits'] ? user['instanceLimits']['maxMemory'] : 0}"
         | 
| 100 | 
            +
                    # puts "CPU Count: #{user['instanceLimits'] ? user['instanceLimits']['maxCpu'] : 0}"
         | 
| 113 101 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 102 | 
            +
                    if options[:include_feature_access]
         | 
| 103 | 
            +
                      if @user_permissions
         | 
| 104 | 
            +
                        print "\n" ,cyan, bold, "Feature Permissions\n","==================", reset, "\n\n"
         | 
| 105 | 
            +
                        print cyan
         | 
| 106 | 
            +
                        rows = @user_permissions.collect do |code, access|
         | 
| 107 | 
            +
                          {code: code, access: get_access_string(access) }
         | 
| 108 | 
            +
                        end
         | 
| 109 | 
            +
                        tp rows, [:code, :access]
         | 
| 110 | 
            +
                      else
         | 
| 111 | 
            +
                        puts yellow,"No permissions found.",reset
         | 
| 112 | 
            +
                      end
         | 
| 113 | 
            +
                    end
         | 
| 126 114 |  | 
| 127 | 
            -
             | 
| 115 | 
            +
                    print "\n" ,cyan, bold, "Remote Appliance\n","==================", reset, "\n\n"
         | 
| 116 | 
            +
                    # print cyan
         | 
| 117 | 
            +
                    if @appliance_name
         | 
| 118 | 
            +
                      puts "Name: #{@appliance_name}"
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                    if @appliance_url
         | 
| 121 | 
            +
                      puts "Url: #{@appliance_url}"
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
                    if @appliance_build_verison
         | 
| 124 | 
            +
                      puts "Build Version: #{@appliance_build_verison}"
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                    # print cyan
         | 
| 128 127 |  | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
             | 
| 128 | 
            +
                    if group
         | 
| 129 | 
            +
                      print "\n" ,cyan, bold, "Active Group\n","==================", reset, "\n\n"
         | 
| 130 | 
            +
                      print "ID: #{group['id']}\n" 
         | 
| 131 | 
            +
                      print "Name: #{group['name']}\n" 
         | 
| 132 | 
            +
                    else
         | 
| 133 | 
            +
                      print "\n"
         | 
| 134 | 
            +
                      print "No active group. See `groups use`\n",reset
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    print reset,"\n"
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
                rescue RestClient::Exception => e
         | 
| 140 | 
            +
                  print_rest_exception(e, options)
         | 
| 141 | 
            +
                  if e.response && e.response.code == 401
         | 
| 142 | 
            +
                    puts "It looks like you need to login to the remote appliance [#{@appliance_name}] #{@appliance_url}"
         | 
| 143 | 
            +
                    if Morpheus::Cli::OptionTypes.confirm("Would you like to login now?")
         | 
| 144 | 
            +
                      return Morpheus::Cli::Login.new.login([])
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
                  exit 1
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
              end
         | 
| 135 150 |  | 
| 136 | 
            -
            	
         | 
| 137 151 |  | 
| 138 152 | 
             
            end
         | 
| @@ -6,189 +6,342 @@ require 'table_print' | |
| 6 6 | 
             
            require 'morpheus/cli/cli_command'
         | 
| 7 7 |  | 
| 8 8 | 
             
            class Morpheus::Cli::Workflows
         | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 140 | 
            -
             | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 9 | 
            +
              include Morpheus::Cli::CliCommand
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              register_subcommands :list, :get, :add, :update, :remove
         | 
| 12 | 
            +
              set_default_subcommand :list
         | 
| 13 | 
            +
              
         | 
| 14 | 
            +
              # def initialize()
         | 
| 15 | 
            +
              #   @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
         | 
| 16 | 
            +
              # end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def connect(opts)
         | 
| 19 | 
            +
                @api_client = establish_remote_appliance_connection(opts)
         | 
| 20 | 
            +
                @tasks_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).tasks
         | 
| 21 | 
            +
                @task_sets_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).task_sets
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 25 | 
            +
              def handle(args)
         | 
| 26 | 
            +
                handle_subcommand(args)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
              def list(args)
         | 
| 31 | 
            +
                options = {}
         | 
| 32 | 
            +
                optparse = OptionParser.new do|opts|
         | 
| 33 | 
            +
                  opts.banner = subcommand_usage()
         | 
| 34 | 
            +
                  build_common_options(opts, options, [:list, :json, :dry_run, :remote])
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
                optparse.parse!(args)
         | 
| 37 | 
            +
                connect(options)
         | 
| 38 | 
            +
                begin
         | 
| 39 | 
            +
                  params = {}
         | 
| 40 | 
            +
                  [:phrase, :offset, :max, :sort, :direction].each do |k|
         | 
| 41 | 
            +
                    params[k] = options[k] unless options[k].nil?
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                  if options[:dry_run]
         | 
| 44 | 
            +
                    print_dry_run @task_sets_interface.dry.get(params)
         | 
| 45 | 
            +
                    return
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  json_response = @task_sets_interface.get(params)
         | 
| 48 | 
            +
                  if options[:json]
         | 
| 49 | 
            +
                    print JSON.pretty_generate(json_response)
         | 
| 50 | 
            +
                  else
         | 
| 51 | 
            +
                    task_sets = json_response['taskSets']
         | 
| 52 | 
            +
                    print "\n" ,cyan, bold, "Morpheus Workflows\n","==================", reset, "\n\n"
         | 
| 53 | 
            +
                    if task_sets.empty?
         | 
| 54 | 
            +
                      puts yellow,"No workflows currently configured.",reset
         | 
| 55 | 
            +
                    else
         | 
| 56 | 
            +
                      print cyan
         | 
| 57 | 
            +
                      print_workflows_table(task_sets)
         | 
| 58 | 
            +
                      print_results_pagination(json_response)
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                    print reset,"\n"
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                rescue RestClient::Exception => e
         | 
| 63 | 
            +
                  print_rest_exception(e, options)
         | 
| 64 | 
            +
                  exit 1
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              def add(args)
         | 
| 69 | 
            +
                options = {}
         | 
| 70 | 
            +
                optparse = OptionParser.new do|opts|
         | 
| 71 | 
            +
                  opts.banner = subcommand_usage("[name] --tasks x,y,z")
         | 
| 72 | 
            +
                  opts.on("--tasks x,y,z", Array, "List of tasks to run in order") do |list|
         | 
| 73 | 
            +
                    options[:task_names] = list
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                  build_common_options(opts, options, [:json, :dry_run, :remote])
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
                optparse.parse!(args)
         | 
| 78 | 
            +
                if args.count < 1 || options[:task_names].empty?
         | 
| 79 | 
            +
                  puts optparse
         | 
| 80 | 
            +
                  exit 1
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
                workflow_name = args[0]
         | 
| 83 | 
            +
                connect(options)
         | 
| 84 | 
            +
                begin
         | 
| 85 | 
            +
                  tasks = []
         | 
| 86 | 
            +
                  options[:task_names].each do |task_name|
         | 
| 87 | 
            +
                    tasks << find_task_by_name_or_id(task_name.to_s.strip)['id']
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  payload = {taskSet: {name: workflow_name, tasks: tasks}}
         | 
| 91 | 
            +
                  if options[:dry_run]
         | 
| 92 | 
            +
                    print_dry_run @task_sets_interface.dry.create(payload)
         | 
| 93 | 
            +
                    return
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                  json_response = @task_sets_interface.create(payload)
         | 
| 96 | 
            +
                  if options[:json]
         | 
| 97 | 
            +
                    print JSON.pretty_generate(json_response)
         | 
| 98 | 
            +
                  else
         | 
| 99 | 
            +
                    print "\n", cyan, "Workflow #{json_response['taskSet']['name']} created successfully", reset, "\n\n"
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                rescue RestClient::Exception => e
         | 
| 102 | 
            +
                  print_rest_exception(e, options)
         | 
| 103 | 
            +
                  exit 1
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              def get(args)
         | 
| 108 | 
            +
                options = {}
         | 
| 109 | 
            +
                optparse = OptionParser.new do|opts|
         | 
| 110 | 
            +
                  opts.banner = subcommand_usage("[task]")
         | 
| 111 | 
            +
                  build_common_options(opts, options, [:json, :dry_run, :remote])
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
                optparse.parse!(args)
         | 
| 114 | 
            +
                if args.count < 1
         | 
| 115 | 
            +
                  puts optparse
         | 
| 116 | 
            +
                  exit 1
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
                workflow_name = args[0]
         | 
| 119 | 
            +
                connect(options)
         | 
| 120 | 
            +
                begin
         | 
| 121 | 
            +
                  if options[:dry_run]
         | 
| 122 | 
            +
                    if workflow_name.to_s =~ /\A\d{1,}\Z/
         | 
| 123 | 
            +
                      print_dry_run @task_sets_interface.dry.get(workflow_name.to_i)
         | 
| 124 | 
            +
                    else
         | 
| 125 | 
            +
                      print_dry_run @task_sets_interface.dry.get({name: workflow_name})
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
                    return
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                  workflow = find_workflow_by_name_or_id(workflow_name)
         | 
| 130 | 
            +
                  exit 1 if workflow.nil?
         | 
| 131 | 
            +
                  # refetch it..
         | 
| 132 | 
            +
                  json_response = @task_sets_interface.get(workflow['id'])
         | 
| 133 | 
            +
                  workflow = json_response['taskSet']
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  if options[:json]
         | 
| 136 | 
            +
                    #puts JSON.pretty_generate(workflow)
         | 
| 137 | 
            +
                    puts JSON.pretty_generate(json_response)
         | 
| 138 | 
            +
                    print "\n"
         | 
| 139 | 
            +
                  else
         | 
| 140 | 
            +
                    # tasks = []
         | 
| 141 | 
            +
                    # (workflow['tasks'] || []).each do |task_name|
         | 
| 142 | 
            +
                    #   tasks << find_task_by_name_or_id(task_name)['id']
         | 
| 143 | 
            +
                    # end
         | 
| 144 | 
            +
                    tasks = workflow['taskSetTasks'].sort { |x,y| x['taskOrder'].to_i <=> y['taskOrder'].to_i }
         | 
| 145 | 
            +
                    print "\n" ,cyan, bold, "Workflow Details\n","==================", reset, "\n\n"
         | 
| 146 | 
            +
                    print cyan
         | 
| 147 | 
            +
                    puts "ID: #{workflow['id']}"
         | 
| 148 | 
            +
                    puts "Name: #{workflow['name']}"
         | 
| 149 | 
            +
                    #puts "Description: #{workflow['description']}"
         | 
| 150 | 
            +
                    #task_names = tasks.collect {|it| it['name'] }
         | 
| 151 | 
            +
                    print "\n", cyan, "Tasks:\n"
         | 
| 152 | 
            +
                    tasks.each_with_index do |taskSetTask, index|
         | 
| 153 | 
            +
                      puts "#{(index+1).to_s.rjust(3, ' ')}. #{taskSetTask['task']['name']}"
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
                    print reset,"\n"
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
                rescue RestClient::Exception => e
         | 
| 158 | 
            +
                  print_rest_exception(e, options)
         | 
| 159 | 
            +
                  exit 1
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
              end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
              def update(args)
         | 
| 164 | 
            +
                options = {}
         | 
| 165 | 
            +
                optparse = OptionParser.new do|opts|
         | 
| 166 | 
            +
                  opts.banner = subcommand_usage("[name] --tasks x,y,z")
         | 
| 167 | 
            +
                  opts.on("--tasks x,y,z", Array, "New list of tasks to run in order") do |list|
         | 
| 168 | 
            +
                    options[:task_names]= list
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
                  opts.on("--name NAME", String, "New name for workflow") do |val|
         | 
| 171 | 
            +
                    options[:new_name] = val
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
                  build_common_options(opts, options, [:json, :dry_run, :quiet, :remote])
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
                optparse.parse!(args)
         | 
| 176 | 
            +
                if args.count < 1 || (options[:new_name].empty? && options[:task_names].empty?)
         | 
| 177 | 
            +
                  puts optparse
         | 
| 178 | 
            +
                  exit 1
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
                workflow_name = args[0]
         | 
| 181 | 
            +
                connect(options)
         | 
| 182 | 
            +
                begin
         | 
| 183 | 
            +
                  workflow = find_workflow_by_name_or_id(workflow_name)
         | 
| 184 | 
            +
                  payload = {taskSet: {id: workflow['id']} }
         | 
| 185 | 
            +
                  tasks = []
         | 
| 186 | 
            +
                  if options[:task_names]
         | 
| 187 | 
            +
                    options[:task_names].each do |task_name|
         | 
| 188 | 
            +
                      tasks << find_task_by_name_or_id(task_name)['id']
         | 
| 189 | 
            +
                    end
         | 
| 190 | 
            +
                    payload[:taskSet][:tasks] = tasks
         | 
| 191 | 
            +
                  else
         | 
| 192 | 
            +
                    payload[:taskSet][:tasks] = workflow['tasks']
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
                  if options[:new_name]
         | 
| 195 | 
            +
                    payload[:taskSet][:name] = options[:new_name]
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                  if options[:dry_run]
         | 
| 198 | 
            +
                    print_dry_run @task_sets_interface.dry.update(workflow['id'], payload)
         | 
| 199 | 
            +
                    return
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
                  json_response = @task_sets_interface.update(workflow['id'], payload)
         | 
| 202 | 
            +
                  if options[:json]
         | 
| 203 | 
            +
                    print JSON.pretty_generate(json_response)
         | 
| 204 | 
            +
                  elsif !options[:quiet]
         | 
| 205 | 
            +
                    print "\n", cyan, "Workflow #{json_response['taskSet']['name']} updated successfully", reset, "\n\n"
         | 
| 206 | 
            +
                    get([workflow['id']])
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
                rescue RestClient::Exception => e
         | 
| 209 | 
            +
                  print_rest_exception(e, options)
         | 
| 210 | 
            +
                  exit 1
         | 
| 211 | 
            +
                end
         | 
| 212 | 
            +
              end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
              def remove(args)
         | 
| 215 | 
            +
                options = {}
         | 
| 216 | 
            +
                optparse = OptionParser.new do|opts|
         | 
| 217 | 
            +
                  opts.banner = "Usage: morpheus workflows remove [name]"
         | 
| 218 | 
            +
                  build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
         | 
| 219 | 
            +
                end
         | 
| 220 | 
            +
                optparse.parse!(args)
         | 
| 221 | 
            +
                if args.count < 1
         | 
| 222 | 
            +
                  puts optparse
         | 
| 223 | 
            +
                  exit 1
         | 
| 224 | 
            +
                end
         | 
| 225 | 
            +
                workflow_name = args[0]
         | 
| 226 | 
            +
                connect(options)
         | 
| 227 | 
            +
                begin
         | 
| 228 | 
            +
                  workflow = find_workflow_by_name_or_id(workflow_name)
         | 
| 229 | 
            +
                  exit 1 if workflow.nil?
         | 
| 230 | 
            +
                  unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the workflow #{workflow['name']}?")
         | 
| 231 | 
            +
                    exit 1
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
                  if options[:dry_run]
         | 
| 234 | 
            +
                    print_dry_run @task_sets_interface.dry.destroy(workflow['id'])
         | 
| 235 | 
            +
                    return
         | 
| 236 | 
            +
                  end
         | 
| 237 | 
            +
                  json_response = @task_sets_interface.destroy(workflow['id'])
         | 
| 238 | 
            +
                  if options[:json]
         | 
| 239 | 
            +
                    print JSON.pretty_generate(json_response)
         | 
| 240 | 
            +
                  elsif !options[:quiet]
         | 
| 241 | 
            +
                    print "\n", cyan, "Workflow #{workflow['name']} removed", reset, "\n\n"
         | 
| 242 | 
            +
                  end
         | 
| 243 | 
            +
                rescue RestClient::Exception => e
         | 
| 244 | 
            +
                  print_rest_exception(e, options)
         | 
| 245 | 
            +
                  exit 1
         | 
| 246 | 
            +
                end
         | 
| 247 | 
            +
              end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
             | 
| 250 | 
            +
              private
         | 
| 251 | 
            +
             | 
| 252 | 
            +
              def find_workflow_by_name_or_id(val)
         | 
| 253 | 
            +
                if val.to_s =~ /\A\d{1,}\Z/
         | 
| 254 | 
            +
                  return find_workflow_by_id(val)
         | 
| 255 | 
            +
                else
         | 
| 256 | 
            +
                  return find_workflow_by_name(val)
         | 
| 257 | 
            +
                end
         | 
| 258 | 
            +
              end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
              def find_workflow_by_id(id)
         | 
| 261 | 
            +
                begin
         | 
| 262 | 
            +
                  json_response = @task_sets_interface.get(id.to_i)
         | 
| 263 | 
            +
                  return json_response['taskSet']
         | 
| 264 | 
            +
                rescue RestClient::Exception => e
         | 
| 265 | 
            +
                  if e.response && e.response.code == 404
         | 
| 266 | 
            +
                    print_red_alert "Workflow not found by id #{id}"
         | 
| 267 | 
            +
                  else
         | 
| 268 | 
            +
                    raise e
         | 
| 269 | 
            +
                  end
         | 
| 270 | 
            +
                end
         | 
| 271 | 
            +
              end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
              def find_workflow_by_name(name)
         | 
| 274 | 
            +
                workflows = @task_sets_interface.get({name: name.to_s})['taskSets']
         | 
| 275 | 
            +
                if workflows.empty?
         | 
| 276 | 
            +
                  print_red_alert "Workflow not found by name #{name}"
         | 
| 277 | 
            +
                  return nil
         | 
| 278 | 
            +
                elsif workflows.size > 1
         | 
| 279 | 
            +
                  print_red_alert "#{workflows.size} workflows by name #{name}"
         | 
| 280 | 
            +
                  print_workflows_table(workflows, {color: red})
         | 
| 281 | 
            +
                  print reset,"\n\n"
         | 
| 282 | 
            +
                  return nil
         | 
| 283 | 
            +
                else
         | 
| 284 | 
            +
                  return workflows[0]
         | 
| 285 | 
            +
                end
         | 
| 286 | 
            +
              end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
              def find_task_by_name_or_id(val)
         | 
| 289 | 
            +
                if val.to_s =~ /\A\d{1,}\Z/
         | 
| 290 | 
            +
                  return find_task_by_id(val)
         | 
| 291 | 
            +
                else
         | 
| 292 | 
            +
                  return find_task_by_name(val)
         | 
| 293 | 
            +
                end
         | 
| 294 | 
            +
              end
         | 
| 295 | 
            +
             | 
| 296 | 
            +
              def find_task_by_id(id)
         | 
| 297 | 
            +
                begin
         | 
| 298 | 
            +
                  json_response = @tasks_interface.get(id.to_i)
         | 
| 299 | 
            +
                  return json_response['task']
         | 
| 300 | 
            +
                rescue RestClient::Exception => e
         | 
| 301 | 
            +
                  if e.response && e.response.code == 404
         | 
| 302 | 
            +
                    print_red_alert "Task not found by id #{id}"
         | 
| 303 | 
            +
                  else
         | 
| 304 | 
            +
                    raise e
         | 
| 305 | 
            +
                  end
         | 
| 306 | 
            +
                end
         | 
| 307 | 
            +
              end
         | 
| 308 | 
            +
             | 
| 309 | 
            +
              def find_task_by_name(name)
         | 
| 310 | 
            +
                tasks = @tasks_interface.get({name: name.to_s})['tasks']
         | 
| 311 | 
            +
                if tasks.empty?
         | 
| 312 | 
            +
                  print_red_alert "Task not found by name #{name}"
         | 
| 313 | 
            +
                  return nil
         | 
| 314 | 
            +
                elsif tasks.size > 1
         | 
| 315 | 
            +
                  print_red_alert "#{tasks.size} tasks by name #{name}"
         | 
| 316 | 
            +
                  print_tasks_table(tasks, {color: red})
         | 
| 317 | 
            +
                  print reset,"\n\n"
         | 
| 318 | 
            +
                  return nil
         | 
| 319 | 
            +
                else
         | 
| 320 | 
            +
                  return tasks[0]
         | 
| 321 | 
            +
                end
         | 
| 322 | 
            +
              end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
              def print_workflows_table(workflows, opts={})
         | 
| 325 | 
            +
                table_color = opts[:color] || cyan
         | 
| 326 | 
            +
                rows = workflows.collect do |workflow|
         | 
| 327 | 
            +
                  task_names = []
         | 
| 328 | 
            +
                  workflow['taskSetTasks'].sort { |x,y| x['taskOrder'].to_i <=> y['taskOrder'].to_i }.each do |taskSetTask|
         | 
| 329 | 
            +
                    task_names << taskSetTask['task']['name']
         | 
| 330 | 
            +
                  end
         | 
| 331 | 
            +
                  {
         | 
| 332 | 
            +
                    id: workflow['id'],
         | 
| 333 | 
            +
                    name: workflow['name'],
         | 
| 334 | 
            +
                    tasks: task_names.join(', '),
         | 
| 335 | 
            +
                    dateCreated: format_local_dt(workflow['dateCreated'])
         | 
| 336 | 
            +
                  }
         | 
| 337 | 
            +
                end
         | 
| 338 | 
            +
                print table_color
         | 
| 339 | 
            +
                tp rows, [
         | 
| 340 | 
            +
                  :id,
         | 
| 341 | 
            +
                  :name,
         | 
| 342 | 
            +
                  :tasks
         | 
| 343 | 
            +
                ]
         | 
| 344 | 
            +
                print reset
         | 
| 345 | 
            +
              end
         | 
| 346 | 
            +
             | 
| 194 347 | 
             
            end
         |