percolate 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/percolate.rb +18 -0
- data/lib/percolate/adapter/base_adapter.rb +89 -0
- data/lib/percolate/adapter/chef_data_bag_adapter.rb +84 -0
- data/lib/percolate/adapter/fixture_adapter.rb +48 -0
- data/lib/percolate/facet/base_facet.rb +40 -0
- data/lib/percolate/facet/fixture_facet.rb +50 -0
- data/lib/percolate/facet/hostname_facet.rb +56 -0
- data/lib/percolate/facet/tag_facet.rb +213 -0
- data/lib/percolate/percolator.rb +124 -0
- data/lib/percolate/util.rb +48 -0
- data/lib/percolate/version.rb +34 -0
- data/spec/percolate_spec.rb +28 -0
- data/spec/spec_helper.rb +20 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2b8382f1bf97ccb41b8ef91a2072f5693d7c5b94ef9c5dee541b788b622683d3
|
4
|
+
data.tar.gz: 13845c16938b31f54a1e9e3e28b01bed59f7817342743e4b00a7594c2dd85a32
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2284c4ca4102771ec8664ebfa0c980bf416df8425274418fb1d1af8ffe34293d50de1640da86def3ce954037118bad5038018c4916042faf77b4bba3b8bc4275
|
7
|
+
data.tar.gz: 807c807e84365ffdb38f53fe1dd4569e2f03a1a4017be2312e760bb6eb24ba6e6a1d763ba3333aab758574ff75a4fd89969e3485259dfeff0fdd3d32509bee9d
|
data/lib/percolate.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "percolate/percolator"
|
18
|
+
require "percolate/version"
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "active_support/inflector"
|
18
|
+
|
19
|
+
module Percolate
|
20
|
+
module Adapter
|
21
|
+
# A base class to build off of.
|
22
|
+
class BaseAdapter
|
23
|
+
# The constructor.
|
24
|
+
#
|
25
|
+
# @param data_source [Object] the data source.
|
26
|
+
def initialize(data_source = nil)
|
27
|
+
@data_source = data_source
|
28
|
+
end
|
29
|
+
|
30
|
+
# Loads entities in an adapter-specific way.
|
31
|
+
#
|
32
|
+
# @return [Hash] the loaded entities.
|
33
|
+
def load_entities
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Loads a facet.
|
38
|
+
#
|
39
|
+
# @param context [String] the lookup context.
|
40
|
+
# @param facet_name [Symbol] the facet name.
|
41
|
+
#
|
42
|
+
# @return [Object] the loaded facet.
|
43
|
+
def load_facet(context, facet_name)
|
44
|
+
create_facet(facet_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates a facet from the given name.
|
48
|
+
#
|
49
|
+
# @param facet_name [Symbol] the facet name.
|
50
|
+
#
|
51
|
+
# @return [Object] the facet.
|
52
|
+
def create_facet(facet_name)
|
53
|
+
const_str = ActiveSupport::Inflector.camelize(facet_name) + "Facet"
|
54
|
+
|
55
|
+
begin
|
56
|
+
require "percolate/facet/#{facet_name}_facet"
|
57
|
+
rescue LoadError
|
58
|
+
# Do nothing. Give the benefit of the doubt if the file doesn't exist.
|
59
|
+
end if !Facet.const_defined?(const_str)
|
60
|
+
|
61
|
+
Facet.const_get(const_str).new
|
62
|
+
end
|
63
|
+
|
64
|
+
# Configures a facet according to the given attribute hash.
|
65
|
+
#
|
66
|
+
# @param facet [Object] the facet.
|
67
|
+
# @param attr_hash [Hash] the attribute hash.
|
68
|
+
#
|
69
|
+
# @return [Object] the facet.
|
70
|
+
def configure_facet(facet, attr_hash)
|
71
|
+
attr_hash.each_pair do |attr, value|
|
72
|
+
facet.send((attr + "=").to_sym, value)
|
73
|
+
end
|
74
|
+
|
75
|
+
facet
|
76
|
+
end
|
77
|
+
|
78
|
+
# If the given method isn't found, check for a setter of the same name.
|
79
|
+
def method_missing(sym, *args, &block)
|
80
|
+
if sym[-1] != "="
|
81
|
+
sym_set = (sym.to_s + "=").to_sym
|
82
|
+
return send(sym_set, *args, &block) if respond_to?(sym_set)
|
83
|
+
end
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "active_support/inflector"
|
18
|
+
|
19
|
+
require "percolate/adapter/base_adapter"
|
20
|
+
require "percolate/util"
|
21
|
+
|
22
|
+
module Percolate
|
23
|
+
module Adapter
|
24
|
+
# An adapter for loading from Chef data bags.
|
25
|
+
class ChefDataBagAdapter < BaseAdapter
|
26
|
+
attr_writer :entities_data_bag
|
27
|
+
|
28
|
+
def initialize(data_source)
|
29
|
+
super
|
30
|
+
|
31
|
+
@entities_data_bag = "entities"
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_entities
|
35
|
+
begin
|
36
|
+
@data_source.data_bag(@entities_data_bag).reduce({}) do |current, item_name|
|
37
|
+
Percolate::Util.merge_attributes(
|
38
|
+
current,
|
39
|
+
@data_source.data_bag_item(@entities_data_bag, item_name).raw_data["entities"] || {}
|
40
|
+
)
|
41
|
+
end
|
42
|
+
rescue Net::HTTPServerException => e
|
43
|
+
# Reraise the exception if the status code isn't 404 Not Found.
|
44
|
+
if e.response.code != "404"
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_facet(context, name)
|
53
|
+
name = name.to_s
|
54
|
+
|
55
|
+
facets = begin
|
56
|
+
@data_source.data_bag(context).map do |item_name|
|
57
|
+
facets_hash = @data_source.data_bag_item(context, item_name).raw_data["facets"]
|
58
|
+
|
59
|
+
facet_hash = facets_hash[name] || {}
|
60
|
+
facet_type = facet_hash.fetch("type", name)
|
61
|
+
facet_attrs = facet_hash.fetch("attrs", {})
|
62
|
+
|
63
|
+
configure_facet(create_facet(facet_type), facet_attrs)
|
64
|
+
end
|
65
|
+
rescue Net::HTTPServerException => e
|
66
|
+
# Reraise the exception if the status code isn't 404 Not Found.
|
67
|
+
if e.response.code != "404"
|
68
|
+
raise
|
69
|
+
end
|
70
|
+
|
71
|
+
[]
|
72
|
+
end
|
73
|
+
|
74
|
+
if facets.size > 0
|
75
|
+
facets[1...facets.size].reduce(facets[0]) do |current, other|
|
76
|
+
current.merge(other)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "percolate/adapter/base_adapter"
|
18
|
+
|
19
|
+
module Percolate
|
20
|
+
module Adapter
|
21
|
+
# An adapter for exposing a fixed attribute `Hash`.
|
22
|
+
class FixtureAdapter < BaseAdapter
|
23
|
+
def initialize(data_source)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_entities
|
28
|
+
@data_source["entities"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_facet(context, name)
|
32
|
+
name = name.to_s
|
33
|
+
|
34
|
+
facets_hash = @data_source["contexts"][context]["facets"]
|
35
|
+
|
36
|
+
if facets_hash.include?(name)
|
37
|
+
facet_hash = facets_hash[name]
|
38
|
+
facet_type = facet_hash.fetch("type", name)
|
39
|
+
facet_attrs = facet_hash.fetch("attrs", {})
|
40
|
+
|
41
|
+
configure_facet(create_facet(facet_type), facet_attrs)
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
module Percolate
|
18
|
+
module Facet
|
19
|
+
# A base class to build off of.
|
20
|
+
class BaseFacet
|
21
|
+
# Finds entity information from the given input.
|
22
|
+
#
|
23
|
+
# @param args [Array] the argument splat.
|
24
|
+
#
|
25
|
+
# @return [String] the entity name.
|
26
|
+
def find(*args)
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Merges the given facet with this one.
|
31
|
+
#
|
32
|
+
# @param other [BaseFacet] the other facet.
|
33
|
+
#
|
34
|
+
# @return [BaseFacet] this facet.
|
35
|
+
def merge(other)
|
36
|
+
BaseFacet.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "percolate/facet/base_facet"
|
18
|
+
|
19
|
+
module Percolate
|
20
|
+
module Facet
|
21
|
+
# A facet for looking up entities based on a fixed attribute `Hash`.
|
22
|
+
class FixtureFacet < BaseFacet
|
23
|
+
attr_accessor :fixtures
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@fixtures = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Gets the fixtures.
|
30
|
+
#
|
31
|
+
# @return [Hash] the fixtures.
|
32
|
+
def fixtures
|
33
|
+
@fixtures
|
34
|
+
end
|
35
|
+
|
36
|
+
def find(key)
|
37
|
+
@fixtures[key]
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge(other)
|
41
|
+
raise ArgumentError, "Please provide another #{self.class}" if !other.is_a?(FixtureFacet)
|
42
|
+
|
43
|
+
merged = FixtureFacet.new
|
44
|
+
merged.fixtures = @fixtures.merge(other.fixtures)
|
45
|
+
|
46
|
+
merged
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "percolate/facet/base_facet"
|
18
|
+
|
19
|
+
module Percolate
|
20
|
+
module Facet
|
21
|
+
# A facet for looking up entities based on hostname.
|
22
|
+
class HostnameFacet < BaseFacet
|
23
|
+
attr_accessor :hostnames, :domains, :organizations
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@hostnames = {}
|
27
|
+
@domains = {}
|
28
|
+
@organizations = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def find(hostname)
|
32
|
+
return @hostnames[hostname] if @hostnames.include?(hostname)
|
33
|
+
|
34
|
+
comps = hostname.split(".", -1)
|
35
|
+
domain = comps[1...3].join(".")
|
36
|
+
|
37
|
+
return @domains[domain] if @domains.include?(domain)
|
38
|
+
|
39
|
+
organization = comps[1]
|
40
|
+
|
41
|
+
@organizations.fetch(organization, organization)
|
42
|
+
end
|
43
|
+
|
44
|
+
def merge(other)
|
45
|
+
raise ArgumentError, "Please provide another #{self.class}" if !other.is_a?(HostnameFacet)
|
46
|
+
|
47
|
+
merged = HostnameFacet.new
|
48
|
+
merged.hostnames = @hostnames.merge(other.hostnames)
|
49
|
+
merged.domains = @domains.merge(other.domains)
|
50
|
+
merged.organizations = @organizations.merge(other.organizations)
|
51
|
+
|
52
|
+
merged
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "set"
|
18
|
+
|
19
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0.0")
|
20
|
+
require "backports/2.0.0/array"
|
21
|
+
end
|
22
|
+
|
23
|
+
require "percolate/facet/base_facet"
|
24
|
+
|
25
|
+
module Percolate
|
26
|
+
module Facet
|
27
|
+
# A facet for looking up entities based on collections of tags.
|
28
|
+
class TagFacet < BaseFacet
|
29
|
+
attr_reader :poset_root
|
30
|
+
|
31
|
+
def initialize(poset_root = nil)
|
32
|
+
@poset_root = poset_root
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sets the tag lookup rules.
|
36
|
+
#
|
37
|
+
# @param rules_hash [Hash] the lookup rules.
|
38
|
+
def rules=(rules_hash)
|
39
|
+
@poset_root = TagPoset.new
|
40
|
+
|
41
|
+
rules_hash.each do |rule_hash|
|
42
|
+
@poset_root.insert(rule_hash["tags"], rule_hash["value"])
|
43
|
+
end
|
44
|
+
|
45
|
+
rules_hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def find(tags)
|
49
|
+
@poset_root.matches(tags).sort
|
50
|
+
end
|
51
|
+
|
52
|
+
def merge(other)
|
53
|
+
raise ArgumentError, "Please provide another #{self.class}" if !other.is_a?(TagFacet)
|
54
|
+
|
55
|
+
TagFacet.new(@poset_root.merge(other.poset_root))
|
56
|
+
end
|
57
|
+
|
58
|
+
# A data structure representing the partial order induced on collections of tags.
|
59
|
+
class TagPoset
|
60
|
+
attr_reader :tags, :value, :supersets
|
61
|
+
|
62
|
+
# The constructor.
|
63
|
+
#
|
64
|
+
# @param tags [Array] the tags.
|
65
|
+
# @param value [Object] the associated value.
|
66
|
+
# @param supersets [Array] the {TagPoset}s that contain tag supersets.
|
67
|
+
def initialize(tags = [], value = nil, supersets = [])
|
68
|
+
@tags = tags
|
69
|
+
@value = value
|
70
|
+
@supersets = supersets
|
71
|
+
end
|
72
|
+
|
73
|
+
# Inserts the given collection of tags and its associated value into the partial order.
|
74
|
+
#
|
75
|
+
# @param tags [Array] the tags.
|
76
|
+
# @param value [Object] the associated value.
|
77
|
+
# @param visited [Set] the visited {TagPoset}s so far, memoized by their tags.
|
78
|
+
def insert(tags, value, visited = Set.new)
|
79
|
+
tags = tags.sort
|
80
|
+
|
81
|
+
# We got an exact match. Override the value and return.
|
82
|
+
if @tags == tags
|
83
|
+
@value = value
|
84
|
+
|
85
|
+
return value
|
86
|
+
end
|
87
|
+
|
88
|
+
n_supersets = 0
|
89
|
+
subset_indices = []
|
90
|
+
|
91
|
+
@supersets.each_with_index do |superset, i|
|
92
|
+
if superset.tags - tags == []
|
93
|
+
superset.insert(tags, value, visited) if !visited.add?(superset.tags).nil?
|
94
|
+
n_supersets = n_supersets + 1
|
95
|
+
elsif tags - superset.tags == []
|
96
|
+
subset_indices.push(i)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# We visited a superset; there's no more work to be done in this frame.
|
101
|
+
if n_supersets > 0
|
102
|
+
return value
|
103
|
+
end
|
104
|
+
|
105
|
+
if !subset_indices.empty?
|
106
|
+
# The tags are a subset of at least one superset; insert them in between this poset and the superset(s).
|
107
|
+
tp = TagPoset.new(tags, value, subset_indices.map { |i| @supersets[i] })
|
108
|
+
|
109
|
+
subset_indices.each { |i| @supersets[i] = nil }
|
110
|
+
@supersets = @supersets.select { |item| !item.nil? }.to_a
|
111
|
+
|
112
|
+
else
|
113
|
+
# The tags are not a subset of any superset; insert them separately.
|
114
|
+
tp = TagPoset.new(tags, value, transitives(tags))
|
115
|
+
end
|
116
|
+
|
117
|
+
# Insert the new poset with binary search for stability.
|
118
|
+
insertion_index = (0...@supersets.size).bsearch { |i| (@supersets[i].tags <=> tags) >= 0 } || @supersets.size
|
119
|
+
@supersets.insert(insertion_index, tp)
|
120
|
+
|
121
|
+
value
|
122
|
+
end
|
123
|
+
|
124
|
+
# Finds transitive supersets that contain the given collection of tags.
|
125
|
+
#
|
126
|
+
# @param remainder [Array] the remaining tags to look for.
|
127
|
+
# @param visited [Set] the visited {TagPoset}s so far, memoized by their tags.
|
128
|
+
#
|
129
|
+
# @return [Array] the transitive supersets.
|
130
|
+
def transitives(remainder, visited = Set.new)
|
131
|
+
transitives = []
|
132
|
+
|
133
|
+
@supersets.each do |superset|
|
134
|
+
r_remainder = remainder - superset.tags
|
135
|
+
|
136
|
+
if r_remainder != []
|
137
|
+
r_transitives = superset.transitives(r_remainder, visited)
|
138
|
+
|
139
|
+
r_transitives = r_transitives.select do |r_superset|
|
140
|
+
transitives.select do |superset|
|
141
|
+
superset.tags - r_superset.tags == []
|
142
|
+
end.empty?
|
143
|
+
end.to_a
|
144
|
+
|
145
|
+
transitives = transitives.select do |superset|
|
146
|
+
r_transitives.select do |r_superset|
|
147
|
+
r_superset.tags - superset.tags == []
|
148
|
+
end.empty?
|
149
|
+
end.to_a
|
150
|
+
|
151
|
+
transitives.concat(r_transitives)
|
152
|
+
else
|
153
|
+
transitives.push(superset)
|
154
|
+
end if !visited.add?(superset.tags).nil?
|
155
|
+
end
|
156
|
+
|
157
|
+
transitives
|
158
|
+
end
|
159
|
+
|
160
|
+
# Calculates the best matches for the given collection of tags.
|
161
|
+
#
|
162
|
+
# @param tags [Array] the tags.
|
163
|
+
# @param visited [Set] the visited {TagPoset}s so far, memoized by their tags.
|
164
|
+
#
|
165
|
+
# @return [Array] the values associated with best matches.
|
166
|
+
def matches(tags, visited = Set.new)
|
167
|
+
tags = tags.sort
|
168
|
+
|
169
|
+
matches = []
|
170
|
+
n_supersets = 0
|
171
|
+
|
172
|
+
@supersets.each do |superset|
|
173
|
+
if superset.tags - tags == []
|
174
|
+
matches.concat(superset.matches(tags, visited)) if !visited.add?(superset.tags).nil?
|
175
|
+
n_supersets = n_supersets + 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# We didn't visit a superset; push on the associated value as a best match.
|
180
|
+
if n_supersets == 0 && !@value.nil?
|
181
|
+
matches.push(@value)
|
182
|
+
end
|
183
|
+
|
184
|
+
matches
|
185
|
+
end
|
186
|
+
|
187
|
+
# Merges the given {TagPoset} with this one.
|
188
|
+
#
|
189
|
+
# @param other [TagPoset] the other {TagPoset}.
|
190
|
+
#
|
191
|
+
# @return [TagPoset] the merged result.
|
192
|
+
def merge(other)
|
193
|
+
TagPoset.new.merge!(self).merge!(other)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Mutatively merges the given {TagPoset} with this one.
|
197
|
+
#
|
198
|
+
# @param other [TagPoset] the other {TagPoset}.
|
199
|
+
#
|
200
|
+
# @return [TagPoset] `self`.
|
201
|
+
def merge!(other, visited = Set.new)
|
202
|
+
insert(other.tags, other.value)
|
203
|
+
|
204
|
+
other.supersets.each do |superset|
|
205
|
+
merge!(superset, visited) if !visited.add?(superset.tags).nil?
|
206
|
+
end
|
207
|
+
|
208
|
+
self
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "active_support/inflector"
|
18
|
+
|
19
|
+
module Percolate
|
20
|
+
# Creates a {Percolator} from the given adapter name and data source.
|
21
|
+
#
|
22
|
+
# @param adapter_name [Symbol] the adapter name.
|
23
|
+
# @param data_source [Object] the data source.
|
24
|
+
# @param block [Proc] the configuration block.
|
25
|
+
#
|
26
|
+
# @return [Percolator] the {Percolator}.
|
27
|
+
def self.create(adapter_name, data_source = nil, &block)
|
28
|
+
const_str = ActiveSupport::Inflector.camelize(adapter_name) + "Adapter"
|
29
|
+
|
30
|
+
begin
|
31
|
+
require "percolate/adapter/#{adapter_name}_adapter"
|
32
|
+
rescue LoadError
|
33
|
+
# Do nothing. Give the benefit of the doubt if the file doesn't exist.
|
34
|
+
end if !Adapter.const_defined?(const_str)
|
35
|
+
|
36
|
+
adapter = Adapter.const_get(const_str).new(data_source)
|
37
|
+
percolator = Percolator.new(adapter)
|
38
|
+
percolator.load(&block)
|
39
|
+
percolator
|
40
|
+
end
|
41
|
+
|
42
|
+
# The class that percolates information from entities through facets to the user.
|
43
|
+
class Percolator
|
44
|
+
attr_reader :adapter, :entities
|
45
|
+
|
46
|
+
# The constructor.
|
47
|
+
#
|
48
|
+
# @param adapter [Object] the adapter to a data source.
|
49
|
+
def initialize(adapter)
|
50
|
+
@adapter = adapter
|
51
|
+
@facet_cache = {}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Configures and loads the underlying adapter's entities.
|
55
|
+
#
|
56
|
+
# @param block [Proc] the configuration block.
|
57
|
+
def load(&block)
|
58
|
+
@adapter.instance_eval(&block) if !block.nil?
|
59
|
+
@entities = @adapter.load_entities
|
60
|
+
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# Finds an entity or entities.
|
65
|
+
#
|
66
|
+
# @param context [String] the lookup context.
|
67
|
+
# @param facet_name [Symbol] the facet name.
|
68
|
+
# @param args [Array] the argument splat passed to the facet.
|
69
|
+
#
|
70
|
+
# @return [Object] the retrieved entity or entities.
|
71
|
+
def find(context, facet_name, *args)
|
72
|
+
facet = find_facet(context, facet_name)
|
73
|
+
|
74
|
+
if !facet
|
75
|
+
return nil
|
76
|
+
end
|
77
|
+
|
78
|
+
case result = facet.find(*args)
|
79
|
+
when Array
|
80
|
+
result.map { |item| @entities[item] }
|
81
|
+
when String
|
82
|
+
@entities[result]
|
83
|
+
when NilClass
|
84
|
+
nil
|
85
|
+
else
|
86
|
+
raise "Bad facet return type #{result.class.name.dump}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Finds a facet.
|
91
|
+
#
|
92
|
+
# @param context [String] the lookup context.
|
93
|
+
# @param facet_name [Symbol] the facet name.
|
94
|
+
#
|
95
|
+
# @return [Object] the facet.
|
96
|
+
def find_facet(context, facet_name)
|
97
|
+
cache_key = [context, facet_name]
|
98
|
+
|
99
|
+
if @facet_cache.include?(cache_key)
|
100
|
+
facet = @facet_cache[cache_key]
|
101
|
+
else
|
102
|
+
begin
|
103
|
+
require "percolate/facet/#{facet_name}_facet"
|
104
|
+
rescue LoadError
|
105
|
+
# Do nothing. Give the benefit of the doubt if the file doesn't exist.
|
106
|
+
end
|
107
|
+
|
108
|
+
if facet = @adapter.load_facet(context, facet_name)
|
109
|
+
@facet_cache[cache_key] = facet
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
facet
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# The namespace for adapters.
|
118
|
+
module Adapter
|
119
|
+
end
|
120
|
+
|
121
|
+
# The namespace for facets.
|
122
|
+
module Facet
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "active_support/inflector"
|
18
|
+
|
19
|
+
module Percolate
|
20
|
+
# Contains utility methods.
|
21
|
+
module Util
|
22
|
+
# Merges the given attributes, which can take the form of nested `Hash`es, `Array`s, and `String`s. If there is a
|
23
|
+
# conflict, the right hand side wins.
|
24
|
+
def self.merge_attributes(lhs, rhs)
|
25
|
+
if lhs.is_a?(Hash) && rhs.is_a?(Hash)
|
26
|
+
res = {}
|
27
|
+
|
28
|
+
lhs.each_pair do |key, value|
|
29
|
+
if rhs.include?(key)
|
30
|
+
res[key] = merge_attributes(lhs[key], rhs[key])
|
31
|
+
else
|
32
|
+
res[key] = value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
rhs.each_pair do |key, value|
|
37
|
+
res[key] = value if !res.include?(key)
|
38
|
+
end
|
39
|
+
elsif lhs.is_a?(Array) && rhs.is_a?(Array)
|
40
|
+
res = lhs + rhs
|
41
|
+
else
|
42
|
+
res = rhs
|
43
|
+
end
|
44
|
+
|
45
|
+
res
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
module Percolate
|
18
|
+
# A module containing the gem version information.
|
19
|
+
module Version
|
20
|
+
# The major version.
|
21
|
+
MAJOR = 0
|
22
|
+
|
23
|
+
# The minor version.
|
24
|
+
MINOR = 9
|
25
|
+
|
26
|
+
# The patch version.
|
27
|
+
PATCH = 4
|
28
|
+
|
29
|
+
# Gets the String representation of the gem version.
|
30
|
+
def self.to_s
|
31
|
+
"#{MAJOR}.#{MINOR}.#{PATCH}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "percolate/adapter/base_adapter"
|
18
|
+
|
19
|
+
require "spec_helper"
|
20
|
+
|
21
|
+
describe Percolate do
|
22
|
+
it "finds adapters by the naming convention" do
|
23
|
+
percolator = Percolate.create(:base)
|
24
|
+
|
25
|
+
expect(percolator).to be_an_instance_of Percolate::Percolator
|
26
|
+
expect(percolator.adapter).to be_an_instance_of Percolate::Adapter::BaseAdapter
|
27
|
+
end
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2014 Roy Liu
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require "chefspec"
|
18
|
+
require "chefspec/librarian"
|
19
|
+
|
20
|
+
require "percolate"
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: percolate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roy Liu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.2
|
27
|
+
description: Percolate is a library for organizing and distributing configuration
|
28
|
+
settings. It contains adapters for frameworks like Chef, with which the user can
|
29
|
+
take full advantage of a declarative syntax for Chef data bags and avoid the antipattern
|
30
|
+
of representing initialization state with node attributes.
|
31
|
+
email:
|
32
|
+
- carsomyr@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/percolate.rb
|
38
|
+
- lib/percolate/adapter/base_adapter.rb
|
39
|
+
- lib/percolate/adapter/chef_data_bag_adapter.rb
|
40
|
+
- lib/percolate/adapter/fixture_adapter.rb
|
41
|
+
- lib/percolate/facet/base_facet.rb
|
42
|
+
- lib/percolate/facet/fixture_facet.rb
|
43
|
+
- lib/percolate/facet/hostname_facet.rb
|
44
|
+
- lib/percolate/facet/tag_facet.rb
|
45
|
+
- lib/percolate/percolator.rb
|
46
|
+
- lib/percolate/util.rb
|
47
|
+
- lib/percolate/version.rb
|
48
|
+
- spec/percolate_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
homepage: https://github.com/carsomyr/percolate
|
51
|
+
licenses:
|
52
|
+
- Apache-2.0
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.7.3
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: Percolate is a library for organizing and distributing configuration settings
|
74
|
+
test_files:
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
- spec/percolate_spec.rb
|