Class: Aspera::Cli::Wizard

Inherits:
Object
  • Object
show all
Defined in:
lib/aspera/cli/wizard.rb

Overview

The wizard detects applications and generates a config

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent, main_folder) ⇒ Wizard

Returns a new instance of Wizard.



25
26
27
28
29
# File 'lib/aspera/cli/wizard.rb', line 25

def initialize(parent, main_folder)
  @parent = parent
  @main_folder = main_folder
  self.class.declare_options(options)
end

Class Method Details

.declare_options(options) ⇒ Object

Declare all wizard CLI options (metadata only - no handler binding yet).

Parameters:



18
19
20
21
22
# File 'lib/aspera/cli/wizard.rb', line 18

def declare_options(options)
  options.declare(:override,  description: 'Wizard: override existing value',                                                    allowed: Allowed::TYPES_BOOLEAN, default: false)
  options.declare(:default,   description: 'Wizard: set as default configuration for specified plugin (also: update)',           allowed: Allowed::TYPES_BOOLEAN, default: true)
  options.declare(:key_path,  description: 'Wizard: path to private key for JWT')
end

Instance Method Details

#ask_private_key(user:, url:, page:) ⇒ String

To be called in public wizard method to get private key

Parameters:

  • user (String)

    User's email

  • url (String)

    Instance URL

  • page (String)

    URL of page to enter pub key

Returns:

  • (String)

    Private key path (can contain ~ for home)



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
# File 'lib/aspera/cli/wizard.rb', line 93

def ask_private_key(user:, url:, page:)
  # Lets see if path to priv key is provided
  private_key_path = options.get_option(:key_path)
  # Give a chance to provide
  if private_key_path.nil?
    formatter.display_status('Path to private RSA key (leave empty to generate):')
    private_key_path = options.get_option(:key_path, mandatory: true).to_s
  end
  # Else generate path
  private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME) if private_key_path.empty?
  if File.exist?(File.expand_path(private_key_path))
    formatter.display_status('Using existing key:')
  else
    formatter.display_status("Generating #{OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH} bit RSA key...")
    OAuth::Jwt.generate_rsa_private_key(path: private_key_path)
    formatter.display_status('Created key:')
  end
  formatter.display_status(private_key_path)
  private_key_pem = File.read(File.expand_path(private_key_path))
  pub_key_pem = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
  options.set_option(:private_key, private_key_pem)
  formatter.display_status("Please Log in as user #{user.red} at: #{url.red}")
  formatter.display_status("Navigate to: #{page}")
  formatter.display_status("Check or update the value to (#{'including BEGIN/END lines'.red}):".blink)
  formatter.display_status(pub_key_pem, hide_secrets: false)
  formatter.display_status('Once updated or validated, press [Enter].')
  Environment.instance.open_uri(url)
  $stdin.gets if required
  private_key_path
end

#check_email(email) ⇒ Object



44
45
46
# File 'lib/aspera/cli/wizard.rb', line 44

def check_email(email)
  Aspera.assert(email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, type: ParameterError){"Username shall be an email: #{email}"}
end

#find(apps, preset_name: '') ⇒ Object

Wizard function, creates configuration

Parameters:

  • apps (Array)

    list of detected apps



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
# File 'lib/aspera/cli/wizard.rb', line 126

def find(apps, preset_name: '')
  identification = if apps.length.eql?(1)
    Log.log.debug{"Detected: #{identification}"}
    apps.first
  else
    formatter.display_status('Multiple applications detected, please select from:')
    require 'aspera/cli/result'
    formatter.display_results(Result::ObjectList.new(apps, fields: %w[product url version]))
    answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
    apps.find{ |a| a[:product].eql?(answer)}
  end
  wiz_preset_name = preset_name
  Log.dump(:identification, identification)
  wiz_url = identification[:url]
  formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
  # Set url for instantiation of plugin
  options.add_option_preset({url: wiz_url}, 'wizard')
  # Instantiate plugin: command line options will be known, e.g. private_key, and wizard can be called
  plugin_instance = Plugins::Factory.instance.plugin_class(identification[:product]).new(context: @parent.context)
  Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
  # Call the wizard
  wizard_result = plugin_instance.wizard(self, wiz_url)
  Log.log.debug{"wizard result: #{wizard_result}"}
  Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
  # Get preset name from user or default
  if wiz_preset_name.empty?
    elements = [
      identification[:product],
      URI.parse(wiz_url).host
    ]
    elements.push(options.get_option(:username, mandatory: true)) unless wizard_result[:preset_value].key?(:link) rescue nil
    wiz_preset_name = elements.join('_').strip.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_')
  end
  # Write configuration file
  formatter.display_status("Preparing preset: #{wiz_preset_name}")
  # Init defaults if necessary
  option_override = options.get_option(:override, mandatory: true)
  option_default = options.get_option(:default, mandatory: true)
  @parent.context.presets.defaults_set(identification[:product], wiz_preset_name, wizard_result[:preset_value].stringify_keys, option_default, option_override)
  test_args = wizard_result[:test_args]
  test_args = "-P#{wiz_preset_name} #{test_args}" unless option_default
  # TODO: actually test the command
  test_cmd = "#{Info::CMD_NAME} #{identification[:product]} #{test_args}"
  return Result::Status.new("You can test with:\n#{test_cmd.red}")
end

#formatterObject



40
41
42
# File 'lib/aspera/cli/wizard.rb', line 40

def formatter
  @parent.formatter
end

#identify_plugins_for_url(url:, plugin_name: nil) ⇒ Hash

Find a plugin, and issue the "require"

Returns:

  • (Hash)

    plugin info: { product:, name:, url:, version: }



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
# File 'lib/aspera/cli/wizard.rb', line 50

def identify_plugins_for_url(url:, plugin_name: nil)
  app_url = url
  check_only = plugin_name&.to_sym
  found_apps = []
  my_self_plugin_sym = self.class.name.split('::').last.downcase.to_sym
  Plugins::Factory.instance.plugin_list.each do |plugin_name_sym|
    # No detection for internal plugin
    next if plugin_name_sym.eql?(my_self_plugin_sym)
    next if check_only && !check_only.eql?(plugin_name_sym)
    # Load plugin class
    plugin_klass = Plugins::Factory.instance.plugin_class(plugin_name_sym)
    # Requires detection method
    next unless plugin_klass.respond_to?(:detect)
    detection_info = nil
    begin
      Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
      RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
      detection_info = plugin_klass.detect(app_url)
    rescue OpenSSL::SSL::SSLError => e
      Log.log.warn(e.message)
      Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
    rescue StandardError => e
      Log.log.debug{"detect error: [#{e.class}] #{e}"}
      next
    end
    next if detection_info.nil?
    Aspera.assert_type(detection_info, Hash)
    Aspera.assert_type(detection_info[:url], String) if detection_info.key?(:url)
    app_name = plugin_klass.application_name
    # If there is a redirect, then the detector can override the url.
    found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
  end
  RestParameters.instance.spinner_cb.call(action: :success)
  Aspera.assert(!found_apps.empty?){"No known application found at #{app_url}"}
  Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)}, 'all app info keys must be symbols')
  return found_apps
end

#optionsObject



36
37
38
# File 'lib/aspera/cli/wizard.rb', line 36

def options
  @parent.options
end

#requiredBoolean

Returns false if in test mode to avoid interactive input.

Returns:

  • (Boolean)

    false if in test mode to avoid interactive input



32
33
34
# File 'lib/aspera/cli/wizard.rb', line 32

def required
  !ENV['ASCLI_WIZ_TEST']
end