lib/suse/connect/cli.rb
require 'optparse'
require 'suse/connect'
module SUSE
module Connect
# Command line interface for interacting with SUSEConnect
class Cli # rubocop:disable ClassLength
include Logger
SUMA_SYSTEM_ID = '/etc/sysconfig/rhn/systemid'.freeze
attr_reader :config, :options
def initialize(argv)
@options = {}
@argv = argv
extract_options
@config = Config.new.merge!(@options)
end
def execute! # rubocop:disable MethodLength, CyclomaticComplexity, PerceivedComplexity, AbcSize
# check for parameter dependencies
if @config.status
status.print_product_statuses(:json)
elsif @config.status_text
status.print_product_statuses(:text)
elsif @config.deregister
Client.new(@config).deregister!
elsif @config.keepalive
Client.new(@config).keepalive!
elsif @config.cleanup
System.cleanup!
elsif @config.rollback
Migration.rollback
elsif @config.list_extensions
if status.activated_base_product?
status.print_extensions_list
else
log.error 'To list extensions, you must first register the base product, using: SUSEConnect -r <registration code>'
exit(1)
end
else
if @config.instance_data_file && @config.url_default?
log.error 'Please use --instance-data only in combination with --url pointing to your RMT or SMT server'
exit(1)
elsif @config.url_default? && !@config.token && !@config.product
puts @opts
exit(1)
elsif File.exist?(SUMA_SYSTEM_ID)
log.error 'This system is managed by SUSE Manager / Uyuni, do not use SUSEconnect.'
exit(1)
else
Client.new(@config).register!
end
end
rescue Errno::ECONNREFUSED
log.fatal "Error: Connection refused by server #{@config.url}"
exit 64
rescue Errno::EACCES => e
log.fatal "Error: Access error - #{e.message}"
exit 65
rescue JSON::ParserError
log.fatal complain_if_broken_smt || 'Error: Cannot parse response from server'
exit 66
rescue ApiError => e
if e.code == 401 && System.credentials?
log.fatal 'Error: Invalid system credentials, probably because the registered system was deleted in SUSE Customer Center.' \
" Check #{@options[:url] || 'https://scc.suse.com'} whether your system appears there." \
' If it does not, please call SUSEConnect --cleanup and re-register this system.'
else
log.fatal complain_if_broken_smt || "Error: Registration server returned '#{e.message}' (#{e.code})"
end
exit 67
rescue FileError => e
log.fatal "FileError: '#{e.message}'"
exit 68
rescue ZypperError => e
# Zypper errors are in the range 1-7 and 100-105 (which connect will not cause)
log.fatal "Error: zypper returned (#{e.exitstatus}) with '#{e.output}'"
exit e.exitstatus
rescue SystemNotRegisteredError
log.fatal 'Deregistration failed. Check if the system has been '\
'registered using the --status-text option or use the '\
'--regcode parameter to register it.'
exit 69
rescue BaseProductDeactivationError
log.fatal 'Can not deregister base product. Use SUSEConnect -d to deactivate the whole system.'
exit 70
rescue PingNotAllowed => e
# Note that this exit code is then catched by the systemd service that
# runs `SUSEConnect --keepalive`. See the `SuccessExitStatus` entry on
# `package/suseconnect-keepalive.service`.
log.fatal "Error sending keepalive: #{e.message}"
exit 71
ensure
@config.write! if @config.write_config
end
private
def complain_if_broken_smt
unless @config.url_default? || Client.new(@config).api.up_to_date?
return "Your Registration Proxy server doesn't support this function. Please update it and try again."
end
end
def extract_options # rubocop:disable MethodLength
@opts = OptionParser.new
@opts.separator 'Register SUSE Linux Enterprise installations with the SUSE Customer Center.'
@opts.separator 'Registration allows access to software repositories (including updates)'
@opts.separator 'and allows online management of subscriptions and organizations.'
@opts.separator ''
@opts.separator 'Manage subscriptions at https://scc.suse.com'
@opts.separator ''
@opts.on('-p', '--product [PRODUCT]',
'Specify a product for activation/deactivation. Only',
'one product can be processed at a time. Defaults to',
'the base SUSE Linux Enterprise product on this ',
'system. Product identifiers can be obtained',
'with `--list-extensions`.',
'Format: <name>/<version>/<architecture>') do |opt|
check_if_param(opt, 'Please provide a product identifier')
# rubocop:disable RegexpLiteral
check_if_param((opt =~ /\S+\/\S+\/\S+/), 'Please provide the product identifier in this format: ' \
'<internal name>/<version>/<architecture>. You can find these values by calling: ' \
'\'SUSEConnect --list-extensions\'. ')
identifier, version, arch = opt.split('/')
@options[:product] = Remote::Product.new(identifier: identifier, version: version, arch: arch)
end
@opts.on('-r', '--regcode [REGCODE]',
'Subscription registration code for the product to',
'be registered.',
'Relates that product to the specified subscription,',
'and enables software repositories for that product.') do |opt|
@options[:token] = opt
end
@opts.on('-d', '--de-register',
'De-registers the system and base product, or in',
'conjunction with --product, a single extension, and',
'removes all its services installed by SUSEConnect.',
'After de-registration the system no longer consumes',
'a subscription slot in SCC.') do |_opt|
@options[:deregister] = true
end
@opts.on('--keepalive',
'Sends data to SCC to update the system information.') do |_opt|
@options[:keepalive] = true
end
@opts.on('--instance-data [path to file]', 'Path to the XML file holding the public key and',
'instance data for cloud registration with SMT.') do |opt|
check_if_param(opt, 'Please provide the path to your instance data file')
@options[:instance_data_file] = opt
end
@opts.on('-e', '--email <email>', 'Email address for product registration.') do |opt|
check_if_param(opt, 'Please provide an email address')
@options[:email] = opt
end
@opts.on('--url [URL]', 'URL of registration server',
'(e.g. https://scc.suse.com).',
'Implies --write-config so that subsequent',
'invocations use the same registration server.') do |opt|
check_if_param(opt, 'Please provide registration server URL')
@options[:url] = opt
@options[:write_config] = true
end
@opts.on('--namespace [NAMESPACE]', 'Namespace option for use with SMT staging',
'environments.') do |opt|
check_if_param(opt, 'Please provide a namespace')
@options[:namespace] = opt
@options[:write_config] = true
end
@opts.on('-s', '--status', 'Get current system registration status in json',
'format.') do |_opt|
@options[:status] = true
end
@opts.on('--status-text', 'Get current system registration status in text',
'format.') do |_opt|
@options[:status_text] = true
end
@opts.on('--list-extensions', 'List all extensions and modules available for',
'installation on this system.') do |_opt|
@options[:list_extensions] = true
end
@opts.on('--write-config', 'Write options to config file at /etc/SUSEConnect.') do |_opt|
@options[:write_config] = true
end
@opts.on('--cleanup', 'Remove old system credentials and all zypper',
'services installed by SUSEConnect.') do |_opt|
@options[:cleanup] = true
end
@opts.on('--rollback', 'Revert the registration state in case of a failed',
'migration.') do |_opt|
@options[:rollback] = true
end
@opts.separator ''
@opts.separator 'Common options:'
@opts.on('--root [PATH]', 'Path to the root folder, uses the same parameter',
'for zypper.') do |opt|
check_if_param(opt, 'Please provide path parameter')
@options[:filesystem_root] = opt
SUSE::Connect::System.filesystem_root = opt
end
@opts.on('--version', 'Print program version.') do
puts VERSION
exit 0
end
@opts.on('--debug', 'Provide debug output.') do |opt|
@options[:debug] = opt
SUSE::Connect::GlobalLogger.instance.log.level = ::Logger::DEBUG if opt
end
@opts.on_tail('-h', '--help', 'Show this message.') do
puts @opts
exit 0
end
@opts.set_summary_width(24)
@opts.parse(@argv)
@options[:language] = ENV['LANG'] if ENV['LANG']
log.debug("cmd options: '#{@options}'")
end
def check_if_param(opt, message)
unless opt
log.error message
exit 1
end
end
def status
@status ||= Status.new(@config)
end
end
end
end