automateit 0.70923
Sign up to get free protection for your applications and to get access to all the features.
- 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
|