automate-it 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/README.txt ADDED
@@ -0,0 +1,40 @@
1
+ == AutomateIt
2
+
3
+ <em>AutomateIt is an open source tool for automating the setup and maintenance of servers, applications and their dependencies.</em>
4
+
5
+ 1. http://AutomateIt.org -- website explaining what it is and why it's useful
6
+ 2. Screenshots[http://AutomateIt.org/screenshots] -- quick tour of sample AutomateIt code
7
+ 3. TUTORIAL.txt[link:files/TUTORIAL_txt.html] -- hands-on tutorial
8
+
9
+ === Frequently-used commands
10
+
11
+ Execute these from a terminal, use <tt>--help</tt> option for help:
12
+ * +automateit+ or +ai+ -- Run a recipe or create a project.
13
+ * +aitag+ -- Query project's tags.
14
+ * +aifield+ -- Query project's fields.
15
+
16
+ === Frequently-used classes
17
+
18
+ * AutomateIt::Interpreter -- Runs AutomateIt commands.
19
+ * AutomateIt::Project -- Collection of related recipes, tags, fields and custom plugins.
20
+ * AutomateIt::AccountManager -- Manipulates users and groups.
21
+ * AutomateIt::AddressManager -- Manipulates host's network addresses.
22
+ * AutomateIt::DownloadManager -- Downloads files.
23
+ * AutomateIt::EditManager::EditSession -- Commands for editing files.
24
+ * AutomateIt::FieldManager -- Queries configuration variables.
25
+ * AutomateIt::PackageManager -- Manipulates software packages.
26
+ * AutomateIt::PlatformManager -- Queries platform, such as its OS version.
27
+ * AutomateIt::ServiceManager -- Manipulates services, such as Unix daemons.
28
+ * AutomateIt::ShellManager -- Manipulates files and executes Unix commands.
29
+ * AutomateIt::TagManager -- Groups hosts by role and queries membership.
30
+ * AutomateIt::TemplateManager -- Renders templates to files.
31
+
32
+ === Legal
33
+
34
+ Copyright (C) 2007-2008 Igal Koshevoy (igal@pragmaticraft.com)
35
+
36
+ AutomateIt is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
37
+
38
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
39
+
40
+ You should have received a copy of the GNU General Public License along with this program. If not, see: http://www.gnu.org/licenses/
data/Rakefile ADDED
@@ -0,0 +1,256 @@
1
+ task :default => :spec
2
+
3
+ #---[ Wrappers ]--------------------------------------------------------
4
+
5
+ # Return an AutomateIt interpreter
6
+ def automateit
7
+ return @automateit ||= begin
8
+ $LOAD_PATH.unshift('lib')
9
+ require 'automateit'
10
+ AutomateIt.new
11
+ end
12
+ end
13
+
14
+ # Run a hoe +task+.
15
+ def hoe(task)
16
+ # XXX Hoe provides many tasks I don't need, don't like the implementation of,
17
+ # or don't like their names. I'd use Rake's 'import' and 'invoke' but the Hoe
18
+ # tasks have names that clash with the ones in this Rakefile. The lame
19
+ # workaround is to invoke Rake via shell, rather than through Ruby.
20
+ sh "rake -f Hoe.rake #{task}"
21
+ end
22
+
23
+ #---[ RSpec ]-----------------------------------------------------------
24
+
25
+ # Run rspec on the +files+
26
+ def specify(*files)
27
+ require 'rubygems'
28
+ require 'spec/rake/spectask'
29
+ Spec::Rake::SpecTask.new(:spec_internal) do |t|
30
+ t.rcov = @rcov
31
+ t.rcov_opts = ['--text-summary', '--include', 'lib', '--exclude', 'spec,.irbrc']
32
+ t.spec_files = FileList[*files]
33
+ end
34
+
35
+ Rake::Task[:spec_internal].invoke
36
+
37
+ # Change the ownership of the newly-created coverage directory back to that
38
+ # of the user which owns the top-level directory.
39
+ if @rcov
40
+ Rake::Task[:chown].invoke
41
+ end
42
+ end
43
+
44
+ desc "Run the unit test suite"
45
+ task "spec" do
46
+ target = ENV['F'] || ENV['FILE'] || 'spec/unit/**/*_spec.rb'
47
+ specify(target)
48
+ end
49
+
50
+ desc "Generate a code coverage report for the unit tests in the 'coverage' directory"
51
+ task "rcov" do
52
+ @rcov = true
53
+ Rake::Task["spec"].invoke
54
+ end
55
+
56
+ desc "Run all the test suites, including unit and integration"
57
+ task "spec:all" do
58
+ puts "=> Running integration test suite. This may take a few minutes and nothing may seem to be happening for a while -- this is normal and expected."
59
+ specify('spec/unit/**/*_spec.rb', 'spec/functional/**/*_spec.rb', 'spec/integration/**/*_spec.rb')
60
+ end
61
+
62
+ desc "Generate a code coverage report for the unit and integration tests"
63
+ task "rcov:all" do
64
+ @rcov = true
65
+ Rake::Task["spec:all"].invoke
66
+ end
67
+
68
+ desc "Print verbose descriptions while running specs"
69
+ task "verbose" do
70
+ ENV["SPEC_OPTS"] = "-fs"
71
+ end
72
+
73
+ desc "Profile the specs"
74
+ task :prof do
75
+ sh "ruby-prof -f prof.txt `which spec` spec/unit/*.rb"
76
+ end
77
+
78
+ #---[ Lines of code ]---------------------------------------------------
79
+
80
+ class Numeric
81
+ def commify() (s=self.to_s;x=s.length;s).rjust(x+(3-(x%3))).gsub(/(\d)(?=\d{3}+(\.\d*)?$)/,'\1,').strip end
82
+ end
83
+
84
+ namespace :loc do
85
+ desc "Display lines of code using loccount"
86
+ task :count do
87
+ sh "loccount helpers/* bin/* lib/ spec/ examples/ *.rake"
88
+ end
89
+
90
+ desc "Display the lines of code changed in the repository"
91
+ task :diff do
92
+ if File.directory?(".hg")
93
+ puts "%s lines added and removed through SCM operations" % `hg log --patch`.scan(/^[+-][^+-].+/).size.commify
94
+ else
95
+ raise NotImplementedError.new("Sorry, this only works for a Mercurial checkout")
96
+ end
97
+ end
98
+
99
+ desc "Display lines of churn"
100
+ task :churn do
101
+ automateit # Load libraries
102
+ puts "%s lines of Hg churn" % (`hg churn`.scan(/^[^\s]+\s+(\d+)\s/).flatten.map(&:to_i).sum).commify
103
+ end
104
+
105
+ desc "Display lines of code based on sloccount"
106
+ task :sloc do
107
+ sh "sloccount lib spec misc examples bin helpers"
108
+ end
109
+ end
110
+
111
+ desc "Display the lines of source code and how many lines were changed in the repository"
112
+ task :loc => ["loc:count", "loc:diff", "loc:churn", "loc:sloc"]
113
+
114
+ #---[ RubyGems ]--------------------------------------------------------
115
+
116
+ desc "Generate manifest"
117
+ task :manifest do
118
+ hoe(:manifest)
119
+ end
120
+
121
+ desc "RFC-822 time for right now, optional D=x where x is delta like '1.day' ago"
122
+ task :now do
123
+ automateit # Loads libraries
124
+ time = Time.now
125
+ if delta = ENV["D"]
126
+ time = eval "time - #{delta}"
127
+ end
128
+ puts time.to_s(:rfc822)
129
+ end
130
+
131
+ desc "RFC-822 time for yesterday"
132
+ task :yesterday do
133
+ automateit # Loads libraries
134
+ time = Time.now - 1.day
135
+ puts time.to_s(:rfc822)
136
+ end
137
+
138
+ namespace :gem do
139
+ desc "View Gem metadata"
140
+ task :metadata do
141
+ sh "cd pkg/; tar xvf *.gem; gunzip *.gz; less metadata"
142
+ end
143
+ end
144
+
145
+ desc "Create a gem"
146
+ task :gem => [:manifest] do
147
+ hoe(:gem)
148
+ end
149
+
150
+ desc "Release gem to RubyForge"
151
+ task :release do
152
+ automateit # Loads libraries
153
+ hoe("release VERSION=#{AutomateIt::VERSION}")
154
+ end
155
+
156
+ desc "Public docs to RubyForge"
157
+ task :publish_docs do
158
+ hoe("publish_docs")
159
+ end
160
+
161
+ desc "Tag a stable release"
162
+ task :tag do
163
+ automateit # Loads libraries
164
+ sh "hg tag #{AutomateIt::VERSION}"
165
+ sh "hg tag -f stable"
166
+ end
167
+
168
+ desc "Push a stable release to local repo for uploading"
169
+ task :push do
170
+ sh "hg push -r stable ../app_stable"
171
+ end
172
+
173
+ #---[ Install and uninstall ]-------------------------------------------
174
+
175
+ =begin
176
+ # Uninstall is similar to:
177
+ gem uninstall -a -x automateit
178
+ rm -rf /usr/lib/ruby/gems/*/gems/automateit-*/ /usr/bin/{automateit,field_lookup} /usr/lib/ruby/gems/*/doc/automateit-*/
179
+
180
+ # Install is similar to:
181
+ gem install -y pkg/automateit-*.gem --no-ri --no-rdoc
182
+ =end
183
+
184
+ namespace :install do
185
+ desc "Install Gem from 'pkg' dir without docs, removing existing Gem first"
186
+ task :local do
187
+ Rake::Task[:uninstall].invoke
188
+ #sh "sudo gem install -y pkg/*.gem --no-ri --no-rdoc"
189
+ puts automateit.package_manager.install({"automateit" => Dir["pkg/*.gem"].first}, :with => :gem, :docs => false)
190
+ end
191
+
192
+ desc "Install Gem from RubyForge without docs, removing existing Gem first"
193
+ task :rubyforge do
194
+ install_wrapper "http://gems.rubyforge.org"
195
+ end
196
+
197
+ task :rf => :rubyforge
198
+
199
+ desc "Install Gem from website without docs, removing existing Gem first"
200
+ task :site do
201
+ install_wrapper "http://automateit.org/pub", :source => "http://automateit.org/pub", :reset => true
202
+ end
203
+
204
+ # Options:
205
+ # * :url -- URL to clear
206
+ # * :opts -- Hash to pass to PackageManager#install
207
+ def install_wrapper(url, opts={})
208
+ Rake::Task[:uninstall].invoke
209
+ sh "gem sources -r #{url}" rescue nil if opts.delete(:reset)
210
+ opts[:with] ||= :gem
211
+ opts[:docs] ||= false
212
+ automateit.package_manager.install("automateit", opts)
213
+ end
214
+ end
215
+
216
+ desc "Uninstall automateit gem"
217
+ task :uninstall do
218
+ automateit.package_manager.uninstall("automateit", :with => :gem)
219
+ end
220
+
221
+ #---[ RDoc ]------------------------------------------------------------
222
+
223
+ namespace :rdoc do
224
+ desc "List aliased_methods for inclusion into rdoc"
225
+ task :aliased_methods do
226
+ automateit.instance_eval do
227
+ methods_and_plugins = plugins.values.inject([]){|results,plugin| plugin.aliased_methods && plugin.aliased_methods.each{|method| results << [method.to_s, plugin.class.to_s]}; results}
228
+
229
+ for method, plugin in methods_and_plugins.sort_by{|x| x[0]}
230
+ puts " # * %s -- %s#%s" % [method, plugin, method]
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ desc "Generate documentation"
237
+ task :rdoc do
238
+ hoe(:docs)
239
+
240
+ # Create a tutorial index
241
+ File.open("doc/tutorial.html", "w+") do |writer|
242
+ writer.write(File.read("doc/index.html").sub(/README_txt.html/, 'TUTORIAL_txt.html'))
243
+ end
244
+ end
245
+
246
+ #---[ Misc ]------------------------------------------------------------
247
+
248
+ desc "Chown files in checkout if needed"
249
+ task :chown do
250
+ if automateit.superuser?
251
+ stat = File.stat("..")
252
+ automateit.chown_R(stat.uid, stat.gid, FileList["*", ".*"], :details => true)
253
+ end
254
+ end
255
+
256
+ #===[ fin ]=============================================================
data/TESTING.txt ADDED
@@ -0,0 +1,57 @@
1
+ == AutomateIt's self-test
2
+
3
+ AutomateIt ensures that it works correctly, and stays working, by using a rigorous self-test suite. This suite is written using the RSpec tool and its files are stored in the software installation's +spec+ directory. You must install the +rake+ and +rspec+ Gems to run the tests.
4
+
5
+ === Types of tests
6
+
7
+ <b>Unit tests</b> are quick and safe because they don't write to the disk or alter your system.
8
+
9
+ For example, <tt>spec/unit/template_manager_erb_spec.rb</tt> exercises AutomateIt::TemplateManager::ERB by rendering templates to memory. Because it doesn't write to the disk, you can safely run it without worrying about it damaging your system.
10
+
11
+ <b>Integration tests</b> are slow and potentially dangerous because they write to disk and alter your system. Great care is taken to make sure these don't cause damage, but because they modify the system, there is a potential risk. These tests are designed to fail before doing anything if they detect that they might cause damage. The tests will clean up after themselves when done to remove any changes made to the system. To put "dangerous" in context, these tests are run before every release, so the likelihood of actual damage is very low.
12
+
13
+ For example, the <tt>spec/integration/account_manager_spec.rb</tt> exercises the AutomateIt::AccountManager by adding and deleting users and groups. To make sure it doesn't cause harm, it uses dummy users with names like +automateit_testuser+. Before it starts the test, it will check for these users and fail the entire test if they're present. This ensures that it won't destroy an existing account in the unlikely event that you have a user with such a name.
14
+
15
+ === Understanding warnings
16
+
17
+ When the tests can't check something, they'll print warning messages. These warnings are normal and expected. They are *not* errors and do not imply that something is broken.
18
+
19
+ For example, if you run the integration test suite as a non-root user, it will warn you that it can't test some commands because you don't have the necessary privileges:
20
+
21
+ NOTE: Must be root to check 'chown' in ./spec/integration/shell_manager_spec.rb
22
+
23
+ You will also get warnings about drivers that are not available on your platform. For example, Ubuntu systems don't include the YUM package manager, so there's no way to test it on an Ubuntu system. So if you run the integration tests on an Ubuntu system, you'll get a warning like:
24
+
25
+ NOTE: Can't check AutomateIt::PackageManager::YUM on this platform, ./spec/integration/package_manager_spec.rb
26
+
27
+ === Running an individual test
28
+
29
+ Run an individual test, like <tt>spec/unit/template_manager_erb_spec.rb</tt>, by executing the Unix shell command:
30
+
31
+ spec spec/unit/template_manager_erb_spec.rb
32
+
33
+ === Running test suites
34
+
35
+ To run the +unit+ test suite, execute the following command from the Unix shell:
36
+
37
+ rake spec
38
+
39
+ To run all the test suites, including +unit+ and +integration+, execute the following command from the Unix shell:
40
+
41
+ rake spec:all
42
+
43
+ The integration test can take a few minutes and will pause for long periods of time while appearing to do nothing. This is normal, expected and there's nothing that can be done to "fix" this. For example, when testing the PackageManager::Gem driver, the test must wait for the +gem+ program to download fresh package indexes and this can take a long time.
44
+
45
+ === Code coverage reports
46
+
47
+ You can generate a code coverage report for the +unit+ test suite by running:
48
+
49
+ rake rcov
50
+
51
+ Or a report for all the suites, including +unit+ and +integration+, by running:
52
+
53
+ rake rcov:all
54
+
55
+ These tasks will create a +coverage+ directory with the report files.
56
+
57
+ Note that because no single platform can run all the code, you'll never be able to get 100% coverage.
data/TODO.txt ADDED
@@ -0,0 +1,50 @@
1
+ = AutomateIt's todo list
2
+
3
+ === Software
4
+
5
+ Bugs
6
+ * Interpreter -- "def" in recipes isn't visible, why?
7
+ * ShellManager -- #ln_s of a directory source to a specific non-subdirectory name creates a link
8
+ * AccountManager::NSCD -- Uses "ps -ef", needs abstraction, create and use ProcessManager?
9
+ * AccountManager -- Solaris fails 5% of the time on the last spec. WTF?
10
+ * AccountManager -- OpenBSD stalls if it thinks a password's quality sucks. What to do?
11
+ * AccountManager -- OpenBSD fails "should add groups to a user" and "should add users to group".
12
+
13
+ Needs improvement
14
+ * Interpreter -- #unique_methods should use :symbols
15
+ * Interpreter#invoke and HelpfulERB -- Extract error context code into separate, reusable classes
16
+ * FieldManager -- Wrap #lookup("my#deep#non-existent#path") with friendly exceptions
17
+ * TagManager -- Wrap YAML and ERB errors using friendly exceptions
18
+ * ServiceManager -- Write tests for start_and_enable and such
19
+ * Shell -- Expand glob patterns, e.g. chown_R(500, 500, "*")
20
+ * Edit -- Display summary of edits, return with :details as [rv, list]
21
+ * Shell#chperm -- With symbolic mode, wrap `chmod -v` as temporary workaround?
22
+ * Shell#chperm -- Accept varargs as argument, not just string or array
23
+ * ServiceManager -- Create new #stop_and_start, and add new #restart as #tell wrapper
24
+ * PackageManager -- Improve PEAR spec by having it check files with and without channel URL
25
+
26
+ Needs redesign
27
+ * PackageManager -- How to specify command to use? E.g. 'gem1.8', 'python2.5.1' and '/usr/local/bin/perl'. Generalize CPAN driver's approach?
28
+ * PackageManager -- What's a reasonable way to leave out the ':with' option when using a hash argument to install? E.g., sudo ai -e "package_manager.install({'swiftfox-prescott' => '/tmp/swiftfox_3.0b3pre-1_prescott.deb'}, :with => :dpkg)"
29
+ * Shell -- Consistently return single items or arrays, alter specs to match
30
+ * Driver -- How to determine if a manager or driver method is available? Manager#available?, Manager#available and Driver#suitable? only say if it should be a default.
31
+
32
+ New features
33
+ * ScheduleManager -- Design, or write wrapper for RubyCron or such
34
+ * ProcessManger -- Design (provides "ps -ef" and such), add #pgrep and #pmatch methods
35
+ * Shell -- Write #su(user, *command) as a wrapper around #sh
36
+ * Interpeter -- Implement #track_changes, #changed?
37
+ * SourceManager -- Design (e.g. svn wrapper)
38
+
39
+ New drivers
40
+ * PackageManager::FreeBSD_Ports - Implement (or make generic ::Ports? and FreeBSD_Pkg
41
+ * PackageManager -- Fink and MacPorts (or generic ::Ports?)
42
+ * PackageManager -- Upgrade or install specific versions
43
+ * PackageManager::Blastwave -- Implement
44
+ * PackageManager::SunOS_Pkg -- Implement
45
+ * ServiceManager::SMF -- Implement
46
+
47
+ === Website
48
+
49
+ * CSS -- Highlight active section of site
50
+ * Page -- Add error page
data/TUTORIAL.txt ADDED
@@ -0,0 +1,391 @@
1
+ == A hands-on tutorial for learning AutomateIt
2
+
3
+ <em>AutomateIt is an open source tool for automating the setup and maintenance of servers, applications and their dependencies.</em>
4
+
5
+ This hands-on guide will teach you to use AutomateIt and explain where to find more detailed instructions.
6
+
7
+ It's recommended that you see the Screenshots[http://automateit.org/screenshots] of AutomateIt in action to get a quick idea of what AutomateIt is all about.
8
+
9
+ AutomateIt is feature-complete, exceeds the capabilities of similar products, and ensures its quality with a self-test suite. However, this is a young product and users are expected to be technically proficient, willing to accept rough spots, to work through problems and upgrade frequently.
10
+
11
+ Please sign up for RSS change notifications[http://automateit.org/changes] so you know when upgrades are available.
12
+
13
+ === Ruby
14
+
15
+ AutomateIt is written using the Ruby programming language. If you haven't used Ruby, you'll find it easy to learn and a pleasure to use. If you already know the basics of Perl, Python, PHP or Java, you'll be able to pick up Ruby almost instantly. Although AutomateIt provides much of the structure and commands needed, you still need to know the basic Ruby syntax to get by.
16
+
17
+ Some Ruby resources:
18
+ * Ruby's official documentation page: http://www.ruby-lang.org/en/documentation/
19
+ * The online "Ruby user's guide" is a gentle introduction to the Ruby language: http://www.ruby-doc.org/docs/UsersGuide/rg/
20
+ * The online "Ruby syntax" is a condensed, one-page reference of the language's syntax: http://www.ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html
21
+ * The online "Programming Ruby" site provides a fairly detailed walk-through of the language: http://www.ruby-doc.org/docs/ProgrammingRuby/
22
+ * The "Practical Ruby for System Administration" book is a gentle introduction for sysadmins, which may be much easier for them to understand than a book aimed at software engineers, and it covers many system administration automation topics that'll be of use for Automateit recipes: http://www.apress.com/book/view/1590598210
23
+ * The "Programming Ruby" book provides a much more complete language reference than any online reference: http://www.pragmaticprogrammer.com/titles/ruby/index.html
24
+
25
+ === Typographical conventions
26
+
27
+ AutomateIt's technical documentation uses the following typographical conventions:
28
+
29
+ * +automateit+ -- A file, command or variable.
30
+ * <tt>:verbosity</tt> -- A symbol, usually used in an options hash.
31
+ * AutomateIt::ShellManager -- A class, with a link to its documentation. You can also find a link to this class's documentation in the "Classes" pane on the left.
32
+ * AutomateIt::ShellManager#sh -- A method, with a link to its documentation. You can also find a link to this method's documentation in the "Methods" pane on the left.
33
+ * Ruby code:
34
+ puts "Ruby code"
35
+ * Unix shell command, although the prompt may be left off when obvious:
36
+ you@host:myproject> echo "Unix command run from the 'myproject' directory"
37
+ * AutomateIt interactive shell command, although the prompt may be left off when obvious:
38
+ ai> puts "I'm in the Interpreter"
39
+
40
+ === Glossary
41
+
42
+ AutomateIt uses a number of unique terms:
43
+
44
+ * *Recipe* -- A file that contains AutomateIt commands.
45
+ * *Project* -- A special directory that contains related recipes and helper files.
46
+ * *Interpreter* -- The part of AutomateIt that runs commands.
47
+ * *Plugin* -- A part of AutomateIt that describes related features.
48
+ * *Driver* -- An implementation of a plugin. There are often multiple drivers per plugin.
49
+
50
+ === Interactive shell
51
+
52
+ AutomateIt comes with an interactive shell, which is useful for exploring commands and developing recipes.
53
+
54
+ Here's what an interactive shell session looks like (text following the <tt>#</tt> symbol explains the line and is not actually part of the session):
55
+
56
+ you@host:tmp> automateit # Start the AutomateIt interactive shell
57
+ => AutomateIt Shell v0.5.0 # Welcome message from AutomateIt
58
+ ai> puts "Hello world!" # AutomateIt's prompt and a user command
59
+ Hello world! # Output from the "puts" command
60
+ => nil # Return value of the "puts" command
61
+ ai> self.class # Prompt and another user command
62
+ => AutomateIt::Interpreter # Return value of "self.class"
63
+ ai> <CTRL-D> # Press <CTRL-D> to exit the shell
64
+ you@host:tmp> # We're back to the Unix shell
65
+
66
+ === Recipe files
67
+
68
+ The Interpreter can run recipe files that contain AutomateIt commands.
69
+
70
+ For example, create a <tt>/tmp/hello.rb</tt> file that contains:
71
+
72
+ puts "Hello, I'm an #{self.class}"
73
+
74
+ Then run the recipe:
75
+
76
+ you@host:tmp> automateit /tmp/hello.rb
77
+ Hello, I'm an AutomateIt::Interpreter
78
+
79
+ === String evaluation
80
+
81
+ The Interpreter can also evaluate commands as strings:
82
+
83
+ you@host:tmp> automateit -e 'puts self.class'
84
+ AutomateIt::Interpreter
85
+
86
+ === Exploring the Interpreter's unique methods
87
+
88
+ The Interpreter provides some unique methods:
89
+
90
+ ai> unique_methods
91
+ => ["account_manager", "address_manager", "cd", "chmod", ...
92
+
93
+ The names are Interpreter methods. Names ending with +_manager+ are methods for accessing plugins, while the rest are normal methods. The AutomateIt::Interpreter documentation provides a list of these methods and links to their individual documentation.
94
+
95
+ For example, one of the commands listed is +pwd+, which can be used like this:
96
+
97
+ ai> pwd
98
+ => "/tmp"
99
+
100
+ AutomateIt provides many commands that work just like the Unix shell commands you already know, so you'll be productive quickly.
101
+
102
+ === Conditional execution
103
+
104
+ AutomateIt executes only the commands needed to achieve a desired state, which makes recipes repeatable, reusable and maintainable.
105
+
106
+ Eliminating the distinction between setup and maintenance means the same command can perform both tasks without concern for the host's condition. A properly-written recipe can be run and re-run immediately afterwards, and it will do nothing the second time because all changes have already been applied.
107
+
108
+ An example can make this clearer -- again the text following the <tt>#</tt> symbol explains the commands and is not part of the session:
109
+
110
+ you@host:tmp> automateit # Start the AutomateIt interactive shell
111
+ => AutomateIt Shell v0.5.0 # Welcome message
112
+ ai> mkdir "asdfasdf" # Create a directory called "asdfasdf"
113
+ ** mkdir asdfasdf # Message showing that directory was created
114
+ => ["asdfasdf"] # Return value with array of directories created
115
+ ai> mkdir "asdfasdf" # Try to create the same directory again
116
+ => false # Nothing happened, the directory already exists
117
+ ai> rmdir "asdfasdf" # Remove the directory
118
+ ** rmdir asdfasdf # Message showing that directory was removed
119
+ => ["asdfasdf"] # Return value with array of directories removed
120
+ ai> rmdir "asdfasdf" # Try to remove the directory again
121
+ => false # Nothing happened, there's no directory to remove
122
+
123
+ Notice how the second time +mkdir+ was run above, it returned +false+ and didn't create a directory? That's the conditional execution in action, realizing the action has already been performed. Similarly, the +rmdir+ only ran the first time, but returned +false+ and took no action when the directory was already gone. You can use the return values of these commands to write sophisticated logic of your own based on what actions took place.
124
+
125
+ === Plugins
126
+
127
+ AutomateIt uses an extensible plugin architecture to group together related commands:
128
+
129
+ * AutomateIt::AccountManager -- Manipulates users and groups.
130
+ * AutomateIt::AddressManager -- Manipulates host's network addresses.
131
+ * AutomateIt::DownloadManager -- Downloads files.
132
+ * AutomateIt::EditManager -- Edits files and strings.
133
+ * AutomateIt::FieldManager -- Queries configuration variables.
134
+ * AutomateIt::PackageManager -- Manipulates software packages.
135
+ * AutomateIt::PlatformManager -- Queries platform, such as its OS version.
136
+ * AutomateIt::ServiceManager -- Manipulates services, such as Unix daemons.
137
+ * AutomateIt::ShellManager -- Manipulates files and executes Unix commands.
138
+ * AutomateIt::TagManager -- Groups hosts by role and queries membership.
139
+ * AutomateIt::TemplateManager -- Renders templates to files.
140
+
141
+ Plugins can be accessed from the Interpreter like this:
142
+
143
+ ai> shell_manager.pwd
144
+ => "/tmp"
145
+
146
+ The most common plugin methods have aliased shortcuts. For example, +pwd+ is the alias for <tt>shell_manager.pwd</tt>. These are documented in AutomateIt::Interpreter's "Aliased methods."
147
+
148
+ === Drivers
149
+
150
+ Each plugin has one or more drivers that implement its functionality.
151
+
152
+ For example:
153
+
154
+ * AutomateIt::ShellManager -- A plugin for running shell commands.
155
+ * AutomateIt::ShellManager::Portable -- A portable but limited-functionality driver that implements the ShellManager's methods.
156
+ * AutomateIt::ShellManager::Unix -- A full-featured driver implementing ShellManager's methods that only runs on Unix-like systems.
157
+
158
+ === Plugins provide APIs, drivers provide implementations
159
+
160
+ Plugins describe consistent interfaces for related features, and drivers implement this API for different tools.
161
+
162
+ For example, AutomateIt provides a common API for all packaging tools: AutomateIt::PackageManager. It provides drivers for packaging tools like APT, YUM, Gem, Egg and others. To install a package called +foo+ with the +apt+ driver:
163
+
164
+ ai> package_manager.install "foo", :with => :apt
165
+
166
+ AutomateIt will check if +foo+ is installed, install it if needed, or do nothing if the package is present. This API is the same for all packaging tools, making it easy to get work done by using high-level AutomateIt commands instead of cryptic tool-specific commands. Although AutomateIt uses the low-level tools, it uses them with best-practices approaches and hides the senseless complexity from the user.
167
+
168
+ What's the big deal? Consider how one would install packages from the Unix shell. Most packaging tools are pathologically dysfunctional and make it bafflingly difficult to programmatically install or uninstall packages, or tell if a package is installed. Many don't use exit values and require complex output parsing. Others require user-input even when it's obvious that none is needed. Almost all make it necessary to write conditional code because they'll either fail with errors if told to install an existing package, destroy an existing setup, or install duplicate packages. Writing Unix shell code to handle all these quirks is frustrating and risky.
169
+
170
+ Here's a sample Unix shell command for installing a package using one of the simplest, most reasonable tools available -- although note that unlike AutomateIt, this shell command can't handle multiple packages, is slower, can't be previewed, and has no consistent error handling:
171
+
172
+ if dpkg-query -W --showformat '${Status}\n' foo 2>&1 | \
173
+ egrep -q '(^| )installed'; then
174
+ apt-get install -y -q some_package_name < /dev/null
175
+ done
176
+
177
+ Now compare that hideous command to the simple, clear and consistent AutomateIt command before it. AutomateIt's plugins and drivers are easy to install and write. As more are written, more people will hopefully be freed from needlessly convoluted low-level commands, and able to get simple things done simply. AutomateIt's consistent API, multiple drivers, sane defaults and conditional-checking make it easier to write clear and maintainable recipes than using the low-level directly commands from the Unix shell.
178
+
179
+ === Driver auto-detection
180
+
181
+ AutomateIt can automatically detect the most suitable driver for each plugin command.
182
+
183
+ For example, the AutomateIt::PackageManager plugin has drivers called AutomateIt::PackageManager::APT and AutomateIt::PackageManager::YUM. On a Debian system, which uses the <tt>apt-get</tt> packaging tool, AutomateIt will default to using the AutomateIt::PackageManager::APT driver:
184
+
185
+ ai> package_manager.installed? "apache2"
186
+ => true
187
+
188
+ === Using a specific driver
189
+
190
+ Sometimes it's necessary to specify the driver to use. The recommended way to do this is to pass a <tt>:with => :driver_name</tt> option to the plugin command.
191
+
192
+ For example, tell the package manager to use the Gem driver:
193
+
194
+ ai> package_manager.installed? "automateit", :with => :gem
195
+ => true
196
+
197
+ You can also completely bypass the plugin and its auto-detection to directly interact with the driver:
198
+
199
+ ai> package_manager[:gem].installed? "automateit"
200
+ => true
201
+
202
+ === Projects
203
+
204
+ A project is a special directory that contains related recipes and helper files. Although it's possible to run recipe files without a project, a project provides many useful features, described in the AutomateIt::Project documentation.
205
+
206
+ A project is created by specifying the directory to create:
207
+
208
+ you@host:tmp> cd /tmp
209
+ you@host:tmp> automateit --create myproject
210
+ ** mkdir -p myproject
211
+ => Creating AutomateIt project at: myproject
212
+ ** cd myproject
213
+ ** mkdir config
214
+ ** cd config
215
+ => Rendering 'tags.yml' because of it doesn't exist
216
+ => Rendering 'fields.yml' because of it doesn't exist
217
+ => Rendering 'automateit_env.rb' because of it doesn't exist
218
+ ** cd -
219
+ ** mkdir dist
220
+ ** cd dist
221
+ => Rendering 'README.txt' because of it doesn't exist
222
+ ** cd -
223
+ ** mkdir lib
224
+ ** cd lib
225
+ => Rendering 'README.txt' because of it doesn't exist
226
+ ** cd -
227
+ ** mkdir recipes
228
+ ** cd recipes
229
+ => Rendering 'README.txt' because of it doesn't exist
230
+ ** cd -
231
+ => DONE!
232
+
233
+ This creates a <tt>/tmp/myproject</tt> directory with the newly-created project. It contains directories, each with a <tt>README.txt</tt> file explaining the directory's purpose, and individual files like <tt>tags.yml</tt> that contain comments with basic usage instructions.
234
+
235
+ The project creator is an AutomateIt recipe, so it's smart enough to only execute the commands needed. If the project creator is re-run against an existing project, it won't make any changes because none are needed:
236
+
237
+ you@host:tmp> automateit --create myproject
238
+ => Found AutomateIt project at: myproject
239
+ => DONE!
240
+
241
+ === Project recipes
242
+
243
+ To associate a recipe with a project, put it into the project's +recipes+ directory.
244
+
245
+ For example, create a <tt>recipes/hello_project.rb</tt> file with these contents:
246
+
247
+ puts "Hello, this is: " + project
248
+
249
+ And run it:
250
+
251
+ you@host:myproject> automateit recipes/hello_project.rb
252
+ Hello, this is: /tmp/myproject
253
+
254
+ The Interpreter automatically loads the project. The +project+ method contains the project path and is only available when executing a recipe associated with a project.
255
+
256
+ === Fields
257
+
258
+ A project's <tt>config/fields.yml</tt> file is meant to store configuration constants, like custom paths for applications. Fields abstract configuration variables for recipes, improving maintainability by separating data and logic. Fields can also be easily queried from Unix using the +aifield+ command. More information about fields and +aifield+ can be found in AutomateIt::FieldManager.
259
+
260
+ For example, consider a fields file with the following contents:
261
+
262
+ myuser: dhh
263
+ myapp:
264
+ path: /var/www/rails
265
+
266
+ These fields can be used from the interactive shell like this:
267
+
268
+ you@host:myproject> pwd
269
+ /tmp/myproject
270
+ you@host:myproject> automateit -p . # (1) Load project from current directory
271
+ => AutomateIt Shell v0.5.0
272
+ ai> lookup :myuser # (2) Lookup string value by symbol key
273
+ => "dhh"
274
+ ai> lookup "myuser" # (3) Lookup string value by string key
275
+ => "dhh"
276
+ ai> lookup "myapp" # (4) Lookup hash value by string key
277
+ => {"path"=>"/var/www/rails"}
278
+ ai> lookup "myapp#path" # (5) Lookup string value by compound key
279
+ => "/var/www/rails"
280
+
281
+ To load a project's fields, the AutomateIt interactive shell needs to know which project to load. On the line annotated (1), <tt>automateit -p .</tt> tells it to load the project from the "." directory, the current directory. The argument can be an absolute or relative path. The command accepts other arguments and environmental options, run <tt>automateit --help</tt> for details.
282
+
283
+ Fields can be queried by string (2) or symbol (3) keys. The +lookup+ command can return any associated data, usually a string, but it can be a complex type, such as a hash (4). The compound-key (5) syntax is a convenient syntax for looking up nested keys in a hash. For example, <tt>lookup "myapp#path"</tt> is a shortcut for <tt>lookup("myapp")["path"] -- these query the +myapp+ hash and return the value of its +path+ key.
284
+
285
+ The Interpreter loads a project and its fields automatically for recipes, so there is no need to specify the <tt>-p</tt> option. For example, a project recipe file <tt>recipes/hello_fields.rb</tt> contains:
286
+
287
+ puts lookup("myapp#path")
288
+
289
+ This recipe will load its project and fields automatically:
290
+
291
+ you@host:myproject> automateit recipes/hello_fields.rb
292
+ /var/www/rails
293
+
294
+ === Tags
295
+
296
+ A project's <tt>config/tags.yml</tt> file describes tags assigned to hosts, grouping together hosts by their roles and attributes.
297
+
298
+ For example, this tags file describes a "desktops" tag with three hosts named "satori", "sunyata" and "michiru", and another tag named "notebooks" with other hosts:
299
+ desktops:
300
+ - satori
301
+ - sunyata
302
+ - michiru
303
+ notebooks:
304
+ - rheya
305
+ - avijja
306
+
307
+ The tags can be accessed like this when run on a host named "satori":
308
+
309
+ you@satori:myproject> automateit -p .
310
+ ai> tags
311
+ => ["satori", "desktops", "localhost", ...] # Tags for this host
312
+ ai> tagged?("desktops") # Is this host tagged with "desktops"?
313
+ => true
314
+ ai> tagged?("notebooks")
315
+ => false
316
+ ai> tagged?(:satori) # Strings and symbols are treated the same.
317
+ => true
318
+ ai> tagged?("satori")
319
+ => true
320
+ ai> tagged?("satori || desktops") # Query by simple boolean expression
321
+ => true
322
+ ai> tagged?("(satori || desktops) && !notebooks") # Query by complex boolean expression
323
+ => true
324
+
325
+ Role-based behavior can be demonstrated by creating a file called <tt>recipes/hello_tags.rb</tt>:
326
+
327
+ if tagged?("localhost")
328
+ puts "I'm a localhost!"
329
+ end
330
+ if tagged?("desktops")
331
+ puts "Special commands to execute on desktops"
332
+ elsif tagged?("notebooks")
333
+ puts "Special commands to execute on notebooks"
334
+ end
335
+
336
+ Here are the results when executed on a host called "satori":
337
+
338
+ you@satori:myproject> automateit recipes/hello_tags.rb
339
+ I'm a localhost!
340
+ Special commands to execute on desktops
341
+
342
+ Notice how the +notebooks+ section wasn't executed? Tags make it possible to create sophisticated recipes that run commands on only the appropriate systems.
343
+
344
+ More documentation on tags can be found in AutomateIt::TagManager.
345
+
346
+ === Previewing commands
347
+
348
+ AutomateIt lets you preview commands the recipe will run without actually letting them actually modify the system.
349
+
350
+ The Interpreter has a boolean that determines if it'll make changes to your system. This one variable can be called from two different ways:
351
+ * <tt>writing?</tt> -- Will it write changes?
352
+ * <tt>noop?</tt> -- Will it not write changes and just preview them? The +noop+ means "no-operation"
353
+
354
+ Here's an example with the interactive shell, with comments for annotations:
355
+
356
+ ai> noop true # Enter noop mode
357
+ ai> noop? # Currently in noop mode?
358
+ => true # Yes, in noop mode
359
+ ai> writing? # Writing to disk? The opposite of noop
360
+ => false # No, not writing
361
+ ai> mkdir "foo" # Try to create a directory
362
+ ** mkdir foo # Message showing directory will be creating
363
+ => ["foo"] # Return value with directories to create
364
+ ai> File.directory? "foo" # Was the directory actually made?
365
+ => false # No, noop mode prevented the change
366
+
367
+ Recipes can take advantage of the noop mode as well, consider a <tt>mkdir_example.rb</tt> recipe:
368
+
369
+ mkdir "foo"
370
+
371
+ To run this recipe with noop mode, pass the <tt>-n</tt> option to the Interpreter shell:
372
+
373
+ you@host:tmp> automateit -n mkdir_example.rb
374
+ ** mkdir foo
375
+ you@host:tmp> ls foo
376
+ ls: foo: No such file or directory
377
+
378
+ Notice how the directory wasn't actually created? This is great because you can see exactly what commands AutomateIt will run without actually having it apply the changes.
379
+
380
+ *WARNING*: Previewing code can be dangerous. Read
381
+ previews.txt[link:files/docs/previews_txt.html] for instructions on how to
382
+ write code that can be safely previewed.
383
+
384
+ === Conclusion
385
+
386
+ I hope you enjoy working with AutomateIt and look forward to hearing about your
387
+ experiences with it. Drivers, patches, documentation and ideas are welcome.
388
+
389
+ Thank you for taking the time to read this!
390
+
391
+ - Igal Koshevoy (igal@pragmaticraft.com)