automateit 0.70923 → 0.70928

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.
@@ -27,6 +27,7 @@ module AutomateIt
27
27
  # * cp -- AutomateIt::ShellManager#cp
28
28
  # * cp_r -- AutomateIt::ShellManager#cp_r
29
29
  # * edit -- AutomateIt::EditManager#edit
30
+ # * download -- AutomateIt::DownloadManager#download
30
31
  # * hosts_tagged_with -- AutomateIt::TagManager#hosts_tagged_with
31
32
  # * install -- AutomateIt::ShellManager#install
32
33
  # * ln -- AutomateIt::ShellManager#ln
@@ -124,9 +125,8 @@ module AutomateIt
124
125
  # * :verbosity -- Alias for :log_level
125
126
  # * :log_level -- Log level to use, defaults to Logger::INFO.
126
127
  # * :preview -- Turn on preview mode, defaults to false.
127
- # * :project -- Set project path.
128
- # * :friendly_exceptions -- Throw user-friendly exceptions that make it
129
- # easier to see errors in recipes, defaults to true.
128
+ # * :project -- Project directory to use.
129
+ # * :tags -- Array of tags to add to this run.
130
130
  #
131
131
  # Options for internal use:
132
132
  # * :parent -- Parent plugin instance.
@@ -135,6 +135,8 @@ module AutomateIt
135
135
  # guessed, won't throw exceptions if project wasn't found at the
136
136
  # specified path. If not guessed, will throw exception in such a
137
137
  # situation.
138
+ # * :friendly_exceptions -- Throw user-friendly exceptions that make it
139
+ # easier to see errors in recipes, defaults to true.
138
140
  def setup(opts={})
139
141
  super(opts.merge(:interpreter => self))
140
142
 
@@ -210,6 +212,8 @@ module AutomateIt
210
212
  raise ArgumentError.new("Couldn't find project at: #{project_path}")
211
213
  end
212
214
  end
215
+
216
+ tags.merge(opts[:tags]) if opts[:tags]
213
217
  end
214
218
 
215
219
  # Hash of plugin tokens to plugin instances for this Interpreter.
@@ -38,12 +38,8 @@ module AutomateIt
38
38
 
39
39
  # Methods to alias into the Interpreter, specified as an array of symbols.
40
40
  def self.alias_methods(*methods)
41
- if methods.empty?
42
- self.aliased_methods
43
- else
44
- self.aliased_methods ||= Set.new
45
- self.aliased_methods.merge(methods.flatten)
46
- end
41
+ self.aliased_methods ||= Set.new
42
+ self.aliased_methods.merge(methods.flatten)
47
43
  end
48
44
 
49
45
  # Drivers for this manager as a hash of driver tokens to driver
@@ -1,5 +1,8 @@
1
1
  # See AutomateIt::Interpreter for usage information.
2
2
  module AutomateIt # :nodoc:
3
+ # AutomateIt version
4
+ VERSION=Gem::Version.new("0.70928")
5
+
3
6
  # Instantiates a new Interpreter. See documentation for
4
7
  # Interpreter#setup.
5
8
  def self.new(opts={})
@@ -7,7 +7,7 @@
7
7
  # previews.txt[link:files/docs/previews_txt.html] for instructions on how to
8
8
  # write code that can be safely previewed.
9
9
  class AutomateIt::ShellManager < AutomateIt::Plugin::Manager
10
- alias_methods :sh, :which, :which!, :mktemp, :mktempdir, :mktempdircd, :chperm, :umask
10
+ alias_methods :backup, :sh, :which, :which!, :mktemp, :mktempdir, :mktempdircd, :chperm, :umask
11
11
  alias_methods :cd, :pwd, :mkdir, :mkdir_p, :rmdir, :ln, :ln_s, :ln_sf, :cp, :cp_r, :cp_R, :mv, :rm, :rm_r, :rm_rf, :install, :chmod, :chmod_R, :chown, :chown_R, :touch
12
12
 
13
13
  #...[ Detection commands ]..............................................
@@ -26,6 +26,27 @@ class AutomateIt::ShellManager < AutomateIt::Plugin::Manager
26
26
 
27
27
  #...[ Custom commands ].................................................
28
28
 
29
+ # Backup +sources+ if they exist. Returns the names of the backups created.
30
+ #
31
+ # These backups are copies of the original sources saved into the same
32
+ # directories as the originals. The pathnames of these copies are timestamped
33
+ # and guaranteed to be unique, so you can have multiple backups of the same
34
+ # sources.
35
+ #
36
+ # *WARNING*: This method is not conditional. It will make a backup every time
37
+ # it's called if the sources exist. Therefore, only execute this method when
38
+ # its needed.
39
+ #
40
+ # For example, backup a file:
41
+ #
42
+ # backup("/tmp/myfile") # => "/tmp/myfile.1190994237_M2xhLrC6Sj.bak
43
+ #
44
+ # In the above example, the backup's name contains two special strings. The
45
+ # "1190994237" is the time the backup was made in seconds since the Epoch.
46
+ # The "M2xhLrC6Sj" is a random string used to guarantee the uniqueness of
47
+ # this backup in case two are made at exactly the same time.
48
+ def backup(*sources) dispatch(*sources) end
49
+
29
50
  # Execute a shell command.
30
51
  def sh(*commands) dispatch(*commands) end
31
52
 
@@ -250,7 +271,29 @@ class AutomateIt::ShellManager::BaseDriver < AutomateIt::Plugin::Driver
250
271
  opts[:noop] = true if preview?
251
272
  return opts
252
273
  end
253
- private :_fileutils_opts
274
+ protected :_fileutils_opts
275
+
276
+ # Return array of all the directory's top-level contents, including hidden
277
+ # files with "." prefix on UNIX. Directories are returned just as a name,
278
+ # you'll need to expand those separately if needed.
279
+ def _directory_contents(directory)
280
+ return Dir[directory+"/{,.}*"].reject{|t| t =~ /(^|#{File::SEPARATOR})\.{1,2}$/}
281
+ end
282
+ protected :_directory_contents
283
+
284
+ # Returns derived filename to use as a peer given the +source+ and +target+.
285
+ # This is necessary for differentiating between directory and file targets.
286
+ #
287
+ # For example:
288
+ #
289
+ # # Get the peer for an extant target directory:
290
+ # peer_for("foo", "/tmp") # => "/tmp/foo"
291
+ #
292
+ # # Get the peer for anything else:
293
+ # peer_for("foo", "/bar") # => "/bar"
294
+ def peer_for(source, target)
295
+ return FileUtils.send(:fu_each_src_dest0, source, target){|a, b| b}
296
+ end
254
297
  end
255
298
 
256
299
  # Drivers
@@ -24,7 +24,7 @@ class AutomateIt::ShellManager::BaseLink < AutomateIt::ShellManager::BaseDriver
24
24
  end
25
25
 
26
26
  for source in sources
27
- peer = File.directory?(target) ? File.join(target, File.basename(source)) : target
27
+ peer = peer_for(source, target)
28
28
  begin
29
29
  peer_stat = File.stat(peer)
30
30
  source_stat = File.stat(source)
@@ -28,6 +28,42 @@ class AutomateIt::ShellManager::Portable < AutomateIt::ShellManager::BaseDriver
28
28
 
29
29
  #...[ Custom commands ].................................................
30
30
 
31
+ # See ShellManager#backup
32
+ def backup(*sources)
33
+ targets = []
34
+ for source in sources
35
+ is_dir = File.directory?(source)
36
+
37
+ tempster_opts = {
38
+ :verbose => false,
39
+ :noop => noop?,
40
+ :delete => false,
41
+ :dir => File.dirname(source),
42
+ :prefix => "%s.%s" % [File.basename(source), Time.now.to_i],
43
+ :suffix => ".bak",
44
+ :kind => is_dir ? :directory : :file,
45
+ }
46
+
47
+ target = ::Tempster.tempster(tempster_opts)
48
+
49
+ if is_dir
50
+ cp_opts = {}
51
+ cp_opts[:recursive] = true if is_dir
52
+ cp_opts[:preserve] = true if superuser?
53
+
54
+ source_children = _directory_contents(source)
55
+ #puts "sc: %s" % source_children.inspect
56
+
57
+ interpreter.cp_r(source_children, target, cp_opts)
58
+ else
59
+ interpreter.cp(source, target)
60
+ end
61
+
62
+ targets << target
63
+ end
64
+ return sources.size == 1 ? targets.first : targets
65
+ end
66
+
31
67
  # See ShellManager#sh
32
68
  def sh(*commands)
33
69
  args, opts = args_and_opts(*commands)
@@ -88,11 +124,7 @@ class AutomateIt::ShellManager::Portable < AutomateIt::ShellManager::BaseDriver
88
124
  if writing? or File.directory?(dir)
89
125
  FileUtils.cd(dir, &block)
90
126
  else
91
- begin
92
- block.call(true)
93
- rescue Exception => e
94
- raise e
95
- end
127
+ block.call(true)
96
128
  end
97
129
  rescue Exception => e
98
130
  raise e
@@ -124,6 +156,7 @@ class AutomateIt::ShellManager::Portable < AutomateIt::ShellManager::BaseDriver
124
156
  cmd = kind.to_s.gsub(/_/, ' -')
125
157
  log.info(PEXEC+"#{cmd} #{missing.join(" ")}")
126
158
  result = [FileUtils.send(kind, missing, _fileutils_opts)].flatten
159
+ result = result.first if [dirs].flatten.size == 1
127
160
  end
128
161
  if block
129
162
  if missing.size > 1
@@ -159,7 +192,7 @@ class AutomateIt::ShellManager::Portable < AutomateIt::ShellManager::BaseDriver
159
192
  chmod_rv = nil
160
193
  log.silence(Logger::WARN) do
161
194
  cp_rv = cp(source, target)
162
- chmod_rv = chmod(mode, target) if mode
195
+ chmod_rv = chmod(mode, peer_for(source, target)) if mode
163
196
  end
164
197
 
165
198
  return false unless cp_rv or chmod_rv
@@ -186,13 +219,8 @@ class AutomateIt::ShellManager::Portable < AutomateIt::ShellManager::BaseDriver
186
219
  Find.find(parent) do |child|
187
220
  source_fn = File.directory?(child) ? child+"/" : child
188
221
  target_dir = File.directory?(target)
189
- target_fn = \
190
- if target_dir
191
- #File.join(target, source_fn.match(/#{parent}\/?(.*)$/)[1])
192
- File.join(target, source_fn)
193
- else
194
- target
195
- end
222
+ target_fn = peer_for(source_fn, target)
223
+
196
224
  log.debug(PNOTE+"comparing %s => %s" % [source_fn, target_fn])
197
225
  source_st = File.stat(source_fn)
198
226
  is_copy = false
@@ -2,10 +2,13 @@
2
2
  #
3
3
  # The TagManager provides a way of querying tags. Tags are keywords
4
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.
5
+ # together hosts and defining common behavior for them.
7
6
  #
8
- # For example, consider a <tt>tags.yml</tt> file that contains YAML like:
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:
9
12
  # desktops:
10
13
  # - satori
11
14
  # - sunyata
@@ -24,6 +27,67 @@
24
27
  # tagged?("satori") # => true
25
28
  # tagged?("satori || desktops") # => true
26
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"]
27
91
  class AutomateIt::TagManager < AutomateIt::Plugin::Manager
28
92
  alias_methods :hosts_tagged_with, :tags, :tagged?, :tags_for
29
93
 
@@ -32,27 +32,35 @@ require 'fileutils'
32
32
  # * Random string generator taken from
33
33
  # http://pleac.sourceforge.net/pleac_ruby/numbers.html
34
34
  class Tempster
35
- DEFAULT_NAME = "tempster"
35
+ DEFAULT_PREFIX = "tempster"
36
36
  DEFAULT_FILE_MODE = 0600
37
37
  DEFAULT_DIRECTORY_MODE = 0700
38
38
  DEFAULT_ARMOR_LENGTH = 10
39
39
  ARMOR_CHARACTERS = ["A".."Z","a".."z","0".."9"].collect{|r| r.to_a}.join
40
40
 
41
+ # Alias for ::tempster.
42
+ def self._tempster(opts={}, &block) # :nodoc:
43
+ tempster(opts, &block)
44
+ end
45
+
41
46
  # Options:
42
- # * :name -- Name prefix to usse, defaults to "tempster".
43
47
  # * :kind -- Create a :file or :directory, required.
48
+ # * :prefix -- String to prefix the temporary name with, defaults to "tempster".
49
+ # * :suffix -- String to suffix the temporary name with, defaults is no suffix.
50
+ # * :name -- Same as :prefix
44
51
  # * :dir -- Base directory to create temporary entries in, uses system-wide temporary directory (e.g., <tt>/tmp</tt>) by default.
45
52
  # * :cd -- Change into the newly directory created using +ch+ within the block, and then switch back to the previous directory. Only used when a block is given and the :kind is :directory. Default is false. See WARNING at the top of this class's documentation!
46
53
  # * :noop -- no-operation mode, pretends to do actions without actually creating or deleting temporary entries. Default is false. WARNING: See WARNING at the top of this class's documentation!
47
54
  # * :verbose -- Print status messages about creating and deleting the temporary entries. Default is false.
48
55
  # * :delete -- Delete the temporary entries when exiting block. Default is true when given a block, false otherwise. If you don't use a block, you're responsible for deleting the entries yourself.
49
56
  # * :tries -- Number of tries to create a temporary entry, usually it'll succeed on the first try. Default is 10.
50
- # * :armor -- Length of armor to add to the name. These are random characters padding out the temporary entry names to prevent them from using existing files. If you have a very short armor, you're likely to get a collision and the algorithm will have to try again for the specified number of +tries+.
57
+ # * :armor -- Length of armor to append to the prefix. These are random characters padding out the temporary entry names to prevent them from using existing files. If you have a very short armor, you're likely to get a collision and the algorithm will have to try again for the specified number of +tries+.
51
58
  # * :message_callback -- +lambda+ called when there's a message, e.g., <tt>lambda{|message| puts message}</tt>, regardless of :verbose state. By default :messaging is nil and messages are printed to STDOUT only when :verbose is true.
52
59
  # * :message_prefix -- String to put in front of messages, e.g., "# "
53
- def self._tempster(opts={}, &block)
54
- name = opts.delete(:name) || DEFAULT_NAME
60
+ def self.tempster(opts={}, &block)
55
61
  kind = opts.delete(:kind) or raise ArgumentError.new("'kind' option not specified")
62
+ prefix = opts.delete(:prefix) || opts.delete(:name) || DEFAULT_PREFIX
63
+ suffix = opts.delete(:suffix) || nil
56
64
  dir = opts.delete(:dir) || Dir.tmpdir
57
65
  cd = opts.delete(:cd) || false
58
66
  noop = opts.delete(:noop) || false
@@ -82,7 +90,10 @@ class Tempster
82
90
  success = false
83
91
  for i in 1..tries
84
92
  begin
85
- path = File.join(dir, name+"_"+_armor_string(armor))
93
+ name = prefix+"_"+_armor_string(armor)
94
+ name << suffix if suffix
95
+ path = File.join(dir, name)
96
+
86
97
  unless noop
87
98
  case kind
88
99
  when :file
@@ -94,13 +105,9 @@ class Tempster
94
105
  raise ArgumentError.new("unknown kind: #{kind}")
95
106
  end
96
107
  end
97
- # XXX Should we pretend that it's mktemp? Or give users something more useful?
98
- # messager.puts("mktemp -m 0%o%s -p %s %s # => %s" % [mode, kind == :directory ? ' -d' : '', dir, name, path])
99
- if block
100
- messager.puts("mktempster --mode=0%o --kind=%s --dir=%s --name=%s" % [mode, kind, dir, name])
101
- else
102
- messager.puts("mktempster --mode=0%o --kind=%s --dir=%s --name=%s # => %s" % [mode, kind, dir, name, path])
103
- end
108
+ message = "mktempster --mode=0%o --kind=%s --dir=%s --prefix=%s" % [mode, kind, dir, prefix]
109
+ message << (" --suffix=%s" % suffix) if suffix
110
+ message << (" # => %s" % path) if block
104
111
  success = true
105
112
  break
106
113
  rescue Errno::EEXIST
@@ -142,14 +149,14 @@ class Tempster
142
149
 
143
150
  # Creates a temporary file.
144
151
  def self.mktemp(opts={}, &block)
145
- _tempster({:kind => :file}.merge(opts), &block)
152
+ tempster({:kind => :file}.merge(opts), &block)
146
153
  end
147
154
 
148
155
  # Creates a temporary directory.
149
156
  #
150
157
  # *WARNING*: See WARNING text at the top of this class's documentation!
151
158
  def self.mktempdir(opts={}, &block)
152
- _tempster({:kind => :directory}.merge(opts), &block)
159
+ tempster({:kind => :directory}.merge(opts), &block)
153
160
 
154
161
  end
155
162
 
@@ -158,7 +165,7 @@ class Tempster
158
165
  #
159
166
  # *WARNING*: See WARNING text at the top of this class's documentation!
160
167
  def self.mktempdircd(opts={}, &block)
161
- _tempster({:kind => :directory, :cd => true}.merge(opts), &block)
168
+ tempster({:kind => :directory, :cd => true}.merge(opts), &block)
162
169
  end
163
170
 
164
171
  class Messager
@@ -180,6 +187,9 @@ class Tempster
180
187
  end
181
188
  end
182
189
 
190
+ =begin
191
+ # Mostly recreated this in spec/integration/tempster_spec.rb
192
+
183
193
  if __FILE__ == $0
184
194
  # TODO Tempster -- write a spec
185
195
 
@@ -237,3 +247,4 @@ if __FILE__ == $0
237
247
  puts "after dir exists?: %s" % File.directory?(path)
238
248
  puts "after pwd: "+Dir.pwd
239
249
  end
250
+ =end
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "/../spec_helper.rb")
2
+
3
+ describe "AutomateIt::DownloadManager" do
4
+ before :all do
5
+ @a = AutomateIt.new(:verbosity => Logger::WARN)
6
+ end
7
+
8
+ it "should download a web page to a file" do
9
+ @a.mktempdircd do
10
+ source = "http://www.google.com/"
11
+ target = "google.html"
12
+
13
+ @a.download(source, :to => target).should be_true
14
+
15
+ File.exists?(target).should be_true
16
+ File.read(target).should =~ /<html>.*Google/im
17
+ end
18
+ end
19
+
20
+ it "should download a web page to a directory" do
21
+ @a.mktempdircd do
22
+ source = "http://www.google.com/"
23
+ intermediate = "."
24
+ target = "www.google.com"
25
+
26
+ @a.download(source, :to => intermediate).should be_true
27
+
28
+ File.exists?(target).should be_true
29
+ File.read(target).should =~ /<html>.*Google/im
30
+ end
31
+ end
32
+
33
+ it "should download a web page to current directory" do
34
+ @a.mktempdircd do
35
+ source = "http://www.google.com/"
36
+ target = "www.google.com"
37
+
38
+ @a.download(source).should be_true
39
+
40
+ File.exists?(target).should be_true
41
+ File.read(target).should =~ /<html>.*Google/im
42
+ end
43
+ end
44
+ end