From a5b77fa30ed5548978c0907b74ba986cdbf6c11a Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 30 Sep 2014 13:36:26 -0700 Subject: environment pinning: new commands `leap env`, `leap env pin X` and `leap env unpin`. See `leap help env` for more information. --- lib/leap_cli/commands/compile.rb | 7 ++- lib/leap_cli/commands/deploy.rb | 14 +----- lib/leap_cli/commands/env.rb | 53 ++++++++++++++++++++ lib/leap_cli/commands/list.rb | 9 ++-- lib/leap_cli/commands/pre.rb | 7 --- lib/leap_cli/config/manager.rb | 104 ++++++++++++++++++++++++++++----------- lib/leap_cli/config/tag.rb | 7 +++ lib/leap_cli/leapfile.rb | 70 ++++++++++++++++++++++---- lib/leap_cli/version.rb | 2 +- 9 files changed, 206 insertions(+), 67 deletions(-) create mode 100644 lib/leap_cli/commands/env.rb diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb index eaedfbf..13fa9ac 100644 --- a/lib/leap_cli/commands/compile.rb +++ b/lib/leap_cli/commands/compile.rb @@ -9,10 +9,13 @@ module LeapCli c.command :all do |all| all.action do |global_options,options,args| environment = args.first + if !LeapCli.leapfile.environment.nil? && environment != LeapCli.leapfile.environment + bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned." + end if environment && manager.environment_names.include?(environment) - compile_hiera_files(manager.filter(args)) + compile_hiera_files(manager.filter([environment])) else - compile_hiera_files + compile_hiera_files(manager.filter) end end end diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb index 553b2b1..bd1f479 100644 --- a/lib/leap_cli/commands/deploy.rb +++ b/lib/leap_cli/commands/deploy.rb @@ -36,7 +36,7 @@ module LeapCli init_submodules end - nodes = filter_deploy_nodes(args) + nodes = manager.filter!(args) if nodes.size > 1 say "Deploying to these nodes: #{nodes.keys.join(', ')}" if !global[:yes] && !agree("Continue? ") @@ -253,17 +253,5 @@ module LeapCli tags.join(',') end - # - # for safety, we allow production deploys to be turned off in the Leapfile. - # - def filter_deploy_nodes(filter) - nodes = manager.filter!(filter) - if !leapfile.allow_production_deploy - nodes = nodes[:environment => "!production"] - assert! nodes.any?, "Skipping deploy because @allow_production_deploy is disabled." - end - nodes - end - end end diff --git a/lib/leap_cli/commands/env.rb b/lib/leap_cli/commands/env.rb new file mode 100644 index 0000000..d81e82f --- /dev/null +++ b/lib/leap_cli/commands/env.rb @@ -0,0 +1,53 @@ +module LeapCli + module Commands + + desc "Manipulate and query environment information." + long_desc "The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. "+ + "A node in one environment will never interact with a node from another environment. "+ + "Environment pinning works by modifying your ~/.leaprc file and is dependent on the "+ + "absolute file path of your provider directory (pins don't apply if you move the directory)" + command :env do |c| + c.desc "List the available environments. The pinned environment, if any, will be marked with '*'." + c.command :ls do |ls| + ls.action do |global_options, options, args| + envs = ["default"] + manager.environment_names.compact.sort + envs.each do |env| + if env + if LeapCli.leapfile.environment == env + puts "* #{env}" + else + puts " #{env}" + end + end + end + end + end + + c.desc 'Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.' + c.arg_name 'ENVIRONMENT' + c.command :pin do |pin| + pin.action do |global_options,options,args| + environment = args.first + if environment == 'default' || + (environment && manager.environment_names.include?(environment)) + LeapCli.leapfile.set('environment', environment) + log 0, :saved, "Leapfile with environment set to #{environment}." + end + end + end + + c.desc "Unpin the environment. All subsequent commands will apply to all nodes." + c.command :unpin do |unpin| + unpin.action do |global_options, options, args| + LeapCli.leapfile.unset('environment') + log 0, :saved, "Leapfile, removing environment property." + end + end + + c.default_command :ls + end + + protected + + end +end \ No newline at end of file diff --git a/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb index be9163b..b8d7739 100644 --- a/lib/leap_cli/commands/list.rb +++ b/lib/leap_cli/commands/list.rb @@ -30,9 +30,10 @@ module LeapCli; module Commands if args.any? NodeTable.new(manager.filter(args), colors).run else - TagTable.new('SERVICES', manager.services, colors).run - TagTable.new('TAGS', manager.tags, colors).run - NodeTable.new(manager.nodes, colors).run + environment = LeapCli.leapfile.environment || '_all_' + TagTable.new('SERVICES', manager.env(environment).services, colors).run + TagTable.new('TAGS', manager.env(environment).tags, colors).run + NodeTable.new(manager.filter(), colors).run end end end @@ -41,7 +42,6 @@ module LeapCli; module Commands private def self.print_node_properties(nodes, properties) - node_list = manager.nodes properties = properties.split(',') max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max} nodes.each_node do |node| @@ -75,6 +75,7 @@ module LeapCli; module Commands column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2 end tags.each do |tag| + next if @tag_list[tag].node_list.empty? row :color => @colors[1] do column tag column @tag_list[tag].node_list.keys.sort.join(', ') diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb index 835eada..2e5c34e 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -48,13 +48,6 @@ module LeapCli; module Commands bail! { log :missing, "platform directory '#{Path.platform}'" } end - if LeapCli.leapfile.platform_branch && LeapCli::Util.is_git_directory?(Path.platform) - branch = LeapCli::Util.current_git_branch(Path.platform) - if branch != LeapCli.leapfile.platform_branch - bail! "Wrong branch for #{Path.platform}. Was '#{branch}', should be '#{LeapCli.leapfile.platform_branch}'. Edit Leapfile to disable this check." - end - end - # # set log file # diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index 21dafd1..e11d5c6 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -64,9 +64,24 @@ module LeapCli e end - def services; env('default').services; end - def tags; env('default').tags; end - def provider; env('default').provider; end + # + # The default accessors for services, tags, and provider. + # For these defaults, use 'default' environment, or whatever + # environment is pinned. + # + def services + env(default_environment).services + end + def tags + env(default_environment).tags + end + def provider + env(default_environment).provider + end + + def default_environment + LeapCli.leapfile.environment + end ## ## IMPORT EXPORT @@ -90,8 +105,8 @@ module LeapCli @secrets = load_json( Path.named_path(:secrets_config, @provider_dir), Config::Secrets) @common.inherit_from! @base_common - # load provider services, tags, and provider.json, DEFAULT environment - log 3, :loading, 'default environment.........' + # For the default environment, load provider services, tags, and provider.json + log 3, :loading, 'default environment...' env('default') do |e| e.services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag, :no_dots => true) e.tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag, :no_dots => true) @@ -102,17 +117,28 @@ module LeapCli validate_provider(e.provider) end - # load provider services, tags, and provider.json, OTHER environments + # create a special '_all_' environment, used for tracking the union + # of all the environments + env('_all_') do |e| + e.services = Config::ObjectList.new + e.tags = Config::ObjectList.new + e.provider = Config::Provider.new + e.services.inherit_from! env('default').services + e.tags.inherit_from! env('default').tags + e.provider.inherit_from! env('default').provider + end + + # For each defined environment, load provider services, tags, and provider.json. environment_names.each do |ename| next unless ename - log 3, :loading, '%s environment.........' % ename + log 3, :loading, '%s environment...' % ename env(ename) do |e| e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag) e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag) e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider) - e.services.inherit_from! env.services - e.tags.inherit_from! env.tags - e.provider.inherit_from! env.provider + e.services.inherit_from! env('default').services + e.tags.inherit_from! env('default').tags + e.provider.inherit_from! env('default').provider validate_provider(e.provider) end end @@ -123,10 +149,8 @@ module LeapCli @nodes[name] = apply_inheritance(node) end - # remove disabled nodes - unless options[:include_disabled] - remove_disabled_nodes - end + # do some node-list post-processing + cleanup_node_lists(options) # apply control files @nodes.each do |name, node| @@ -209,22 +233,31 @@ module LeapCli # # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR. # + # The environment is pinned, then all filters get an automatic +environment_name + # applied (whatever the name happens to be). + # # 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, options={}) - if filters.empty? + def filter(filters=nil, options={}) + # handle empty filter + if filters.nil? || filters.empty? + node_list = self.nodes + if LeapCli.leapfile.environment + node_list = node_list[:environment => LeapCli.leapfile.environment_filter] + end if options[:local] === false - return nodes[:environment => '!local'] - else - return nodes + node_list = node_list[:environment => '!local'] end + return node_list end + + # handle non-empty filters if filters[0] =~ /^\+/ # don't let the first filter have a + prefix filters[0] = filters[0][1..-1] end - node_list = Config::ObjectList.new filters.each do |filter| if filter =~ /^\+/ @@ -240,6 +273,12 @@ module LeapCli node_list.merge!(nodes_for_name(filter)) end end + + # optionally apply environment pin + if !options[:nopin] && LeapCli.leapfile.environment + node_list = node_list[:environment => environment_filter] + end + return node_list end @@ -414,7 +453,6 @@ module LeapCli raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions else new_node.deep_merge!(service) - self.services[node_service].node_list.add(name, new_node) end end end @@ -432,7 +470,6 @@ module LeapCli raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions else new_node.deep_merge!(tag) - self.tags[node_tag].node_list.add(name, new_node) end end end @@ -446,28 +483,35 @@ module LeapCli apply_inheritance(node, true) end - def remove_disabled_nodes + # + # 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 @nodes.each do |name, node| - unless node.enabled - log 2, :skipping, "disabled node #{name}." - @nodes.delete(name) - @disabled_nodes[name] = node + if node.enabled || options[:include_disabled] if node['services'] node['services'].to_a.each do |node_service| - self.services[node_service].node_list.delete(node.name) + 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| - self.tags[node_tag].node_list.delete(node.name) + 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}." + @nodes.delete(name) + @disabled_nodes[name] = node end end end - # # returns a set of nodes corresponding to a single name, where name could be a node name, service name, or tag name. # diff --git a/lib/leap_cli/config/tag.rb b/lib/leap_cli/config/tag.rb index e5e719d..31f4f76 100644 --- a/lib/leap_cli/config/tag.rb +++ b/lib/leap_cli/config/tag.rb @@ -13,6 +13,13 @@ module LeapCli; module Config super(manager) @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 bdf2c37..8895f4d 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -16,13 +16,28 @@ module LeapCli attr_accessor :leap_version attr_accessor :log attr_accessor :vagrant_network - attr_accessor :platform_branch - attr_accessor :allow_production_deploy + attr_accessor :environment def initialize @vagrant_network = '10.5.5.0/24' end + # + # The way the Leapfile handles pinning of environment (self.environment) is a little tricky. + # If self.environment is nil, then there is no pin. If self.environment is 'default', then + # there is a pin to the default environment. The problem is that an environment of nil + # is used to indicate the default environment in node properties. + # + # This method returns the environment tag as needed when filtering nodes. + # + def environment_filter + if self.environment == 'default' + nil + else + self.environment + end + end + def load(search_directory=nil) directory = File.expand_path(find_in_directory_tree('Leapfile', search_directory)) if directory == '/' @@ -33,7 +48,7 @@ module LeapCli # @provider_directory_path = directory read_settings(directory + '/Leapfile') - read_settings(ENV['HOME'] + '/.leaprc') + read_settings(leaprc_path) @platform_directory_path = File.expand_path(@platform_directory_path || '../leap_platform', @provider_directory_path) # @@ -51,20 +66,55 @@ module LeapCli "You need platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}." end - # - # set defaults - # - if @allow_production_deploy.nil? - # by default, only allow production deploys from 'master' or if not a git repo - @allow_production_deploy = !LeapCli::Util.is_git_directory?(@provider_directory_path) || - LeapCli::Util.current_git_branch(@provider_directory_path) == 'master' + 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 return true end end + def set(property, value) + edit_leaprc(property, value) + end + + def unset(property) + edit_leaprc(property) + end + private + # + # adds or removes a line to .leaprc for this particular provider directory. + # if value is nil, the line is removed. if not nil, it is added or replaced. + # + def edit_leaprc(property, value=nil) + file_path = leaprc_path + lines = [] + if File.exists?(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 + lines << line + end + end + end + unless value.nil? + lines << "self.#{property} = #{value.inspect} if @provider_directory_path == '#{@provider_directory_path}'\n" + end + File.open(file_path, 'w') do |f| + f.write(lines.join) + end + rescue Errno::EACCES, IOError => exc + Util::bail! :error, "trying to save ~/.leaprc (#{exc})." + end + + def leaprc_path + File.join(ENV['HOME'], '.leaprc') + end + def read_settings(file) if File.exists? file Util::log 2, :read, file diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index f8a725b..019e267 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.5.8' + VERSION = '1.5.9' COMPATIBLE_PLATFORM_VERSION = '0.5.3'..'1.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