diff options
Diffstat (limited to 'lib/leap_cli/config')
| -rw-r--r-- | lib/leap_cli/config/filter.rb | 146 | ||||
| -rw-r--r-- | lib/leap_cli/config/manager.rb | 61 | ||||
| -rw-r--r-- | lib/leap_cli/config/object_list.rb | 90 | 
3 files changed, 194 insertions, 103 deletions
| 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 | 
