automateit 0.70923
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.
- data.tar.gz.sig +1 -0
- data/CHANGES.txt +100 -0
- data/Hoe.rake +35 -0
- data/Manifest.txt +111 -0
- data/README.txt +44 -0
- data/Rakefile +284 -0
- data/TESTING.txt +57 -0
- data/TODO.txt +26 -0
- data/TUTORIAL.txt +390 -0
- data/bin/ai +3 -0
- data/bin/aifield +82 -0
- data/bin/aitag +128 -0
- data/bin/automateit +117 -0
- data/docs/friendly_errors.txt +50 -0
- data/docs/previews.txt +86 -0
- data/env.sh +4 -0
- data/examples/basic/Rakefile +26 -0
- data/examples/basic/config/automateit_env.rb +16 -0
- data/examples/basic/config/fields.yml +3 -0
- data/examples/basic/config/tags.yml +13 -0
- data/examples/basic/dist/README.txt +9 -0
- data/examples/basic/dist/myapp_server.erb +30 -0
- data/examples/basic/install.log +15 -0
- data/examples/basic/lib/README.txt +10 -0
- data/examples/basic/recipes/README.txt +4 -0
- data/examples/basic/recipes/install.rb +53 -0
- data/examples/basic/recipes/uninstall.rb +6 -0
- data/gpl.txt +674 -0
- data/lib/automateit.rb +66 -0
- data/lib/automateit/account_manager.rb +106 -0
- data/lib/automateit/account_manager/linux.rb +171 -0
- data/lib/automateit/account_manager/passwd.rb +69 -0
- data/lib/automateit/account_manager/portable.rb +136 -0
- data/lib/automateit/address_manager.rb +165 -0
- data/lib/automateit/address_manager/linux.rb +80 -0
- data/lib/automateit/address_manager/portable.rb +37 -0
- data/lib/automateit/cli.rb +80 -0
- data/lib/automateit/common.rb +65 -0
- data/lib/automateit/constants.rb +33 -0
- data/lib/automateit/edit_manager.rb +292 -0
- data/lib/automateit/error.rb +10 -0
- data/lib/automateit/field_manager.rb +103 -0
- data/lib/automateit/interpreter.rb +641 -0
- data/lib/automateit/package_manager.rb +242 -0
- data/lib/automateit/package_manager/apt.rb +63 -0
- data/lib/automateit/package_manager/egg.rb +64 -0
- data/lib/automateit/package_manager/gem.rb +179 -0
- data/lib/automateit/package_manager/portage.rb +69 -0
- data/lib/automateit/package_manager/yum.rb +65 -0
- data/lib/automateit/platform_manager.rb +47 -0
- data/lib/automateit/platform_manager/darwin.rb +30 -0
- data/lib/automateit/platform_manager/debian.rb +26 -0
- data/lib/automateit/platform_manager/freebsd.rb +25 -0
- data/lib/automateit/platform_manager/gentoo.rb +26 -0
- data/lib/automateit/platform_manager/lsb.rb +40 -0
- data/lib/automateit/platform_manager/struct.rb +78 -0
- data/lib/automateit/platform_manager/uname.rb +29 -0
- data/lib/automateit/platform_manager/windows.rb +33 -0
- data/lib/automateit/plugin.rb +7 -0
- data/lib/automateit/plugin/base.rb +32 -0
- data/lib/automateit/plugin/driver.rb +218 -0
- data/lib/automateit/plugin/manager.rb +232 -0
- data/lib/automateit/project.rb +460 -0
- data/lib/automateit/root.rb +14 -0
- data/lib/automateit/service_manager.rb +79 -0
- data/lib/automateit/service_manager/chkconfig.rb +39 -0
- data/lib/automateit/service_manager/rc_update.rb +37 -0
- data/lib/automateit/service_manager/sysv.rb +126 -0
- data/lib/automateit/service_manager/update_rcd.rb +35 -0
- data/lib/automateit/shell_manager.rb +261 -0
- data/lib/automateit/shell_manager/base_link.rb +67 -0
- data/lib/automateit/shell_manager/link.rb +24 -0
- data/lib/automateit/shell_manager/portable.rb +421 -0
- data/lib/automateit/shell_manager/symlink.rb +32 -0
- data/lib/automateit/shell_manager/which.rb +25 -0
- data/lib/automateit/tag_manager.rb +63 -0
- data/lib/automateit/tag_manager/struct.rb +101 -0
- data/lib/automateit/tag_manager/tag_parser.rb +91 -0
- data/lib/automateit/tag_manager/yaml.rb +29 -0
- data/lib/automateit/template_manager.rb +55 -0
- data/lib/automateit/template_manager/base.rb +172 -0
- data/lib/automateit/template_manager/erb.rb +17 -0
- data/lib/ext/metaclass.rb +17 -0
- data/lib/ext/object.rb +18 -0
- data/lib/hashcache.rb +22 -0
- data/lib/helpful_erb.rb +63 -0
- data/lib/nested_error.rb +33 -0
- data/lib/queued_logger.rb +68 -0
- data/lib/tempster.rb +239 -0
- data/misc/index_gem_repository.rb +303 -0
- data/misc/setup_egg.rb +12 -0
- data/misc/setup_gem_dependencies.sh +7 -0
- data/misc/setup_rubygems.sh +21 -0
- data/misc/which.cmd +6 -0
- data/spec/extras/automateit_service_sysv_test +50 -0
- data/spec/extras/scratch.rb +15 -0
- data/spec/extras/simple_recipe.rb +8 -0
- data/spec/integration/account_manager_spec.rb +218 -0
- data/spec/integration/address_manager_linux_spec.rb +119 -0
- data/spec/integration/address_manager_portable_spec.rb +30 -0
- data/spec/integration/cli_spec.rb +215 -0
- data/spec/integration/examples_spec.rb +54 -0
- data/spec/integration/examples_spec_editor.rb +71 -0
- data/spec/integration/package_manager_spec.rb +104 -0
- data/spec/integration/platform_manager_spec.rb +69 -0
- data/spec/integration/service_manager_sysv_spec.rb +115 -0
- data/spec/integration/shell_manager_spec.rb +471 -0
- data/spec/integration/template_manager_erb_spec.rb +31 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/unit/edit_manager_spec.rb +162 -0
- data/spec/unit/field_manager_spec.rb +79 -0
- data/spec/unit/hashcache_spec.rb +28 -0
- data/spec/unit/interpreter_spec.rb +98 -0
- data/spec/unit/platform_manager_spec.rb +44 -0
- data/spec/unit/plugins_spec.rb +253 -0
- data/spec/unit/tag_manager_spec.rb +189 -0
- data/spec/unit/template_manager_erb_spec.rb +137 -0
- metadata +249 -0
- metadata.gz.sig +0 -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,25 @@
|
|
|
1
|
+
# == ShellManager::Which
|
|
2
|
+
#
|
|
3
|
+
# A ShellManager driver providing access to the +which+ command found on
|
|
4
|
+
# Unix-like systems.
|
|
5
|
+
class AutomateIt::ShellManager::Which < AutomateIt::ShellManager::BaseDriver
|
|
6
|
+
depends_on :programs => %w(which)
|
|
7
|
+
|
|
8
|
+
def suitability(method, *args) # :nodoc:
|
|
9
|
+
# Level must be higher than Portable
|
|
10
|
+
return available? ? 2 : 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# See ShellManager#which
|
|
14
|
+
def which(command)
|
|
15
|
+
data = `which "#{command}" 2>&1`.chomp
|
|
16
|
+
return (! data.blank? && File.exists?(data)) ? data : nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# See ShellManager#which!
|
|
20
|
+
def which!(command)
|
|
21
|
+
if which(command).nil?
|
|
22
|
+
raise ArgumentError.new("command not found: #{command}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
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. The tags are
|
|
6
|
+
# typically stored in a Project's <tt>config/tags.yml</tt> file.
|
|
7
|
+
#
|
|
8
|
+
# For example, consider a <tt>tags.yml</tt> file that contains YAML like:
|
|
9
|
+
# desktops:
|
|
10
|
+
# - satori
|
|
11
|
+
# - sunyata
|
|
12
|
+
# - michiru
|
|
13
|
+
# notebooks:
|
|
14
|
+
# - rheya
|
|
15
|
+
# - avijja
|
|
16
|
+
#
|
|
17
|
+
# With the above file, if we're on the host called "satori", we can query the
|
|
18
|
+
# fields like this:
|
|
19
|
+
# tags # => ["satori", "desktops", "localhost", ...]
|
|
20
|
+
#
|
|
21
|
+
# tagged?("desktops") # => true
|
|
22
|
+
# tagged?("notebooks") # => false
|
|
23
|
+
# tagged?(:satori) # => true
|
|
24
|
+
# tagged?("satori") # => true
|
|
25
|
+
# tagged?("satori || desktops") # => true
|
|
26
|
+
# tagged?("(satori || desktops) && !notebooks") # => true
|
|
27
|
+
class AutomateIt::TagManager < AutomateIt::Plugin::Manager
|
|
28
|
+
alias_methods :hosts_tagged_with, :tags, :tagged?, :tags_for
|
|
29
|
+
|
|
30
|
+
# Return a list of hosts that match the query. See #tagged? for information
|
|
31
|
+
# on query syntax.
|
|
32
|
+
def hosts_tagged_with(query) dispatch(query) end
|
|
33
|
+
|
|
34
|
+
# Return a list of tags for this host.
|
|
35
|
+
def tags() dispatch() end
|
|
36
|
+
|
|
37
|
+
# Is this host tagged with the +query+?
|
|
38
|
+
#
|
|
39
|
+
# Examples:
|
|
40
|
+
# tags # => ["localhost", "foo", "bar", ...]
|
|
41
|
+
#
|
|
42
|
+
# tagged?(:localhost) # => true
|
|
43
|
+
# tagged?("localhost") # => true
|
|
44
|
+
# tagged?("localhost && foo") # => true
|
|
45
|
+
# tagged?("localhost || foo") # => true
|
|
46
|
+
# tagged?("!foo") # => false
|
|
47
|
+
# tagged?("(localhost || foo) && bar") # => true
|
|
48
|
+
def tagged?(query, hostname=nil) dispatch(query, hostname) end
|
|
49
|
+
|
|
50
|
+
# Return a list of tags for the host.
|
|
51
|
+
def tags_for(hostname) dispatch(hostname) end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# == TagManager::BaseDriver
|
|
55
|
+
#
|
|
56
|
+
# Base class for all TagManager drivers.
|
|
57
|
+
class AutomateIt::TagManager::BaseDriver < AutomateIt::Plugin::Driver
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Drivers
|
|
61
|
+
require 'automateit/tag_manager/tag_parser'
|
|
62
|
+
require 'automateit/tag_manager/struct'
|
|
63
|
+
require 'automateit/tag_manager/yaml'
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
|
|
17
|
+
if opts[:struct]
|
|
18
|
+
@struct = AutomateIt::TagManager::TagParser.expand(opts[:struct])
|
|
19
|
+
@tags = Set.new
|
|
20
|
+
else
|
|
21
|
+
@struct ||= {}
|
|
22
|
+
@tags ||= Set.new
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Return tags, populate them if necessary.
|
|
27
|
+
def tags
|
|
28
|
+
if @tags.empty?
|
|
29
|
+
begin
|
|
30
|
+
hostnames = interpreter.address_manager.hostnames # SLOW 0.4s
|
|
31
|
+
@tags.merge(hostnames)
|
|
32
|
+
@tags.merge(tags_for(hostnames))
|
|
33
|
+
@tags.merge(interpreter.address_manager.addresses)
|
|
34
|
+
rescue NotImplementedError => e
|
|
35
|
+
log.debug("Can't find AddressManager for this platform: #{e}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
@tags.merge(interpreter.platform_manager.tags)
|
|
40
|
+
rescue NotImplementedError => e
|
|
41
|
+
log.debug("Can't find PlatformManager for this platform: #{e}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
@tags
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# See TagManager#hosts_tagged_with
|
|
48
|
+
def hosts_tagged_with(query)
|
|
49
|
+
hosts = @struct.values.flatten.uniq
|
|
50
|
+
return hosts.select{|hostname| tagged?(query, hostname)}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# See TagManager#tagged?
|
|
54
|
+
def tagged?(query, hostname=nil)
|
|
55
|
+
query = query.to_s
|
|
56
|
+
selected_tags = hostname ? tags_for(hostname) : tags
|
|
57
|
+
# XXX This tokenization process discards unknown characters, which may hide errors in the query
|
|
58
|
+
tokens = query.scan(%r{\!|\(|\)|\&+|\|+|!?[\.\w]+})
|
|
59
|
+
if tokens.size > 1
|
|
60
|
+
booleans = tokens.map do |token|
|
|
61
|
+
if matches = token.match(/^(!?)([\.\w]+)$/)
|
|
62
|
+
selected_tags.include?(matches[2]) && matches[1].empty?
|
|
63
|
+
else
|
|
64
|
+
token
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
code = booleans.join(" ")
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
return eval(code) # XXX What could possibly go wrong?
|
|
71
|
+
rescue Exception => e
|
|
72
|
+
raise ArgumentError.new("Invalid query -- #{query}")
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
return selected_tags.include?(query)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# See TagManager#tags_for
|
|
80
|
+
def tags_for(hostnames)
|
|
81
|
+
hostnames = \
|
|
82
|
+
case hostnames
|
|
83
|
+
when String
|
|
84
|
+
interpreter.address_manager.hostnames_for(hostnames)
|
|
85
|
+
when Array, Set
|
|
86
|
+
hostnames.inject(Set.new) do |sum, hostname|
|
|
87
|
+
sum.merge(interpreter.address_manager.hostnames_for(hostname)); sum
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
raise TypeError.new("invalid hostnames argument type: #{hostnames.class}")
|
|
91
|
+
end
|
|
92
|
+
return @struct.inject(Set.new) do |sum, role_and_members|
|
|
93
|
+
role, members = role_and_members
|
|
94
|
+
members_aliases = members.inject(Set.new) do |aliases, member|
|
|
95
|
+
aliases.merge(interpreter.address_manager.hostnames_for(member)); aliases
|
|
96
|
+
end.to_a
|
|
97
|
+
sum.add(role) unless (hostnames & members_aliases).empty?
|
|
98
|
+
sum
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# == TagManager::TagParser
|
|
2
|
+
#
|
|
3
|
+
# Helper class for parsing tags. Not useful for users -- for internal use only.
|
|
4
|
+
class AutomateIt::TagManager::TagParser
|
|
5
|
+
attr_accessor :struct
|
|
6
|
+
attr_accessor :is_trace
|
|
7
|
+
|
|
8
|
+
# Create a parser for the +struct+, a hash of tag keys to values with arrays of items.
|
|
9
|
+
def initialize(struct, is_trace=false)
|
|
10
|
+
self.struct = struct
|
|
11
|
+
self.is_trace = is_trace
|
|
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
|
+
# Display debugging information if +is_trace+ is enabled.
|
|
37
|
+
def trace(msg)
|
|
38
|
+
puts msg if is_trace
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Return array of hosts for the +tag+.
|
|
42
|
+
def hosts_for(tag)
|
|
43
|
+
raise IndexError.new("Unknown tag - #{tag}") unless struct[tag]
|
|
44
|
+
trace "\nAA %s" % tag
|
|
45
|
+
hosts = Set.new
|
|
46
|
+
for item in struct[tag]
|
|
47
|
+
case item
|
|
48
|
+
when /^INCLUDE_TAG (\w+)$/
|
|
49
|
+
trace "+g %s" % $1
|
|
50
|
+
hosts.merge(hosts_for($1))
|
|
51
|
+
when /^EXCLUDE_TAG (\w+)$/
|
|
52
|
+
trace "-g %s" % $1
|
|
53
|
+
hosts.subtract(hosts_for($1))
|
|
54
|
+
when /^EXCLUDE_HOST (\w+)$/
|
|
55
|
+
trace "-h %s" % $1
|
|
56
|
+
hosts.delete($1)
|
|
57
|
+
else
|
|
58
|
+
trace "+h %s" % item
|
|
59
|
+
hosts << item
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
result = hosts.to_a
|
|
63
|
+
trace "ZZ %s for %s" % [result.inspect, tag]
|
|
64
|
+
return result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Return array of tags.
|
|
68
|
+
def tags
|
|
69
|
+
return struct.keys
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Expand the include/exclude/group rules and return a struct with only the
|
|
73
|
+
# hosts these rules produce.
|
|
74
|
+
def expand
|
|
75
|
+
result = {}
|
|
76
|
+
for tag in tags
|
|
77
|
+
result[tag] = hosts_for(tag)
|
|
78
|
+
end
|
|
79
|
+
result
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Replace the internal struct with an expanded version, see #expand.
|
|
83
|
+
def expand!
|
|
84
|
+
struct.replace(expand)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Expand the +struct+.
|
|
88
|
+
def self.expand(struct, is_trace=false)
|
|
89
|
+
self.new(struct, is_trace).expand
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# == TagManager::YAML
|
|
2
|
+
#
|
|
3
|
+
# A TagManager driver that reads tags from a YAML file.
|
|
4
|
+
class AutomateIt::TagManager::YAML < AutomateIt::TagManager::Struct
|
|
5
|
+
depends_on :nothing
|
|
6
|
+
|
|
7
|
+
def suitability(method, *args) # :nodoc:
|
|
8
|
+
return 5
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Options:
|
|
12
|
+
# * :file -- File to read tags from. The file is preprocessed with ERB and
|
|
13
|
+
# must produce YAML content.
|
|
14
|
+
def setup(opts={})
|
|
15
|
+
if filename = opts.delete(:file)
|
|
16
|
+
contents = _read(filename)
|
|
17
|
+
output = HelpfulERB.new(contents, filename).result
|
|
18
|
+
|
|
19
|
+
text = AutomateIt::TagManager::TagParser.normalize(output)
|
|
20
|
+
opts[:struct] = ::YAML::load(text)
|
|
21
|
+
end
|
|
22
|
+
super(opts)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def _read(filename)
|
|
26
|
+
return File.read(filename)
|
|
27
|
+
end
|
|
28
|
+
private :_read
|
|
29
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# == TemplateManager
|
|
2
|
+
#
|
|
3
|
+
# TemplateManager renders templates to files.
|
|
4
|
+
#
|
|
5
|
+
# See the #render method for details.
|
|
6
|
+
class AutomateIt::TemplateManager < AutomateIt::Plugin::Manager
|
|
7
|
+
alias_methods :render
|
|
8
|
+
|
|
9
|
+
# Render a template.
|
|
10
|
+
#
|
|
11
|
+
# You may specify the +source+ and +target+ as arguments or options. For
|
|
12
|
+
# example, <tt>render(:file => "input", :to => "output")</tt> is the same as
|
|
13
|
+
# <tt>render("input", "output")</tt>.
|
|
14
|
+
#
|
|
15
|
+
# Options:
|
|
16
|
+
# * :file -- Read the template from this file.
|
|
17
|
+
# * :text -- Read the template from this string.
|
|
18
|
+
# * :to -- Render to a file, otherwise returns the rendered string.
|
|
19
|
+
# * :locals -- Hash of variables passed to template as local variables.
|
|
20
|
+
# * :dependencies -- When checking timestamps, include this Array of
|
|
21
|
+
# filenames when checking timestamps.
|
|
22
|
+
# * :force -- Boolean to force rendering without checking timestamps.
|
|
23
|
+
# * :check -- Determines when to render, otherwise uses value of
|
|
24
|
+
# +default_check+, possible values:
|
|
25
|
+
# * :compare -- Only render if rendered template is different than the
|
|
26
|
+
# target's contents or if the target doesn't exist.
|
|
27
|
+
# * :timestamp -- Only render if the target is older than the template and
|
|
28
|
+
# dependencies.
|
|
29
|
+
# * :exists -- Only render if the target doesn't exist.
|
|
30
|
+
#
|
|
31
|
+
# For example, if the file +my_template_file+ contains:
|
|
32
|
+
#
|
|
33
|
+
# Hello <%=entity%>!
|
|
34
|
+
#
|
|
35
|
+
# You could then execute:
|
|
36
|
+
#
|
|
37
|
+
# render("my_template_file", "/tmp/out", :check => :compare,
|
|
38
|
+
# :locals => {:entity => "world"})
|
|
39
|
+
#
|
|
40
|
+
# And this will create a <tt>/tmp/out</tt> file with:
|
|
41
|
+
#
|
|
42
|
+
# Hello world!
|
|
43
|
+
def render(*options) dispatch(*options) end
|
|
44
|
+
|
|
45
|
+
# Get name of default algorithm for performing checks.
|
|
46
|
+
def default_check() dispatch() end
|
|
47
|
+
|
|
48
|
+
# Set name of default algorithms to perform checks, e.g., :compare. See the
|
|
49
|
+
# #render :check option for list of check algorithms.
|
|
50
|
+
def default_check=(value) dispatch(value) end
|
|
51
|
+
end # class TemplateManager
|
|
52
|
+
|
|
53
|
+
# Drivers
|
|
54
|
+
require 'automateit/template_manager/base.rb'
|
|
55
|
+
require 'automateit/template_manager/erb.rb'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# == TemplateManager::BaseDriver
|
|
2
|
+
#
|
|
3
|
+
# Base class for all TemplateManager drivers.
|
|
4
|
+
class AutomateIt::TemplateManager::BaseDriver < AutomateIt::Plugin::Driver
|
|
5
|
+
# Name of default algorithm for performing checks, e.g., :compare
|
|
6
|
+
attr_accessor :default_check
|
|
7
|
+
|
|
8
|
+
# Options:
|
|
9
|
+
# * :default_check - Set the #default_check, e.g., :compare
|
|
10
|
+
def setup(opts={})
|
|
11
|
+
super(opts)
|
|
12
|
+
if opts[:default_check]
|
|
13
|
+
@default_check = opts[:default_check]
|
|
14
|
+
else
|
|
15
|
+
@default_check ||= :compare
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Return Array of +dependencies+ newer than +filename+. Will be empty if
|
|
20
|
+
# +filename+ is newer than all of the +dependencies+.
|
|
21
|
+
def _newer(filename, *dependencies)
|
|
22
|
+
updated = []
|
|
23
|
+
timestamp = _mtime(filename)
|
|
24
|
+
for dependency in dependencies
|
|
25
|
+
updated << dependency if _mtime(dependency) > timestamp
|
|
26
|
+
end
|
|
27
|
+
return updated
|
|
28
|
+
end
|
|
29
|
+
protected :_newer
|
|
30
|
+
|
|
31
|
+
# Does +filename+ exist?
|
|
32
|
+
def _exists?(filename)
|
|
33
|
+
return File.exists?(filename)
|
|
34
|
+
end
|
|
35
|
+
protected :_exists?
|
|
36
|
+
|
|
37
|
+
# Return the contents of +filename+.
|
|
38
|
+
def _read(filename)
|
|
39
|
+
begin
|
|
40
|
+
result = File.read(filename)
|
|
41
|
+
return result
|
|
42
|
+
rescue Errno::ENOENT => e
|
|
43
|
+
if writing?
|
|
44
|
+
raise e
|
|
45
|
+
else
|
|
46
|
+
return ""
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
protected :_read
|
|
51
|
+
|
|
52
|
+
# Write +contents+ to +filename+.
|
|
53
|
+
def _write(filename, contents)
|
|
54
|
+
File.open(filename, "w+"){|writer| writer.write(contents)} if writing?
|
|
55
|
+
return true
|
|
56
|
+
end
|
|
57
|
+
protected :_write
|
|
58
|
+
|
|
59
|
+
# Return the modification date for +filename+.
|
|
60
|
+
def _mtime(filename)
|
|
61
|
+
return _exists? ? File.mtime(filename) : nil
|
|
62
|
+
end
|
|
63
|
+
protected :_mtime
|
|
64
|
+
|
|
65
|
+
# Render a template specified in the block. It takes the same arguments and
|
|
66
|
+
# returns the same results as the #render call.
|
|
67
|
+
#
|
|
68
|
+
# This method is used by the #render methods for different template drivers
|
|
69
|
+
# and provides all the logic for parsing arguments, figuring out if a
|
|
70
|
+
# template should be rendered, what to do with the rendering, etc.
|
|
71
|
+
#
|
|
72
|
+
# This method calls the supplied +block+ with a hash containing:
|
|
73
|
+
# * :text -- Template's text.
|
|
74
|
+
# * :filename -- Template's filename, or nil if none. The template
|
|
75
|
+
# * :binder -- Binding containing the locals as variables.
|
|
76
|
+
# * :locals -- Hash of locals.
|
|
77
|
+
# * :opts -- Hash of options passed to the #_render_helper.
|
|
78
|
+
#
|
|
79
|
+
# The supplied block must return the text of the rendered template.
|
|
80
|
+
#
|
|
81
|
+
# See the TemplateManager::ERB#render method for a usage example.
|
|
82
|
+
def _render_helper(*options, &block) # :yields: block_opts
|
|
83
|
+
args, opts = args_and_opts(*options)
|
|
84
|
+
source_filename = args[0] || opts[:file]
|
|
85
|
+
target_filename = args[1] || opts[:to]
|
|
86
|
+
source_text = opts[:text]
|
|
87
|
+
|
|
88
|
+
raise ArgumentError.new("No source specified with :file or :text") if not source_filename and not source_text
|
|
89
|
+
raise Errno::ENOENT.new(source_filename) if writing? and source_filename and not _exists?(source_filename)
|
|
90
|
+
raise ArgumentError.new("No target specified with :to") if not target_filename
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
# source_filename, target_filename, opts={}
|
|
94
|
+
opts[:check] ||= @default_check
|
|
95
|
+
target_exists = _exists?(target_filename)
|
|
96
|
+
updates = []
|
|
97
|
+
|
|
98
|
+
unless opts[:force]
|
|
99
|
+
case opts[:check]
|
|
100
|
+
when :exists
|
|
101
|
+
if target_exists
|
|
102
|
+
log.debug(PNOTE+"Rendering for '#{target_filename}' skipped because it already exists")
|
|
103
|
+
return false
|
|
104
|
+
else
|
|
105
|
+
log.info(PNOTE+"Rendering '#{target_filename}' because of it doesn't exist")
|
|
106
|
+
end
|
|
107
|
+
when :timestamp
|
|
108
|
+
if target_exists
|
|
109
|
+
updates = _newer(target_filename, \
|
|
110
|
+
*[source_filename, opts[:dependencies]].reject{|t| t.nil?}.flatten)
|
|
111
|
+
if updates.empty?
|
|
112
|
+
log.debug(PNOTE+"Rendering for '#{target_filename}' skipped because dependencies haven't been updated")
|
|
113
|
+
return false
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
target_contents = target_exists ? _read(target_filename) : ""
|
|
120
|
+
source_text ||= _read(source_filename)
|
|
121
|
+
|
|
122
|
+
if source_text.blank? and preview?
|
|
123
|
+
return true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
binder = nil
|
|
127
|
+
if opts[:locals]
|
|
128
|
+
# Create a binding that the template can get variables from without
|
|
129
|
+
# polluting the Driver's namespace.
|
|
130
|
+
callback = lambda{
|
|
131
|
+
code = ""
|
|
132
|
+
for key in opts[:locals].keys
|
|
133
|
+
code << "#{key} = opts[:locals][:#{key}]\n"
|
|
134
|
+
end
|
|
135
|
+
eval code
|
|
136
|
+
binding
|
|
137
|
+
}
|
|
138
|
+
binder = callback.call
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
block_opts = {
|
|
142
|
+
:binder => binder,
|
|
143
|
+
:filename => source_filename,
|
|
144
|
+
:text => source_text,
|
|
145
|
+
:locals => opts[:locals],
|
|
146
|
+
:opts => opts,
|
|
147
|
+
}
|
|
148
|
+
output = block.call(block_opts)
|
|
149
|
+
|
|
150
|
+
case opts[:check]
|
|
151
|
+
when :compare
|
|
152
|
+
if not target_exists
|
|
153
|
+
log.info(PNOTE+"Rendering '#{target_filename}' because of it doesn't exist")
|
|
154
|
+
elsif output == target_contents
|
|
155
|
+
log.debug(PNOTE+"Rendering for '#{target_filename}' skipped because contents are the same")
|
|
156
|
+
return false
|
|
157
|
+
else
|
|
158
|
+
log.info(PNOTE+"Rendering '#{target_filename} because its contents changed")
|
|
159
|
+
end
|
|
160
|
+
when :timestamp
|
|
161
|
+
log.info(PNOTE+"Rendering '#{target_filename}' because of updated: #{updates.join(' ')}")
|
|
162
|
+
end
|
|
163
|
+
result = _write(target_filename, output)
|
|
164
|
+
return result
|
|
165
|
+
ensure
|
|
166
|
+
if opts[:mode] or opts[:user] or opts[:group]
|
|
167
|
+
interpreter.chperm(target_filename, :mode => opts[:mode], :user => opts[:user], :group => opts[:group])
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
protected :_render_helper
|
|
172
|
+
end
|