class Kontena::Cli::Config

Helper to access and update the CLI configuration file.

Also provides a “fake” config hash that behaves just like the file based config when ENV-variables are used instead of config file.

Constants

TokenExpiredError

Attributes

current_account[R]
current_server[RW]
logger[RW]

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/kontena/cli/config.rb, line 40
def initialize
  super
  @logger = Kontena.logger

  load_settings_from_config_file

  override_master_settings_from_env
  override_cloud_settings_from_env

  debug { "Configuration loaded with #{servers.count} servers and #{accounts.count} accounts." }
  debug { "Current master: #{current_server || '(not selected)'}" }
  debug { "Current grid: #{current_grid || '(not selected)'}" }
  debug { "Current account: #{current_account.nil? ? '(not selected)' : current_account.name}" }
end
reset_instance() click to toggle source
# File lib/kontena/cli/config.rb, line 33
def self.reset_instance
  Singleton.send :__init__, self
  self
end

Public Instance Methods

accounts() click to toggle source

List of configured accounts

@return [Array]

# File lib/kontena/cli/config.rb, line 300
def accounts
  @accounts ||= []
end
add_server(data) click to toggle source

Add a new server to the configuration

@param [Hash] server_data

# File lib/kontena/cli/config.rb, line 307
def add_server(data)
  token = Token.new(
    access_token: data.delete('token'),
    refresh_token: data.delete('refresh_token'),
    expires_at: data.delete('token_expires_at'),
    parent_type: :master,
    parent_name: data['name'] || data[:name]
  )
  server = Server.new(data.merge(token: token))
  if (existing_index = find_server_index(server.name))
    servers[existing_index] = server
  else
    servers << server
  end
  write
end
config_file_available?() click to toggle source

Verifies access to existing configuration file

@return [Boolean]

# File lib/kontena/cli/config.rb, line 225
def config_file_available?
  File.exist?(config_filename) && File.readable?(config_filename)
end
config_filename() click to toggle source

Return the configuration file path. You can override the default by using KONTENA_CONFIG environment variable.

@return [String] path

# File lib/kontena/cli/config.rb, line 275
def config_filename
  return @config_filename if @config_filename
  if ENV['KONTENA_CONFIG']
    debug { "Using #{ENV['KONTENA_CONFIG']} as config file set through env KONTENA_CONFIG"}
    @config_filename = ENV['KONTENA_CONFIG']
  else
    @config_filename = default_config_filename
  end
end
current_account=(name) click to toggle source
# File lib/kontena/cli/config.rb, line 468
def current_account=(name)
  if name.nil?
    @current_account = nil
  elsif name == 'master'
    raise ArgumentError, "The master account can not be used as current account."
  else
    account = find_account(name.respond_to?(:name) ? name.name : name)
    if account
      @current_account = account
    else
      raise ArgumentError, "Account '#{name}' not found in configuration"
    end
  end
end
current_grid() click to toggle source

Name of the currently selected grid. Can override using KONTENA_GRID environment variable.

@return [String, NilClass]

# File lib/kontena/cli/config.rb, line 450
def current_grid
  return ENV['KONTENA_GRID'] unless ENV['KONTENA_GRID'].to_s.empty?
  return nil unless current_master
  current_master.grid
end
current_grid=(name) click to toggle source

Set the current grid name.

@param [String] grid_name @raise [ArgumentError] if current master hasn't been selected

# File lib/kontena/cli/config.rb, line 460
def current_grid=(name)
  if current_master
    current_master.grid = name
  else
    raise ArgumentError, "Current master not selected, can't set grid."
  end
end
current_master() click to toggle source

Currently selected master's configuration data

@return [Server]

# File lib/kontena/cli/config.rb, line 371
def current_master
  return servers[@current_master_index] if @current_master_index
  return nil unless current_server
  @current_master_index = find_server_index(current_server)
  servers[@current_master_index] if @current_master_index
end
current_master=(name) click to toggle source

Set the current master.

@param [String] server_name @raise [ArgumentError] if server by that name doesn't exist

# File lib/kontena/cli/config.rb, line 423
def current_master=(name)
  @current_master_index = nil
  if name.nil?
    self.current_server = nil
  else
    index = find_server_index(name.respond_to?(:name) ? name.name : name)
    if index
      self.current_server = servers[index].name
    else
      raise ArgumentError, "Server '#{name}' does not exist, can't add as current master."
    end
  end
end
debug(&block) click to toggle source
# File lib/kontena/cli/config.rb, line 55
def debug(&block)
  Kontena.logger.add(Logger::DEBUG, nil, 'CONFIG', &block)
end
default_config_filename() click to toggle source

Generate the default configuration filename

# File lib/kontena/cli/config.rb, line 286
def default_config_filename
  File.join(Dir.home, '.kontena_client.json')
end
default_settings() click to toggle source

Default settings hash, used when configuration file does not exist.

@return [Hash]

# File lib/kontena/cli/config.rb, line 232
def default_settings
  debug { 'Configuration file not found, using default settings.' }
  {
    'current_server' => 'default',
    'servers' => []
  }
end
extract_token!(hash={}) click to toggle source
# File lib/kontena/cli/config.rb, line 133
def extract_token!(hash={})
  Token.new(
    access_token: hash.delete('token'),
    refresh_token: hash.delete('refresh_token'),
    expires_at: hash.delete('token_expires_at').to_i
  )
end
find_account(name) click to toggle source
# File lib/kontena/cli/config.rb, line 360
def find_account(name)
  accounts.find{|a| a['name'] == name.to_s}
end
find_account_index(name) click to toggle source
# File lib/kontena/cli/config.rb, line 364
def find_account_index(name)
  accounts.find_index{|a| a['name'] == name.to_s}
end
find_server(name) click to toggle source

Shortcut to find_server_by(name: name)

@param [String] server_name @return [Server, NilClass]

# File lib/kontena/cli/config.rb, line 348
def find_server(name)
  find_server_by(name: name)
end
find_server_by(criteria = {}) click to toggle source

Search the server list for a server by field(s) and value(s). @example

find_server_by(url: 'https://localhost', token: 'abcd')

@param [Hash] search_criteria @return [Server, NilClass]

# File lib/kontena/cli/config.rb, line 329
def find_server_by(criteria = {})
  servers.find{|s| criteria.none? {|k,v| v != s[k]}}
end
find_server_index(name) click to toggle source

Shortcut to find_server_index_by(name: name)

@param [String] server_name @return [Fixnum, NilClass]

# File lib/kontena/cli/config.rb, line 356
def find_server_index(name)
  find_server_index_by(name: name)
end
find_server_index_by(criteria = {}) click to toggle source

Search the server list for a server by field(s) and value(s) and return its index.

@example

find_server_index(url: 'https://localhost')

@param [Hash] search_criteria @return [Fixnum, NilClass]

# File lib/kontena/cli/config.rb, line 340
def find_server_index_by(criteria = {})
  servers.find_index{|s| criteria.none? {|k,v| v != s[k]}}
end
kontena_account_data() click to toggle source
# File lib/kontena/cli/config.rb, line 193
def kontena_account_data
  {
    name: 'kontena',
    url: ENV['KONTENA_CLOUD_URL'] || 'https://cloud-api.kontena.io',
    stacks_url: ENV['KONTENA_STACK_REGISTRY_URL'] || 'https://stacks.kontena.io',
    token_endpoint: ENV['AUTH_TOKEN_ENDPOINT'] || 'https://cloud-api.kontena.io/oauth2/token',
    authorization_endpoint: ENV['AUTH_AUTHORIZE_ENDPOINT'] || 'https://cloud.kontena.io/login/oauth/authorize',
    userinfo_endpoint: ENV['AUTH_USERINFO_ENDPOINT'] || 'https://cloud-api.kontena.io/user',
    token_post_content_type: ENV['AUTH_TOKEN_POST_CONTENT_TYPE'] || 'application/x-www-form-urlencoded',
    code_requires_basic_auth: ENV['AUTH_CODE_REQUIRES_BASIC_AUTH'].to_s == true,
    token_method: ENV['AUTH_TOKEN_METHOD'] || 'post',
    scope: ENV['AUTH_USERINFO_SCOPE'] || 'user',
    client_id: nil,
    stacks_read_authentication: ENV['KONTENA_STACK_REGISTRY_READ_AUTHENTICATION'].to_s == 'true'
  }
end
kontena_account_hash() click to toggle source

Returns a cleaned up version of the kontena account data with only the token and name.

# File lib/kontena/cli/config.rb, line 484
def kontena_account_hash
  hash = { name: 'kontena' }
  acc  = find_account('kontena')
  if acc && acc.token
    hash[:username] = acc.username if acc.username
    hash.merge!(acc.token.to_h)
  end
  hash
end
load_settings_from_config_file() click to toggle source

Load configuration from default location ($HOME/.kontena_client.json)

# File lib/kontena/cli/config.rb, line 142
def load_settings_from_config_file
  settings = config_file_available? ? parse_config_file : default_settings

  Array(settings['servers']).each do |server_data|
    if server_data['token']
      token = extract_token!(server_data)
      token.parent_type = :master
      token.parent_name = server_data['name']
      server = Server.new(server_data)
      server.token = token
    else
      server = Server.new(server_data)
    end
    server.account ||= 'master'
    if servers.find { |s| s['name'] == server.name}
      server.name = "#{server.name}-2"
      server.name.succ! until servers.find { |s| s['name'] == server.name }.nil?
      debug { "Renamed server to #{server.name} because a duplicate was found in config" }
    end
    servers << server
  end

  self.current_server = settings['current_server']

  Array(settings['accounts']).each do |account_data|
    if account_data['token']
      token = extract_token!(account_data)
      token.parent_type = :account
      token.parent_name = account_data['name']
      account = Account.new(account_data)
      account.token = token
    else
      account = Account.new(account_data)
    end
    accounts << account
  end

  ka = find_account('kontena')
  if ka
    kontena_account_data.each {|k,v| ka[k] = v}
  else
    accounts << Account.new(kontena_account_data)
  end

  master_index = find_account_index('master')
  accounts.delete_at(master_index) if master_index
  accounts << Account.new(master_account_data)

  self.current_account = settings['current_account'] || 'kontena'
end
master_account_data() click to toggle source
# File lib/kontena/cli/config.rb, line 210
def master_account_data
  {
    name: 'master',
    token_endpoint: '/oauth2/token',
    authorization_endpoint: '/oauth2/authorize',
    userinfo_endpoint: '/v1/user',
    token_post_content_type: 'application/json',
    token_method: 'post',
    code_requires_basic_auth: false
  }
end
migrate_legacy_settings(settings) click to toggle source

Converts old style settings hash into modern one

@param [Hash] settings_hash @return [Hash] migrated_settings_hash

# File lib/kontena/cli/config.rb, line 244
def migrate_legacy_settings(settings)
  debug { "Migrating from legacy style configuration" }
  {
    'current_server' => 'default',
    'servers' => [
      settings['server'].merge(
        'name' => 'default',
        'account' => 'kontena'
      )
    ],
    'accounts' => [ kontena_account_data ]
  }
end
override_cloud_settings_from_env() click to toggle source
# File lib/kontena/cli/config.rb, line 103
def override_cloud_settings_from_env
  if ENV['KONTENA_CLOUD']
    account = find_account(ENV['KONTENA_CLOUD'])
    if account
      debug { "Using cloud account #{ENV['KONTENA_CLOUD']} from config selected by env KONTENA_CLOUD" }
      self.current_account = account.name
    else
      debug { "Using new cloud account #{ENV['KONTENA_CLOUD']} from env" }
      account = Accout.new(kontena_account_data.merge(name: ENV['KONTENA_CLOUD']))
      accounts << account
    end
  elsif current_account
    account = current_account
  end

  if account.nil?
    debug { 'No account data from config or env' }
    self.current_account = nil
    return
  end

  if ENV['KONTENA_CLOUD_TOKEN']
    debug { 'Using cloud token from env KONTENA_CLOUD_TOKEN' }
    account.token ||= Token.new(parent_type: :account, parent_name: account.name)
    account.token.access_token = ENV['KONTENA_CLOUD_TOKEN']
    account.token.refresh_token = nil
    account.token.expires_at = nil
  end
end
override_master_settings_from_env() click to toggle source
# File lib/kontena/cli/config.rb, line 59
def override_master_settings_from_env
  if ENV['KONTENA_URL']
    server = find_server_by(url: ENV['KONTENA_URL'])
    if server.nil?
      debug { 'Using new master url from env KONTENA_URL' }
      server = Server.new(
        url: ENV['KONTENA_URL'],
        name: ENV['KONTENA_MASTER'] || 'default'
      )
      servers << server
    else
      debug { "Using master #{server.name} in config found via url in env KONTENA_URL" }
    end
  elsif ENV['KONTENA_MASTER']
    server = find_server_by(name: ENV['KONTENA_MASTER'])
    if server
      debug { "Using master #{ENV['KONTENA_MASTER']} set via env KONTENA_MASTER" }
    end
  elsif current_master
    server = current_master
  end

  if server.nil?
    debug { 'Could not determine a master through config or env' }
    self.current_master = nil
    return
  else
    self.current_master = server.name
  end

  if ENV['KONTENA_GRID']
    debug { "Using grid #{ENV['KONTENA_GRID']} from env KONTENA_GRID" }
    server.grid = ENV['KONTENA_GRID']
  end

  if ENV['KONTENA_TOKEN']
    debug { 'Using master token from env KONTENA_TOKEN' }
    server.token ||= Token.new(parent_type: :master, parent_name: server.name)
    server.token.access_token = ENV['KONTENA_TOKEN']
    server.token.refresh_token = nil
    server.token.expires_at = nil
  end
end
parse_config_file() click to toggle source

Read, parse and migrate the configuration file

@return [Hash] config_data

# File lib/kontena/cli/config.rb, line 261
def parse_config_file
  debug { "Loading configuration from #{config_filename}" }
  settings = JSON.load(File.read(config_filename))
  if settings.has_key?('server')
    settings = migrate_legacy_settings(settings)
  else
    settings
  end
end
require_current_account() click to toggle source

Raises unless current account is selected.

@return [Account] current_account @raise [ArgumentError] if no account is selected

# File lib/kontena/cli/config.rb, line 405
def require_current_account
  return @current_account if @current_account
  raise ArgumentError, "You are not logged into an authorization provider. Use: kontena cloud login"
end
require_current_account_token() click to toggle source
# File lib/kontena/cli/config.rb, line 410
def require_current_account_token
  account = require_current_account
  if !account || account.token.nil? || account.token.access_token.nil?
    raise ArgumentError, "You are not logged in to Kontena Cloud. Use: kontena cloud login"
  elsif account.token.expired?
    raise TokenExpiredError, "The cloud access token has expired and needs to be refreshed." unless cloud_client.refresh_token
  end
end
require_current_grid() click to toggle source

Raises unless current grid is selected.

@return [String] current_grid_name @raise [ArgumentError] if no grid is selected

# File lib/kontena/cli/config.rb, line 441
def require_current_grid
  return current_grid if current_grid
  raise ArgumentError, "You have not selected a grid. Use: kontena grid"
end
require_current_master() click to toggle source

Raises unless current master is selected.

@return [Server] current_master @raise [ArgumentError] if no account is selected

# File lib/kontena/cli/config.rb, line 396
def require_current_master
  return current_master if current_master
  raise ArgumentError, "You are not logged into a Kontena Master. Use: kontena master login"
end
require_current_master_token() click to toggle source

Raises unless current master has token.

@return [Token] current_master_token @raise [ArgumentError] if no token available

# File lib/kontena/cli/config.rb, line 382
def require_current_master_token
  require_current_master
  token = current_master.token
  if token && token.access_token
    return token unless token.expired?
    raise TokenExpiredError, "The access token has expired and needs to be refreshed."
  end
  raise ArgumentError, "You are not logged into a Kontena Master. Use: kontena master login"
end
servers() click to toggle source

List of configured servers

@return [Array]

# File lib/kontena/cli/config.rb, line 293
def servers
  @servers ||= []
end
to_hash() click to toggle source

Generate a hash from the current configuration.

@return [Hash]

# File lib/kontena/cli/config.rb, line 497
def to_hash
  hash = {
    current_server: (self.current_server && find_server(self.current_server)) ? self.current_server : nil,
    current_account: self.current_account ? self.current_account.name : nil,
    servers: servers.map(&:to_h),
    accounts: accounts.reject{|a| a.name == 'master' || a.name == 'kontena'}.map(&:to_h) + [kontena_account_hash]
  }
  hash[:servers].each do |server|
    server.delete(:account) if server[:account] == 'master'
  end
  hash
end
to_json() click to toggle source

Generate a JSON string from the current configuration

@return [String]

# File lib/kontena/cli/config.rb, line 513
def to_json
  JSON.pretty_generate(to_hash)
end
write() click to toggle source

Write the current configuration to config file. Does nothing if using settings from environment variables.

# File lib/kontena/cli/config.rb, line 519
def write
  return nil if ENV['KONTENA_URL']
  debug { "Writing configuration to #{config_filename}" }
  File.write(config_filename, to_json)
end