class Morpheus::Cli::Apps

Public Class Methods

new() click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 26
def initialize()
  # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
end

Public Instance Methods

_get(id, options={}) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 670
def _get(id, options={})
  app = nil
  if id.to_s !~ /\A\d{1,}\Z/
    app = find_app_by_name_or_id(id)
    id = app['id']
  end
  @apps_interface.setopts(options)
  if options[:dry_run]
    print_dry_run @apps_interface.dry.get(id)
    return
  end
  json_response = @apps_interface.get(id.to_i)
  render_response(json_response, options, 'app') do
    app = json_response['app']
    # API used to only return apps.appTiers including instances with lots of detail
    # now returns simplified instance list as "instances" for terraform, etc with only id,name
    # so load the instance details if needed
    app_tiers = []
    instances = []
    if app['appTiers'] && !app['appTiers'].empty?
      # appTiers contains instances with lots of detail
      app_tiers = app['appTiers']
      app_tiers.each do |app_tier|
        instances += (app_tier['appInstances'] || []).collect {|it| it['instance']}.flatten().compact
      end
    elsif app['instances'] && !app['instances'].empty?
      # need to load instance details which are not returned in this simple list
      instance_ids = app['instances'].collect {|it| it['id'] }
      instances = @instances_interface.list({id: instance_ids})['instances']
    end
    print_h1 "App Details", [], options
    print cyan
    description_cols = {
      "ID" => 'id',
      "Name" => 'name',
      "Labels" => lambda {|it| format_list(it['labels']) rescue '' },
      "Description" => 'description',
      "Type" => lambda {|it| 
        if it['type']
          format_blueprint_type(it['type']) 
        else
          format_blueprint_type(it['blueprint'] ? it['blueprint']['type'] : nil) 
        end
      },
      "Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : '' },
      "Group" => lambda {|it| it['group'] ? it['group']['name'] : it['siteId'] },
      "Environment" => lambda {|it| it['appContext'] },
      "Owner" => lambda {|it| it['owner'] ? it['owner']['username'] : '' },
      #"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
      "Tiers" => lambda {|it| 
        # it['instanceCount']
        tiers = []
        app_tiers.each do |app_tier|
          tiers << app_tier['tier']
        end
        "(#{(tiers || []).size()}) #{tiers.collect {|it| it.is_a?(Hash) ? it['name'] : it }.join(',')}"
      },
      "Instances" => lambda {|it| 
        # it['instanceCount']
        "(#{instances.count}) #{instances.collect {|it| it['name'] }.join(',')}"
      },
      "Containers" => lambda {|it| 
        #it['containerCount']
        containers = []
        instances.each do |instance|
          containers += (instance['containers'] || [])
        end
        #"(#{containers.count})"
        "(#{containers.count}) #{containers.collect {|it| it }.join(',')}"
      },
      "Status" => lambda {|it| format_app_status(it) }
    }

    description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if app['status'] == 'pendingRemoval'

    # if app['blueprint'].nil?
    #   description_cols.delete("Blueprint")
    # end
    # if app['description'].nil?
    #   description_cols.delete("Description")
    # end
    print_description_list(description_cols, app)

    stats = app['stats']
    if app['instanceCount'].to_i > 0
      print_h2 "App Usage", options
      print_stats_usage(stats, {include: [:memory, :storage]})
    end

    if app_tiers.empty?
      print reset,"\n"
      if instances.empty?
        print cyan, "This app is empty", reset, "\n\n"
      else
        print_h2 "Instances", options
        instances_rows = instances.collect do |instance|
          connection_string = ''
          if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
            connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
          end
          {id: instance['id'], name: instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: (instance['containers'] || []).count, status: format_instance_status(instance), type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
        end
        instances_rows = instances_rows.sort {|x,y| x[:id] <=> y[:id] } #oldest to newest..
        print cyan
        print as_pretty_table(instances_rows, [:id, :name, :cloud, :type, :environment, :nodes, :connection, :status], {border_style: options[:border_style]})
        print reset
        print "\n"
      end
    else
      app_tiers.each do |app_tier|
        # print_h2 "Tier: #{app_tier['tier']['name']}", options
        print_h2 "#{app_tier['tier']['name']}", options
        print cyan
        tier_instances = (app_tier['appInstances'] || []).collect {|it| it['instance']}
        instance_list = tier_instances.collect { |tier_instance| instances.find { |i| i['id'] == tier_instance['id'] } }
        if instance_list.empty?
          puts yellow, "This tier is empty", reset
        else
          instances_rows = instance_list.collect do |instance|
            connection_string = ''
            if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
              connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
            end
            {id: instance['id'], name: instance['displayName'] ? instance['displayName'] : instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: (instance['containers'] || []).count, status: format_instance_status(instance), type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
          end
          instances_rows = instances_rows.sort {|x,y| x[:id] <=> y[:id] } #oldest to newest..
          print cyan
          print as_pretty_table(instances_rows, [:id, :name, :cloud, :type, :environment, :nodes, :connection, :status], {border_style: options[:border_style]})
          print reset
          print "\n"
        end
      end
    end
    print cyan


    # refresh until a status is reached
    if options[:refresh_until_status]
      if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
        options[:refresh_interval] = default_refresh_interval
      end
      statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
      if !statuses.include?(app['status'])
        print cyan, "Refreshing in #{options[:refresh_interval] > 1 ? options[:refresh_interval].to_i : options[:refresh_interval]} seconds"
        sleep_with_dots(options[:refresh_interval])
        print "\n"
        _get(app['id'], options)
      end
    end
  end
add(args) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 211
def add(args)
  options = {}
  params = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[name] [options]")
    #build_option_type_options(opts, options, add_app_option_types(false))
    # these come from build_options_types
    opts.on( '-b', '--blueprint BLUEPRINT', "Blueprint Name or ID. The default value is 'existing' which means no blueprint, for creating a blank app and adding existing instances." ) do |val|
      options[:blueprint] = val
    end
    opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
      options[:group] = val
    end
    opts.on( '-c', '--cloud CLOUD', "Default Cloud Name or ID." ) do |val|
      options[:cloud] = val
    end
    opts.on( '--name VALUE', String, "Name" ) do |val|
      options[:name] = val
    end
    opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
      options[:options]['labels'] = parse_labels(val)
    end
    opts.on( '--description VALUE', String, "Description" ) do |val|
      options[:description] = val
    end
    opts.on( '-e', '--environment VALUE', "Environment Name" ) do |val|
      options[:environment] = val.to_s == 'null' ? nil : val
    end
    # config is being deprecated in favor of the standard --payload options
    # opts.add_hidden_option(['config', 'config-dir', 'config-file', 'config-yaml'])
    opts.on('--validate','--validate', "Validate Only. Validates the configuration and skips creating it.") do
      options[:validate_only] = true
    end
    opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
      options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
    end
    build_common_options(opts, options, [:options, :payload, :json, :yaml, :dry_run, :quiet])
    opts.footer = "Create a new app.\n" +
                  "[name] is required. This is the name of the new app. It may also be passed as --name or inside your config."
  end
  optparse.parse!(args)
  if args.count > 1
    print_error Morpheus::Terminal.angry_prompt
    puts_error  "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args}\n#{optparse}"
    return 1
  end
  # allow name as first argument
  if args[0] # && !options[:name]
    options[:name] = args[0]
  end
  connect(options)
  begin
    options[:options] ||= {}
    passed_options = (options[:options] || {}).reject {|k,v| k.is_a?(Symbol) }
    payload = {}
    if options[:payload]
      # payload is from parsed json|yaml files or arguments.
      payload = options[:payload]
      # merge -O options
      payload.deep_merge!(passed_options) unless passed_options.empty?
      # support some options on top of --payload
      [:name, :description, :environment].each do |k|
        if options.key?(k)
          payload[k.to_s] = options[k]
        end
      end
    else
      # prompt for payload
      payload = {}
      # merge -O options
      payload.deep_merge!(passed_options) unless passed_options.empty?

      # this could have some special -O context, like -O tier.Web.0.instance.name
      # tier_config_options = payload.delete('tier')

      # Blueprint
      blueprint_id = 'existing'
      blueprint = nil
      if options[:blueprint]
        blueprint_id = options[:blueprint]
        options[:options]['blueprint'] = options[:blueprint]
      end
      v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'selectOptions' => get_available_blueprints(), 'required' => true, 'defaultValue' => 'existing', 'description' => "The blueprint to use. The default value is 'existing' which means no template, for creating a blank app and adding existing instances."}], options[:options])
      blueprint_id = v_prompt['blueprint']
      
      if blueprint_id.to_s.empty? || blueprint_id == 'existing'
        blueprint = {"id" => "existing", "name" => "Existing Instances", "value" => "existing", "type" => "morpheus"}
      else
        blueprint = find_blueprint_by_name_or_id(blueprint_id)
        if blueprint.nil?
          #print_red_alert "Blueprint not found by name or id '#{blueprint_id}'"
          return 1
        end
      end
      
      payload['templateId'] = blueprint['id'] # for pre-3.6 api
      payload['blueprintId'] = blueprint['id']
      payload['blueprintName'] = blueprint['name'] #for future api plz

      # Name
      options[:options]['name'] = options[:name] if options.key?(:name)
      v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this app'}], options[:options])
      payload['name'] = v_prompt['name']
      
      # Description
      options[:options]['description'] = options[:description] if options.key?(:description)
      v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false}], options[:options])
      payload['description'] = v_prompt['description']
      

      # Group
      group_id = nil
      options[:options]['group'] = options[:group] if options.key?(:group)
      v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => get_available_groups(), 'required' => true, 'defaultValue' => @active_group_id}], options[:options])
      group_id = v_prompt['group']
      
      group = find_group_by_name_or_id_for_provisioning(group_id)
      return 1 if group.nil?
      payload['group'] = {'id' => group['id'], 'name' => group['name']}

      # Default Cloud
      cloud_id = nil
      cloud = nil
      scoped_available_clouds = get_available_clouds(group['id'])
      if options[:cloud]
        cloud_id = options[:cloud]
      else
        v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'fieldLabel' => 'Default Cloud', 'type' => 'select', 'selectOptions' => scoped_available_clouds}], options[:options])
        cloud_id = v_prompt['cloud'] unless v_prompt['cloud'].to_s.empty?
      end
      if cloud_id
        cloud = find_cloud_by_name_or_id_for_provisioning(group['id'], cloud_id)
        payload['defaultCloud'] = {'id' => cloud ? cloud['id'] : cloud_id}
      end
      
      # Environment
      selected_environment = nil
      available_environments = get_available_environments()
      if options[:environment]
        payload['environment'] = options[:environment]
      else
        v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'select', 'selectOptions' => available_environments}], options[:options], @api_client)
        payload['environment'] = v_prompt['environment'] unless v_prompt['environment'].to_s.empty?
      end
      selected_environment = nil
      if payload['environment']
        selected_environment = available_environments.find {|it| it['code'] == payload['environment'] || it['name'] == payload['environment'] }
        if selected_environment.nil?
          print_red_alert "Environment not found by name or code '#{payload['environment']}'"
          return 1
        end
      end
      # payload['appContext'] = payload['environment'] if payload['environment']

      # Labels
      v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text'}], options[:options])
      payload['labels'] = parse_labels(v_prompt['labels']) unless v_prompt['labels'].to_s.empty?

      # Configure (Tiers and their instances)
      if !payload['tiers']
        if payload['blueprintId'] != 'existing'
          
          # fetch the app template
          blueprint = find_blueprint_by_name_or_id(payload['blueprintId'])
          return 1 if blueprint.nil?
          
          unless options[:quiet]
            print cyan, "Configuring app with blueprint id: #{blueprint['id']}, name: #{blueprint['name']}, type: #{blueprint['type']}\n"
          end
          
          blueprint_type = blueprint['type'] || 'morpheus'
          if blueprint_type == 'morpheus'
            # configure each tier and instance in the blueprint
            # tiers are a map, heh, sort them by tierIndex
            tiers = blueprint["config"]["tiers"] ? blueprint["config"]["tiers"] : (blueprint["tiers"] || {})
            sorted_tiers = tiers.collect {|k,v| [k,v] }.sort {|a,b| a[1]['tierIndex'] <=> b[1]['tierIndex'] }
            payload.delete('defaultCloud')
            sorted_tiers.each do |tier_obj|
              tier_name = tier_obj[0]
              tier_config = tier_obj[1]
              payload['tiers'] ||= {}
              payload['tiers'][tier_name] ||= tier_config.clone
              # remove instances, they will be iterated over and merged back in
              tier_instances = payload['tiers'][tier_name].delete("instances")
              # remove other blank stuff
              if payload['tiers'][tier_name]['linkedTiers'] && payload['tiers'][tier_name]['linkedTiers'].empty?
                payload['tiers'][tier_name].delete('linkedTiers')
              end
              # remove extra instance options at tierName.index, probabl need a namespace here like tier.TierName.index
              tier_extra_options = {}
              if payload[tier_name]
                tier_extra_options = payload.delete(tier_name)
              end
              tier_instance_types = tier_instances ? tier_instances.collect {|it| (it['instance'] && it['instance']['type']) ? it['instance']['type'].to_s : 'unknown'}.compact : []
              unless options[:quiet]
                # print cyan, "Configuring Tier: #{tier_name} (#{tier_instance_types.empty? ? 'empty' : tier_instance_types.join(', ')})", "\n"
                print cyan, "Configuring tier #{tier_name}", reset, "\n"
              end
              # todo: also prompt for tier settings here, like linkedTiers: []
              if tier_instances
                tier_instances = tier_config['instances'] || []
                tier_instances.each_with_index do |instance_config, instance_index|
                  instance_type_code = instance_config['type']
                  if instance_config['instance'] && instance_config['instance']['type']
                    instance_type_code = instance_config['instance']['type']
                  end
                  if instance_type_code.nil?
                    print_red_alert "Unable to determine instance type for tier: #{tier_name} index: #{instance_index}"
                    return 1
                  else
                    unless options[:quiet]
                      print cyan, "Configuring #{instance_type_code} instance #{tier_name}.#{instance_index}", reset, "\n"
                    end

                    # Cloud
                    # cloud_id = nil
                    # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'selectOptions' => scoped_available_clouds, 'defaultValue' => cloud ? cloud['name'] : nil}], options[:options])
                    # cloud_id = v_prompt['cloud'] unless v_prompt['cloud'].to_s.empty?
                    # if cloud_id
                    #   # cloud = find_cloud_by_name_or_id_for_provisioning(group['id'], cloud_id)
                    #   cloud = scoped_available_clouds.find {|it| it['name'] == cloud_id.to_s } || scoped_available_clouds.find {|it| it['id'].to_s == cloud_id.to_s }
                    #   return 1 if cloud.nil?
                    # else
                    #   # prompt still happens inside get_scoped_instance_config
                    # end
                    
                    
                    # prompt for the cloud for this instance
                    # the cloud is part of finding the scoped config in the blueprint
                    scoped_instance_config = get_scoped_instance_config(instance_config.clone, selected_environment ? selected_environment['name'] : nil, group ? group['name'] : nil, cloud ? cloud['name'] : nil)

                    # now configure an instance like normal, use the config as default options with :always_prompt
                    instance_prompt_options = {}
                    instance_prompt_options[:group] = group ? group['id'] : nil
                    #instance_prompt_options[:cloud] = cloud ? cloud['name'] : nil
                    instance_prompt_options[:default_cloud] = cloud ? cloud['name'] : nil
                    instance_prompt_options[:environment] = selected_environment ? selected_environment['code'] : nil
                    instance_prompt_options[:default_security_groups] = scoped_instance_config['securityGroups'] ? scoped_instance_config['securityGroups'] : nil

                    instance_prompt_options[:no_prompt] = options[:no_prompt]
                    #instance_prompt_options[:always_prompt] = options[:no_prompt] != true # options[:always_prompt]
                    instance_prompt_options[:options] = scoped_instance_config # meh, actually need to make these default values instead..
                    #instance_prompt_options[:options][:always_prompt] = instance_prompt_options[:no_prompt] != true
                    instance_prompt_options[:options][:no_prompt] = instance_prompt_options[:no_prompt]
                    # also allow arbritrary options passed as tierName.instanceIndex like Web.0.instance.layout.id=75
                    instance_extra_options = {}
                    if tier_extra_options && tier_extra_options[instance_index.to_s]
                      instance_extra_options = tier_extra_options[instance_index.to_s]
                    end
                    instance_prompt_options[:options].deep_merge!(instance_extra_options)

                    #instance_prompt_options[:name_required] = true
                    instance_prompt_options[:instance_type_code] = instance_type_code
                    # todo: an effort to render more useful help eg.  -O Web.0.instance.name
                    help_field_prefix = "#{tier_name}.#{instance_index}" 
                    instance_prompt_options[:help_field_prefix] = help_field_prefix
                    instance_prompt_options[:options][:help_field_prefix] = help_field_prefix
                    instance_prompt_options[:locked_fields] = scoped_instance_config['lockedFields']
                    instance_prompt_options[:for_app] = true
                    instance_prompt_options[:skip_labels_prompt] = true
                    # or could do this: instance_prompt_options[:labels] = default_labels
                    # this provisioning helper method handles all (most) of the parsing and prompting
                    scoped_instance_config = Marshal.load( Marshal.dump(scoped_instance_config) )
                    instance_config_payload = prompt_new_instance(instance_prompt_options)

                    # strip all empty string and nil
                    instance_config_payload.deep_compact!
                    # use the blueprint config as the base
                    final_config = scoped_instance_config.clone
                    # merge the prompted values
                    final_config.deep_merge!(instance_config_payload)
                    final_config.delete('environments')
                    final_config.delete('groups')
                    final_config.delete('clouds')
                    final_config.delete('lockedFields')
                    final_config.delete('userRemovedFields')
                    # should not need this...
                    final_config.delete(:no_prompt)
                    final_config.delete(:help_field_prefix)
                    # add config to payload
                    payload['tiers'][tier_name]['instances'] ||= []
                    payload['tiers'][tier_name]['instances'] << final_config
                  end
                end
              else
                puts yellow, "Tier '#{tier_name}' is empty", reset
              end
            end
          elsif blueprint_type == 'terraform'
            # prompt for Terraform config
            # todo
          elsif blueprint_type == 'arm'
            # prompt for ARM config
            # todo
          elsif blueprint_type == 'cloudFormation'
            # prompt for cloudFormation config
            # todo
          else
            print yellow, "Unknown template type: #{template_type})", "\n"
          end
        end
      end
    end

    @apps_interface.setopts(options)

    # Validate Only
    if options[:validate_only] == true
      # Validate Only Dry run
      if options[:dry_run]
        if options[:json]
          puts as_json(payload, options)
        elsif options[:yaml]
          puts as_yaml(payload, options)
        else
          print_dry_run @apps_interface.dry.validate(payload)
        end
        return 0
      end
      json_response = @apps_interface.validate(payload)

      if options[:json]
        puts as_json(json_response, options)
      else
        if !options[:quiet]
          if json_response['success'] == true
            print_green_success "New app '#{payload['name']}' validation passed. #{json_response['msg']}".strip
          else
            print_red_alert "New app '#{payload['name']}' validation failed. #{json_response['msg']}".strip
            # looks for special error format like instances.instanceErrors
            if json_response['errors'] && json_response['errors']['instances']
              json_response['errors']['instances'].each do |error_obj|
                tier_name = error_obj['tier']
                instance_index = error_obj['index']
                instance_errors = error_obj['instanceErrors']
                print_error red, "#{tier_name} : #{instance_index}", reset, "\n"
                if instance_errors
                  instance_errors.each do |err_key, err_msg|
                    print_error red, " * #{err_key} : #{err_msg}", reset, "\n"
                  end
                end
              end
            else
              # a default way to print errors
              (json_response['errors'] || {}).each do |error_key, error_msg|
                if error_key != 'instances'
                  print_error red, " * #{error_key} : #{error_msg}", reset, "\n"
                end
              end
            end
          end
        end
      end
      if json_response['success'] == true
        return 0
      else
        return 1
      end
    end

    @apps_interface.setopts(options)
    if options[:dry_run]
      print_dry_run @apps_interface.dry.create(payload)
      return 0
    end

    json_response = @apps_interface.create(payload)

    if options[:json]
      puts as_json(json_response, options)
      print "\n"
    elsif !options[:quiet]
      app = json_response["app"]
      print_green_success "Added app #{app['name']}"
      # add existing instances to blank app now?
      if !options[:no_prompt] && !payload['tiers'] && payload['id'] == 'existing'
        if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add an instance now?", options.merge({default: false}))
          add_instance([app['id']])
          while ::Morpheus::Cli::OptionTypes::confirm("Add another instance?", options.merge({default: false})) do
            add_instance([app['id']])
          end
        end
      end
      # print details
      get_args = [app['id']] + (options[:remote] ? ["-r",options[:remote]] : []) + (options[:refresh_interval] ? ['--refresh', options[:refresh_interval].to_s] : [])
      get(get_args)
    end
    return 0
  rescue RestClient::Exception => e
    #print_rest_exception(e, options)
    json_response = nil
    begin
      json_response = JSON.parse(e.response.to_s)
    rescue TypeError, JSON::ParserError => ex
      print_error red, "Failed to parse JSON response: #{ex}", reset, "\n"
    end
    if json_response && (json_response['errors'].nil? || json_response['errors'].empty?)
      # The default way to print error msg
      print_rest_exception(e, options)
    else
      # print errors and look for special errors.instances
      # todo: just handle sub lists of errors default error handler (print_rest_exception)
      (json_response['errors'] || {}).each do |error_key, error_msg|
        if error_key != 'instances'
          print_error red, " * #{error_key} : #{error_msg}", reset, "\n"
        end
      end
      # looks for special error format like instances.instanceErrors
      if json_response['errors'] && json_response['errors']['instances']
        json_response['errors']['instances'].each do |error_obj|
          tier_name = error_obj['tier']
          instance_index = error_obj['index']
          instance_errors = error_obj['instanceErrors']
          print_error red, "#{tier_name} : #{instance_index}", reset, "\n"
          if instance_errors
            instance_errors.each do |err_key, err_msg|
              print_error red, " * #{err_key} : #{err_msg}", reset, "\n"
            end
          end
        end
      end
    end
    exit 1
  end
end
connect(opts) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 30
def connect(opts)
  @api_client = establish_remote_appliance_connection(opts)
  @accounts_interface = @api_client.accounts
  @account_users_interface = @api_client.account_users
  @apps_interface = @api_client.apps
  @blueprints_interface = @api_client.blueprints
  @instance_types_interface = @api_client.instance_types
  @library_layouts_interface = @api_client.library_layouts
  @instances_interface = @api_client.instances
  @options_interface = @api_client.options
  @groups_interface = @api_client.groups
  @clouds_interface = @api_client.clouds
  @logs_interface = @api_client.logs
  @processes_interface = @api_client.processes
  @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
end
count(args) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 163
def count(args)
  options = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[options]")
    opts.on( '--owner USER', "Owner Username or ID" ) do |val|
      options[:owner] = val
    end
    opts.on( '--created-by USER', "Alias for --owner" ) do |val|
      options[:owner] = val
    end
    opts.add_hidden_option('--created-by')
    opts.on( '-s', '--search PHRASE', "Search Phrase" ) do |phrase|
      options[:phrase] = phrase
    end
    build_common_options(opts, options, [:query, :remote, :dry_run])
    opts.footer = "Get the number of apps."
  end
  optparse.parse!(args)
  connect(options)
  begin
    params = {}
    params.merge!(parse_list_options(options))
    account = nil
    if options[:owner]
      created_by_ids = find_all_user_ids(account ? account['id'] : nil, options[:owner])
      return if created_by_ids.nil?
      params['createdBy'] = created_by_ids
      # params['ownerId'] = created_by_ids # 4.2.1+
    end
    @apps_interface.setopts(options)
    if options[:dry_run]
      print_dry_run @apps_interface.dry.list(params)
      return
    end
    json_response = @apps_interface.list(params)
    # print number only
    if json_response['meta'] && json_response['meta']['total']
      print cyan, json_response['meta']['total'], reset, "\n"
    else
      print yellow, "unknown", reset, "\n"
    end
    return 0
  rescue RestClient::Exception => e
    print_rest_exception(e, options)
    exit 1
  end
end
get(args) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 637
def get(args)
  options = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[app]")
    opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
      options[:refresh_until_status] ||= "running,failed"
      if !val.to_s.empty?
        options[:refresh_interval] = val.to_f
      end
    end
    opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
      options[:refresh_until_status] = val.to_s.downcase
    end
    build_standard_get_options(opts, options)
    opts.footer = "Get details about an app.\n" +
                  "[app] is required. This is the name or id of an app. Supports 1-N [app] arguments."
  end
  optparse.parse!(args)
  if args.count < 1
    print_error Morpheus::Terminal.angry_prompt
    puts_error  "#{command_name} get expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
    return 1
  end

  connect(options)

  id_list = parse_id_list(args)
  return run_command_for_each_arg(id_list) do |arg|
    _get(arg, options)
  end
  
end
handle(args) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 47
def handle(args)
  handle_subcommand(args)
end
list(args) click to toggle source
# File lib/morpheus/cli/commands/apps.rb, line 51
def list(args)
  params = {}
  options = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage()
    opts.on( '-t', '--type TYPE', "Filter by type" ) do |val|
      options[:type] = val
    end
    opts.on( '--blueprint BLUEPRINT', "Blueprint Name or ID" ) do |val|
      options[:blueprint] = val
    end
    opts.on( '--owner USER', "Owner Username or ID" ) do |val|
      options[:owner] = val
    end
    opts.on( '--created-by USER', "[DEPRECATED] Alias for --owner" ) do |val|
      options[:owner] = val
    end
    opts.add_hidden_option('--created-by')
    opts.on('--pending-removal', "Include apps pending removal.") do
      options[:showDeleted] = true
    end
    opts.on('--pending-removal-only', "Only apps pending removal.") do
      options[:deleted] = true
    end
    opts.on('--environment ENV', "Filter by environment code (appContext)") do |val|
      # environment means appContext
      params['environment'] = (params['environment'] || []) + val.to_s.split(',').collect {|s| s.strip }.select {|s| s != "" }
    end
    opts.on('--status STATUS', "Filter by status.") do |val|
      params['status'] = (params['status'] || []) + val.to_s.split(',').collect {|s| s.strip }.select {|s| s != "" }
    end
    opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
      add_query_parameter(params, 'labels', parse_labels(val))
    end
    opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
      add_query_parameter(params, 'allLabels', parse_labels(val))
    end
    opts.on('-a', '--details', "Display all details: memory and storage usage used / max values." ) do
      options[:details] = true
    end
    build_standard_list_options(opts, options)
    opts.footer = "List apps."
  end
  optparse.parse!(args)
  # verify_args!(args:args, optparse:optparse, count:0)
  if args.count > 0
    options[:phrase] = args.join(" ")
  end
  connect(options)
  
  if options[:type]
    params['type'] = [options[:type]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
  end
  if options[:blueprint]
    blueprint_ids = [options[:blueprint]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
    params['blueprintId'] = blueprint_ids.collect do |blueprint_id|
      if blueprint_id.to_s =~ /\A\d{1,}\Z/
        return blueprint_id
      else
        blueprint = find_blueprint_by_name_or_id(blueprint_id)
        return 1 if blueprint.nil?
        blueprint['id']
      end
    end
  end
  if options[:owner]
    owner_ids = [options[:owner]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
    params['ownerId'] = owner_ids.collect do |owner_id|
      if owner_id.to_s =~ /\A\d{1,}\Z/
        return owner_id
      else
        user = find_available_user_option(owner_id)
        return 1 if user.nil?
        user['id']
      end
    end
  end
  params.merge!(parse_list_options(options))
  account = nil
  if options[:owner]
    created_by_ids = find_all_user_ids(account ? account['id'] : nil, options[:owner])
    return if created_by_ids.nil?
    params['createdBy'] = created_by_ids
    # params['ownerId'] = created_by_ids # 4.2.1+
  end

  params['showDeleted'] = options[:showDeleted] if options.key?(:showDeleted)
  params['deleted'] = options[:deleted] if options.key?(:deleted)

  @apps_interface.setopts(options)
  if options[:dry_run]
    print_dry_run @apps_interface.dry.list(params)
    return
  end
  json_response = @apps_interface.list(params)
  render_response(json_response, options, "apps") do
    apps = json_response['apps']
    title = "Morpheus Apps"
    subtitles = []
    subtitles += parse_list_subtitles(options)
    print_h1 title, subtitles, options
    if apps.empty?
      print cyan,"No apps found.",reset,"\n"
    else
      print_apps_table(apps, options)
      print_results_pagination(json_response)
    end
   print reset,"\n"
  end
  return 0, nil
end