percolate 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|