summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2014-10-05 23:19:24 -0700
committerelijah <elijah@riseup.net>2014-10-05 23:19:24 -0700
commitce140bdde2c5c58eab7eba8e8fc9d6e8fe12755f (patch)
treeff68bdc3d3525ca1965c98eedeb686b80e78b6d2
parent5ee57cffa04dafd9174a47d7f9f29e5ed16d6927 (diff)
more robust env pinning, fixes several edge case bugs.
-rw-r--r--lib/leap_cli.rb1
-rw-r--r--lib/leap_cli/config/filter.rb146
-rw-r--r--lib/leap_cli/config/manager.rb61
-rw-r--r--lib/leap_cli/config/object_list.rb90
4 files changed, 195 insertions, 103 deletions
diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb
index fbfde59..be16cf4 100644
--- a/lib/leap_cli.rb
+++ b/lib/leap_cli.rb
@@ -34,6 +34,7 @@ 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/manager'
require 'leap_cli/markdown_document_listener'
diff --git a/lib/leap_cli/config/filter.rb b/lib/leap_cli/config/filter.rb
new file mode 100644
index 0000000..ce218da
--- /dev/null
+++ b/lib/leap_cli/config/filter.rb
@@ -0,0 +1,146 @@
+#
+# 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.
+#
+
+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
+ #
+ 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|
+ 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
+
+ # 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
+ 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]
+ @manager.env('_all_').services[name].node_list
+ elsif @manager.tags[name]
+ @manager.env('_all_').tags[name].node_list
+ 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
+ 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
index 26d45c3..be95831 100644
--- a/lib/leap_cli/config/manager.rb
+++ b/lib/leap_cli/config/manager.rb
@@ -229,57 +229,19 @@ module LeapCli
# 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]
+ # 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.
#
- # The environment is pinned, then all filters get an automatic +environment_name
- # applied (whatever the name happens to be).
+ # 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={})
- # 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
- 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 =~ /^\+/
- 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
-
- # optionally apply environment pin
- if !options[:nopin] && LeapCli.leapfile.environment
- node_list = node_list[:environment => LeapCli.leapfile.environment_filter]
- end
-
- return node_list
+ Filter.new(filters, options, self).nodes()
end
#
@@ -512,21 +474,6 @@ module LeapCli
end
end
- #
- # returns a set of nodes corresponding to a single name, where name could be a node name, service name, or tag name.
- #
- def nodes_for_name(name)
- if node = self.nodes[name]
- Config::ObjectList.new(node)
- elsif service = self.services[name]
- service.node_list
- elsif tag = self.tags[name]
- tag.node_list
- else
- {}
- end
- end
-
def validate_provider(provider)
# nothing yet.
end
diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb
index cd69d9b..33ca4dd 100644
--- a/lib/leap_cli/config/object_list.rb
+++ b/lib/leap_cli/config/object_list.rb
@@ -20,8 +20,6 @@ module LeapCli
# 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.
#
- # If the key is an array, it is treated as an array of node names
- #
# Examples:
#
# nodes['vpn1']
@@ -30,47 +28,22 @@ module LeapCli
# nodes[:public_dns => true]
# all nodes with public dns
#
- # nodes[:services => 'openvpn', :services => 'tor']
+ # 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
- results = Config::ObjectList.new
- key.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
- elsif key.is_a? Array
- key.inject(Config::ObjectList.new) do |list, node_name|
- list[node_name] = super(node_name.to_s)
- list
- end
+ if key.is_a?(Hash) || key.is_a?(Array)
+ filter(key)
else
super key.to_s
end
@@ -88,15 +61,40 @@ module LeapCli
end
end
- # def <<(object)
- # if object.is_a? Config::ObjectList
- # self.merge!(object)
- # elsif object['name']
- # self[object['name']] = object
- # else
- # raise ArgumentError.new('argument must be a Config::Object or a Config::ObjectList')
- # 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