class Rzo::App::Subcommand

The base class for subcommands rubocop:disable Metrics/ClassLength

Attributes

config[R]

The Rizzo configuration after loading ~/.rizzo.yaml (–config). See load_config!

opts[R]

The options hash injected from the application controller via the initialize method.

pwd[R]

The present working directory at startup

Public Class Methods

load_rizzo_config(fpath) click to toggle source

Delegated method to mock with fixture data.

# File lib/rzo/app/subcommand.rb, line 25
def self.load_rizzo_config(fpath)
  config_file = Pathname.new(fpath).expand_path
  raise ErrorAndExit, "Cannot read config file #{config_file}" unless config_file.readable?

  config = YAML.safe_load(config_file.read)
  log.debug "Loaded #{config_file}"
  config
rescue Psych::SyntaxError => e
  raise ErrorAndExit, "Could not parse rizzo config #{config_file} #{e.message}"
end
new(opts = {}, stdout = $stdout, stderr = $stderr) click to toggle source

Initialize a subcommand with options injected by the application controller.

@param [Hash] opts the Options hash initialized by the Application controller.

# File lib/rzo/app/subcommand.rb, line 41
def initialize(opts = {}, stdout = $stdout, stderr = $stderr)
  @opts = opts
  @stdout = stdout
  @stderr = stderr
  reset_logging!(opts)
  @pwd = Dir.pwd
end

Public Instance Methods

run() click to toggle source

Default run method. Override this method in a subcommand sub-class

@return [Fixnum] the exit status of the subcommand.

# File lib/rzo/app/subcommand.rb, line 53
def run
  error "Implement the run method in subclass #{self.class}"
  1
end

Private Instance Methods

load_config!() click to toggle source

Load rizzo configuration. Populate @config.

Read rizzo configuration by looping through control repos and stopping at first match and merge on top of local, defaults (~/.rizzo.yaml)

# File lib/rzo/app/subcommand.rb, line 65
def load_config!
  config = load_rizzo_config(opts[:config])
  validate_personal_config!(config)
  repos = reorder_repos(config['control_repos'])
  config['control_repos'] = repos
  @config = load_repo_configs(config, repos)
  debug "Merged configuration: \n#{@config.to_yaml}"
  # TODO: Move these validations to an instance method?
  validate_complete_config!(@config)
  # validate_forwarded_ports(@config)
  # validate_ip_addresses(@config)
  @config
end
load_repo_configs(config = {}, repos = []) click to toggle source

Given a list of repository paths, load .rizzo.yaml from the root of each repository and return the result merged onto config. The merging behavior is implemented by [deep_merge](www.rubydoc.info/gems/deep_merge/1.1.1)

@param [Hash] config the starting config hash. Repo config maps will be

merged on top of this starting map.

@param [Array] repos the list of repositories to load .rizzo.yaml from.

@return [Hash] the merged configuration hash.

# File lib/rzo/app/subcommand.rb, line 91
def load_repo_configs(config = {}, repos = [])
  repos.each_with_object(config.dup) do |repo, hsh|
    fp = Pathname.new(repo).expand_path + '.rizzo.yaml'
    if readable?(fp.to_s)
      hsh.deep_merge!(load_rizzo_config(fp.to_s))
    else
      log.debug "Skipped #{fp} (it is not readable)"
    end
  end
end
load_rizzo_config(fpath) click to toggle source

Load the base configuration and return it as a hash. This is necessary to get access to the `'control_repos'` top level key, which is expected to be an Array of fully qualified paths to control repo base directories.

@param [String] fpath The fully qualified path to the configuration file

to load.

@return [Hash] The configuration map

# File lib/rzo/app/subcommand.rb, line 157
def load_rizzo_config(fpath)
  self.class.load_rizzo_config(fpath)
end
project_dir(path) click to toggle source

Memoized method to return the fully qualified path to the current rizzo project directory, based on the pwd. The project directory is the dirname of the full path to a `.rizzo.yaml` config file. Return false if not a project directory. ~/.rizzo.yaml is considered a personal configuration and not a project configuration.

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity

# File lib/rzo/app/subcommand.rb, line 185
def project_dir(path)
  return @project_dir unless @project_dir.nil?

  rizzo_file = Pathname.new("#{path}/.rizzo.yaml")
  personal_config = Pathname.new(File.expand_path('~/.rizzo.yaml'))
  iterations = 0
  while @project_dir.nil? && iterations < 100
    iterations += 1
    if readable?(rizzo_file.to_s) && rizzo_file != personal_config
      @project_dir = rizzo_file.dirname.to_s
    else
      rizzo_file = rizzo_file.dirname.dirname + '.rizzo.yaml'
      @project_dir = false if rizzo_file.dirname.root?
    end
  end
  @project_dir
end
raise_ip_err(ip, node) click to toggle source

Helper to raise a duplicate port error

# File lib/rzo/app/subcommand.rb, line 141
def raise_ip_err(ip, node)
  raise ErrorAndExit, "host ip #{ip} on node #{node} " \
    'is a duplicate.  IP addresses must be unique.  Check .rizzo.yaml ' \
    'files in each control repository for duplicate ip entries'
end
raise_port_err(port, node) click to toggle source

Helper to raise a duplicate port error

# File lib/rzo/app/subcommand.rb, line 133
def raise_port_err(port, node)
  raise ErrorAndExit, "host port #{port} on node #{node} " \
    'is a duplicate.  Ports must be unique.  Check .rizzo.yaml ' \
    'files in each control repository for duplicate forwarded_ports entries.'
end
readable?(path) click to toggle source

helper method to to stub in tests

# File lib/rzo/app/subcommand.rb, line 173
def readable?(path)
  File.readable?(path)
end
reorder_repos(repos = []) click to toggle source

Given a list of control repositories, determine if the user's runtime pwd is in a control repository. If it is, move that control repository to the top level. If the user is inside a control repository and

# File lib/rzo/app/subcommand.rb, line 208
def reorder_repos(repos = [])
  if path = project_dir(pwd)
    new_repos = repos - [path]
    new_repos.unshift(path)
  else
    repos
  end
end
validate_forwarded_ports(config) click to toggle source

Check for duplicate forwarded host ports across all hosts and exit non-zero with an error message if found.

# File lib/rzo/app/subcommand.rb, line 105
def validate_forwarded_ports(config)
  host_ports = []
  [*config['nodes']].each do |node|
    [*node['forwarded_ports']].each do |hsh|
      port = hsh['host'].to_i
      raise_port_err(port, node['name']) if host_ports.include?(port)
      host_ports.push(port)
    end
  end
  log.debug "host_ports = #{host_ports}"
end
validate_ip_addresses(config) click to toggle source

Check for duplicate forwarded host ports across all hosts and exit non-zero with an error message if found.

# File lib/rzo/app/subcommand.rb, line 120
def validate_ip_addresses(config)
  ips = []
  [*config['nodes']].each do |node|
    if ip = node['ip']
      raise_ip_err(ip, node['name']) if ips.include?(ip)
      ips.push(ip)
    end
  end
  log.debug "ips = #{ips}"
end
write_file(filepath) { |stdout| ... } click to toggle source

Write a file by yielding a file descriptor to the passed block. In the case of opening a file, the FD will automatically be closed.

# File lib/rzo/app/subcommand.rb, line 164
def write_file(filepath)
  case filepath
  when 'STDOUT' then yield @stdout
  when 'STDERR' then yield @stderr
  else File.open(filepath, 'w') { |fd| yield fd }
  end
end