automate-it 0.9.0

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.
Files changed (137) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hgignore +10 -0
  4. data/.loadpath +5 -0
  5. data/.project +17 -0
  6. data/CHANGES.txt +314 -0
  7. data/Hoe.rake +40 -0
  8. data/Manifest.txt +164 -0
  9. data/README.txt +40 -0
  10. data/Rakefile +256 -0
  11. data/TESTING.txt +57 -0
  12. data/TODO.txt +50 -0
  13. data/TUTORIAL.txt +391 -0
  14. data/automate-it.gemspec +25 -0
  15. data/bin/ai +3 -0
  16. data/bin/aifield +75 -0
  17. data/bin/aissh +93 -0
  18. data/bin/aitag +134 -0
  19. data/bin/automateit +133 -0
  20. data/docs/friendly_errors.txt +50 -0
  21. data/docs/previews.txt +86 -0
  22. data/examples/basic/Rakefile +26 -0
  23. data/examples/basic/config/automateit_env.rb +16 -0
  24. data/examples/basic/config/fields.yml +3 -0
  25. data/examples/basic/config/tags.yml +7 -0
  26. data/examples/basic/dist/README.txt +9 -0
  27. data/examples/basic/dist/myapp_server.erb +30 -0
  28. data/examples/basic/install.log +15 -0
  29. data/examples/basic/lib/README.txt +10 -0
  30. data/examples/basic/recipes/README.txt +4 -0
  31. data/examples/basic/recipes/install.rb +61 -0
  32. data/examples/basic/recipes/uninstall.rb +6 -0
  33. data/gpl.txt +674 -0
  34. data/helpers/cpan_wrapper.pl +220 -0
  35. data/helpers/which.cmd +7 -0
  36. data/lib/automateit.rb +55 -0
  37. data/lib/automateit/account_manager.rb +114 -0
  38. data/lib/automateit/account_manager/base.rb +138 -0
  39. data/lib/automateit/account_manager/etc.rb +128 -0
  40. data/lib/automateit/account_manager/nscd.rb +33 -0
  41. data/lib/automateit/account_manager/passwd_expect.rb +40 -0
  42. data/lib/automateit/account_manager/passwd_pty.rb +69 -0
  43. data/lib/automateit/account_manager/posix.rb +138 -0
  44. data/lib/automateit/address_manager.rb +88 -0
  45. data/lib/automateit/address_manager/base.rb +171 -0
  46. data/lib/automateit/address_manager/bsd.rb +28 -0
  47. data/lib/automateit/address_manager/freebsd.rb +59 -0
  48. data/lib/automateit/address_manager/linux.rb +42 -0
  49. data/lib/automateit/address_manager/openbsd.rb +66 -0
  50. data/lib/automateit/address_manager/portable.rb +37 -0
  51. data/lib/automateit/address_manager/sunos.rb +34 -0
  52. data/lib/automateit/cli.rb +85 -0
  53. data/lib/automateit/common.rb +65 -0
  54. data/lib/automateit/constants.rb +35 -0
  55. data/lib/automateit/download_manager.rb +48 -0
  56. data/lib/automateit/edit_manager.rb +321 -0
  57. data/lib/automateit/error.rb +10 -0
  58. data/lib/automateit/field_manager.rb +103 -0
  59. data/lib/automateit/interpreter.rb +631 -0
  60. data/lib/automateit/package_manager.rb +257 -0
  61. data/lib/automateit/package_manager/apt.rb +27 -0
  62. data/lib/automateit/package_manager/cpan.rb +101 -0
  63. data/lib/automateit/package_manager/dpkg.rb +54 -0
  64. data/lib/automateit/package_manager/egg.rb +64 -0
  65. data/lib/automateit/package_manager/gem.rb +201 -0
  66. data/lib/automateit/package_manager/pear.rb +95 -0
  67. data/lib/automateit/package_manager/pecl.rb +80 -0
  68. data/lib/automateit/package_manager/portage.rb +69 -0
  69. data/lib/automateit/package_manager/yum.rb +65 -0
  70. data/lib/automateit/platform_manager.rb +49 -0
  71. data/lib/automateit/platform_manager/darwin.rb +30 -0
  72. data/lib/automateit/platform_manager/debian.rb +26 -0
  73. data/lib/automateit/platform_manager/freebsd.rb +29 -0
  74. data/lib/automateit/platform_manager/gentoo.rb +26 -0
  75. data/lib/automateit/platform_manager/lsb.rb +44 -0
  76. data/lib/automateit/platform_manager/openbsd.rb +28 -0
  77. data/lib/automateit/platform_manager/struct.rb +80 -0
  78. data/lib/automateit/platform_manager/sunos.rb +39 -0
  79. data/lib/automateit/platform_manager/uname.rb +29 -0
  80. data/lib/automateit/platform_manager/windows.rb +40 -0
  81. data/lib/automateit/plugin.rb +7 -0
  82. data/lib/automateit/plugin/base.rb +32 -0
  83. data/lib/automateit/plugin/driver.rb +256 -0
  84. data/lib/automateit/plugin/manager.rb +224 -0
  85. data/lib/automateit/project.rb +493 -0
  86. data/lib/automateit/root.rb +17 -0
  87. data/lib/automateit/service_manager.rb +93 -0
  88. data/lib/automateit/service_manager/chkconfig.rb +39 -0
  89. data/lib/automateit/service_manager/rc_update.rb +37 -0
  90. data/lib/automateit/service_manager/sysv.rb +139 -0
  91. data/lib/automateit/service_manager/update_rcd.rb +35 -0
  92. data/lib/automateit/shell_manager.rb +316 -0
  93. data/lib/automateit/shell_manager/base_link.rb +67 -0
  94. data/lib/automateit/shell_manager/link.rb +24 -0
  95. data/lib/automateit/shell_manager/portable.rb +523 -0
  96. data/lib/automateit/shell_manager/symlink.rb +32 -0
  97. data/lib/automateit/shell_manager/which_base.rb +30 -0
  98. data/lib/automateit/shell_manager/which_unix.rb +16 -0
  99. data/lib/automateit/shell_manager/which_windows.rb +20 -0
  100. data/lib/automateit/tag_manager.rb +127 -0
  101. data/lib/automateit/tag_manager/struct.rb +121 -0
  102. data/lib/automateit/tag_manager/tag_parser.rb +93 -0
  103. data/lib/automateit/tag_manager/yaml.rb +29 -0
  104. data/lib/automateit/template_manager.rb +56 -0
  105. data/lib/automateit/template_manager/base.rb +181 -0
  106. data/lib/automateit/template_manager/erb.rb +17 -0
  107. data/lib/ext/metaclass.rb +17 -0
  108. data/lib/ext/object.rb +18 -0
  109. data/lib/ext/shell_escape.rb +7 -0
  110. data/lib/hashcache.rb +22 -0
  111. data/lib/helpful_erb.rb +63 -0
  112. data/lib/inactive_support.rb +53 -0
  113. data/lib/inactive_support/basic_object.rb +6 -0
  114. data/lib/inactive_support/clean_logger.rb +127 -0
  115. data/lib/inactive_support/core_ext/array/extract_options.rb +19 -0
  116. data/lib/inactive_support/core_ext/blank.rb +50 -0
  117. data/lib/inactive_support/core_ext/class/attribute_accessors.rb +48 -0
  118. data/lib/inactive_support/core_ext/class/inheritable_attributes.rb +140 -0
  119. data/lib/inactive_support/core_ext/enumerable.rb +63 -0
  120. data/lib/inactive_support/core_ext/hash/keys.rb +54 -0
  121. data/lib/inactive_support/core_ext/module/aliasing.rb +70 -0
  122. data/lib/inactive_support/core_ext/numeric/time.rb +91 -0
  123. data/lib/inactive_support/core_ext/string/inflections.rb +153 -0
  124. data/lib/inactive_support/core_ext/symbol.rb +14 -0
  125. data/lib/inactive_support/core_ext/time/conversions.rb +96 -0
  126. data/lib/inactive_support/duration.rb +96 -0
  127. data/lib/inactive_support/inflections.rb +53 -0
  128. data/lib/inactive_support/inflector.rb +282 -0
  129. data/lib/nested_error.rb +33 -0
  130. data/lib/nitpick.rb +33 -0
  131. data/lib/queued_logger.rb +68 -0
  132. data/lib/tempster.rb +250 -0
  133. data/misc/index_gem_repository.rb +304 -0
  134. data/misc/setup_egg.rb +12 -0
  135. data/misc/setup_gem_dependencies.sh +6 -0
  136. data/misc/setup_rubygems.sh +21 -0
  137. metadata +279 -0
@@ -0,0 +1,32 @@
1
+ # == ShellManager::Symlink
2
+ #
3
+ # A ShellManager driver providing access to the symbolic link +ln_s+ command
4
+ # found on Unix-like systems.
5
+ class AutomateIt::ShellManager::Symlink < AutomateIt::ShellManager::BaseLink
6
+ depends_on \
7
+ :libraries => %w(pathname),
8
+ :callbacks => [lambda{
9
+ # JRuby can make symlinks but can't read them.
10
+ RUBY_PLATFORM !~ /java|mswin/i and File.respond_to?(:symlink)
11
+ }]
12
+
13
+ def suitability(method, *args) # :nodoc:
14
+ # Level must be higher than Portable
15
+ return available? ? 2 : 0
16
+ end
17
+
18
+ # See ShellManager#provides_symlink?
19
+ def provides_symlink?
20
+ available? ? true : false
21
+ end
22
+
23
+ # See ShellManager#ln_s
24
+ def ln_s(sources, target, opts={})
25
+ _ln(sources, target, {:symbolic => true}.merge(opts))
26
+ end
27
+
28
+ # See ShellManager#ln_sf
29
+ def ln_sf(sources, target, opts={})
30
+ _ln(sources, target, {:symbolic => true, :force => true}.merge(opts))
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # == ShellManager::WhichBase
2
+ #
3
+ # Provides abstract helper methods for other drivers implementing the +which+.
4
+ class AutomateIt::ShellManager::WhichBase < AutomateIt::ShellManager::BaseDriver
5
+ abstract_driver
6
+
7
+ def suitability(method, *args) # :nodoc:
8
+ # Level must be higher than Portable
9
+ return available? ? 2 : 0
10
+ end
11
+
12
+ # See ShellManager#which!
13
+ def which!(command)
14
+ result = which(command)
15
+ if result.nil?
16
+ raise ArgumentError.new("command not found: #{command}")
17
+ else
18
+ true
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def _which_helper(&block)
25
+ data = block.call
26
+ data.strip! if data
27
+ return (! data.blank? && File.exists?(data)) ? data : nil
28
+ end
29
+
30
+ end
@@ -0,0 +1,16 @@
1
+ # == ShellManager::WhichUnix
2
+ #
3
+ # A ShellManager driver providing access to the +which+ command found on
4
+ # Unix-like systems.
5
+ class AutomateIt::ShellManager::WhichUnix < AutomateIt::ShellManager::WhichBase
6
+ depends_on :programs => %w(which)
7
+
8
+ # Inherits WhichBase#suitability
9
+
10
+ # See ShellManager#which
11
+ def which(command)
12
+ _which_helper do
13
+ `which #{command} 2>&1`
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # == ShellManager::WhichWindows
2
+ #
3
+ # A ShellManager driver providing access to +which+ command by faking it on
4
+ # Windows systems.
5
+ class ::AutomateIt::ShellManager::WhichWindows < ::AutomateIt::ShellManager::WhichBase
6
+ WHICH_HELPER = File.join(::AutomateIt::Constants::HELPERS_DIR, "which.cmd")
7
+
8
+ # FIXME how to detect windows through Java?
9
+ depends_on :callbacks => lambda { RUBY_PLATFORM =~ /mswin/i }
10
+
11
+ # Inherits WhichBase#suitability
12
+
13
+ # See ShellManager#which
14
+ def which(command)
15
+ _which_helper do
16
+ data = `#{WHICH_HELPER} #{command} 2>&1`
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,127 @@
1
+ # == TagManager
2
+ #
3
+ # The TagManager provides a way of querying tags. Tags are keywords
4
+ # associated with a specific hostname or group. These are useful for grouping
5
+ # together hosts and defining common behavior for them.
6
+ #
7
+ # === Basics
8
+ #
9
+ # The tags are typically stored in a Project's <tt>config/tags.yml</tt> file.
10
+ #
11
+ # For example, consider this <tt>config/tags.yml</tt> file:
12
+ # desktops:
13
+ # - satori
14
+ # - sunyata
15
+ # - michiru
16
+ # notebooks:
17
+ # - rheya
18
+ # - avijja
19
+ #
20
+ # With the above file, if we're on the host called "satori", we can query the
21
+ # fields like this:
22
+ # tags # => ["satori", "desktops", "localhost", ...]
23
+ #
24
+ # tagged?("desktops") # => true
25
+ # tagged?("notebooks") # => false
26
+ # tagged?(:satori) # => true
27
+ # tagged?("satori") # => true
28
+ # tagged?("satori || desktops") # => true
29
+ # tagged?("(satori || desktops) && !notebooks") # => true
30
+ #
31
+ # === Traits
32
+ #
33
+ # Your system may also automatically add tags that describe your system's
34
+ # traits, such as the name of the operating system, distribution release,
35
+ # hardware architecture, hostnames, IP addresses, etc.
36
+ #
37
+ # For example, here is a full set of tags for a system:
38
+ #
39
+ # ai> pp tags.sort # Pretty print the tags in sorted order
40
+ # ["10.0.0.6", # IPv4 addresses
41
+ # "127.0.0.1", # ...
42
+ # "192.168.130.1", # ...
43
+ # "::1/128", # IPv6 addresses
44
+ # "fe80::250:56ff:fec0:8/64", # ...
45
+ # "fe80::250:8dff:fe95:8fe9/64", # ...
46
+ # "i686", # Hardware architecture
47
+ # "linux", # OS
48
+ # "linux_i686", # OS and architecture
49
+ # "localhost", # Variants of hostname
50
+ # "localhost.localdomain", # ...
51
+ # "michiru", # ...
52
+ # "michiru.koshevoy", # ...
53
+ # "michiru.koshevoy.net", # ...
54
+ # "myapp_servers", # User defined tags
55
+ # "rails_servers", # ...
56
+ # "ubuntu", # OS distribution name
57
+ # "ubuntu_6.06"] # OS distribution name and release version
58
+ #
59
+ # To execute code only on an Ubuntu system:
60
+ #
61
+ # if tagged?("ubuntu")
62
+ # # Code will only be run on Ubuntu systems
63
+ # end
64
+ #
65
+ # These additional tags are retrieved from the PlatformManager and
66
+ # AddressManager. If your platform does not provide drivers for these, you will
67
+ # not get these tags. If you're on an unsupported platform and do not want to
68
+ # write drivers, you can work around this by manually declaring the missing
69
+ # tags in <tt>config/tags.yml</tt> on a host-by-host basis.
70
+ #
71
+ # === Inclusion and negation
72
+ #
73
+ # You can include and negate tags declaratively by giving "@" and "!" prefixes
74
+ # to arguments.
75
+ #
76
+ # For example, consider this <tt>config/tags.yml</tt> file:
77
+ #
78
+ # apache_servers:
79
+ # - kurou
80
+ # - shirou
81
+ # apache_servers_except_kurou:
82
+ # - @apache_servers
83
+ # - !kurou
84
+ #
85
+ # This will produce the following results:
86
+ #
87
+ # ai> hosts_tagged_with("apache_servers")
88
+ # => ["kurou", "shirou"]
89
+ # ai> hosts_tagged_with("apache_servers_except_kurou")
90
+ # => ["shirou"]
91
+ class AutomateIt::TagManager < AutomateIt::Plugin::Manager
92
+ alias_methods :hosts_tagged_with, :tags, :tagged?, :tags_for
93
+
94
+ # Return a list of hosts that match the query. See #tagged? for information
95
+ # on query syntax.
96
+ def hosts_tagged_with(query) dispatch(query) end
97
+
98
+ # Return a list of tags for this host.
99
+ def tags() dispatch() end
100
+
101
+ # Is this host tagged with the +query+?
102
+ #
103
+ # Examples:
104
+ # tags # => ["localhost", "foo", "bar", ...]
105
+ #
106
+ # tagged?(:localhost) # => true
107
+ # tagged?("localhost") # => true
108
+ # tagged?("localhost && foo") # => true
109
+ # tagged?("localhost || foo") # => true
110
+ # tagged?("!foo") # => false
111
+ # tagged?("(localhost || foo) && bar") # => true
112
+ def tagged?(query, hostname=nil) dispatch(query, hostname) end
113
+
114
+ # Return a list of tags for the host.
115
+ def tags_for(hostname) dispatch(hostname) end
116
+ end
117
+
118
+ # == TagManager::BaseDriver
119
+ #
120
+ # Base class for all TagManager drivers.
121
+ class AutomateIt::TagManager::BaseDriver < AutomateIt::Plugin::Driver
122
+ end
123
+
124
+ # Drivers
125
+ require 'automateit/tag_manager/tag_parser'
126
+ require 'automateit/tag_manager/struct'
127
+ require 'automateit/tag_manager/yaml'
@@ -0,0 +1,121 @@
1
+ # == TagManager::Struct
2
+ #
3
+ # A TagManager driver for querying a data structure. It's not useful on its
4
+ # own, but can be subclassed by other drivers that actually load tags.
5
+ class AutomateIt::TagManager::Struct < AutomateIt::TagManager::BaseDriver
6
+ depends_on :nothing
7
+
8
+ def suitability(method, *args) # :nodoc:
9
+ return 1
10
+ end
11
+
12
+ # Options:
13
+ # * :struct -- Hash to use for queries.
14
+ def setup(opts={})
15
+ super(opts)
16
+ @struct ||= {}
17
+ @tags ||= Set.new
18
+ @struct_updated = false
19
+ @our_hostname_tags ||= Set.new
20
+ @our_platform_tags ||= Set.new
21
+
22
+ if opts[:struct]
23
+ @struct_updated = true
24
+ @struct.merge!(AutomateIt::TagManager::TagParser.expand(opts[:struct]))
25
+ end
26
+ end
27
+
28
+ # Return tags, populate them if necessary.
29
+ def tags
30
+ if @our_hostname_tags.empty?
31
+ @struct_updated = true
32
+
33
+ begin
34
+ @our_hostname_tags.merge(interpreter.address_manager.hostnames) # SLOW 0.4s
35
+ @our_hostname_tags.merge(interpreter.address_manager.addresses)
36
+ rescue NotImplementedError => e
37
+ log.debug("Can't find AddressManager for this platform: #{e}")
38
+ end
39
+
40
+ @tags.merge(@our_hostname_tags)
41
+ end
42
+
43
+ if @our_platform_tags.empty?
44
+ @struct_updated = true
45
+
46
+ begin
47
+ @our_platform_tags = interpreter.platform_manager.tags
48
+ @tags.merge(@our_platform_tags)
49
+ rescue NotImplementedError => e
50
+ log.debug("Can't find PlatformManager for this platform: #{e}")
51
+ end
52
+ end
53
+
54
+ if @struct_updated
55
+ @tags.merge(tags_for(@our_hostname_tags))
56
+ @struct_updated = false
57
+ end
58
+
59
+ @tags
60
+ end
61
+
62
+ # See TagManager#hosts_tagged_with
63
+ def hosts_tagged_with(query)
64
+ hosts = @struct.values.flatten.uniq
65
+ return hosts.select{|hostname| tagged?(query, hostname)}
66
+ end
67
+
68
+ TAG_NEGATION = %r{!?}
69
+ TAG_WORD = %r{[\w\.\-]+}
70
+ TAG_TOKENIZER = %r{\!|\(|\)|\&{1,2}|\|{1,2}|#{TAG_NEGATION}#{TAG_WORD}}
71
+
72
+ # See TagManager#tagged?
73
+ def tagged?(query, hostname=nil)
74
+ query = query.to_s
75
+ selected_tags = hostname ? tags_for(hostname) : tags
76
+ # XXX Tokenizer discards unknown characters, which may hide errors in the query
77
+ tokens = query.scan(TAG_TOKENIZER)
78
+ if tokens.size > 1
79
+ booleans = tokens.map do |token|
80
+ if matches = token.match(/^(#{TAG_NEGATION})(#{TAG_WORD})$/)
81
+ selected_tags.include?(matches[2]) && matches[1].empty?
82
+ else
83
+ token
84
+ end
85
+ end
86
+ code = booleans.join(" ")
87
+
88
+ begin
89
+ return eval(code) # XXX What could possibly go wrong?
90
+ rescue Exception => e
91
+ raise ArgumentError.new("Invalid query -- #{query}")
92
+ end
93
+ else
94
+ return selected_tags.include?(query)
95
+ end
96
+ end
97
+
98
+ # See TagManager#tags_for
99
+ def tags_for(hostnames)
100
+ hostnames = \
101
+ case hostnames
102
+ when String
103
+ interpreter.address_manager.hostnames_for(hostnames)
104
+ when Array, Set
105
+ hostnames.inject(Set.new) do |sum, hostname|
106
+ sum.merge(interpreter.address_manager.hostnames_for(hostname)); sum
107
+ end
108
+ else
109
+ raise TypeError.new("invalid hostnames argument type: #{hostnames.class}")
110
+ end
111
+ result = @struct.inject(Set.new) do |sum, role_and_members|
112
+ role, members = role_and_members
113
+ members_aliases = members.inject(Set.new) do |aliases, member|
114
+ aliases.merge(interpreter.address_manager.hostnames_for(member)); aliases
115
+ end.to_a
116
+ sum.add(role) unless (hostnames & members_aliases).empty?
117
+ sum
118
+ end
119
+ return result.to_a.sort
120
+ end
121
+ end
@@ -0,0 +1,93 @@
1
+ # == TagManager::TagParser
2
+ #
3
+ # Helper class for parsing tags. Not useful for users -- for internal use only.
4
+ class AutomateIt::TagManager::TagParser
5
+ include Nitpick
6
+
7
+ attr_accessor :struct
8
+
9
+ # Create a parser for the +struct+, a hash of tag keys to values with arrays of items.
10
+ def initialize(struct)
11
+ self.struct = struct
12
+ normalize!
13
+ end
14
+
15
+ # Normalize a block of text to replace shortcut symbols that cause YAML to choke.
16
+ def self.normalize(text)
17
+ return text \
18
+ .gsub(/^(\s*-\s+)(!@)/, '\1EXCLUDE_TAG ') \
19
+ .gsub(/^(\s*-\s+)(!)/, '\1EXCLUDE_HOST ') \
20
+ .gsub(/^(\s*-\s+)(@)/, '\1INCLUDE_TAG ')
21
+ end
22
+
23
+ # Normalize the contents of the internal struct.
24
+ def normalize!
25
+ for tag, items in struct
26
+ next unless items
27
+ for item in items
28
+ next unless item
29
+ item.gsub!(/^(\!@|\^@)\s*/, 'EXCLUDE_TAG ')
30
+ item.gsub!(/^(\!|\^)\s*/, 'EXCLUDE_HOST ')
31
+ item.gsub!(/^(@)\s*/, 'INCLUDE_TAG ')
32
+ end
33
+ end
34
+ end
35
+
36
+ HOSTS_FOR_VALUE = /(.+?)/
37
+ HOSTS_FOR_INCLUDE_TAG_RE = /^INCLUDE_TAG #{HOSTS_FOR_VALUE}$/
38
+ HOSTS_FOR_EXCLUDE_TAG_RE = /^EXCLUDE_TAG #{HOSTS_FOR_VALUE}$/
39
+ HOSTS_FOR_EXCLUDE_HOST_RE = /^EXCLUDE_HOST #{HOSTS_FOR_VALUE}$/
40
+
41
+ # Return array of hosts for the +tag+.
42
+ def hosts_for(tag)
43
+ raise IndexError.new("Unknown tag - #{tag}") unless struct.has_key?(tag)
44
+ return [] if struct[tag].nil? # Tag has no leaves
45
+
46
+ nitpick "\nAA %s" % tag
47
+ hosts = Set.new
48
+ for item in struct[tag]
49
+ case item
50
+ when HOSTS_FOR_INCLUDE_TAG_RE
51
+ nitpick "+g %s" % $1
52
+ hosts.merge(hosts_for($1))
53
+ when HOSTS_FOR_EXCLUDE_TAG_RE
54
+ nitpick "-g %s" % $1
55
+ hosts.subtract(hosts_for($1))
56
+ when HOSTS_FOR_EXCLUDE_HOST_RE
57
+ nitpick "-h %s" % $1
58
+ hosts.delete($1)
59
+ else
60
+ nitpick "+h %s" % item
61
+ hosts << item
62
+ end
63
+ end
64
+ result = hosts.to_a
65
+ nitpick "ZZ %s for %s" % [result.inspect, tag]
66
+ return result
67
+ end
68
+
69
+ # Return array of tags.
70
+ def tags
71
+ return struct.keys
72
+ end
73
+
74
+ # Expand the include/exclude/group rules and return a struct with only the
75
+ # hosts these rules produce.
76
+ def expand
77
+ result = {}
78
+ for tag in tags
79
+ result[tag] = hosts_for(tag)
80
+ end
81
+ result
82
+ end
83
+
84
+ # Replace the internal struct with an expanded version, see #expand.
85
+ def expand!
86
+ struct.replace(expand)
87
+ end
88
+
89
+ # Expand the +struct+.
90
+ def self.expand(struct)
91
+ self.new(struct).expand
92
+ end
93
+ end