From 5780f5dcc024d4f140fe8f6e8dc3f7c4e905a8ec Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 29 Jun 2016 16:55:06 -0700 Subject: leap cli: move everything we can from leap_cli to leap_platform --- lib/leap_cli/config/object_list.rb | 209 +++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 lib/leap_cli/config/object_list.rb (limited to 'lib/leap_cli/config/object_list.rb') diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb new file mode 100644 index 00000000..f9299a61 --- /dev/null +++ b/lib/leap_cli/config/object_list.rb @@ -0,0 +1,209 @@ +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 -- cgit v1.2.3