From 470ddd6f461d5659b746724864f16aaf206532e2 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 26 May 2016 11:54:12 -0700 Subject: ensure that local nodes get the 'local' environment. --- lib/leap_cli/config/manager.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index ecc59f3..5af046f 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -352,15 +352,19 @@ module LeapCli # determine the node's properties. # def guess_node_env(node) - environment = self.env(default_environment) - if node['tags'] - node['tags'].to_a.each do |tag| - if self.environment_names.include?(tag) - environment = self.env(tag) + if node.vagrant? + return self.env("local") + else + environment = self.env(default_environment) + if node['tags'] + node['tags'].to_a.each do |tag| + if self.environment_names.include?(tag) + environment = self.env(tag) + end end end + return environment end - return environment end # -- cgit v1.2.3 From d4ee04322ce642c602269738e45f63b800d78cf7 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 21 Jun 2016 15:59:27 -0700 Subject: fix ruby deprecation warnings --- lib/leap_cli.rb | 6 +-- lib/leap_cli/bootstrap.rb | 15 ++++--- lib/leap_cli/commands/common.rb | 61 ---------------------------- lib/leap_cli/commands/new.rb | 2 +- lib/leap_cli/config/environment.rb | 4 +- lib/leap_cli/config/manager.rb | 5 +-- lib/leap_cli/leapfile.rb | 12 ++---- lib/leap_cli/log.rb | 81 +++++++++++++++++-------------------- lib/leap_cli/path.rb | 8 ++-- lib/leap_cli/remote/leap_plugin.rb | 2 +- lib/leap_cli/ssh_key.rb | 4 +- lib/leap_cli/util.rb | 20 ++++----- lib/leap_cli/util/remote_command.rb | 6 +-- 13 files changed, 75 insertions(+), 151 deletions(-) delete mode 100644 lib/leap_cli/commands/common.rb (limited to 'lib') diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index 0563327..fc8ab2b 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -55,7 +55,7 @@ require 'leap_cli/markdown_document_listener' # allow everyone easy access to log() command. # module LeapCli - Util.send(:extend, LeapCli::Log) - Config::Manager.send(:include, LeapCli::Log) - extend LeapCli::Log + Util.send(:extend, LeapCli::LogCommand) + Config::Manager.send(:include, LeapCli::LogCommand) + extend LeapCli::LogCommand end diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb index b7bc8e9..a6b1759 100644 --- a/lib/leap_cli/bootstrap.rb +++ b/lib/leap_cli/bootstrap.rb @@ -5,7 +5,6 @@ module LeapCli module Bootstrap - extend LeapCli::Log extend self # @@ -36,7 +35,7 @@ module LeapCli # called from leap executable. # def load_libraries(app) - if LeapCli.log_level >= 2 + if LeapCli.logger.log_level >= 2 log_version end load_commands(app) @@ -72,14 +71,14 @@ module LeapCli options = parse_logging_options(argv) verbose = (options[:verbose] || 1).to_i if verbose - LeapCli.set_log_level(verbose) + LeapCli.logger.log_level = verbose end if options[:log] - LeapCli.log_file = options[:log] - LeapCli::Util.log_raw(:log) { $0 + ' ' + argv.join(' ')} + LeapCli.logger.log_file = options[:log] + LeapCli.logger.log_raw(:log) { $0 + ' ' + argv.join(' ')} end unless options[:color].nil? - LeapCli.log_in_color = options[:color] + LeapCli.logger.log_in_color = options[:color] end end @@ -97,8 +96,8 @@ module LeapCli if !Path.platform || !File.directory?(Path.platform) bail! { log :missing, "platform directory '#{Path.platform}'" } end - if LeapCli.log_file.nil? && LeapCli.leapfile.log - LeapCli.log_file = LeapCli.leapfile.log + if LeapCli.logger.log_file.nil? && LeapCli.leapfile.log + LeapCli.logger.log_file = LeapCli.leapfile.log end elsif !leapfile_optional?(argv) puts diff --git a/lib/leap_cli/commands/common.rb b/lib/leap_cli/commands/common.rb deleted file mode 100644 index 7bf49db..0000000 --- a/lib/leap_cli/commands/common.rb +++ /dev/null @@ -1,61 +0,0 @@ -# -# Some common helpers available to all LeapCli::Commands -# -# This also includes utility methods, and makes all instance -# methods available as class methods. -# - -module LeapCli - module Commands - - extend self - extend LeapCli::Log - extend LeapCli::Util - extend LeapCli::Util::RemoteCommand - - protected - - def path(name) - Path.named_path(name) - end - - # - # keeps prompting the user for a numbered choice, until they pick a good one or bail out. - # - # block is yielded and is responsible for rendering the choices. - # - def numbered_choice_menu(msg, items, &block) - while true - say("\n" + msg + ':') - items.each_with_index &block - say("q. quit") - index = ask("number 1-#{items.length}> ") - if index.empty? - next - elsif index =~ /q/ - bail! - else - i = index.to_i - 1 - if i < 0 || i >= items.length - bail! - else - return i - end - end - end - end - - def parse_node_list(nodes) - if nodes.is_a? Config::Object - Config::ObjectList.new(nodes) - elsif nodes.is_a? Config::ObjectList - nodes - elsif nodes.is_a? String - manager.filter!(nodes) - else - bail! "argument error" - end - end - - end -end \ No newline at end of file diff --git a/lib/leap_cli/commands/new.rb b/lib/leap_cli/commands/new.rb index 838b80e..5c9fd74 100644 --- a/lib/leap_cli/commands/new.rb +++ b/lib/leap_cli/commands/new.rb @@ -54,7 +54,7 @@ module LeapCli; module Commands unless directory && directory.any? help! "Directory name is required." end - unless File.exists?(directory) + unless File.exist?(directory) if global[:yes] || agree("Create directory #{directory}? ") ensure_dir directory else diff --git a/lib/leap_cli/config/environment.rb b/lib/leap_cli/config/environment.rb index df4b56c..398fd02 100644 --- a/lib/leap_cli/config/environment.rb +++ b/lib/leap_cli/config/environment.rb @@ -106,7 +106,7 @@ module LeapCli; module Config # def template(template) path = Path.named_path([:template_config, template], Path.provider_base) - if File.exists?(path) + if File.exist?(path) return load_json(path, Config::Object) else return nil @@ -133,7 +133,7 @@ module LeapCli; module Config end def load_json(filename, object_class, options={}) - if !File.exists?(filename) + if !File.exist?(filename) return object_class.new(self) end diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index 5af046f..80ccbad 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -258,7 +258,7 @@ module LeapCli # yields each node, in sorted order # def each_node(&block) - env.nodes.each_node &block + env.nodes.each_node(&block) end def reload_node!(node) @@ -294,7 +294,6 @@ module LeapCli # def apply_inheritance(node, throw_exceptions=false) new_node = Config::Node.new(nil) - name = node.name node_env = guess_node_env(node) new_node.set_environment(node_env, new_node) @@ -409,7 +408,7 @@ module LeapCli [['services', :service_config], ['tags', :tag_config]].each do |attribute, path_sym| node[attribute].each do |attr_value| path = Path.named_path([path_sym, "#{attr_value}.rb"], provider_dir).sub(/\.json$/,'') - if File.exists?(path) + if File.exist?(path) files << path end end diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index 9164d0a..ac40237 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -62,7 +62,7 @@ module LeapCli # load the platform # platform_file = "#{@platform_directory_path}/platform.rb" - unless File.exists?(platform_file) + unless File.exist?(platform_file) Util.bail! "ERROR: The file `#{platform_file}` does not exist. Please check the value of `@platform_directory_path` in `Leapfile` or `~/.leaprc`." end require "#{@platform_directory_path}/platform.rb" @@ -73,12 +73,6 @@ module LeapCli "You need either leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last} or " + "platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}" end - unless @allow_production_deploy.nil? - Util::log 0, :warning, "in Leapfile: @allow_production_deploy is no longer supported." - end - unless @platform_branch.nil? - Util::log 0, :warning, "in Leapfile: @platform_branch is no longer supported." - end @valid = true return @valid end @@ -105,7 +99,7 @@ module LeapCli def edit_leaprc(property, value=nil) file_path = leaprc_path lines = [] - if File.exists?(file_path) + if File.exist?(file_path) regexp = /self\.#{Regexp.escape(property)} = .*? if @provider_directory_path == '#{Regexp.escape(@provider_directory_path)}'/ File.readlines(file_path).each do |line| unless line =~ regexp @@ -128,7 +122,7 @@ module LeapCli end def read_settings(file) - if File.exists? file + if File.exist? file Util::log 2, :read, file instance_eval(File.read(file), file) validate(file) diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 6589ad4..0497275 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -3,56 +3,50 @@ require 'paint' ## ## LOGGING ## -## Ugh. This class does not work well with multiple threads! -## module LeapCli - extend self - - attr_accessor :log_in_color - - # logging options - def log_level - @log_level ||= 1 - end - def set_log_level(value) - @log_level = value - end - - def indent_level - @indent_level ||= 0 - end - def indent_level=(value) - @indent_level = value - end + module LogCommand + def log(*args) + logger.log(*args) + end - def log_file - @log_file - end - def log_file=(value) - @log_file = value - if @log_file - if !File.directory?(File.dirname(@log_file)) - Util.bail!('Invalid log file "%s", directory "%s" does not exist' % [@log_file, File.dirname(@log_file)]) - end - @log_output_stream = File.open(@log_file, 'a') + def log_raw(*args) + logger.log(*args) end - end - def log_output_stream - @log_output_stream + def logger + @logger ||= LeapCli::LeapLogger.new + end end - end module LeapCli - module Log + class LeapLogger # # these are log titles typically associated with files # FILE_TITLES = [:updated, :created, :removed, :missing, :nochange, :loading] + attr_reader :log_output_stream, :log_file + attr_accessor :indent_level, :log_level, :log_in_color + + def initialize() + @log_level = 1 + @indent_level = 0 + @log_file = nil + @log_output_stream = nil + end + + def log_file=(value) + @log_file = value + if @log_file + if !File.directory?(File.dirname(@log_file)) + Util.bail!('Invalid log file "%s", directory "%s" does not exist' % [@log_file, File.dirname(@log_file)]) + end + @log_output_stream = File.open(@log_file, 'a') + end + end # # master logging function. @@ -65,13 +59,12 @@ module LeapCli # [:error, :warning, :info, :updated, :created, :removed, :no_change, :missing] # * Hash: a hash of options. so far, only :indent is supported. # - def log(*args) level = args.grep(Integer).first || 1 title = args.grep(Symbol).first message = args.grep(String).first options = args.grep(Hash).first || {} - unless message && LeapCli.log_level >= level + unless message && @log_level >= level return end @@ -117,7 +110,7 @@ module LeapCli end log_raw(:log, nil) { [clear_prefix, message].join } - if LeapCli.log_in_color + if @log_in_color log_raw(:stdout, options[:indent]) { [colored_prefix, message].join } else log_raw(:stdout, options[:indent]) { [clear_prefix, message].join } @@ -125,9 +118,9 @@ module LeapCli # run block, if given if block_given? - LeapCli.indent_level += 1 + @indent_level += 1 yield - LeapCli.indent_level -= 1 + @indent_level -= 1 end end @@ -142,20 +135,20 @@ module LeapCli def log_raw(mode, indent=nil, &block) # NOTE: print message (using 'print' produces better results than 'puts' when multiple threads are logging) if mode == :log - if LeapCli.log_output_stream + if @log_output_stream messages = [yield].compact.flatten if messages.any? timestamp = Time.now.strftime("%b %d %H:%M:%S") messages.each do |message| - LeapCli.log_output_stream.print("#{timestamp} #{message}\n") + @log_output_stream.print("#{timestamp} #{message}\n") end - LeapCli.log_output_stream.flush + @log_output_stream.flush end end elsif mode == :stdout messages = [yield].compact.flatten if messages.any? - indent ||= LeapCli.indent_level + indent ||= @indent_level indent_str = "" indent_str += " " * indent.to_i if indent.to_i > 0 diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb index fd2e3fc..11fc0f1 100644 --- a/lib/leap_cli/path.rb +++ b/lib/leap_cli/path.rb @@ -40,14 +40,14 @@ module LeapCli; module Path [Path.provider, Path.provider_base].each do |base| if arg.is_a?(Symbol) || arg.is_a?(Array) named_path(arg, base).tap {|path| - return path if File.exists?(path) + return path if File.exist?(path) } else File.join(base, arg).tap {|path| - return path if File.exists?(path) + return path if File.exist?(path) } File.join(base, 'files', arg).tap {|path| - return path if File.exists?(path) + return path if File.exist?(path) } end end @@ -83,7 +83,7 @@ module LeapCli; module Path end def self.exists?(name, provider_dir=nil) - File.exists?(named_path(name, provider_dir)) + File.exist?(named_path(name, provider_dir)) end def self.defined?(name) diff --git a/lib/leap_cli/remote/leap_plugin.rb b/lib/leap_cli/remote/leap_plugin.rb index b48f433..ee33cea 100644 --- a/lib/leap_cli/remote/leap_plugin.rb +++ b/lib/leap_cli/remote/leap_plugin.rb @@ -148,7 +148,7 @@ module LeapCli; module Remote; module LeapPlugin ssh_failures = [] exitcode_failures = [] succeeded = [] - task = LeapCli.log_level > 1 ? :standard_task : :skip_errors_task + task = LeapCli.logger.log_level > 1 ? :standard_task : :skip_errors_task with_task(task) do log :querying, 'facts' do progress " " diff --git a/lib/leap_cli/ssh_key.rb b/lib/leap_cli/ssh_key.rb index 138f444..2570557 100644 --- a/lib/leap_cli/ssh_key.rb +++ b/lib/leap_cli/ssh_key.rb @@ -30,7 +30,7 @@ module LeapCli if arg1 =~ /^ssh-/ type, data = arg1.split(' ') key = SshKey.new load_from_data(data, type) - elsif File.exists? arg1 + elsif File.exist? arg1 key = SshKey.new load_from_file(arg1) key.filename = arg1 else @@ -38,7 +38,7 @@ module LeapCli end end return key - rescue StandardError => exc + rescue StandardError end def self.load_from_file(filename) diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 5014238..248a59c 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -38,7 +38,7 @@ module LeapCli # def bail!(*message) if block_given? - LeapCli.set_log_level(3) + LeapCli.logger.log_level = 3 yield elsif message log 0, *message @@ -119,7 +119,7 @@ module LeapCli base = options[:base] || Path.provider file_list = files.collect { |file_path| file_path = Path.named_path(file_path, base) - File.exists?(file_path) ? Path.relative_path(file_path, base) : nil + File.exist?(file_path) ? Path.relative_path(file_path, base) : nil }.compact if file_list.length > 1 bail! do @@ -138,7 +138,7 @@ module LeapCli options = files.last.is_a?(Hash) ? files.pop : {} file_list = files.collect { |file_path| file_path = Path.named_path(file_path) - !File.exists?(file_path) ? Path.relative_path(file_path) : nil + !File.exist?(file_path) ? Path.relative_path(file_path) : nil }.compact if file_list.length > 1 bail! do @@ -157,7 +157,7 @@ module LeapCli def file_exists?(*files) files.each do |file_path| file_path = Path.named_path(file_path) - if !File.exists?(file_path) + if !File.exist?(file_path) return false end end @@ -233,7 +233,7 @@ module LeapCli # def replace_file!(filepath, &block) filepath = Path.named_path(filepath) - if !File.exists?(filepath) + if !File.exist?(filepath) content = yield(nil) unless content.nil? write_file!(filepath, content) @@ -258,7 +258,7 @@ module LeapCli def remove_file!(filepath) filepath = Path.named_path(filepath) - if File.exists?(filepath) + if File.exist?(filepath) if File.directory?(filepath) remove_directory!(filepath) else @@ -298,7 +298,7 @@ module LeapCli def write_file!(filepath, contents) filepath = Path.named_path(filepath) ensure_dir File.dirname(filepath) - existed = File.exists?(filepath) + existed = File.exist?(filepath) if existed if file_content_equals?(filepath, contents) log :nochange, filepath, 2 @@ -320,11 +320,11 @@ module LeapCli def rename_file!(oldpath, newpath) oldpath = Path.named_path(oldpath) newpath = Path.named_path(newpath) - if File.exists? newpath + if File.exist? newpath log :skipping, "#{Path.relative_path(newpath)}, file already exists" return end - if !File.exists? oldpath + if !File.exist? oldpath log :skipping, "#{Path.relative_path(oldpath)}, file is missing" return end @@ -429,7 +429,7 @@ module LeapCli Dir.chdir(dir) do branch = `git symbolic-ref HEAD 2>/dev/null`.strip if branch.chars.any? - branch.sub /^refs\/heads\//, '' + branch.sub(/^refs\/heads\//, '') else nil end diff --git a/lib/leap_cli/util/remote_command.rb b/lib/leap_cli/util/remote_command.rb index 10a5ca8..c2f1ace 100644 --- a/lib/leap_cli/util/remote_command.rb +++ b/lib/leap_cli/util/remote_command.rb @@ -13,7 +13,7 @@ module LeapCli; module Util; module RemoteCommand node_list = parse_node_list(nodes) cap = new_capistrano - cap.logger = LeapCli::Logger.new(:level => [LeapCli.log_level,3].min) + cap.logger = LeapCli::Logger.new(:level => [LeapCli.logger.log_level,3].min) user = options[:user] || 'root' cap.set :user, user cap.set :ssh_options, ssh_options # ssh options common to all nodes @@ -85,7 +85,7 @@ module LeapCli; module Util; module RemoteCommand def net_ssh_log_level if DEBUG - case LeapCli.log_level + case LeapCli.logger.log_level when 1 then 3 when 2 then 2 when 3 then 1 @@ -145,7 +145,7 @@ module LeapCli; module Util; module RemoteCommand opts[:keys] = [vagrant_ssh_key_file] opts[:keys_only] = true # only use the keys specified above, and ignore whatever keys the ssh-agent is aware of. opts[:paranoid] = false # we skip host checking for vagrant nodes, because fingerprint is different for everyone. - if LeapCli::log_level <= 1 + if LeapCli.logger.log_level <= 1 opts[:verbose] = :error # suppress all the warnings about adding host keys to known_hosts, since it is not actually doing that. end end -- cgit v1.2.3 From 52dd4eb58bda84cb084cc119447cb83073b47510 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 21 Jun 2016 17:48:07 -0700 Subject: vendor base32 gem --- lib/leap_cli/version.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index 2d7040f..31bf729 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -4,6 +4,10 @@ module LeapCli COMPATIBLE_PLATFORM_VERSION = '0.8'..'0.99' SUMMARY = 'Command line interface to the LEAP platform' DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.' - LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib'] + LOAD_PATHS = ['lib', + 'vendor/certificate_authority/lib', + 'vendor/rsync_command/lib', + 'vendor/base32/lib' + ] end end -- cgit v1.2.3 From c7ebb220bc79d3a84e55745ed18d0d7b5baeacdd Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 21 Jun 2016 17:49:42 -0700 Subject: remove highline gem dependency --- lib/leap_cli/bootstrap.rb | 1 + lib/leap_cli/commands/common.rb | 104 ++++++++++++++++++++++++++++++++++++++++ lib/leap_cli/commands/new.rb | 39 ++++++++------- lib/leap_cli/commands/pre.rb | 4 +- 4 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 lib/leap_cli/commands/common.rb (limited to 'lib') diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb index a6b1759..9ccb3dd 100644 --- a/lib/leap_cli/bootstrap.rb +++ b/lib/leap_cli/bootstrap.rb @@ -6,6 +6,7 @@ module LeapCli module Bootstrap extend self + extend LeapCli::LogCommand # # the argument leapfile_path is only used for tests diff --git a/lib/leap_cli/commands/common.rb b/lib/leap_cli/commands/common.rb new file mode 100644 index 0000000..695a9f6 --- /dev/null +++ b/lib/leap_cli/commands/common.rb @@ -0,0 +1,104 @@ +require 'readline' + +module LeapCli; module Commands + + extend LeapCli::LogCommand + extend LeapCli::Util + extend LeapCli::Util::RemoteCommand + + def path(name) + Path.named_path(name) + end + + # + # keeps prompting the user for a numbered choice, until they pick a good one or bail out. + # + # block is yielded and is responsible for rendering the choices. + # + def numbered_choice_menu(msg, items, &block) + while true + say("\n" + msg + ':') + items.each_with_index(&block) + say("q. quit") + index = ask("number 1-#{items.length}> ") + if index.empty? + next + elsif index =~ /q/ + bail! + else + i = index.to_i - 1 + if i < 0 || i >= items.length + bail! + else + return i + end + end + end + end + + def parse_node_list(nodes) + if nodes.is_a? Config::Object + Config::ObjectList.new(nodes) + elsif nodes.is_a? Config::ObjectList + nodes + elsif nodes.is_a? String + manager.filter!(nodes) + else + bail! "argument error" + end + end + + def say(statement) + if ends_in_whitespace?(statement) + $stdout.print(statement) + $stdout.flush + else + $stdout.puts(statement) + end + end + + def ask(question, options={}) + default = options[:default] + if default + if ends_in_whitespace?(question) + question = question + "|" + default + "| " + else + question = question + "|" + default + "|" + end + end + response = Readline.readline(question, true) # set to false if ever reading passwords. + if response + response = response.strip + if response.empty? + return default + else + return response + end + else + return default + end + end + + def agree(question, options={}) + while true + response = ask(question, options) + if response.nil? + say('Please enter "yes" or "no".') + elsif ["y","yes", "ye"].include?(response.downcase) + return true + elsif ["n", "no"].include?(response.downcase) + return false + else + say('Please enter "yes" or "no".') + end + end + end + + private + + # true if str ends in whitespace before a color escape code. + def ends_in_whitespace?(str) + /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ str + end + +end; end diff --git a/lib/leap_cli/commands/new.rb b/lib/leap_cli/commands/new.rb index 5c9fd74..6b60e7d 100644 --- a/lib/leap_cli/commands/new.rb +++ b/lib/leap_cli/commands/new.rb @@ -4,7 +4,6 @@ module LeapCli; module Commands desc 'Creates a new provider instance in the specified directory, creating it if necessary.' arg_name 'DIRECTORY' - #skips_pre command :new do |c| c.flag 'name', :desc => "The name of the provider." #, :default_value => 'Example' c.flag 'domain', :desc => "The primary domain of the provider." #, :default_value => 'example.org' @@ -12,19 +11,7 @@ module LeapCli; module Commands c.flag 'contacts', :desc => "Default email address contacts." #, :default_value => 'root' c.action do |global, options, args| - unless args.first - # this should not be needed, but GLI is not making it required. - bail! "Argument DIRECTORY is required." - end - directory = File.expand_path(args.first) - create_provider_directory(global, directory) - options[:domain] ||= ask_string("The primary domain of the provider: ") {|q| q.default = 'example.org'} - options[:name] ||= ask_string("The name of the provider: ") {|q| q.default = 'Example'} - options[:platform] ||= ask_string("File path of the leap_platform directory: ") {|q| q.default = File.expand_path('../leap_platform', directory)} - options[:platform] = "./" + options[:platform] unless options[:platform] =~ /^\// - options[:contacts] ||= ask_string("Default email address contacts: ") {|q| q.default = 'root@' + options[:domain]} - options[:platform] = relative_path(options[:platform]) - create_initial_provider_files(directory, global, options) + new_provider_action(global, options, args) end end @@ -32,13 +19,33 @@ module LeapCli; module Commands DEFAULT_REPO = 'https://leap.se/git/leap_platform.git' + def new_provider_action(global, options, args) + unless args.first + # this should not be needed, but GLI is not making it required. + bail! "Argument DIRECTORY is required." + end + directory = File.expand_path(args.first) + create_provider_directory(global, directory) + options[:domain] ||= ask_string("The primary domain of the provider: ", + default: 'example.org') + options[:name] ||= ask_string("The name of the provider: ", + default: 'Example') + options[:platform] ||= ask_string("File path of the leap_platform directory: ", + default: File.expand_path('../leap_platform', directory)) + options[:platform] = "./" + options[:platform] unless options[:platform] =~ /^\// + options[:contacts] ||= ask_string("Default email address contacts: ", + default: 'root@' + options[:domain]) + options[:platform] = relative_path(options[:platform]) + create_initial_provider_files(directory, global, options) + end + # # don't let the user specify any of the following: y, yes, n, no # they must actually input a real string # - def ask_string(str, &block) + def ask_string(str, options={}) while true - value = ask(str, &block) + value = ask(str, options) if value =~ /^(y|yes|n|no)$/i say "`#{value}` is not a valid value. Try again" else diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb index f4bf7bb..0b7e98b 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -1,9 +1,11 @@ - # # check to make sure we can find the root directory of the platform # module LeapCli; module Commands + extend self # this is a trick to make all instance methods + # available as class methods. + desc 'Verbosity level 0..5' arg_name 'LEVEL' default_value '1' -- cgit v1.2.3 From f770ce487345900a9208f7b72a74e84dec5d6429 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 21 Jun 2016 23:25:33 -0700 Subject: remove dependency on paint gem --- lib/leap_cli/log.rb | 56 ++++++++++++++++++++++++++++++++++---- lib/leap_cli/remote/leap_plugin.rb | 2 +- 2 files changed, 52 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 0497275..c83b385 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -1,11 +1,11 @@ -require 'paint' - ## ## LOGGING ## module LeapCli module LogCommand + @@logger = nil + def log(*args) logger.log(*args) end @@ -15,7 +15,12 @@ module LeapCli end def logger - @logger ||= LeapCli::LeapLogger.new + @@logger ||= LeapCli::LeapLogger.new + end + + # deprecated + def log_level + logger.log_level end end end @@ -36,6 +41,7 @@ module LeapCli @indent_level = 0 @log_file = nil @log_output_stream = nil + @log_in_color = true end def log_file=(value) @@ -95,10 +101,10 @@ module LeapCli end if options[:host] clear_prefix = "[%s] %s " % [options[:host], prefix_options[0]] - colored_prefix = "[%s] %s " % [Paint[options[:host], prefix_options[1], prefix_options[2]], prefix_options[0]] + colored_prefix = "[%s] %s " % [colorize(options[:host], prefix_options[1], prefix_options[2]), prefix_options[0]] else clear_prefix = "%s " % prefix_options[0] - colored_prefix = "%s " % Paint[prefix_options[0], prefix_options[1], prefix_options[2]] + colored_prefix = "%s " % colorize(prefix_options[0], prefix_options[1], prefix_options[2]) end elsif options[:host] clear_prefix = colored_prefix = "[%s] " % options[:host] @@ -163,5 +169,45 @@ module LeapCli end end + def colorize(str, color, style=nil) + codes = [FG_COLORS[color] || FG_COLORS[:default]] + if style + codes << EFFECTS[style] || EFFECTS[:nothing] + end + ["\033[%sm" % codes.join(';'), str, NO_COLOR].join + end + + private + + EFFECTS = { + :reset => 0, :nothing => 0, + :bright => 1, :bold => 1, + :underline => 4, + :inverse => 7, :swap => 7, + } + NO_COLOR = "\033[0m" + FG_COLORS = { + :black => 30, + :red => 31, + :green => 32, + :yellow => 33, + :blue => 34, + :magenta => 35, + :cyan => 36, + :white => 37, + :default => 39, + } + BG_COLORS = { + :black => 40, + :red => 41, + :green => 42, + :yellow => 43, + :blue => 44, + :magenta => 45, + :cyan => 46, + :white => 47, + :default => 49, + } + end end \ No newline at end of file diff --git a/lib/leap_cli/remote/leap_plugin.rb b/lib/leap_cli/remote/leap_plugin.rb index ee33cea..e6305ae 100644 --- a/lib/leap_cli/remote/leap_plugin.rb +++ b/lib/leap_cli/remote/leap_plugin.rb @@ -10,7 +10,7 @@ module LeapCli; module Remote; module LeapPlugin end def log(*args, &block) - LeapCli::Util::log(*args, &block) + LeapCli.logger.log(*args, &block) end # -- cgit v1.2.3 From c4eff286d707e595ac7d3fbff83f94dbd249e2dc Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 22 Jun 2016 01:27:40 -0700 Subject: bump version to 1.9 --- lib/leap_cli/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index 31bf729..779aa25 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -1,6 +1,6 @@ module LeapCli unless defined?(LeapCli::VERSION) - VERSION = '1.8' + VERSION = '1.9' COMPATIBLE_PLATFORM_VERSION = '0.8'..'0.99' SUMMARY = 'Command line interface to the LEAP platform' DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.' -- cgit v1.2.3 From f55bf5b3ce8a1c899a6e74621c28d214ee67db35 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 23 Jun 2016 10:44:59 -0700 Subject: fix logging of indented blocks --- lib/leap_cli/log.rb | 127 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 45 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index c83b385..b46a7eb 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -6,18 +6,24 @@ module LeapCli module LogCommand @@logger = nil - def log(*args) - logger.log(*args) + def log(*args, &block) + logger.log(*args, &block) end def log_raw(*args) logger.log(*args) end + # global shared logger def logger @@logger ||= LeapCli::LeapLogger.new end + # thread safe logger + def new_logger + LeapCli::LeapLogger.new + end + # deprecated def log_level logger.log_level @@ -73,56 +79,37 @@ module LeapCli unless message && @log_level >= level return end + clear_prefix, colored_prefix = calculate_prefix(title, options) - # prefix - clear_prefix = colored_prefix = "" - if title - prefix_options = case title - when :error then ['error', :red, :bold] - when :fatal_error then ['fatal error:', :red, :bold] - when :warning then ['warning:', :yellow, :bold] - when :info then ['info', :cyan, :bold] - when :note then ['NOTE:', :cyan, :bold] - when :updated then ['updated', :cyan, :bold] - when :updating then ['updating', :cyan, :bold] - when :created then ['created', :green, :bold] - when :removed then ['removed', :red, :bold] - when :nochange then ['no change', :magenta] - when :loading then ['loading', :magenta] - when :missing then ['missing', :yellow, :bold] - when :skipping then ['skipping', :yellow, :bold] - when :run then ['run', :magenta] - when :failed then ['FAILED', :red, :bold] - when :completed then ['completed', :green, :bold] - when :ran then ['ran', :green, :bold] - when :bail then ['bailing out', :red, :bold] - when :invalid then ['invalid', :red, :bold] - else [title.to_s, :cyan, :bold] - end - if options[:host] - clear_prefix = "[%s] %s " % [options[:host], prefix_options[0]] - colored_prefix = "[%s] %s " % [colorize(options[:host], prefix_options[1], prefix_options[2]), prefix_options[0]] - else - clear_prefix = "%s " % prefix_options[0] - colored_prefix = "%s " % colorize(prefix_options[0], prefix_options[1], prefix_options[2]) - end - elsif options[:host] - clear_prefix = colored_prefix = "[%s] " % options[:host] - end - + # # transform absolute path names + # if title && FILE_TITLES.include?(title) && message =~ /^\// message = LeapCli::Path.relative_path(message) end - log_raw(:log, nil) { [clear_prefix, message].join } + # + # log to the log file, always + # + log_raw(:log, nil, clear_prefix) { message } + + # + # log to stdout, maybe in color + # if @log_in_color - log_raw(:stdout, options[:indent]) { [colored_prefix, message].join } + prefix = colored_prefix + if options[:wrap] + message = message.split("\n") + end else - log_raw(:stdout, options[:indent]) { [clear_prefix, message].join } + prefix = clear_prefix end + indent = options[:indent] + log_raw(:stdout, indent, prefix) { message } - # run block, if given + # + # run block indented, if given + # if block_given? @indent_level += 1 yield @@ -138,15 +125,18 @@ module LeapCli # if mode == :stdout, output is sent to STDOUT. # if mode == :log, output is sent to log file, if present. # - def log_raw(mode, indent=nil, &block) - # NOTE: print message (using 'print' produces better results than 'puts' when multiple threads are logging) + def log_raw(mode, indent=nil, prefix=nil, &block) + # NOTE: using 'print' produces better results than 'puts' + # when multiple threads are logging) if mode == :log if @log_output_stream messages = [yield].compact.flatten if messages.any? timestamp = Time.now.strftime("%b %d %H:%M:%S") messages.each do |message| - @log_output_stream.print("#{timestamp} #{message}\n") + message = message.strip + next if message.empty? + @log_output_stream.print("#{timestamp} #{prefix} #{message}\n") end @log_output_stream.flush end @@ -162,7 +152,10 @@ module LeapCli else indent_str += ' = ' end + indent_str += prefix messages.each do |message| + message = message.strip + next if message.empty? STDOUT.print("#{indent_str}#{message}\n") end end @@ -209,5 +202,49 @@ module LeapCli :default => 49, } + def calculate_prefix(title, options) + clear_prefix = colored_prefix = "" + if title + prefix_options = case title + when :error then ['error', :red, :bold] + when :fatal_error then ['fatal error:', :red, :bold] + when :warning then ['warning:', :yellow, :bold] + when :info then ['info', :cyan, :bold] + when :note then ['NOTE:', :cyan, :bold] + when :updated then ['updated', :cyan, :bold] + when :updating then ['updating', :cyan, :bold] + when :created then ['created', :green, :bold] + when :removed then ['removed', :red, :bold] + when :nochange then ['no change', :magenta] + when :loading then ['loading', :magenta] + when :missing then ['missing', :yellow, :bold] + when :skipping then ['skipping', :yellow, :bold] + when :run then ['run', :cyan, :bold] + when :running then ['running', :cyan, :bold] + when :failed then ['FAILED', :red, :bold] + when :completed then ['completed', :green, :bold] + when :ran then ['ran', :green, :bold] + when :bail then ['bailing out', :red, :bold] + when :invalid then ['invalid', :red, :bold] + else [title.to_s, :cyan, :bold] + end + if options[:host] + clear_prefix = "[%s] %s " % [options[:host], prefix_options[0]] + colored_prefix = "[%s] %s " % [colorize(options[:host], prefix_options[1], prefix_options[2]), prefix_options[0]] + else + clear_prefix = "%s " % prefix_options[0] + colored_prefix = "%s " % colorize(prefix_options[0], prefix_options[1], prefix_options[2]) + end + elsif options[:host] + clear_prefix = "[%s] " % options[:host] + if options[:color] + colored_prefix = "[%s] " % colorize(options[:host], options[:color]) + else + colored_prefix = clear_prefix + end + end + return [clear_prefix, colored_prefix] + end + end end \ No newline at end of file -- cgit v1.2.3 From b87938e9c61399a9f9290b528f08226ecabe177c Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 24 Jun 2016 23:15:16 -0700 Subject: fix capistrano logging, closes #8215 --- lib/leap_cli/log.rb | 6 +++--- lib/leap_cli/logger.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index b46a7eb..3bd4f45 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -10,8 +10,8 @@ module LeapCli logger.log(*args, &block) end - def log_raw(*args) - logger.log(*args) + def log_raw(*args, &block) + logger.log_raw(*args, &block) end # global shared logger @@ -152,7 +152,7 @@ module LeapCli else indent_str += ' = ' end - indent_str += prefix + indent_str += prefix if prefix messages.each do |message| message = message.strip next if message.empty? diff --git a/lib/leap_cli/logger.rb b/lib/leap_cli/logger.rb index 9e98321..058322c 100644 --- a/lib/leap_cli/logger.rb +++ b/lib/leap_cli/logger.rb @@ -219,7 +219,7 @@ module LeapCli if color == :hide return nil - elsif mode == :log || (color == :none && style.nil?) || !LeapCli.log_in_color + elsif mode == :log || (color == :none && style.nil?) || !LeapCli.logger.log_in_color return [message, line_prefix, options] else term_color = COLORS[color] -- cgit v1.2.3 From e8de57c6309daeb5e25e1b0973adb8214255077f Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 27 Jun 2016 14:09:11 -0700 Subject: remove capistrano, switch to sshkit --- lib/leap_cli/bootstrap.rb | 11 ++ lib/leap_cli/log.rb | 9 +- lib/leap_cli/remote/leap_plugin.rb | 192 ----------------------------------- lib/leap_cli/remote/puppet_plugin.rb | 26 ----- lib/leap_cli/remote/rsync_plugin.rb | 35 ------- lib/leap_cli/remote/tasks.rb | 51 ---------- 6 files changed, 19 insertions(+), 305 deletions(-) delete mode 100644 lib/leap_cli/remote/leap_plugin.rb delete mode 100644 lib/leap_cli/remote/puppet_plugin.rb delete mode 100644 lib/leap_cli/remote/rsync_plugin.rb delete mode 100644 lib/leap_cli/remote/tasks.rb (limited to 'lib') diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb index 9ccb3dd..bc43115 100644 --- a/lib/leap_cli/bootstrap.rb +++ b/lib/leap_cli/bootstrap.rb @@ -39,6 +39,7 @@ module LeapCli if LeapCli.logger.log_level >= 2 log_version end + add_platform_lib_to_path load_commands(app) load_macros end @@ -193,5 +194,15 @@ module LeapCli end end + # + # makes all the ruby libraries in the leap_platform/lib directory + # available for inclusion. + # + def add_platform_lib_to_path + if Path.platform + path = File.join(Path.platform, 'lib') + $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path) + end + end end end diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 3bd4f45..25f7b74 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -21,7 +21,7 @@ module LeapCli # thread safe logger def new_logger - LeapCli::LeapLogger.new + logger.dup #LeapCli::LeapLogger.new end # deprecated @@ -101,6 +101,9 @@ module LeapCli if options[:wrap] message = message.split("\n") end + if options[:color] && prefix.empty? + message = colorize(message, options[:color], options[:style]) + end else prefix = clear_prefix end @@ -117,6 +120,10 @@ module LeapCli end end + def debug(*args) + self.log(3, *args) + end + # # Add a raw log entry, without any modifications (other than indent). # Content to be logged is yielded by the block. diff --git a/lib/leap_cli/remote/leap_plugin.rb b/lib/leap_cli/remote/leap_plugin.rb deleted file mode 100644 index e6305ae..0000000 --- a/lib/leap_cli/remote/leap_plugin.rb +++ /dev/null @@ -1,192 +0,0 @@ -# -# these methods are made available in capistrano tasks as 'leap.method_name' -# (see RemoteCommand::new_capistrano) -# - -module LeapCli; module Remote; module LeapPlugin - - def required_packages - "puppet rsync lsb-release locales" - end - - def log(*args, &block) - LeapCli.logger.log(*args, &block) - end - - # - # creates directories that are owned by root and 700 permissions - # - def mkdirs(*dirs) - raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any? - run dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join - end - - # - # echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise. - # - def assert_initialized - begin - test_initialized_file = "test -f #{Leap::Platform.init_path}" - check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{required_packages} 2>&1 | grep -q -E '(deinstall|no packages)'" - run "#{test_initialized_file} && #{check_required_packages} && echo ok" - rescue Capistrano::CommandError => exc - LeapCli::Util.bail! do - exc.hosts.each do |host| - node = host.to_s.split('.').first - LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{node}'", :host => host - end - end - end - end - - # - # bails out the deploy if the file /etc/leap/no-deploy exists. - # This kind of sucks, because it would be better to skip over nodes that have no-deploy set instead - # halting the entire deploy. As far as I know, with capistrano, there is no way to close one of the - # ssh connections in the pool and make sure it gets no further commands. - # - def check_for_no_deploy - begin - run "test ! -f /etc/leap/no-deploy" - rescue Capistrano::CommandError => exc - LeapCli::Util.bail! do - exc.hosts.each do |host| - LeapCli::Util.log "Can't continue because file /etc/leap/no-deploy exists", :host => host - end - end - end - end - - # - # dumps debugging information - # # - def debug - run "#{Leap::Platform.leap_dir}/bin/debug.sh" - end - - # - # dumps the recent deploy history to the console - # - def history(lines) - command = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')" - run command - end - - # - # This is a hairy ugly hack, exactly the kind of stuff that makes ruby - # dangerous and too much fun for its own good. - # - # In most places, we run remote ssh without a current 'task'. This works fine, - # except that in a few places, the behavior of capistrano ssh is controlled by - # the options of the current task. - # - # We don't want to create an actual current task, because tasks are no fun - # and can't take arguments or return values. So, when we need to configure - # things that can only be configured in a task, we use this handy hack to - # fake the current task. - # - # This is NOT thread safe, but could be made to be so with some extra work. - # - def with_task(name) - task = @config.tasks[name] - @config.class.send(:alias_method, :original_current_task, :current_task) - @config.class.send(:define_method, :current_task, Proc.new(){ task }) - begin - yield - ensure - @config.class.send(:remove_method, :current_task) - @config.class.send(:alias_method, :current_task, :original_current_task) - end - end - - # - # similar to run(cmd, &block), but with: - # - # * exit codes - # * stdout and stderr are combined - # - def stream(cmd, &block) - command = '%s 2>&1; echo "exitcode=$?"' % cmd - run(command) do |channel, stream, data| - exitcode = nil - if data =~ /exitcode=(\d+)\n/ - exitcode = $1.to_i - data.sub!(/exitcode=(\d+)\n/,'') - end - yield({:host => channel[:host], :data => data, :exitcode => exitcode}) - end - end - - # - # like stream, but capture all the output before returning - # - def capture(cmd, &block) - command = '%s 2>&1; echo "exitcode=$?" 2>&1;' % cmd - host_data = {} - run(command) do |channel, stream, data| - host_data[channel[:host]] ||= "" - if data =~ /exitcode=(\d+)\n/ - exitcode = $1.to_i - data.sub!(/exitcode=(\d+)\n/,'') - host_data[channel[:host]] += data - yield({:host => channel[:host], :data => host_data[channel[:host]], :exitcode => exitcode}) - else - host_data[channel[:host]] += data - end - end - end - - # - # Run a command, with a nice status report and progress indicator. - # Only successful results are returned, errors are printed. - # - # For each successful run on each host, block is yielded with a hash like so: - # - # {:host => 'bluejay', :exitcode => 0, :data => 'shell output'} - # - def run_with_progress(cmd, &block) - ssh_failures = [] - exitcode_failures = [] - succeeded = [] - task = LeapCli.logger.log_level > 1 ? :standard_task : :skip_errors_task - with_task(task) do - log :querying, 'facts' do - progress " " - call_on_failure do |host| - ssh_failures << host - progress 'F' - end - capture(cmd) do |response| - if response[:exitcode] == 0 - progress '.' - yield response - else - exitcode_failures << response - progress 'F' - end - end - end - end - puts "done" - if ssh_failures.any? - log :failed, 'to connect to nodes: ' + ssh_failures.join(' ') - end - if exitcode_failures.any? - log :failed, 'to run successfully:' do - exitcode_failures.each do |response| - log "[%s] exit %s - %s" % [response[:host], response[:exitcode], response[:data].strip] - end - end - end - rescue Capistrano::RemoteError => err - log :error, err.to_s - end - - private - - def progress(str='.') - print str - STDOUT.flush - end - -end; end; end diff --git a/lib/leap_cli/remote/puppet_plugin.rb b/lib/leap_cli/remote/puppet_plugin.rb deleted file mode 100644 index 5a6e908..0000000 --- a/lib/leap_cli/remote/puppet_plugin.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -# these methods are made available in capistrano tasks as 'puppet.method_name' -# (see RemoteCommand::new_capistrano) -# - -module LeapCli; module Remote; module PuppetPlugin - - def apply(options) - run "#{Leap::Platform.leap_dir}/bin/puppet_command set_hostname apply #{flagize(options)}" - end - - private - - def flagize(hsh) - hsh.inject([]) {|str, item| - if item[1] === false - str - elsif item[1] === true - str << "--" + item[0].to_s - else - str << "--" + item[0].to_s + " " + item[1].inspect - end - }.join(' ') - end - -end; end; end diff --git a/lib/leap_cli/remote/rsync_plugin.rb b/lib/leap_cli/remote/rsync_plugin.rb deleted file mode 100644 index a6708f4..0000000 --- a/lib/leap_cli/remote/rsync_plugin.rb +++ /dev/null @@ -1,35 +0,0 @@ -# -# these methods are made available in capistrano tasks as 'rsync.method_name' -# (see RemoteCommand::new_capistrano) -# - -autoload :RsyncCommand, 'rsync_command' - -module LeapCli; module Remote; module RsyncPlugin - - # - # takes a block, yielded a server, that should return a hash with various rsync options. - # supported options include: - # - # {:source => '', :dest => '', :flags => '', :includes => [], :excludes => []} - # - def update - rsync = RsyncCommand.new(:logger => logger) - rsync.asynchronously(find_servers) do |server| - options = yield server - next unless options - remote_user = server.user || fetch(:user, ENV['USER']) - src = options[:source] - dest = {:user => remote_user, :host => server.host, :path => options[:dest]} - options[:ssh] = ssh_options.merge(server.options[:ssh_options]||{}) - options[:chdir] ||= Path.provider - rsync.exec(src, dest, options) - end - if rsync.failed? - LeapCli::Util.bail! do - LeapCli::Util.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}" - end - end - end - -end; end; end diff --git a/lib/leap_cli/remote/tasks.rb b/lib/leap_cli/remote/tasks.rb deleted file mode 100644 index d08d19a..0000000 --- a/lib/leap_cli/remote/tasks.rb +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is evaluated just the same as a typical capistrano "deploy.rb" -# For DSL manual, see https://github.com/capistrano/capistrano/wiki -# - -MAX_HOSTS = 10 - -task :install_authorized_keys, :max_hosts => MAX_HOSTS do - leap.log :updating, "authorized_keys" do - leap.mkdirs '/root/.ssh' - upload LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => '600' - end -end - -# -# for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy -# will overwrite authorized_keys. -# -# why force the insecure vagrant key? -# if we don't do this, then first time initialization might fail if the user has many keys -# (ssh will bomb out before it gets to the vagrant key). -# and it really doesn't make sense to ask users to pin the insecure vagrant key in their -# .ssh/config files. -# -task :install_insecure_vagrant_key, :max_hosts => MAX_HOSTS do - leap.log :installing, "insecure vagrant key" do - leap.mkdirs '/root/.ssh' - upload LeapCli::Path.vagrant_ssh_pub_key_file, '/root/.ssh/authorized_keys2', :mode => '600' - end -end - -task :install_prerequisites, :max_hosts => MAX_HOSTS do - bin_dir = File.join(Leap::Platform.leap_dir, 'bin') - node_init_path = File.join(bin_dir, 'node_init') - - leap.log :running, "node_init script" do - leap.mkdirs bin_dir - upload LeapCli::Path.node_init_script, node_init_path, :mode => '500' - run node_init_path - end -end - -# -# just dummies, used to capture task options -# - -task :skip_errors_task, :on_error => :continue, :max_hosts => MAX_HOSTS do -end - -task :standard_task, :max_hosts => MAX_HOSTS do -end -- cgit v1.2.3 From 263bb5699350a04484463aabde563679c4fed505 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 28 Jun 2016 14:34:47 -0700 Subject: mv sshkey to platform --- lib/leap_cli.rb | 2 - lib/leap_cli/commands/common.rb | 1 - lib/leap_cli/config/node.rb | 3 +- lib/leap_cli/ssh_key.rb | 195 ---------------------------------------- 4 files changed, 2 insertions(+), 199 deletions(-) delete mode 100644 lib/leap_cli/ssh_key.rb (limited to 'lib') diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index fc8ab2b..b74f7e6 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -33,12 +33,10 @@ require 'leap_cli/log' require 'leap_cli/path' require 'leap_cli/util' require 'leap_cli/util/secret' -require 'leap_cli/util/remote_command' require 'leap_cli/util/x509' require 'leap_cli/logger' require 'leap_cli/bootstrap' -require 'leap_cli/ssh_key' require 'leap_cli/config/object' require 'leap_cli/config/node' require 'leap_cli/config/tag' diff --git a/lib/leap_cli/commands/common.rb b/lib/leap_cli/commands/common.rb index 695a9f6..d49490e 100644 --- a/lib/leap_cli/commands/common.rb +++ b/lib/leap_cli/commands/common.rb @@ -4,7 +4,6 @@ module LeapCli; module Commands extend LeapCli::LogCommand extend LeapCli::Util - extend LeapCli::Util::RemoteCommand def path(name) Path.named_path(name) diff --git a/lib/leap_cli/config/node.rb b/lib/leap_cli/config/node.rb index 65735d5..f8ec052 100644 --- a/lib/leap_cli/config/node.rb +++ b/lib/leap_cli/config/node.rb @@ -67,7 +67,8 @@ module LeapCli; module Config # returns a string list of supported ssh host key algorithms for this node. # or an empty string if it could not be determined def supported_ssh_host_key_algorithms - @host_key_algo ||= SshKey.supported_host_key_algorithms( + require 'leap_cli/ssh' + @host_key_algo ||= LeapCli::SSH::Key.supported_host_key_algorithms( Util.read_file([:node_ssh_pub_key, @node.name]) ) end diff --git a/lib/leap_cli/ssh_key.rb b/lib/leap_cli/ssh_key.rb deleted file mode 100644 index 2570557..0000000 --- a/lib/leap_cli/ssh_key.rb +++ /dev/null @@ -1,195 +0,0 @@ -# -# A wrapper around OpenSSL::PKey::RSA instances to provide a better api for dealing with SSH keys. -# -# cipher 'ssh-ed25519' not supported yet because we are waiting for support in Net::SSH -# - -require 'net/ssh' -require 'forwardable' - -module LeapCli - class SshKey - extend Forwardable - - attr_accessor :filename - attr_accessor :comment - - # supported ssh key types, in order of preference - SUPPORTED_TYPES = ['ssh-rsa', 'ecdsa-sha2-nistp256'] - SUPPORTED_TYPES_RE = /(#{SUPPORTED_TYPES.join('|')})/ - - ## - ## CLASS METHODS - ## - - def self.load(arg1, arg2=nil) - key = nil - if arg1.is_a? OpenSSL::PKey::RSA - key = SshKey.new arg1 - elsif arg1.is_a? String - if arg1 =~ /^ssh-/ - type, data = arg1.split(' ') - key = SshKey.new load_from_data(data, type) - elsif File.exist? arg1 - key = SshKey.new load_from_file(arg1) - key.filename = arg1 - else - key = SshKey.new load_from_data(arg1, arg2) - end - end - return key - rescue StandardError - end - - def self.load_from_file(filename) - public_key = nil - private_key = nil - begin - public_key = Net::SSH::KeyFactory.load_public_key(filename) - rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError - begin - private_key = Net::SSH::KeyFactory.load_private_key(filename) - rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError - end - end - public_key || private_key - end - - def self.load_from_data(data, type='ssh-rsa') - public_key = nil - private_key = nil - begin - public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}") - rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError - begin - private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}") - rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError - end - end - public_key || private_key - end - - # - # Picks one key out of an array of keys that we think is the "best", - # based on the order of preference in SUPPORTED_TYPES - # - # Currently, this does not take bitsize into account. - # - def self.pick_best_key(keys) - keys.select {|k| - SUPPORTED_TYPES.include?(k.type) - }.sort {|a,b| - SUPPORTED_TYPES.index(a.type) <=> SUPPORTED_TYPES.index(b.type) - }.first - end - - # - # takes a string with one or more ssh keys, one key per line, - # and returns an array of SshKey objects. - # - # the lines should be in one of these formats: - # - # 1. - # 2. - # - def self.parse_keys(string) - keys = [] - lines = string.split("\n").grep(/^[^#]/) - lines.each do |line| - if line =~ / #{SshKey::SUPPORTED_TYPES_RE} / - # - keys << line.split(' ')[1..2] - elsif line =~ /^#{SshKey::SUPPORTED_TYPES_RE} / - # - keys << line.split(' ') - end - end - return keys.map{|k| SshKey.load(k[1], k[0])} - end - - # - # takes a string with one or more ssh keys, one key per line, - # and returns a string that specified the ssh key algorithms - # that are supported by the keys, in order of preference. - # - # eg: ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519 - # - def self.supported_host_key_algorithms(string) - if string - self.parse_keys(string).map {|key| - key.type - }.join(',') - else - "" - end - end - - ## - ## INSTANCE METHODS - ## - - public - - def initialize(rsa_key) - @key = rsa_key - end - - def_delegator :@key, :fingerprint, :fingerprint - def_delegator :@key, :public?, :public? - def_delegator :@key, :private?, :private? - def_delegator :@key, :ssh_type, :type - def_delegator :@key, :public_encrypt, :public_encrypt - def_delegator :@key, :public_decrypt, :public_decrypt - def_delegator :@key, :private_encrypt, :private_encrypt - def_delegator :@key, :private_decrypt, :private_decrypt - def_delegator :@key, :params, :params - def_delegator :@key, :to_text, :to_text - - def public_key - SshKey.new(@key.public_key) - end - - def private_key - SshKey.new(@key.private_key) - end - - # - # not sure if this will always work, but is seems to for now. - # - def bits - Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8 - end - - def summary - if self.filename - "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, File.basename(self.filename)] - else - "%s %s %s" % [self.type, self.bits, self.fingerprint] - end - end - - def to_s - self.type + " " + self.key - end - - def key - [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "") - end - - def ==(other_key) - return false if other_key.nil? - return false if self.class != other_key.class - return self.to_text == other_key.to_text - end - - def in_known_hosts?(*identifiers) - identifiers.each do |identifier| - Net::SSH::KnownHosts.search_for(identifier).each do |key| - return true if self == key - end - end - return false - end - - end -end -- cgit v1.2.3 From 334e37c6e3686d17d9d7d47c2cefd1bbfe78cf47 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 29 Jun 2016 13:48:49 -0700 Subject: remove capistrano logger, new log filtering --- lib/leap_cli.rb | 2 - lib/leap_cli/log.rb | 271 +++++++++++++++++++++++++++++++++++++++---------- lib/leap_cli/logger.rb | 237 ------------------------------------------ 3 files changed, 216 insertions(+), 294 deletions(-) delete mode 100644 lib/leap_cli/logger.rb (limited to 'lib') diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index b74f7e6..36718e3 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -11,7 +11,6 @@ $:.unshift(File.expand_path('../leap_cli/override',__FILE__)) # for a few gems, things will break if using earlier versions. # enforce the compatible versions here: require 'rubygems' -gem 'net-ssh', '~> 2.7' gem 'gli', '~> 2.12', '>= 2.12.0' require 'leap/platform' @@ -34,7 +33,6 @@ require 'leap_cli/path' require 'leap_cli/util' require 'leap_cli/util/secret' require 'leap_cli/util/x509' -require 'leap_cli/logger' require 'leap_cli/bootstrap' require 'leap_cli/config/object' diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 25f7b74..03789c3 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -21,7 +21,7 @@ module LeapCli # thread safe logger def new_logger - logger.dup #LeapCli::LeapLogger.new + logger.dup end # deprecated @@ -39,6 +39,12 @@ module LeapCli # FILE_TITLES = [:updated, :created, :removed, :missing, :nochange, :loading] + # TODO: use these + IMPORTANT = 0 + INFO = 1 + DEBUG = 2 + TRACE = 3 + attr_reader :log_output_stream, :log_file attr_accessor :indent_level, :log_level, :log_in_color @@ -69,17 +75,20 @@ module LeapCli # * Integer: the log level (0, 1, 2) # * Symbol: the prefix title to colorize. may be one of # [:error, :warning, :info, :updated, :created, :removed, :no_change, :missing] - # * Hash: a hash of options. so far, only :indent is supported. + # * Hash: a hash of options. + # :wrap -- if true, appy intend to each line in message. + # :color -- apply color to message or prefix + # :style -- apply style to message or prefix # def log(*args) level = args.grep(Integer).first || 1 title = args.grep(Symbol).first message = args.grep(String).first options = args.grep(Hash).first || {} + host = options[:host] unless message && @log_level >= level return end - clear_prefix, colored_prefix = calculate_prefix(title, options) # # transform absolute path names @@ -89,26 +98,50 @@ module LeapCli end # - # log to the log file, always + # apply filters + # + if title + title, filter_flags = LogFilter.apply_title_filters(title.to_s) + else + message, filter_flags = LogFilter.apply_message_filters(message) + return if message.nil? + end + options = options.merge(filter_flags) + + # + # set line prefix # - log_raw(:log, nil, clear_prefix) { message } + prefix = "" + prefix += "[" + options[:host] + "] " if options[:host] + prefix += title + " " if title + + # + # write to the log file, always + # + log_raw(:log, nil, prefix) { message } # # log to stdout, maybe in color # if @log_in_color - prefix = colored_prefix if options[:wrap] message = message.split("\n") end - if options[:color] && prefix.empty? - message = colorize(message, options[:color], options[:style]) + if options[:color] + if host + host = "[" + colorize(host, options[:color], options[:style]) + "] " + elsif title + title = colorize(title, options[:color], options[:style]) + " " + else + message = colorize(message, options[:color], options[:style]) + end + elsif title + title = colorize(title, :cyan, :bold) + " " end - else - prefix = clear_prefix + # new colorized prefix: + prefix = [host, title].compact.join(' ') end - indent = options[:indent] - log_raw(:stdout, indent, prefix) { message } + log_raw(:stdout, options[:indent], prefix) { message } # # run block indented, if given @@ -141,7 +174,7 @@ module LeapCli if messages.any? timestamp = Time.now.strftime("%b %d %H:%M:%S") messages.each do |message| - message = message.strip + message = message.rstrip next if message.empty? @log_output_stream.print("#{timestamp} #{prefix} #{message}\n") end @@ -161,7 +194,7 @@ module LeapCli end indent_str += prefix if prefix messages.each do |message| - message = message.strip + message = message.rstrip next if message.empty? STDOUT.print("#{indent_str}#{message}\n") end @@ -209,49 +242,177 @@ module LeapCli :default => 49, } - def calculate_prefix(title, options) - clear_prefix = colored_prefix = "" - if title - prefix_options = case title - when :error then ['error', :red, :bold] - when :fatal_error then ['fatal error:', :red, :bold] - when :warning then ['warning:', :yellow, :bold] - when :info then ['info', :cyan, :bold] - when :note then ['NOTE:', :cyan, :bold] - when :updated then ['updated', :cyan, :bold] - when :updating then ['updating', :cyan, :bold] - when :created then ['created', :green, :bold] - when :removed then ['removed', :red, :bold] - when :nochange then ['no change', :magenta] - when :loading then ['loading', :magenta] - when :missing then ['missing', :yellow, :bold] - when :skipping then ['skipping', :yellow, :bold] - when :run then ['run', :cyan, :bold] - when :running then ['running', :cyan, :bold] - when :failed then ['FAILED', :red, :bold] - when :completed then ['completed', :green, :bold] - when :ran then ['ran', :green, :bold] - when :bail then ['bailing out', :red, :bold] - when :invalid then ['invalid', :red, :bold] - else [title.to_s, :cyan, :bold] - end - if options[:host] - clear_prefix = "[%s] %s " % [options[:host], prefix_options[0]] - colored_prefix = "[%s] %s " % [colorize(options[:host], prefix_options[1], prefix_options[2]), prefix_options[0]] - else - clear_prefix = "%s " % prefix_options[0] - colored_prefix = "%s " % colorize(prefix_options[0], prefix_options[1], prefix_options[2]) - end - elsif options[:host] - clear_prefix = "[%s] " % options[:host] - if options[:color] - colored_prefix = "[%s] " % colorize(options[:host], options[:color]) - else - colored_prefix = clear_prefix + end +end + +# +# A module to hide, modify, and colorize log entries. +# + +module LeapCli + module LogFilter + # + # options for formatters: + # + # :match => regexp for matching a log line + # :color => what color the line should be + # :style => what style the line should be + # :priority => what order the formatters are applied in. higher numbers first. + # :match_level => only apply filter at the specified log level + # :level => make this line visible at this log level or higher + # :replace => replace the matched text + # :prepend => insert text at start of message + # :append => append text to end of message + # :exit => force the exit code to be this (does not interrupt program, just + # ensures a specific exit code when the program eventually exits) + # + FORMATTERS = [ + # TRACE + { :match => /command finished/, :color => :white, :style => :dim, :match_level => 3, :priority => -10 }, + { :match => /executing locally/, :color => :yellow, :match_level => 3, :priority => -20 }, + + # DEBUG + #{ :match => /executing .*/, :color => :green, :match_level => 2, :priority => -10, :timestamp => true }, + #{ :match => /.*/, :color => :yellow, :match_level => 2, :priority => -30 }, + { :match => /^transaction:/, :level => 3 }, + + # INFO + { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :match_level => 1, :priority => -10 }, + { :match => /Permission denied/, :color => :red, :match_level => 1, :priority => -20 }, + { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 }, + + # IMPORTANT + { :match => /^(E|e)rr ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1}, + { :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1}, + #{ :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 }, + + # CLEANUP + #{ :match => /\s+$/, :replace => '', :priority => 0}, + + # DEBIAN PACKAGES + { :match => /^(Hit|Ign) /, :color => :green, :priority => -20}, + { :match => /^Err /, :color => :red, :priority => -20}, + { :match => /^W(ARNING)?: /, :color => :yellow, :priority => -20}, + { :match => /^E: /, :color => :red, :priority => -20}, + { :match => /already the newest version/, :color => :green, :priority => -20}, + { :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10}, + + # PUPPET + { :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10}, + { :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/, :level => 2, :color => :yellow, :priority => -10}, + { :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10}, + { :match => /^(W|w)arning: Scope.*$/, :level => 2, :color => :yellow, :priority => -10}, + #{ :match => /^(N|n)otice:/, :level => 1, :color => :cyan, :priority => -20}, + #{ :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15}, + { :match => /^(W|w)arning:/, :level => 0, :color => :yellow, :priority => -20}, + { :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20}, + #{ :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10}, + { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :style => :bold, :priority => -10}, + { :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :style => :bold, :priority => -10}, + + # PUPPET FATAL ERRORS + { :match => /^(E|e)rr(or|):/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Wrapped exception:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Failed to parse template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Execution of.*returned/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Parameter matches failed:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^Could not find template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^APPLY COMPLETE.*fail/, :level => 0, :color => :red, :style => :bold, :priority => -1, :exit => 1}, + + # TESTS + { :match => /^PASS: /, :color => :green, :priority => -20}, + { :match => /^(FAIL|ERROR): /, :color => :red, :priority => -20}, + { :match => /^(SKIP|WARN): /, :color => :yellow, :priority => -20}, + { :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, + :color => :green, :style => :bold, :priority => -20 }, + { :match => /\d+ tests: \d+ passes, \d+ skips, [1-9][0-9]* warnings, 0 failures, 0 errors/, + :color => :yellow, :style => :bold, :priority => -20 }, + { :match => /\d+ tests: \d+ passes, \d+ skips, \d+ warnings, \d+ failures, [1-9][0-9]* errors/, + :color => :red, :style => :bold, :priority => -20 }, + { :match => /\d+ tests: \d+ passes, \d+ skips, \d+ warnings, [1-9][0-9]* failures, \d+ errors/, + :color => :red, :style => :bold, :priority => -20 }, + + # LOG SUPPRESSION + { :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10}, + { :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10} + ] + + SORTED_FORMATTERS = FORMATTERS.sort_by { |i| -(i[:priority] || i[:prio] || 0) } + + # + # same as normal formatters, but only applies to the title, not the message. + # + TITLE_FORMATTERS = [ + # red + { :match => /error/, :color => :red, :style => :bold }, + { :match => /fatal_error/, :replace => 'fatal error:', :color => :red, :style => :bold }, + { :match => /removed/, :color => :red, :style => :bold }, + { :match => /failed/, :replace => 'FAILED', :color => :red, :style => :bold }, + { :match => /bail/, :replace => 'bailing out', :color => :red, :style => :bold }, + { :match => /invalid/, :color => :red, :style => :bold }, + + # yellow + { :match => /warning/, :replace => 'warning:', :color => :yellow, :style => :bold }, + { :match => /missing/, :color => :yellow, :style => :bold }, + { :match => /skipping/, :color => :yellow, :style => :bold }, + + # green + { :match => /created/, :color => :green, :style => :bold }, + { :match => /completed/, :color => :green, :style => :bold }, + { :match => /ran/, :color => :green, :style => :bold }, + + # cyan + { :match => /note/, :replace => 'NOTE:', :color => :cyan, :style => :bold }, + + # magenta + { :match => /nochange/, :replace => 'no change', :color => :magenta }, + { :match => /loading/, :color => :magenta }, + ] + + def self.apply_message_filters(message) + return self.apply_filters(SORTED_FORMATTERS, message) + end + + def self.apply_title_filters(title) + return self.apply_filters(TITLE_FORMATTERS, title) + end + + private + + def self.apply_filters(formatters, message) + level = LeapCli.logger.log_level + result = {} + formatters.each do |formatter| + if (formatter[:match_level] == level || formatter[:match_level].nil?) + if message =~ formatter[:match] + # puts "applying formatter #{formatter.inspect}" + result[:level] = formatter[:level] if formatter[:level] + result[:color] = formatter[:color] if formatter[:color] + result[:style] = formatter[:style] || formatter[:attribute] # (support original cap colors) + + message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace] + message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil? + message.replace(message + formatter[:append]) unless formatter[:append].nil? + message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp] + + if formatter[:exit] + LeapCli::Util.exit_status(formatter[:exit]) + end + + # stop formatting, unless formatter was just for string replacement + break unless formatter[:replace] + end end end - return [clear_prefix, colored_prefix] + + if result[:color] == :hide + return [nil, {}] + else + return [message, result] + end end end -end \ No newline at end of file +end diff --git a/lib/leap_cli/logger.rb b/lib/leap_cli/logger.rb deleted file mode 100644 index 058322c..0000000 --- a/lib/leap_cli/logger.rb +++ /dev/null @@ -1,237 +0,0 @@ -# -# A drop in replacement for Capistrano::Logger that integrates better with LEAP CLI. -# - -require 'capistrano/logger' - -# -# from Capistrano::Logger -# ========================= -# -# IMPORTANT = 0 -# INFO = 1 -# DEBUG = 2 -# TRACE = 3 -# MAX_LEVEL = 3 -# COLORS = { -# :none => "0", -# :black => "30", -# :red => "31", -# :green => "32", -# :yellow => "33", -# :blue => "34", -# :magenta => "35", -# :cyan => "36", -# :white => "37" -# } -# STYLES = { -# :bright => 1, -# :dim => 2, -# :underscore => 4, -# :blink => 5, -# :reverse => 7, -# :hidden => 8 -# } -# - -module LeapCli - class Logger < Capistrano::Logger - - def initialize(options={}) - @options = options - @level = options[:level] || 0 - @message_buffer = nil - end - - def log(level, message, line_prefix=nil, options={}) - if message !~ /\n$/ && level <= 2 && line_prefix.is_a?(String) - # in some cases, when the message doesn't end with a return, we buffer it and - # wait until we encounter the return before we log the message out. - @message_buffer ||= "" - @message_buffer += message - return - elsif @message_buffer - message = @message_buffer + message - @message_buffer = nil - end - - options[:level] ||= level - [:stdout, :log].each do |mode| - LeapCli::log_raw(mode) do - message_lines(mode, message, line_prefix, options) - end - end - end - - private - - def message_lines(mode, message, line_prefix, options) - formatted_message, formatted_prefix, message_options = apply_formatting(mode, message, line_prefix, options) - if message_options[:level] <= self.level && formatted_message && formatted_message.chars.any? - if formatted_prefix - formatted_message.lines.collect { |line| - "[#{formatted_prefix}] #{line.sub(/\s+$/, '')}" - } - else - formatted_message.lines.collect {|line| line.sub(/\s+$/, '')} - end - else - nil - end - end - - ## - ## FORMATTING - ## - - # - # options for formatters: - # - # :match => regexp for matching a log line - # :color => what color the line should be - # :style => what style the line should be - # :priority => what order the formatters are applied in. higher numbers first. - # :match_level => only apply filter at the specified log level - # :level => make this line visible at this log level or higher - # :replace => replace the matched text - # :exit => force the exit code to be this (does not interrupt program, just - # ensures a specific exit code when the program eventually exits) - # - @formatters = [ - # TRACE - { :match => /command finished/, :color => :white, :style => :dim, :match_level => 3, :priority => -10 }, - { :match => /executing locally/, :color => :yellow, :match_level => 3, :priority => -20 }, - - # DEBUG - #{ :match => /executing .*/, :color => :green, :match_level => 2, :priority => -10, :timestamp => true }, - #{ :match => /.*/, :color => :yellow, :match_level => 2, :priority => -30 }, - { :match => /^transaction:/, :level => 3 }, - - # INFO - { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :match_level => 1, :priority => -10 }, - { :match => /Permission denied/, :color => :red, :match_level => 1, :priority => -20 }, - { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 }, - - # IMPORTANT - { :match => /^(E|e)rr ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1}, - { :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1}, - { :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 }, - - # CLEANUP - { :match => /\s+$/, :replace => '', :priority => 0}, - - # DEBIAN PACKAGES - { :match => /^(Hit|Ign) /, :color => :green, :priority => -20}, - { :match => /^Err /, :color => :red, :priority => -20}, - { :match => /^W(ARNING)?: /, :color => :yellow, :priority => -20}, - { :match => /^E: /, :color => :red, :priority => -20}, - { :match => /already the newest version/, :color => :green, :priority => -20}, - { :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10}, - - # PUPPET - { :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(W|w)arning: Scope.*$/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(N|n)otice:/, :level => 1, :color => :cyan, :priority => -20}, - { :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15}, - { :match => /^(W|w)arning:/, :level => 0, :color => :yellow, :priority => -20}, - { :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20}, - { :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10}, - { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :priority => -10}, - { :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :priority => -10}, - - # PUPPET FATAL ERRORS - { :match => /^(E|e)rr(or|):/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Wrapped exception:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Failed to parse template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Execution of.*returned/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Parameter matches failed:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Could not find template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^APPLY COMPLETE.*fail/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - - # TESTS - { :match => /^PASS: /, :color => :green, :priority => -20}, - { :match => /^(FAIL|ERROR): /, :color => :red, :priority => -20}, - { :match => /^(SKIP|WARN): /, :color => :yellow, :priority => -20}, - { :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, :color => :blue, :priority => -20}, - - # LOG SUPPRESSION - { :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10}, - { :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10} - ] - - def self.sorted_formatters - # Sort matchers in reverse order so we can break if we found a match. - @sorted_formatters ||= @formatters.sort_by { |i| -(i[:priority] || i[:prio] || 0) } - end - - @prefix_formatters = [ - { :match => /(err|out) :: /, :replace => '', :priority => 0}, - { :match => /\s+$/, :replace => '', :priority => 0} - ] - def self.prefix_formatters; @prefix_formatters; end - - def apply_formatting(mode, message, line_prefix = nil, options={}) - message = message.dup - options = options.dup - if !line_prefix.nil? - if !line_prefix.is_a?(String) - line_prefix = line_prefix.to_s.dup - else - line_prefix = line_prefix.dup - end - end - color = options[:color] || :none - style = options[:style] - - if line_prefix - self.class.prefix_formatters.each do |formatter| - if line_prefix =~ formatter[:match] && formatter[:replace] - line_prefix.gsub!(formatter[:match], formatter[:replace]) - end - end - end - - self.class.sorted_formatters.each do |formatter| - if (formatter[:match_level] == level || formatter[:match_level].nil?) - if message =~ formatter[:match] - options[:level] = formatter[:level] if formatter[:level] - color = formatter[:color] if formatter[:color] - style = formatter[:style] || formatter[:attribute] # (support original cap colors) - - message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace] - message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil? - message.replace(message + formatter[:append]) unless formatter[:append].nil? - message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp] - - if formatter[:exit] - LeapCli::Util.exit_status(formatter[:exit]) - end - - # stop formatting, unless formatter was just for string replacement - break unless formatter[:replace] - end - end - end - - if color == :hide - return nil - elsif mode == :log || (color == :none && style.nil?) || !LeapCli.logger.log_in_color - return [message, line_prefix, options] - else - term_color = COLORS[color] - term_style = STYLES[style] - if line_prefix.nil? - message.replace format(message, term_color, term_style) - else - line_prefix.replace format(line_prefix, term_color, term_style).strip # format() appends a \n - end - return [message, line_prefix, options] - end - end - - end -end -- cgit v1.2.3 From 64704deccddd9db46ea9ec4992207b8b2d51f1f8 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 29 Jun 2016 16:52:31 -0700 Subject: move everything we can to leap_platform/lib/leap_cli --- lib/leap/platform.rb | 90 -------- lib/leap_cli.rb | 20 +- lib/leap_cli/bootstrap.rb | 17 +- lib/leap_cli/config/environment.rb | 180 --------------- lib/leap_cli/config/filter.rb | 178 --------------- lib/leap_cli/config/manager.rb | 422 ----------------------------------- lib/leap_cli/config/node.rb | 78 ------- lib/leap_cli/config/object.rb | 428 ------------------------------------ lib/leap_cli/config/object_list.rb | 209 ------------------ lib/leap_cli/config/provider.rb | 22 -- lib/leap_cli/config/secrets.rb | 87 -------- lib/leap_cli/config/sources.rb | 11 - lib/leap_cli/config/tag.rb | 25 --- lib/leap_cli/leapfile.rb | 64 +++--- lib/leap_cli/log.rb | 209 +++--------------- lib/leap_cli/util.rb | 8 +- lib/leap_cli/util/remote_command.rb | 158 ------------- lib/leap_cli/util/secret.rb | 55 ----- lib/leap_cli/util/x509.rb | 33 --- 19 files changed, 86 insertions(+), 2208 deletions(-) delete mode 100644 lib/leap/platform.rb delete mode 100644 lib/leap_cli/config/environment.rb delete mode 100644 lib/leap_cli/config/filter.rb delete mode 100644 lib/leap_cli/config/manager.rb delete mode 100644 lib/leap_cli/config/node.rb delete mode 100644 lib/leap_cli/config/object.rb delete mode 100644 lib/leap_cli/config/object_list.rb delete mode 100644 lib/leap_cli/config/provider.rb delete mode 100644 lib/leap_cli/config/secrets.rb delete mode 100644 lib/leap_cli/config/sources.rb delete mode 100644 lib/leap_cli/config/tag.rb delete mode 100644 lib/leap_cli/util/remote_command.rb delete mode 100644 lib/leap_cli/util/secret.rb delete mode 100644 lib/leap_cli/util/x509.rb (limited to 'lib') diff --git a/lib/leap/platform.rb b/lib/leap/platform.rb deleted file mode 100644 index 9112ef3..0000000 --- a/lib/leap/platform.rb +++ /dev/null @@ -1,90 +0,0 @@ -module Leap - - class Platform - class << self - # - # configuration - # - - attr_reader :version - attr_reader :compatible_cli - attr_accessor :facts - attr_accessor :paths - attr_accessor :node_files - attr_accessor :monitor_username - attr_accessor :reserved_usernames - - attr_accessor :hiera_dir - attr_accessor :hiera_path - attr_accessor :files_dir - attr_accessor :leap_dir - attr_accessor :init_path - - attr_accessor :default_puppet_tags - - def define(&block) - # some defaults: - @reserved_usernames = [] - @hiera_dir = '/etc/leap' - @hiera_path = '/etc/leap/hiera.yaml' - @leap_dir = '/srv/leap' - @files_dir = '/srv/leap/files' - @init_path = '/srv/leap/initialized' - @default_puppet_tags = [] - - self.instance_eval(&block) - - @version ||= Gem::Version.new("0.0") - end - - def version=(version) - @version = Gem::Version.new(version) - end - - def compatible_cli=(range) - @compatible_cli = range - @minimum_cli_version = Gem::Version.new(range.first) - @maximum_cli_version = Gem::Version.new(range.last) - end - - # - # return true if the cli_version is compatible with this platform. - # - def compatible_with_cli?(cli_version) - cli_version = Gem::Version.new(cli_version) - cli_version >= @minimum_cli_version && cli_version <= @maximum_cli_version - end - - # - # return true if the platform version is within the specified range. - # - def version_in_range?(range) - if range.is_a? String - range = range.split('..') - end - minimum_platform_version = Gem::Version.new(range.first) - maximum_platform_version = Gem::Version.new(range.last) - @version >= minimum_platform_version && @version <= maximum_platform_version - end - - def major_version - if @version.segments.first == 0 - @version.segments[0..1].join('.') - else - @version.segments.first - end - end - - def method_missing(method, *args) - puts - puts "WARNING:" - puts " leap_cli is out of date and does not understand `#{method}`." - puts " called from: #{caller.first}" - puts " please upgrade to a newer leap_cli" - end - - end - - end - -end \ No newline at end of file diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index 36718e3..c0e139e 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -1,6 +1,6 @@ module LeapCli - module Commands; end # for commands in leap_cli/commands - module Macro; end # for macros in leap_platform/provider_base/lib/macros + module Commands; end # for commands in leap_platform/lib/leap_cli/commands + module Macro; end # for macros in leap_platform/lib/leap_cli/macros end $ruby_version = RUBY_VERSION.split('.').collect{ |i| i.to_i }.extend(Comparable) @@ -13,8 +13,6 @@ $:.unshift(File.expand_path('../leap_cli/override',__FILE__)) require 'rubygems' gem 'gli', '~> 2.12', '>= 2.12.0' -require 'leap/platform' - require 'leap_cli/version' require 'leap_cli/exceptions' @@ -31,27 +29,13 @@ require 'leap_cli/core_ext/yaml' require 'leap_cli/log' require 'leap_cli/path' require 'leap_cli/util' -require 'leap_cli/util/secret' -require 'leap_cli/util/x509' require 'leap_cli/bootstrap' -require 'leap_cli/config/object' -require 'leap_cli/config/node' -require 'leap_cli/config/tag' -require 'leap_cli/config/provider' -require 'leap_cli/config/secrets' -require 'leap_cli/config/object_list' -require 'leap_cli/config/filter' -require 'leap_cli/config/environment' -require 'leap_cli/config/manager' - require 'leap_cli/markdown_document_listener' # # allow everyone easy access to log() command. # module LeapCli - Util.send(:extend, LeapCli::LogCommand) - Config::Manager.send(:include, LeapCli::LogCommand) extend LeapCli::LogCommand end diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb index bc43115..f33aa42 100644 --- a/lib/leap_cli/bootstrap.rb +++ b/lib/leap_cli/bootstrap.rb @@ -40,6 +40,7 @@ module LeapCli log_version end add_platform_lib_to_path + load_platform_libraries load_commands(app) load_macros end @@ -104,11 +105,10 @@ module LeapCli elsif !leapfile_optional?(argv) puts puts " =" - log :note, "There is no `Leapfile` in this directory, or any parent directory.\n"+ - " = "+ + log :NOTE, "There is no `Leapfile` in this directory, or any parent directory.\n"+ + " = "+ "Without this file, most commands will not be available." puts " =" - puts end end @@ -204,5 +204,16 @@ module LeapCli $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path) end end + + # + # loads libraries that live in the platform and should + # always be available. + # + def load_platform_libraries + if Path.platform + require 'leap_cli/load_libraries' + end + end + end end diff --git a/lib/leap_cli/config/environment.rb b/lib/leap_cli/config/environment.rb deleted file mode 100644 index 398fd02..0000000 --- a/lib/leap_cli/config/environment.rb +++ /dev/null @@ -1,180 +0,0 @@ -# -# All configurations files can be isolated into separate environments. -# -# Each config json in each environment inherits from the default environment, -# which in term inherits from the "_base_" environment: -# -# _base_ -- base provider in leap_platform -# '- default -- environment in provider dir when no env is set -# '- production -- example environment -# - -module LeapCli; module Config - - class Environment - # the String name of the environment - attr_accessor :name - - # the shared Manager object - attr_accessor :manager - - # hashes of {name => Config::Object} - attr_accessor :services, :tags, :partials - - # a Config::Provider - attr_accessor :provider - - # a Config::Object - attr_accessor :common - - # shared, non-inheritable - def nodes; @@nodes; end - def secrets; @@secrets; end - - def initialize(manager, name, search_dir, parent, options={}) - @@nodes ||= nil - @@secrets ||= nil - - @manager = manager - @name = name - - load_provider_files(search_dir, options) - - if parent - @services.inherit_from! parent.services, self - @tags.inherit_from! parent.tags , self - @partials.inherit_from! parent.partials, self - @common.inherit_from! parent.common - @provider.inherit_from! parent.provider - end - - if @provider - @provider.set_env(name) - @provider.validate! - end - end - - def load_provider_files(search_dir, options) - # - # load empty environment if search_dir doesn't exist - # - if search_dir.nil? || !Dir.exist?(search_dir) - @services = Config::ObjectList.new - @tags = Config::ObjectList.new - @partials = Config::ObjectList.new - @provider = Config::Provider.new - @common = Config::Object.new - return - end - - # - # inheritable - # - if options[:scope] - scope = options[:scope] - @services = load_all_json(Path.named_path([:service_env_config, '*', scope], search_dir), Config::Tag, options) - @tags = load_all_json(Path.named_path([:tag_env_config, '*', scope], search_dir), Config::Tag, options) - @partials = load_all_json(Path.named_path([:service_env_config, '_*', scope], search_dir), Config::Tag, options) - @provider = load_json( Path.named_path([:provider_env_config, scope], search_dir), Config::Provider, options) - @common = load_json( Path.named_path([:common_env_config, scope], search_dir), Config::Object, options) - else - @services = load_all_json(Path.named_path([:service_config, '*'], search_dir), Config::Tag, options) - @tags = load_all_json(Path.named_path([:tag_config, '*'], search_dir), Config::Tag, options) - @partials = load_all_json(Path.named_path([:service_config, '_*'], search_dir), Config::Tag, options) - @provider = load_json( Path.named_path(:provider_config, search_dir), Config::Provider, options) - @common = load_json( Path.named_path(:common_config, search_dir), Config::Object, options) - end - - # remove 'name' from partials, since partials get merged with nodes - @partials.values.each {|partial| partial.delete('name'); } - - # - # shared: currently non-inheritable - # load the first ones we find, and only those. - # - if @@nodes.nil? || @@nodes.empty? - @@nodes = load_all_json(Path.named_path([:node_config, '*'], search_dir), Config::Node, options) - end - if @@secrets.nil? || @@secrets.empty? - @@secrets = load_json(Path.named_path(:secrets_config, search_dir), Config::Secrets, options) - end - end - - # - # Loads a json template file as a Hash (used only when creating a new node .json - # file for the first time). - # - def template(template) - path = Path.named_path([:template_config, template], Path.provider_base) - if File.exist?(path) - return load_json(path, Config::Object) - else - return nil - end - end - - private - - def load_all_json(pattern, object_class, options={}) - results = Config::ObjectList.new - Dir.glob(pattern).each do |filename| - next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/ - obj = load_json(filename, object_class) - if obj - name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1') - obj['name'] ||= name - if options[:env] - obj.environment = options[:env] - end - results[name] = obj - end - end - results - end - - def load_json(filename, object_class, options={}) - if !File.exist?(filename) - return object_class.new(self) - end - - Util::log :loading, filename, 3 - - # - # Read a JSON file, strip out comments. - # - # UTF8 is the default encoding for JSON, but others are allowed: - # https://www.ietf.org/rfc/rfc4627.txt - # - buffer = StringIO.new - File.open(filename, "rb", :encoding => 'UTF-8') do |f| - while (line = f.gets) - next if line =~ /^\s*\/\// - buffer << line - end - end - - # - # force UTF-8 - # - if $ruby_version >= [1,9] - string = buffer.string.force_encoding('utf-8') - else - string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string) - end - - # parse json - begin - hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {} - rescue SyntaxError, JSON::ParserError => exc - Util::log 0, :error, 'in file "%s":' % filename - Util::log 0, exc.to_s, :indent => 1 - return nil - end - object = object_class.new(self) - object.deep_merge!(hash) - return object - end - - end # end Environment - -end; end \ No newline at end of file diff --git a/lib/leap_cli/config/filter.rb b/lib/leap_cli/config/filter.rb deleted file mode 100644 index 2c80be8..0000000 --- a/lib/leap_cli/config/filter.rb +++ /dev/null @@ -1,178 +0,0 @@ -# -# Many leap_cli commands accept a list of filters to select a subset of nodes for the command to -# be applied to. This class is a helper for manager to run these filters. -# -# Classes other than Manager should not use this class. -# -# Filter rules: -# -# * A filter consists of a list of tokens -# * A token may be a service name, tag name, environment name, or node name. -# * Each token may be optionally prefixed with a plus sign. -# * Multiple tokens with a plus are treated as an OR condition, -# but treated as an AND condition with the plus sign. -# -# For example -# -# * openvpn +development => all nodes with service 'openvpn' AND environment 'development' -# * openvpn seattle => all nodes with service 'openvpn' OR tag 'seattle'. -# -# There can only be one environment specified. Typically, there are also tags -# for each environment name. These name are treated as environments, not tags. -# -module LeapCli - module Config - class Filter - - # - # filter -- array of strings, each one a filter - # options -- hash, possible keys include - # :nopin -- disregard environment pinning - # :local -- if false, disallow local nodes - # - # A nil value in the filters array indicates - # the default environment. This is in order to support - # calls like `manager.filter(environments)` - # - def initialize(filters, options, manager) - @filters = filters.nil? ? [] : filters.dup - @environments = [] - @options = options - @manager = manager - - # split filters by pulling out items that happen - # to be environment names. - if LeapCli.leapfile.environment.nil? || @options[:nopin] - @environments = [] - else - @environments = [LeapCli.leapfile.environment] - end - @filters.select! do |filter| - if filter.nil? - @environments << nil unless @environments.include?(nil) - false - else - filter_text = filter.sub(/^\+/,'') - if is_environment?(filter_text) - if filter_text == LeapCli.leapfile.environment - # silently ignore already pinned environments - elsif (filter =~ /^\+/ || @filters.first == filter) && !@environments.empty? - LeapCli::Util.bail! do - LeapCli::Util.log "Environments are exclusive: no node is in two environments." do - LeapCli::Util.log "Tried to filter on '#{@environments.join('\' AND \'')}' AND '#{filter_text}'" - end - end - else - @environments << filter_text - end - false - else - true - end - end - end - - # don't let the first filter have a + prefix - if @filters[0] =~ /^\+/ - @filters[0] = @filters[0][1..-1] - end - end - - # actually run the filter, returns a filtered list of nodes - def nodes() - if @filters.empty? - return nodes_for_empty_filter - else - return nodes_for_filter - end - end - - private - - def nodes_for_empty_filter - node_list = @manager.nodes - if @environments.any? - node_list = node_list[ @environments.collect{|e|[:environment, env_to_filter(e)]} ] - end - if @options[:local] === false - node_list = node_list[:environment => '!local'] - end - if @options[:disabled] === false - node_list = node_list[:environment => '!disabled'] - end - node_list - end - - def nodes_for_filter - node_list = Config::ObjectList.new - @filters.each do |filter| - if filter =~ /^\+/ - keep_list = nodes_for_name(filter[1..-1]) - node_list.delete_if do |name, node| - if keep_list[name] - false - else - true - end - end - else - node_list.merge!(nodes_for_name(filter)) - end - end - node_list - end - - private - - # - # returns a set of nodes corresponding to a single name, - # where name could be a node name, service name, or tag name. - # - # For services and tags, we only include nodes for the - # environments that are active - # - def nodes_for_name(name) - if node = @manager.nodes[name] - return Config::ObjectList.new(node) - elsif @environments.empty? - if @manager.services[name] - return @manager.env('_all_').services[name].node_list - elsif @manager.tags[name] - return @manager.env('_all_').tags[name].node_list - else - LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments." - return Config::ObjectList.new - end - else - node_list = Config::ObjectList.new - if @manager.services[name] - @environments.each do |env| - node_list.merge!(@manager.env(env).services[name].node_list) - end - elsif @manager.tags[name] - @environments.each do |env| - node_list.merge!(@manager.env(env).tags[name].node_list) - end - else - LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments." - end - return node_list - end - end - - # - # when pinning, we use the name 'default' to specify nodes - # without an environment set, but when filtering, we need to filter - # on :environment => nil. - # - def env_to_filter(environment) - environment == 'default' ? nil : environment - end - - def is_environment?(text) - text == 'default' || @manager.environment_names.include?(text) - end - - end - end -end diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb deleted file mode 100644 index 80ccbad..0000000 --- a/lib/leap_cli/config/manager.rb +++ /dev/null @@ -1,422 +0,0 @@ -# encoding: utf-8 - -require 'json/pure' - -if $ruby_version < [1,9] - require 'iconv' -end - -module LeapCli - module Config - - # - # A class to manage all the objects in all the configuration files. - # - class Manager - - def initialize - @environments = {} # hash of `Environment` objects, keyed by name. - Config::Object.send(:include, LeapCli::Macro) - end - - ## - ## ATTRIBUTES - ## - - # - # returns the Hash of the contents of facts.json - # - def facts - @facts ||= begin - content = Util.read_file(:facts) - if !content || content.empty? - content = "{}" - end - JSON.parse(content) - rescue SyntaxError, JSON::ParserError => exc - Util::bail! "Could not parse facts.json -- #{exc}" - end - end - - # - # returns an Array of all the environments defined for this provider. - # the returned array includes nil (for the default environment) - # - def environment_names - @environment_names ||= begin - [nil] + (env.tags.field('environment') + env.nodes.field('environment')).compact.uniq - end - end - - # - # Returns the appropriate environment variable - # - def env(env=nil) - @environments[env || 'default'] - end - - # - # The default accessors - # - # For these defaults, use 'default' environment, or whatever - # environment is pinned. - # - # I think it might be an error that these are ever used - # and I would like to get rid of them. - # - def services; env(default_environment).services; end - def tags; env(default_environment).tags; end - def partials; env(default_environment).partials; end - def provider; env(default_environment).provider; end - def common; env(default_environment).common; end - def secrets; env(default_environment).secrets; end - def nodes; env(default_environment).nodes; end - def template(*args) - self.env.template(*args) - end - - def default_environment - LeapCli.leapfile.environment - end - - ## - ## IMPORT EXPORT - ## - - def add_environment(args) - if args[:inherit] - parent = @environments[args.delete(:inherit)] - else - parent = nil - end - @environments[args[:name]] = Environment.new( - self, - args.delete(:name), - args.delete(:dir), - parent, - args - ) - end - - # - # load .json configuration files - # - def load(options = {}) - @provider_dir = Path.provider - - # load base - add_environment(name: '_base_', dir: Path.provider_base) - - # load provider - Util::assert_files_exist!(Path.named_path(:provider_config, @provider_dir)) - add_environment(name: 'default', dir: @provider_dir, - inherit: '_base_', no_dots: true) - - # create a special '_all_' environment, used for tracking - # the union of all the environments - add_environment(name: '_all_', inherit: 'default') - - # load environments - environment_names.each do |ename| - if ename - log 3, :loading, '%s environment...' % ename - add_environment(name: ename, dir: @provider_dir, - inherit: 'default', scope: ename) - end - end - - # apply inheritance - env.nodes.each do |name, node| - Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'" - env.nodes[name] = apply_inheritance(node) - end - - # do some node-list post-processing - cleanup_node_lists(options) - - # apply control files - env.nodes.each do |name, node| - control_files(node).each do |file| - begin - node.eval_file file - rescue ConfigError => exc - if options[:continue_on_error] - exc.log - else - raise exc - end - end - end - end - end - - # - # save compiled hiera .yaml files - # - # if a node_list is specified, only update those .yaml files. - # otherwise, update all files, destroying files that are no longer used. - # - def export_nodes(node_list=nil) - updated_hiera = [] - updated_files = [] - existing_hiera = nil - existing_files = nil - - unless node_list - node_list = env.nodes - existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], @provider_dir)) - existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], @provider_dir)) - end - - node_list.each_node do |node| - filepath = Path.named_path([:node_files_dir, node.name], @provider_dir) - hierapath = Path.named_path([:hiera, node.name], @provider_dir) - Util::write_file!(hierapath, node.dump_yaml) - updated_files << filepath - updated_hiera << hierapath - end - - if @disabled_nodes - # make disabled nodes appear as if they are still active - @disabled_nodes.each_node do |node| - updated_files << Path.named_path([:node_files_dir, node.name], @provider_dir) - updated_hiera << Path.named_path([:hiera, node.name], @provider_dir) - end - end - - # remove files that are no longer needed - if existing_hiera - (existing_hiera - updated_hiera).each do |filepath| - Util::remove_file!(filepath) - end - end - if existing_files - (existing_files - updated_files).each do |filepath| - Util::remove_directory!(filepath) - end - end - end - - def export_secrets(clean_unused_secrets = false) - if env.secrets.any? - Util.write_file!([:secrets_config, @provider_dir], env.secrets.dump_json(clean_unused_secrets) + "\n") - end - end - - ## - ## FILTERING - ## - - # - # returns a node list consisting only of nodes that satisfy the filter criteria. - # - # filter: condition [condition] [condition] [+condition] - # condition: [node_name | service_name | tag_name | environment_name] - # - # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR. - # - # args: - # filter -- array of filter terms, one per item - # - # options: - # :local -- if :local is false and the filter is empty, then local nodes are excluded. - # :nopin -- if true, ignore environment pinning - # - def filter(filters=nil, options={}) - Filter.new(filters, options, self).nodes() - end - - # - # same as filter(), but exits if there is no matching nodes - # - def filter!(filters, options={}) - node_list = filter(filters, options) - Util::assert! node_list.any?, "Could not match any nodes from '#{filters.join ' '}'" - return node_list - end - - # - # returns a single Config::Object that corresponds to a Node. - # - def node(name) - if name =~ /\./ - # probably got a fqdn, since periods are not allowed in node names. - # so, take the part before the first period as the node name - name = name.split('.').first - end - env.nodes[name] - end - - # - # returns a single node that is disabled - # - def disabled_node(name) - @disabled_nodes[name] - end - - # - # yields each node, in sorted order - # - def each_node(&block) - env.nodes.each_node(&block) - end - - def reload_node!(node) - env.nodes[node.name] = apply_inheritance!(node) - end - - ## - ## CONNECTIONS - ## - - class ConnectionList < Array - def add(data={}) - self << { - "from" => data[:from], - "to" => data[:to], - "port" => data[:port] - } - end - end - - def connections - @connections ||= ConnectionList.new - end - - ## - ## PRIVATE - ## - - private - - # - # makes a node inherit options from appropriate the common, service, and tag json files. - # - def apply_inheritance(node, throw_exceptions=false) - new_node = Config::Node.new(nil) - node_env = guess_node_env(node) - new_node.set_environment(node_env, new_node) - - # inherit from common - new_node.deep_merge!(node_env.common) - - # inherit from services - if node['services'] - node['services'].to_a.each do |node_service| - service = node_env.services[node_service] - if service.nil? - msg = 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service] - log 0, :error, msg - raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions - else - new_node.deep_merge!(service) - end - end - end - - # inherit from tags - if node.vagrant? - node['tags'] = (node['tags'] || []).to_a + ['local'] - end - if node['tags'] - node['tags'].to_a.each do |node_tag| - tag = node_env.tags[node_tag] - if tag.nil? - msg = 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag] - log 0, :error, msg - raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions - else - new_node.deep_merge!(tag) - end - end - end - - # inherit from node - new_node.deep_merge!(node) - return new_node - end - - def apply_inheritance!(node) - apply_inheritance(node, true) - end - - # - # Guess the environment of the node from the tag names. - # - # Technically, this is wrong: a tag that sets the environment might not be - # named the same as the environment. This code assumes that it is. - # - # Unfortunately, it is a chicken and egg problem. We need to know the nodes - # likely environment in order to apply the inheritance that will actually - # determine the node's properties. - # - def guess_node_env(node) - if node.vagrant? - return self.env("local") - else - environment = self.env(default_environment) - if node['tags'] - node['tags'].to_a.each do |tag| - if self.environment_names.include?(tag) - environment = self.env(tag) - end - end - end - return environment - end - end - - # - # does some final clean at the end of loading nodes. - # this includes removing disabled nodes, and populating - # the services[x].node_list and tags[x].node_list - # - def cleanup_node_lists(options) - @disabled_nodes = Config::ObjectList.new - env.nodes.each do |name, node| - if node.enabled || options[:include_disabled] - if node['services'] - node['services'].to_a.each do |node_service| - env(node.environment).services[node_service].node_list.add(node.name, node) - env('_all_').services[node_service].node_list.add(node.name, node) - end - end - if node['tags'] - node['tags'].to_a.each do |node_tag| - env(node.environment).tags[node_tag].node_list.add(node.name, node) - env('_all_').tags[node_tag].node_list.add(node.name, node) - end - end - elsif !options[:include_disabled] - log 2, :skipping, "disabled node #{name}." - env.nodes.delete(name) - @disabled_nodes[name] = node - end - end - end - - # - # returns a list of 'control' files for this node. - # a control file is like a service or a tag JSON file, but it contains - # raw ruby code that gets evaluated in the context of the node. - # Yes, this entirely breaks our functional programming model - # for JSON generation. - # - def control_files(node) - files = [] - [Path.provider_base, @provider_dir].each do |provider_dir| - [['services', :service_config], ['tags', :tag_config]].each do |attribute, path_sym| - node[attribute].each do |attr_value| - path = Path.named_path([path_sym, "#{attr_value}.rb"], provider_dir).sub(/\.json$/,'') - if File.exist?(path) - files << path - end - end - end - end - return files - end - - end - end -end diff --git a/lib/leap_cli/config/node.rb b/lib/leap_cli/config/node.rb deleted file mode 100644 index f8ec052..0000000 --- a/lib/leap_cli/config/node.rb +++ /dev/null @@ -1,78 +0,0 @@ -# -# Configuration for a 'node' (a server in the provider's infrastructure) -# - -require 'ipaddr' - -module LeapCli; module Config - - class Node < Object - attr_accessor :file_paths - - def initialize(environment=nil) - super(environment) - @node = self - @file_paths = [] - end - - # - # returns true if this node has an ip address in the range of the vagrant network - # - def vagrant? - begin - vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network - rescue ArgumentError => exc - Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" } - end - - begin - ip_address = IPAddr.new @node.get('ip_address') - rescue ArgumentError => exc - Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'" - end - return vagrant_range.include?(ip_address) - end - - # - # Return a hash table representation of ourselves, with the key equal to the @node.name, - # and the value equal to the fields specified in *keys. - # - # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b' - # - # compare to Object#pick(*keys). This method is the sames as Config::ObjectList#pick_fields, - # but works on a single node. - # - # Example: - # - # node.pick('domain.internal') => - # - # { - # 'node1': { - # 'domain_internal': 'node1.example.i' - # } - # } - # - def pick_fields(*keys) - {@node.name => self.pick(*keys)} - end - - # - # can be overridden by the platform. - # returns a list of node names that should be tested before this node - # - def test_dependencies - [] - end - - # returns a string list of supported ssh host key algorithms for this node. - # or an empty string if it could not be determined - def supported_ssh_host_key_algorithms - require 'leap_cli/ssh' - @host_key_algo ||= LeapCli::SSH::Key.supported_host_key_algorithms( - Util.read_file([:node_ssh_pub_key, @node.name]) - ) - end - - end - -end; end diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb deleted file mode 100644 index b117c2f..0000000 --- a/lib/leap_cli/config/object.rb +++ /dev/null @@ -1,428 +0,0 @@ -# encoding: utf-8 - -require 'erb' -require 'json/pure' # pure ruby implementation is required for our sorted trick to work. - -if $ruby_version < [1,9] - $KCODE = 'UTF8' -end -require 'ya2yaml' # pure ruby yaml - -module LeapCli - module Config - - # - # This class represents the configuration for a single node, service, or tag. - # Also, all the nested hashes are also of this type. - # - # It is called 'object' because it corresponds to an Object in JSON. - # - class Object < Hash - - attr_reader :env - attr_reader :node - - def initialize(environment=nil, node=nil) - raise ArgumentError unless environment.nil? || environment.is_a?(Config::Environment) - @env = environment - # an object that is a node as @node equal to self, otherwise all the - # child objects point back to the top level node. - @node = node || self - end - - def manager - @env.manager - end - - # - # TODO: deprecate node.global() - # - def global - @env - end - - def environment=(e) - self.store('environment', e) - end - - def environment - self['environment'] - end - - def duplicate(env) - new_object = self.deep_dup - new_object.set_environment(env, new_object) - end - - # - # export YAML - # - # We use pure ruby yaml exporter ya2yaml instead of SYCK or PSYCH because it - # allows us greater compatibility regardless of installed ruby version and - # greater control over how the yaml is exported (sorted keys, in particular). - # - def dump_yaml - evaluate(@node) - sorted_ya2yaml(:syck_compatible => true) - end - - # - # export JSON - # - def dump_json(options={}) - evaluate(@node) - if options[:format] == :compact - return self.to_json - else - excluded = {} - if options[:exclude] - options[:exclude].each do |key| - excluded[key] = self[key] - self.delete(key) - end - end - json_str = JSON.sorted_generate(self) - if excluded.any? - self.merge!(excluded) - end - return json_str - end - end - - def evaluate(context=@node) - evaluate_everything(context) - late_evaluate_everything(context) - end - - ## - ## FETCHING VALUES - ## - - def [](key) - get(key) - end - - # Overrride some default methods in Hash that are likely to - # be used as attributes. - alias_method :hkey, :key - def key; get('key'); end - - # - # make hash addressable like an object (e.g. obj['name'] available as obj.name) - # - def method_missing(method, *args, &block) - get!(method) - end - - def get(key) - begin - get!(key) - rescue NoMethodError - nil - end - end - - # override behavior of #default() from Hash - def default - get!('default') - end - - # - # Like a normal Hash#[], except: - # - # (1) lazily eval dynamic values when we encounter them. (i.e. strings that start with "= ") - # - # (2) support for nested references in a single string (e.g. ['a.b'] is the same as ['a']['b']) - # the dot path is always absolute, starting at the top-most object. - # - def get!(key) - key = key.to_s - if self.has_key?(key) - fetch_value(key) - elsif key =~ /\./ - # for keys with with '.' in them, we start from the root object (@node). - keys = key.split('.') - value = self.get!(keys.first) - if value.is_a? Config::Object - value.get!(keys[1..-1].join('.')) - else - value - end - else - raise NoMethodError.new(key, "No method '#{key}' for #{self.class}") - end - end - - ## - ## COPYING - ## - - # - # A deep (recursive) merge with another Config::Object. - # - # If prefer_self is set to true, the value from self will be picked when there is a conflict - # that cannot be merged. - # - # Merging rules: - # - # - If a value is a hash, we recursively merge it. - # - If the value is simple, like a string, the new one overwrites the value. - # - If the value is an array: - # - If both old and new values are arrays, the new one replaces the old. - # - If one of the values is simple but the other is an array, the simple is added to the array. - # - def deep_merge!(object, prefer_self=false) - object.each do |key,new_value| - if self.has_key?('+'+key) - mode = :add - old_value = self.fetch '+'+key, nil - self.delete('+'+key) - elsif self.has_key?('-'+key) - mode = :subtract - old_value = self.fetch '-'+key, nil - self.delete('-'+key) - elsif self.has_key?('!'+key) - mode = :replace - old_value = self.fetch '!'+key, nil - self.delete('!'+key) - else - mode = :normal - old_value = self.fetch key, nil - end - - # clean up boolean - new_value = true if new_value == "true" - new_value = false if new_value == "false" - old_value = true if old_value == "true" - old_value = false if old_value == "false" - - # force replace? - if mode == :replace && prefer_self - value = old_value - - # merge hashes - elsif old_value.is_a?(Hash) || new_value.is_a?(Hash) - value = Config::Object.new(@env, @node) - old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if !old_value.nil?) - new_value.is_a?(Hash) ? value.deep_merge!(new_value, prefer_self) : (value[key] = new_value if !new_value.nil?) - - # merge nil - elsif new_value.nil? - value = old_value - elsif old_value.nil? - value = new_value - - # merge arrays when one value is not an array - elsif old_value.is_a?(Array) && !new_value.is_a?(Array) - (value = (old_value.dup << new_value).compact.uniq).delete('REQUIRED') - elsif new_value.is_a?(Array) && !old_value.is_a?(Array) - (value = (new_value.dup << old_value).compact.uniq).delete('REQUIRED') - - # merge two arrays - elsif old_value.is_a?(Array) && new_value.is_a?(Array) - if mode == :add - value = (old_value + new_value).sort.uniq - elsif mode == :subtract - value = new_value - old_value - elsif prefer_self - value = old_value - else - value = new_value - end - - # catch errors - elsif type_mismatch?(old_value, new_value) - raise 'Type mismatch. Cannot merge %s (%s) with %s (%s). Key is "%s", name is "%s".' % [ - old_value.inspect, old_value.class, - new_value.inspect, new_value.class, - key, self.class - ] - - # merge simple strings & numbers - else - if prefer_self - value = old_value - else - value = new_value - end - end - - # save value - self[key] = value - end - self - end - - def set_environment(env, node) - @env = env - @node = node - self.each do |key, value| - if value.is_a?(Config::Object) - value.set_environment(env, node) - end - end - end - - # - # like a reverse deep merge - # (self takes precedence) - # - def inherit_from!(object) - self.deep_merge!(object, true) - end - - # - # Make a copy of ourselves, except only including the specified keys. - # - # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b' - # - def pick(*keys) - keys.map(&:to_s).inject(self.class.new(@manager)) do |hsh, key| - value = self.get(key) - if !value.nil? - hsh[key.gsub('.','_')] = value - end - hsh - end - end - - def eval_file(filename) - evaluate_ruby(filename, File.read(filename)) - end - - protected - - # - # walks the object tree, eval'ing all the attributes that are dynamic ruby (e.g. value starts with '= ') - # - def evaluate_everything(context) - keys.each do |key| - obj = fetch_value(key, context) - if is_required_value_not_set?(obj) - Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"." - elsif obj.is_a? Config::Object - obj.evaluate_everything(context) - end - end - end - - # - # some keys need to be evaluated 'late', after all the other keys have been evaluated. - # - def late_evaluate_everything(context) - if @late_eval_list - @late_eval_list.each do |key, value| - self[key] = context.evaluate_ruby(key, value) - if is_required_value_not_set?(self[key]) - Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"." - end - end - end - values.each do |obj| - if obj.is_a? Config::Object - obj.late_evaluate_everything(context) - end - end - end - - # - # evaluates the string `value` as ruby in the context of self. - # (`key` is just passed for debugging purposes) - # - def evaluate_ruby(key, value) - self.instance_eval(value, key, 1) - rescue ConfigError => exc - raise exc # pass through - rescue SystemStackError => exc - Util::log 0, :error, "while evaluating node '#{self.name}'" - Util::log 0, "offending key: #{key}", :indent => 1 - Util::log 0, "offending string: #{value}", :indent => 1 - Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1 - raise SystemExit.new(1) - rescue FileMissing => exc - Util::bail! do - if exc.options[:missing] - Util::log :missing, exc.options[:missing].gsub('$node', self.name).gsub('$file', exc.path) - else - Util::log :error, "while evaluating node '#{self.name}'" - Util::log "offending key: #{key}", :indent => 1 - Util::log "offending string: #{value}", :indent => 1 - Util::log "error message: no file '#{exc}'", :indent => 1 - end - raise exc if DEBUG - end - rescue AssertionFailed => exc - Util.bail! do - Util::log :failed, "assertion while evaluating node '#{self.name}'" - Util::log 'assertion: %s' % exc.assertion, :indent => 1 - Util::log "offending key: #{key}", :indent => 1 - raise exc if DEBUG - end - rescue SyntaxError, StandardError => exc - Util::bail! do - Util::log :error, "while evaluating node '#{self.name}'" - Util::log "offending key: #{key}", :indent => 1 - Util::log "offending string: #{value}", :indent => 1 - Util::log "error message: #{exc.inspect}", :indent => 1 - raise exc if DEBUG - end - end - - private - - # - # fetches the value for the key, evaluating the value as ruby if it begins with '=' - # - def fetch_value(key, context=@node) - value = fetch(key, nil) - if value.is_a?(String) && value =~ /^=/ - if value =~ /^=> (.*)$/ - value = evaluate_later(key, $1) - elsif value =~ /^= (.*)$/ - value = context.evaluate_ruby(key, $1) - end - self[key] = value - end - return value - end - - def evaluate_later(key, value) - @late_eval_list ||= [] - @late_eval_list << [key, value] - '' - end - - # - # when merging, we raise an error if this method returns true for the two values. - # - def type_mismatch?(old_value, new_value) - if old_value.is_a?(Boolean) && new_value.is_a?(Boolean) - # note: FalseClass and TrueClass are different classes - # so we can't do old_value.class == new_value.class - return false - elsif old_value.is_a?(String) && old_value =~ /^=/ - # pass through macros, since we don't know what the type will eventually be. - return false - elsif new_value.is_a?(String) && new_value =~ /^=/ - return false - elsif old_value.class == new_value.class - return false - else - return true - end - end - - # - # returns true if the value has not been changed and the default is "REQUIRED" - # - def is_required_value_not_set?(value) - if value.is_a? Array - value == ["REQUIRED"] - else - value == "REQUIRED" - end - end - - end # class - end # module -end # module \ No newline at end of file diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb deleted file mode 100644 index f9299a6..0000000 --- a/lib/leap_cli/config/object_list.rb +++ /dev/null @@ -1,209 +0,0 @@ -require 'tsort' - -module LeapCli - module Config - # - # A list of Config::Object instances (internally stored as a hash) - # - class ObjectList < Hash - include TSort - - def initialize(config=nil) - if config - self.add(config['name'], config) - end - end - - # - # If the key is a string, the Config::Object it references is returned. - # - # If the key is a hash, we treat it as a condition and filter all the Config::Objects using the condition. - # A new ObjectList is returned. - # - # Examples: - # - # nodes['vpn1'] - # node named 'vpn1' - # - # nodes[:public_dns => true] - # all nodes with public dns - # - # nodes[:services => 'openvpn', 'location.country_code' => 'US'] - # all nodes with services containing 'openvpn' OR country code of US - # - # Sometimes, you want to do an OR condition with multiple conditions - # for the same field. Since hash keys must be unique, you can use - # an array representation instead: - # - # nodes[[:services, 'openvpn'], [:services, 'tor']] - # nodes with openvpn OR tor service - # - # nodes[:services => 'openvpn'][:tags => 'production'] - # nodes with openvpn AND are production - # - def [](key) - if key.is_a?(Hash) || key.is_a?(Array) - filter(key) - else - super key.to_s - end - end - - def exclude(node) - list = self.dup - list.delete(node.name) - return list - end - - def each_node(&block) - self.keys.sort.each do |node_name| - yield self[node_name] - end - end - - # - # filters this object list, producing a new list. - # filter is an array or a hash. see [] - # - def filter(filter) - results = Config::ObjectList.new - filter.each do |field, match_value| - field = field.is_a?(Symbol) ? field.to_s : field - match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value - if match_value.is_a?(String) && match_value =~ /^!/ - operator = :not_equal - match_value = match_value.sub(/^!/, '') - else - operator = :equal - end - each do |name, config| - value = config[field] - if value.is_a? Array - if operator == :equal && value.include?(match_value) - results[name] = config - elsif operator == :not_equal && !value.include?(match_value) - results[name] = config - end - else - if operator == :equal && value == match_value - results[name] = config - elsif operator == :not_equal && value != match_value - results[name] = config - end - end - end - end - results - end - - def add(name, object) - self[name] = object - end - - # - # converts the hash of configs into an array of hashes, with ONLY the specified fields - # - def fields(*fields) - result = [] - keys.sort.each do |name| - result << self[name].pick(*fields) - end - result - end - - # - # like fields(), but returns an array of values instead of an array of hashes. - # - def field(field) - field = field.to_s - result = [] - keys.sort.each do |name| - result << self[name].get(field) - end - result - end - - # - # pick_fields(field1, field2, ...) - # - # generates a Hash from the object list, but with only the fields that are picked. - # - # If there are more than one field, then the result is a Hash of Hashes. - # If there is just one field, it is a simple map to the value. - # - # For example: - # - # "neighbors" = "= nodes_like_me[:services => :couchdb].pick_fields('domain.full', 'ip_address')" - # - # generates this: - # - # neighbors: - # couch1: - # domain_full: couch1.bitmask.net - # ip_address: "10.5.5.44" - # couch2: - # domain_full: couch2.bitmask.net - # ip_address: "10.5.5.52" - # - # But this: - # - # "neighbors": "= nodes_like_me[:services => :couchdb].pick_fields('domain.full')" - # - # will generate this: - # - # neighbors: - # couch1: couch1.bitmask.net - # couch2: couch2.bitmask.net - # - def pick_fields(*fields) - self.values.inject({}) do |hsh, node| - value = self[node.name].pick(*fields) - if fields.size == 1 - value = value.values.first - end - hsh[node.name] = value - hsh - end - end - - # - # Applies inherit_from! to all objects. - # - # 'env' specifies what environment should be for - # each object in the list. - # - def inherit_from!(object_list, env) - object_list.each do |name, object| - if self[name] - self[name].inherit_from!(object) - else - self[name] = object.duplicate(env) - end - end - end - - # - # topographical sort based on test dependency - # - def tsort_each_node(&block) - self.each_key(&block) - end - - def tsort_each_child(node_name, &block) - if self[node_name] - self[node_name].test_dependencies.each do |test_me_first| - if self[test_me_first] # TODO: in the future, allow for ability to optionally pull in all dependencies. - # not just the ones that pass the node filter. - yield(test_me_first) - end - end - end - end - - def names_in_test_dependency_order - self.tsort - end - - end - end -end diff --git a/lib/leap_cli/config/provider.rb b/lib/leap_cli/config/provider.rb deleted file mode 100644 index 0d8bc1f..0000000 --- a/lib/leap_cli/config/provider.rb +++ /dev/null @@ -1,22 +0,0 @@ -# -# Configuration class for provider.json -# - -module LeapCli; module Config - class Provider < Object - attr_reader :environment - def set_env(e) - if e == 'default' - @environment = nil - else - @environment = e - end - end - def provider - self - end - def validate! - # nothing here yet :( - end - end -end; end diff --git a/lib/leap_cli/config/secrets.rb b/lib/leap_cli/config/secrets.rb deleted file mode 100644 index ca851c7..0000000 --- a/lib/leap_cli/config/secrets.rb +++ /dev/null @@ -1,87 +0,0 @@ -# encoding: utf-8 -# -# A class for the secrets.json file -# - -module LeapCli; module Config - - class Secrets < Object - attr_reader :node_list - - def initialize(manager=nil) - super(manager) - @discovered_keys = {} - end - - # we can't use fetch() or get(), since those already have special meanings - def retrieve(key, environment) - environment ||= 'default' - self.fetch(environment, {})[key.to_s] - end - - def set(*args, &block) - if block_given? - set_with_block(*args, &block) - else - set_without_block(*args) - end - end - - # searches over all keys matching the regexp, checking to see if the value - # has been already used by any of them. - def taken?(regexp, value, environment) - self.keys.grep(regexp).each do |key| - return true if self.retrieve(key, environment) == value - end - return false - end - - def set_without_block(key, value, environment) - set_with_block(key, environment) {value} - end - - def set_with_block(key, environment, &block) - environment ||= 'default' - key = key.to_s - @discovered_keys[environment] ||= {} - @discovered_keys[environment][key] = true - self[environment] ||= {} - self[environment][key] ||= yield - end - - # - # if clean is true, then only secrets that have been discovered - # during this run will be exported. - # - # if environment is also pinned, then we will clean those secrets - # just for that environment. - # - # the clean argument should only be used when all nodes have - # been processed, otherwise secrets that are actually in use will - # get mistakenly removed. - # - def dump_json(clean=false) - pinned_env = LeapCli.leapfile.environment - if clean - self.each_key do |environment| - if pinned_env.nil? || pinned_env == environment - env = self[environment] - if env.nil? - raise StandardError.new("secrets.json file seems corrupted. No such environment '#{environment}'") - end - env.each_key do |key| - unless @discovered_keys[environment] && @discovered_keys[environment][key] - self[environment].delete(key) - end - end - if self[environment].empty? - self.delete(environment) - end - end - end - end - super() - end - end - -end; end diff --git a/lib/leap_cli/config/sources.rb b/lib/leap_cli/config/sources.rb deleted file mode 100644 index aee860d..0000000 --- a/lib/leap_cli/config/sources.rb +++ /dev/null @@ -1,11 +0,0 @@ -# encoding: utf-8 -# -# A class for the sources.json file -# - -module LeapCli - module Config - class Sources < Object - end - end -end diff --git a/lib/leap_cli/config/tag.rb b/lib/leap_cli/config/tag.rb deleted file mode 100644 index 6bd8d1e..0000000 --- a/lib/leap_cli/config/tag.rb +++ /dev/null @@ -1,25 +0,0 @@ -# -# -# A class for node services or node tags. -# -# - -module LeapCli; module Config - - class Tag < Object - attr_reader :node_list - - def initialize(environment=nil) - super(environment) - @node_list = Config::ObjectList.new - end - - # don't copy the node list pointer when this object is dup'ed. - def initialize_copy(orig) - super - @node_list = Config::ObjectList.new - end - - end - -end; end diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index ac40237..10af224 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -3,6 +3,8 @@ # # It is akin to a Gemfile, Rakefile, or Capfile (e.g. it is a ruby file that gets eval'ed) # +# Additional configuration options are defined in platform's leapfile_extensions.rb +# module LeapCli def self.leapfile @@ -10,17 +12,11 @@ module LeapCli end class Leapfile - attr_accessor :platform_directory_path - attr_accessor :provider_directory_path - attr_accessor :custom_vagrant_vm_line - attr_accessor :leap_version - attr_accessor :log - attr_accessor :vagrant_network - attr_accessor :vagrant_basebox - attr_accessor :environment + attr_reader :platform_directory_path + attr_reader :provider_directory_path + attr_reader :environment def initialize - @vagrant_network = '10.5.5.0/24' end # @@ -61,19 +57,33 @@ module LeapCli # # load the platform # - platform_file = "#{@platform_directory_path}/platform.rb" - unless File.exist?(platform_file) + platform_class = "#{@platform_directory_path}/lib/leap/platform" + platform_definition = "#{@platform_directory_path}/platform.rb" + unless File.exist?(platform_definition) Util.bail! "ERROR: The file `#{platform_file}` does not exist. Please check the value of `@platform_directory_path` in `Leapfile` or `~/.leaprc`." end - require "#{@platform_directory_path}/platform.rb" - if !Leap::Platform.compatible_with_cli?(LeapCli::VERSION) || - !Leap::Platform.version_in_range?(LeapCli::COMPATIBLE_PLATFORM_VERSION) - Util.bail! "This leap command (v#{LeapCli::VERSION}) " + - "is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}).\n " + - "You need either leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last} or " + - "platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}" + require platform_class + require platform_definition + begin + Leap::Platform.validate!(LeapCli::VERSION, LeapCli::COMPATIBLE_PLATFORM_VERSION, self) + rescue StandardError => exc + Util.bail! exc.to_s + end + leapfile_extensions = "#{@platform_directory_path}/lib/leap_cli/leapfile_extensions.rb" + if File.exist?(leapfile_extensions) + require leapfile_extensions + end + + # + # validate + # + instance_variables.each do |var| + var = var.to_s.sub('@', '') + if !self.respond_to?(var) + LeapCli.log :warning, "the variable `#{var}` is set in .leaprc or Leapfile, but it is not supported." + end end - @valid = true + @valid = validate return @valid end end @@ -123,9 +133,8 @@ module LeapCli def read_settings(file) if File.exist? file - Util::log 2, :read, file + LeapCli.log 2, :read, file instance_eval(File.read(file), file) - validate(file) end end @@ -140,11 +149,16 @@ module LeapCli return search_dir end - PRIVATE_IP_RANGES = /(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/ + # to be overridden + def validate + return true + end - def validate(file) - Util::assert! vagrant_network =~ PRIVATE_IP_RANGES do - Util::log 0, :error, "in #{file}: vagrant_network is not a local private network" + def method_missing(method, *args) + if method =~ /=$/ + self.instance_variable_set('@' + method.to_s.sub('=',''), args.first) + else + self.instance_variable_get('@' + method.to_s) end end diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 03789c3..5203c97 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -86,6 +86,9 @@ module LeapCli message = args.grep(String).first options = args.grep(Hash).first || {} host = options[:host] + if title + title = title.to_s + end unless message && @log_level >= level return end @@ -99,21 +102,22 @@ module LeapCli # # apply filters + # LogFilter will not be defined if no platform was loaded. # - if title - title, filter_flags = LogFilter.apply_title_filters(title.to_s) - else - message, filter_flags = LogFilter.apply_message_filters(message) - return if message.nil? + if defined?(LeapCli::LogFilter) + if title + title, filter_flags = LogFilter.apply_title_filters(title) + else + message, filter_flags = LogFilter.apply_message_filters(message) + return if message.nil? + end + options = options.merge(filter_flags) end - options = options.merge(filter_flags) # # set line prefix # - prefix = "" - prefix += "[" + options[:host] + "] " if options[:host] - prefix += title + " " if title + prefix = prefix_str(host, title) # # write to the log file, always @@ -129,17 +133,17 @@ module LeapCli end if options[:color] if host - host = "[" + colorize(host, options[:color], options[:style]) + "] " + host = colorize(host, options[:color], options[:style]) elsif title - title = colorize(title, options[:color], options[:style]) + " " + title = colorize(title, options[:color], options[:style]) else message = colorize(message, options[:color], options[:style]) end elsif title - title = colorize(title, :cyan, :bold) + " " + title = colorize(title, :cyan, :bold) end # new colorized prefix: - prefix = [host, title].compact.join(' ') + prefix = prefix_str(host, title) end log_raw(:stdout, options[:indent], prefix) { message } @@ -212,6 +216,14 @@ module LeapCli private + def prefix_str(host, title) + prefix = "" + prefix += "[" + host + "] " if host + prefix += title + " " if title + prefix += " " if !prefix.empty? && prefix !~ / $/ + return prefix + end + EFFECTS = { :reset => 0, :nothing => 0, :bright => 1, :bold => 1, @@ -245,174 +257,3 @@ module LeapCli end end -# -# A module to hide, modify, and colorize log entries. -# - -module LeapCli - module LogFilter - # - # options for formatters: - # - # :match => regexp for matching a log line - # :color => what color the line should be - # :style => what style the line should be - # :priority => what order the formatters are applied in. higher numbers first. - # :match_level => only apply filter at the specified log level - # :level => make this line visible at this log level or higher - # :replace => replace the matched text - # :prepend => insert text at start of message - # :append => append text to end of message - # :exit => force the exit code to be this (does not interrupt program, just - # ensures a specific exit code when the program eventually exits) - # - FORMATTERS = [ - # TRACE - { :match => /command finished/, :color => :white, :style => :dim, :match_level => 3, :priority => -10 }, - { :match => /executing locally/, :color => :yellow, :match_level => 3, :priority => -20 }, - - # DEBUG - #{ :match => /executing .*/, :color => :green, :match_level => 2, :priority => -10, :timestamp => true }, - #{ :match => /.*/, :color => :yellow, :match_level => 2, :priority => -30 }, - { :match => /^transaction:/, :level => 3 }, - - # INFO - { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :match_level => 1, :priority => -10 }, - { :match => /Permission denied/, :color => :red, :match_level => 1, :priority => -20 }, - { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 }, - - # IMPORTANT - { :match => /^(E|e)rr ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1}, - { :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1}, - #{ :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 }, - - # CLEANUP - #{ :match => /\s+$/, :replace => '', :priority => 0}, - - # DEBIAN PACKAGES - { :match => /^(Hit|Ign) /, :color => :green, :priority => -20}, - { :match => /^Err /, :color => :red, :priority => -20}, - { :match => /^W(ARNING)?: /, :color => :yellow, :priority => -20}, - { :match => /^E: /, :color => :red, :priority => -20}, - { :match => /already the newest version/, :color => :green, :priority => -20}, - { :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10}, - - # PUPPET - { :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10}, - { :match => /^(W|w)arning: Scope.*$/, :level => 2, :color => :yellow, :priority => -10}, - #{ :match => /^(N|n)otice:/, :level => 1, :color => :cyan, :priority => -20}, - #{ :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15}, - { :match => /^(W|w)arning:/, :level => 0, :color => :yellow, :priority => -20}, - { :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20}, - #{ :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10}, - { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :style => :bold, :priority => -10}, - { :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :style => :bold, :priority => -10}, - - # PUPPET FATAL ERRORS - { :match => /^(E|e)rr(or|):/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Wrapped exception:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Failed to parse template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Execution of.*returned/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Parameter matches failed:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Could not find template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^APPLY COMPLETE.*fail/, :level => 0, :color => :red, :style => :bold, :priority => -1, :exit => 1}, - - # TESTS - { :match => /^PASS: /, :color => :green, :priority => -20}, - { :match => /^(FAIL|ERROR): /, :color => :red, :priority => -20}, - { :match => /^(SKIP|WARN): /, :color => :yellow, :priority => -20}, - { :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, - :color => :green, :style => :bold, :priority => -20 }, - { :match => /\d+ tests: \d+ passes, \d+ skips, [1-9][0-9]* warnings, 0 failures, 0 errors/, - :color => :yellow, :style => :bold, :priority => -20 }, - { :match => /\d+ tests: \d+ passes, \d+ skips, \d+ warnings, \d+ failures, [1-9][0-9]* errors/, - :color => :red, :style => :bold, :priority => -20 }, - { :match => /\d+ tests: \d+ passes, \d+ skips, \d+ warnings, [1-9][0-9]* failures, \d+ errors/, - :color => :red, :style => :bold, :priority => -20 }, - - # LOG SUPPRESSION - { :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10}, - { :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10} - ] - - SORTED_FORMATTERS = FORMATTERS.sort_by { |i| -(i[:priority] || i[:prio] || 0) } - - # - # same as normal formatters, but only applies to the title, not the message. - # - TITLE_FORMATTERS = [ - # red - { :match => /error/, :color => :red, :style => :bold }, - { :match => /fatal_error/, :replace => 'fatal error:', :color => :red, :style => :bold }, - { :match => /removed/, :color => :red, :style => :bold }, - { :match => /failed/, :replace => 'FAILED', :color => :red, :style => :bold }, - { :match => /bail/, :replace => 'bailing out', :color => :red, :style => :bold }, - { :match => /invalid/, :color => :red, :style => :bold }, - - # yellow - { :match => /warning/, :replace => 'warning:', :color => :yellow, :style => :bold }, - { :match => /missing/, :color => :yellow, :style => :bold }, - { :match => /skipping/, :color => :yellow, :style => :bold }, - - # green - { :match => /created/, :color => :green, :style => :bold }, - { :match => /completed/, :color => :green, :style => :bold }, - { :match => /ran/, :color => :green, :style => :bold }, - - # cyan - { :match => /note/, :replace => 'NOTE:', :color => :cyan, :style => :bold }, - - # magenta - { :match => /nochange/, :replace => 'no change', :color => :magenta }, - { :match => /loading/, :color => :magenta }, - ] - - def self.apply_message_filters(message) - return self.apply_filters(SORTED_FORMATTERS, message) - end - - def self.apply_title_filters(title) - return self.apply_filters(TITLE_FORMATTERS, title) - end - - private - - def self.apply_filters(formatters, message) - level = LeapCli.logger.log_level - result = {} - formatters.each do |formatter| - if (formatter[:match_level] == level || formatter[:match_level].nil?) - if message =~ formatter[:match] - # puts "applying formatter #{formatter.inspect}" - result[:level] = formatter[:level] if formatter[:level] - result[:color] = formatter[:color] if formatter[:color] - result[:style] = formatter[:style] || formatter[:attribute] # (support original cap colors) - - message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace] - message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil? - message.replace(message + formatter[:append]) unless formatter[:append].nil? - message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp] - - if formatter[:exit] - LeapCli::Util.exit_status(formatter[:exit]) - end - - # stop formatting, unless formatter was just for string replacement - break unless formatter[:replace] - end - end - end - - if result[:color] == :hide - return [nil, {}] - else - return [message, result] - end - end - - end -end diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 248a59c..64b5c63 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -10,6 +10,10 @@ module LeapCli @@exit_status = nil + def log(*args, &block) + LeapCli.log(*args, &block) + end + ## ## QUITTING ## @@ -44,7 +48,7 @@ module LeapCli log 0, *message end log 0, :bail, "" - raise SystemExit.new(@exit_status || 1) + raise SystemExit.new(exit_status || 1) end # @@ -52,7 +56,7 @@ module LeapCli # def quit!(message='') puts(message) - raise SystemExit.new(@exit_status || 0) + raise SystemExit.new(exit_status || 0) end # diff --git a/lib/leap_cli/util/remote_command.rb b/lib/leap_cli/util/remote_command.rb deleted file mode 100644 index c2f1ace..0000000 --- a/lib/leap_cli/util/remote_command.rb +++ /dev/null @@ -1,158 +0,0 @@ -module LeapCli; module Util; module RemoteCommand - extend self - - # - # FYI - # Capistrano::Logger::IMPORTANT = 0 - # Capistrano::Logger::INFO = 1 - # Capistrano::Logger::DEBUG = 2 - # Capistrano::Logger::TRACE = 3 - # - def ssh_connect(nodes, options={}, &block) - options ||= {} - node_list = parse_node_list(nodes) - - cap = new_capistrano - cap.logger = LeapCli::Logger.new(:level => [LeapCli.logger.log_level,3].min) - user = options[:user] || 'root' - cap.set :user, user - cap.set :ssh_options, ssh_options # ssh options common to all nodes - cap.set :use_sudo, false # we may want to change this in the future - - # Allow password authentication when we are bootstraping a single node - # (and key authentication fails). - if options[:bootstrap] && node_list.size == 1 - hostname = node_list.values.first.name - if options[:echo] - cap.set(:password) { ask "Root SSH password for #{user}@#{hostname}> " } - else - cap.set(:password) { Capistrano::CLI.password_prompt " * Typed password will be hidden (use --echo to make it visible)\nRoot SSH password for #{user}@#{hostname}> " } - end - end - - node_list.each do |name, node| - cap.server node.domain.full, :dummy_arg, node_options(node, options[:ssh_options]) - end - - yield cap - rescue Capistrano::ConnectionError => exc - # not sure if this will work if english is not the locale?? - if exc.message =~ /Too many authentication failures/ - at_exit {ssh_config_help_message} - end - raise exc - end - - private - - # - # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start - # - # Capistrano has some very evil behavior in it's ssh.rb: - # - # ssh_options = Net::SSH.configuration_for( - # server.host, ssh_options.fetch(:config, true) - # ).merge(ssh_options) - # # Once we've loaded the config, we don't need Net::SSH to do it again. - # ssh_options[:config] = false - # - # Net:SSH is supposed to call Net::SSH.configuration_for, but Capistrano is doing it - # in advance and then disabling loading of configs. - # - # The result of this is the following: if you have IdentityFile in your ~/.ssh/config - # file, then the above code will transform the ssh_options by reading ~/.ssh/config - # and adding the keys specified via IdentityFile to ssh_options... - # AND IT WILL SET :keys_only TO TRUE. - # - # The problem is that :keys_only will disable Net:SSH's ability to use ssh-agent. - # With :keys_only set to true, it will not consult the ssh-agent at all. - # - # So nice of capistrano to parse ~/.ssh/config for us, but then add flags to the - # ssh_options that prevent's these options from being useful. - # - # The current hackaround is to force :keys_only to be false. This allows the config - # to be read and also allows ssh-agent to still be used. - # - def ssh_options - { - :keys_only => false, # Don't you dare change this. - :global_known_hosts_file => path(:known_hosts), - :user_known_hosts_file => '/dev/null', - :paranoid => true, - :verbose => net_ssh_log_level - } - end - - def net_ssh_log_level - if DEBUG - case LeapCli.logger.log_level - when 1 then 3 - when 2 then 2 - when 3 then 1 - else 0 - end - else - nil - end - end - - # - # For notes on advanced ways to set server-specific options, see - # http://railsware.com/blog/2011/11/02/advanced-server-definitions-in-capistrano/ - # - # if, in the future, we want to do per-node password options, it would be done like so: - # - # password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"} - # return {:password => password_proc} - # - def node_options(node, ssh_options_override=nil) - { - :ssh_options => { - # :host_key_alias => node.name, << incompatible with ports in known_hosts - :host_name => node.ip_address, - :port => node.ssh.port - }.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override||{}) - } - end - - def new_capistrano - # load once the library files - @capistrano_enabled ||= begin - require 'capistrano' - require 'capistrano/cli' - require 'leap_cli/lib_ext/capistrano_connections' - require 'leap_cli/remote/leap_plugin' - require 'leap_cli/remote/puppet_plugin' - require 'leap_cli/remote/rsync_plugin' - Capistrano.plugin :leap, LeapCli::Remote::LeapPlugin - Capistrano.plugin :puppet, LeapCli::Remote::PuppetPlugin - Capistrano.plugin :rsync, LeapCli::Remote::RsyncPlugin - true - end - - # create capistrano instance - cap = Capistrano::Configuration.new - - # add tasks to capistrano instance - cap.load File.dirname(__FILE__) + '/../remote/tasks.rb' - - return cap - end - - def contingent_ssh_options_for_node(node) - opts = {} - if node.vagrant? - opts[:keys] = [vagrant_ssh_key_file] - opts[:keys_only] = true # only use the keys specified above, and ignore whatever keys the ssh-agent is aware of. - opts[:paranoid] = false # we skip host checking for vagrant nodes, because fingerprint is different for everyone. - if LeapCli.logger.log_level <= 1 - opts[:verbose] = :error # suppress all the warnings about adding host keys to known_hosts, since it is not actually doing that. - end - end - if !node.supported_ssh_host_key_algorithms.empty? - opts[:host_key] = node.supported_ssh_host_key_algorithms - end - return opts - end - -end; end; end \ No newline at end of file diff --git a/lib/leap_cli/util/secret.rb b/lib/leap_cli/util/secret.rb deleted file mode 100644 index 749b959..0000000 --- a/lib/leap_cli/util/secret.rb +++ /dev/null @@ -1,55 +0,0 @@ -# encoding: utf-8 -# -# A simple secret generator -# -# Uses OpenSSL random number generator instead of Ruby's rand function -# -autoload :OpenSSL, 'openssl' - -module LeapCli; module Util - class Secret - CHARS = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a) - "i1loO06G".split(//u) - HEX = (0..9).to_a + ('a'..'f').to_a - - # - # generate a secret with with no ambiguous characters. - # - # +length+ is in chars - # - # Only alphanumerics are allowed, in order to make these passwords work - # for REST url calls and to allow you to easily copy and paste them. - # - def self.generate(length = 16) - seed - OpenSSL::Random.random_bytes(length).bytes.to_a.collect { |byte| - CHARS[ byte % CHARS.length ] - }.join - end - - # - # generates a hex secret, instead of an alphanumeric on. - # - # length is in bits - # - def self.generate_hex(length = 128) - seed - OpenSSL::Random.random_bytes(length/4).bytes.to_a.collect { |byte| - HEX[ byte % HEX.length ] - }.join - end - - private - - def self.seed - @pid ||= 0 - pid = $$ - if @pid != pid - now = Time.now - ary = [now.to_i, now.nsec, @pid, pid] - OpenSSL::Random.seed(ary.to_s) - @pid = pid - end - end - - end -end; end diff --git a/lib/leap_cli/util/x509.rb b/lib/leap_cli/util/x509.rb deleted file mode 100644 index 787fdfa..0000000 --- a/lib/leap_cli/util/x509.rb +++ /dev/null @@ -1,33 +0,0 @@ -autoload :OpenSSL, 'openssl' -autoload :CertificateAuthority, 'certificate_authority' - -require 'digest' -require 'digest/md5' -require 'digest/sha1' - -module LeapCli; module X509 - extend self - - # - # returns a fingerprint of a x509 certificate - # - def fingerprint(digest, cert_file) - if cert_file.is_a? String - cert = OpenSSL::X509::Certificate.new(Util.read_file!(cert_file)) - elsif cert_file.is_a? OpenSSL::X509::Certificate - cert = cert_file - elsif cert_file.is_a? CertificateAuthority::Certificate - cert = cert_file.openssl_body - end - digester = case digest - when "MD5" then Digest::MD5.new - when "SHA1" then Digest::SHA1.new - when "SHA256" then Digest::SHA256.new - when "SHA384" then Digest::SHA384.new - when "SHA512" then Digest::SHA512.new - end - digester.hexdigest(cert.to_der) - end - - -end; end -- cgit v1.2.3 From f9284ff22da6eec685782dbc9aa4a4ded0efec29 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 29 Jun 2016 16:52:42 -0700 Subject: removed unused capistrano file --- lib/leap_cli/lib_ext/capistrano_connections.rb | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 lib/leap_cli/lib_ext/capistrano_connections.rb (limited to 'lib') diff --git a/lib/leap_cli/lib_ext/capistrano_connections.rb b/lib/leap_cli/lib_ext/capistrano_connections.rb deleted file mode 100644 index c46455f..0000000 --- a/lib/leap_cli/lib_ext/capistrano_connections.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Capistrano - class Configuration - module Connections - def failed!(server) - @failure_callback.call(server) if @failure_callback - Thread.current[:failed_sessions] << server - end - - def call_on_failure(&block) - @failure_callback = block - end - end - end -end - - -- cgit v1.2.3 From 2cc2648f55ca394b7bcdfa2a2f5b1a8725ad15cf Mon Sep 17 00:00:00 2001 From: elijah Date: Sun, 3 Jul 2016 23:30:46 -0700 Subject: bugfix: better coloring of log lines that wrap --- lib/leap_cli/log.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 5203c97..9f7d28b 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -211,7 +211,13 @@ module LeapCli if style codes << EFFECTS[style] || EFFECTS[:nothing] end - ["\033[%sm" % codes.join(';'), str, NO_COLOR].join + if str.is_a?(String) + ["\033[%sm" % codes.join(';'), str, NO_COLOR].join + elsif str.is_a?(Array) + str.map { |s| + ["\033[%sm" % codes.join(';'), s, NO_COLOR].join + } + end end private -- cgit v1.2.3 From d1a12ae7aa2660f10046bc7baad688819c61168c Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 4 Jul 2016 10:24:47 -0700 Subject: fix tests --- lib/leap_cli/leapfile.rb | 1 + lib/leap_cli/log.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index 10af224..3dadf66 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -15,6 +15,7 @@ module LeapCli attr_reader :platform_directory_path attr_reader :provider_directory_path attr_reader :environment + attr_reader :valid def initialize end diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 9f7d28b..203d92e 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -37,7 +37,7 @@ module LeapCli # # these are log titles typically associated with files # - FILE_TITLES = [:updated, :created, :removed, :missing, :nochange, :loading] + FILE_TITLES = %w(updated created removed missing nochange loading) # TODO: use these IMPORTANT = 0 -- cgit v1.2.3 From 7571e0ecb1ffea65df4498cf002c78c170905df8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 12 Jul 2016 09:00:24 +0200 Subject: add a way to find out if we are dealing with a git subrepo --- lib/leap_cli/util.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 64b5c63..29658da 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -428,6 +428,15 @@ module LeapCli return $? == 0 end end + + def is_git_subrepo?(dir) + Dir.chdir(dir) do + `ls .gitrepo 2>/dev/null` + return $? == 0 + end + end + + def current_git_branch(dir) Dir.chdir(dir) do -- cgit v1.2.3 From a8efdb865dbea99e619c0353d707da39118e6e28 Mon Sep 17 00:00:00 2001 From: elijah Date: Sat, 9 Jul 2016 02:45:23 -0700 Subject: test: added test of quick start tutorial commands --- lib/leap_cli/bootstrap.rb | 4 +++- lib/leap_cli/core_ext/hash.rb | 19 +++++++++++++++++++ lib/leap_cli/leapfile.rb | 5 ----- lib/leap_cli/log.rb | 2 +- lib/leap_cli/path.rb | 2 +- 5 files changed, 24 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/bootstrap.rb b/lib/leap_cli/bootstrap.rb index f33aa42..75edf5b 100644 --- a/lib/leap_cli/bootstrap.rb +++ b/lib/leap_cli/bootstrap.rb @@ -159,7 +159,9 @@ module LeapCli # Yes, hacky. # def leapfile_optional?(argv) - if argv.include?('--version') + if TEST + return true + elsif argv.include?('--version') return true else without_flags = argv.select {|i| i !~ /^-/} diff --git a/lib/leap_cli/core_ext/hash.rb b/lib/leap_cli/core_ext/hash.rb index 7df33b2..4eb3af3 100644 --- a/lib/leap_cli/core_ext/hash.rb +++ b/lib/leap_cli/core_ext/hash.rb @@ -32,4 +32,23 @@ class Hash replace(deep_merge(other_hash)) end + # + # A recursive symbolize_keys + # + unless Hash.method_defined?(:symbolize_keys) + def symbolize_keys + self.inject({}) {|result, (key, value)| + new_key = case key + when String then key.to_sym + else key + end + new_value = case value + when Hash then symbolize_keys(value) + else value + end + result[new_key] = new_value + result + } + end + end end diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index 3dadf66..0b56b71 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -150,11 +150,6 @@ module LeapCli return search_dir end - # to be overridden - def validate - return true - end - def method_missing(method, *args) if method =~ /=$/ self.instance_variable_set('@' + method.to_s.sub('=',''), args.first) diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 203d92e..01d372c 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -89,7 +89,7 @@ module LeapCli if title title = title.to_s end - unless message && @log_level >= level + if @log_level < level || (title.nil? && message.nil?) return end diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb index 11fc0f1..a78dbd2 100644 --- a/lib/leap_cli/path.rb +++ b/lib/leap_cli/path.rb @@ -3,7 +3,7 @@ require 'fileutils' module LeapCli; module Path def self.platform - @platform + @platform ||= nil end def self.provider_base -- cgit v1.2.3 From 47b3bb60a20674d029950ebd39f6aacf67e81866 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 20 Jul 2016 23:46:48 -0700 Subject: include support for AWS via fog --- lib/leap_cli/commands/common.rb | 2 +- lib/leap_cli/log.rb | 14 ++++++++++++++ lib/leap_cli/util.rb | 13 ++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/commands/common.rb b/lib/leap_cli/commands/common.rb index d49490e..3dab2a0 100644 --- a/lib/leap_cli/commands/common.rb +++ b/lib/leap_cli/commands/common.rb @@ -20,7 +20,7 @@ module LeapCli; module Commands items.each_with_index(&block) say("q. quit") index = ask("number 1-#{items.length}> ") - if index.empty? + if index.nil? || index.empty? next elsif index =~ /q/ bail! diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index 01d372c..fe9e1b7 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -28,6 +28,20 @@ module LeapCli def log_level logger.log_level end + + # + # These probably should have been part of the logger originally, + # but they are made available here for convenience: + # + + def bail!(*args, &block) + Util.bail!(*args, &block) + end + + def assert!(*args, &block) + Util.assert!(*args, &block) + end + end end diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 29658da..45606b7 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -40,14 +40,13 @@ module LeapCli # # exit with error code and with a message that we are bailing out. # - def bail!(*message) - if block_given? - LeapCli.logger.log_level = 3 - yield - elsif message - log 0, *message + def bail!(*message, &block) + LeapCli.logger.log_level = 3 if LeapCli.logger.log_level < 3 + if message.any? + log(0, *message, &block) + else + log(0, :bailing, "out", &block) end - log 0, :bail, "" raise SystemExit.new(exit_status || 1) end -- cgit v1.2.3 From 9411049e703a2974c96b42b3e111574f82d7ca65 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 20 Jul 2016 23:49:10 -0700 Subject: linting --- lib/leap_cli/util.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 45606b7..8826d21 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -427,15 +427,13 @@ module LeapCli return $? == 0 end end - + def is_git_subrepo?(dir) - Dir.chdir(dir) do - `ls .gitrepo 2>/dev/null` - return $? == 0 - end + Dir.chdir(dir) do + `ls .gitrepo 2>/dev/null` + return $? == 0 end - - + end def current_git_branch(dir) Dir.chdir(dir) do -- cgit v1.2.3 From 27294be44071b2b6db35f17a5a9220137e123d9b Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 2 Aug 2016 11:40:18 -0700 Subject: pin dependency to platform 0.9 --- lib/leap_cli/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index 779aa25..bb8bbaf 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -1,7 +1,7 @@ module LeapCli unless defined?(LeapCli::VERSION) VERSION = '1.9' - COMPATIBLE_PLATFORM_VERSION = '0.8'..'0.99' + COMPATIBLE_PLATFORM_VERSION = '0.9'..'0.99' SUMMARY = 'Command line interface to the LEAP platform' DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.' LOAD_PATHS = ['lib', -- cgit v1.2.3 From 0f282588f0deca65e1720b40177c67a42246ef85 Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 15 Aug 2016 20:36:45 -0700 Subject: log short host names --- lib/leap_cli/log.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index fe9e1b7..af2fae7 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -131,6 +131,9 @@ module LeapCli # # set line prefix # + if (host) + host = host.split('.').first + end prefix = prefix_str(host, title) # -- cgit v1.2.3 From cd809a6b69790b48344abfaa294edd8c4d4c7231 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 30 Aug 2016 23:27:39 -0700 Subject: added acme-client gem --- lib/leap_cli/version.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index bb8bbaf..bb8b57c 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -7,7 +7,8 @@ module LeapCli LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib', - 'vendor/base32/lib' + 'vendor/base32/lib', + 'vendor/acme-client/lib' ] end end -- cgit v1.2.3 From 5e266e1f9d22fbb78612a896f47a19be5106aee1 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 1 Sep 2016 13:22:33 -0700 Subject: print friendly message if leap_platform is too old (closes #8423) --- lib/leap_cli/leapfile.rb | 15 +++++++++++++-- lib/leap_cli/util.rb | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index 0b56b71..e526703 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -63,8 +63,19 @@ module LeapCli unless File.exist?(platform_definition) Util.bail! "ERROR: The file `#{platform_file}` does not exist. Please check the value of `@platform_directory_path` in `Leapfile` or `~/.leaprc`." end - require platform_class - require platform_definition + begin + require platform_class + require platform_definition + rescue LoadError + Util.log "The leap_platform at #{platform_directory_path} is not compatible with this version of leap_cli" + Util.log "You can either:" do + Util.log "Upgrade leap_platform to version " + LeapCli::COMPATIBLE_PLATFORM_VERSION.first + Util.log "Or, downgrade leap_cli to version 1.8" + end + Util.bail! + rescue StandardError => exc + Util.bail! exc.to_s + end begin Leap::Platform.validate!(LeapCli::VERSION, LeapCli::COMPATIBLE_PLATFORM_VERSION, self) rescue StandardError => exc diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 8826d21..ae73731 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -45,7 +45,7 @@ module LeapCli if message.any? log(0, *message, &block) else - log(0, :bailing, "out", &block) + log(0, :bailing, "out", :color => :red, :style => :bold, &block) end raise SystemExit.new(exit_status || 1) end -- cgit v1.2.3