necktie 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ 1.0.5 (2009-11-13)
2
+ * Added erb: a file task that creates a file form an ERB template.
3
+ * Changed: The write and update file methods now perform atomic writes (create
4
+ temporary file, rename to desired path).
5
+ * Fix: MySQL examples used task instead of file task.
6
+
1
7
  1.0.4 (2009-11-4)
2
8
  * When used with -U option and no other argument, necktie command updates the
3
9
  local repo but does not run any task.
data/README.rdoc CHANGED
@@ -1,29 +1,22 @@
1
- I use Necktie to setup and upgrade multiple servers.
1
+ = Minimalistic server configuration tool
2
2
 
3
+ Necktie is tool for running configuration tasks: setting up new servers and
4
+ upgrading production servers. You can use it to manage services, mount
5
+ volumes, change configuration, manage cronjobs, etc.
3
6
 
4
- == Dress to impress
5
-
6
- Necktie runs a set of scripts (or configuration tasks) that can create and edit
7
- files, install and setup services, mount volumes, and basically anything you
8
- can script with Ruby and a shell.
9
-
10
- To use Necktie:
11
- - Write your Necktie tasks
12
- - Include support files (config, binaries, etc)
13
- - git push && cap necktie
14
-
15
- I use it to:
16
-
17
- - gem install, apt-get
18
- - Configure Nginx, install and setup Unicorn
19
- - Change memcached configuration from default
20
- - Setup MySQL to use a mounted (EBS) volume
21
- - Install and update cron scripts
7
+ http://19.media.tumblr.com/tumblr_kspcaa4Djr1qzo4cfo1_250.jpg
22
8
 
23
9
  To install Necktie:
24
10
 
25
11
  $ gem install necktie --source http://gemcutter.org
26
12
 
13
+ To use Necktie:
14
+ 1. Write a Necktie file with configurtaion tasks
15
+ 2. Include any support files: config, scripts, binaries, etc
16
+ 3. git push && cap necktie
17
+
18
+ Introduction to Necktie: http://blog.labnotes.org/2009/11/04/necktie-dress-to-impress/
19
+
27
20
 
28
21
  == Tasking
29
22
 
@@ -215,4 +208,8 @@ Includes Session, created by Ara T. Howard and released under the Ruby License
215
208
  http://raa.ruby-lang.org/project/session
216
209
  http://www.codeforpeople.com/lib/ruby/session
217
210
 
211
+ Atomic file write adapted from Rails.
212
+
213
+ ERB task adapted from Luke Bayes http://gist.github.com/215270.
214
+
218
215
  Developed for managing EC2 instances for http://apartly.com
data/example/tasks/db.rb CHANGED
@@ -3,13 +3,13 @@ file "/etc/cron.hourly/snapshot"=>"etc/cron/snapshot" do
3
3
  chmod 0755, "/etc/cron.hourly/snapshot"
4
4
  end
5
5
 
6
- task "/vol" do
6
+ file "/vol" do
7
7
  # Assumes we attach EBS volume to /dev/sdh, formatted it to XFS, mounted to /vol.
8
8
  append "/etc/fstab", "/dev/sdh /vol xfs noatime,nobarrier 0 0\n" unless read("/etc/fstab")["/dev/sdh "]
9
9
  sh "mount /vol"
10
10
  end
11
11
 
12
- task "/etc/mysql"=>"/vol" do
12
+ file "/etc/mysql"=>"/vol" do
13
13
  # Mount the respective MySQL directories. Make sure they exist on your EBS volume first, see:
14
14
  # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1663
15
15
  mounts = { "/vol/etc/mysql"=>"/etc/mysql",
@@ -23,7 +23,7 @@ task "/etc/mysql"=>"/vol" do
23
23
  chmod 0755, "/etc/mysql/debian-start"
24
24
  end
25
25
 
26
- task "mysql"=>"/etc/mysql" do
26
+ task :mysql=>"/etc/mysql" do
27
27
  services.start "mysql" unless services.running?("mysql")
28
28
  end
29
29
 
data/lib/necktie.rb CHANGED
@@ -3,6 +3,7 @@ require "necktie/files"
3
3
  require "necktie/gems"
4
4
  require "necktie/services"
5
5
  require "necktie/rush"
6
+ require "necktie/erb"
6
7
 
7
8
 
8
9
  # Includes all methods from Necktie::Files and Necktie::Gems methods.
@@ -10,7 +10,6 @@ module Necktie
10
10
  @rakefiles = ["Necktie", "necktie", "Necktie.rb", "necktie.rb"]
11
11
  options.nosearch = true
12
12
  options.env = "production"
13
- @syslog = Syslog.open("necktie")
14
13
  end
15
14
 
16
15
  def run
@@ -31,7 +30,7 @@ module Necktie
31
30
  sh "git checkout #{options.ref}" if options.ref
32
31
  @sha = `git rev-parse --verify HEAD --short`.strip
33
32
  puts "(in #{Dir.pwd}, head is #{@sha}, environment is #{options.env})"
34
- syslog :info, "environment is #{options.env}"
33
+ syslog :info, "environment is %s", options.env
35
34
 
36
35
  load_rakefile
37
36
  top_level unless options.pull && ARGV.empty?
@@ -141,8 +140,10 @@ module Necktie
141
140
  load_imports
142
141
  end
143
142
 
144
- def syslog(level, message)
145
- @syslog.send level, "[#{@sha}] #{message.strip.gsub(/%/, '%%')}" # syslog(3) freaks on % (printf)
143
+ def syslog(level, message, *args)
144
+ Syslog.open("necktie") do |s|
145
+ s.send level, "[#{@sha}] #{message}", *args
146
+ end
146
147
  end
147
148
 
148
149
  end
@@ -158,10 +159,10 @@ end
158
159
  class Rake::Task #:nodoc:
159
160
  alias :execute_without_syslog :execute
160
161
  def execute(args=nil)
161
- application.syslog :info, "execute: #{name}"
162
+ application.syslog :info, "execute: %s", name
162
163
  execute_without_syslog args
163
164
  rescue
164
- application.syslog :err, "#{$!.backtrace.first}: #{$!.class}: #{$!}"
165
+ application.syslog :err, "%s: %s: %s", $!.backtrace.first, $!.class.name, $!.to_s
165
166
  raise
166
167
  end
167
168
  end
@@ -13,7 +13,7 @@ Capistrano::Configuration.instance.load do
13
13
  desc "[internal] Pull updates from Git"
14
14
  task :pull do
15
15
  fail "You need to set :necktie_url, <git_url>" unless necktie_url
16
- sudo "necktie --source #{necktie_url} --update"
16
+ sudo "necktie --environment #{fetch(:rails_env, "production")} --source #{necktie_url} --update"
17
17
  end
18
18
 
19
19
  desc "[internal] Run necktie upgrade"
@@ -0,0 +1,66 @@
1
+ require "erb"
2
+
3
+ module Necktie
4
+
5
+ # Rake task that makes it stupid-easy to render ERB templates to disk. Just
6
+ # add parameters to the yielded object, and they will be available to your
7
+ # Template.
8
+ #
9
+ # Loosely based on: http://gist.github.com/215270
10
+ class ErbTask < Rake::FileTask
11
+
12
+ # Path to the input ERB template. This value will default to the value of
13
+ # "#{output}.erb"
14
+ attr_accessor :template
15
+
16
+ def initialize(name, app) # :nodoc:
17
+ super
18
+ @output = name
19
+ @template = "#{name}.erb"
20
+ @values = {}
21
+ end
22
+
23
+ def execute(*args)
24
+ super
25
+ content = File.read(template)
26
+ result = ERB.new(content, nil, '>').result(binding)
27
+ write name, result
28
+ puts " * Created ERB output at: #{name}" if application.options.trace
29
+ end
30
+
31
+ def self.define_task(args)
32
+ task = super
33
+ if task.is_a?(Necktie::ErbTask)
34
+ yield task if block_given?
35
+ task.prerequisites << file(task.template)
36
+ end
37
+ return task
38
+ end
39
+
40
+ def method_missing(method, *params, &block)
41
+ if method.to_s[/^(.*)=$/, 1]
42
+ return @values[$1] = params.first
43
+ elsif @values.keys.include?(method.to_s)
44
+ return @values[method.to_s]
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+ # Rake task that makes it stupid-easy to render ERB templates to disk. Just add
54
+ # parameters to the yielded object, and they will be available to your
55
+ # Template.
56
+ #
57
+ # For example:
58
+ # erb 'config/SomeFile.xml' do |t|
59
+ # t.param1 = 'value'
60
+ # t.other_param = 'other value'
61
+ # t.another_param = ['a', 'b', 'c']
62
+ # t.template = 'config/SomeFile.xml.erb' # Optional - will automatically look here...
63
+ # end
64
+ def erb(args, &block)
65
+ Necktie::ErbTask.define_task(args, &block)
66
+ end
data/lib/necktie/files.rb CHANGED
@@ -1,44 +1,67 @@
1
- module Necktie::Files
2
- # Return the contents of the file (same as File.read).
3
- def read(name)
4
- File.read(name)
5
- end
1
+ require "tempfile"
6
2
 
7
- # Writes contents to a new file, or overwrites existing file.
8
- # Takes string as second argument, or yields to block. For example:
9
- # write "/etc/mailname", "example.com"
10
- # write("/var/run/bowtie.pid") { Process.pid }
11
- def write(name, contents = nil)
12
- contents ||= yield
13
- File.open name, "w" do |f|
14
- f.write contents
3
+ module Necktie
4
+ module Files
5
+ # Return the contents of the file (same as File.read).
6
+ def read(name)
7
+ File.read(name)
15
8
  end
16
- end
17
9
 
18
- # Append contents to a file, creating it if necessary.
19
- # Takes string as second argument, or yields to block. For example:
20
- # append "/etc/fstab", "/dev/sdh /vol xfs\n" unless read("/etc/fstab")["/dev/sdh "]
21
- def append(name, contents = nil)
22
- contents ||= yield
23
- File.open name, "a" do |f|
24
- f.write contents
10
+ # Writes contents to a new file, or overwrites existing file. Takes string
11
+ # as second argument, or yields to block. For example:
12
+ #
13
+ # write "/etc/mailname", "example.com"
14
+ # write("/var/run/bowtie.pid") { Process.pid }
15
+ #
16
+ # This method performs an atomic write using TMPDIR or (/tmp) as the
17
+ # temporary directory and renaming the file over to the new location.
18
+ # Ownership and premission are retained if replacing existing file.
19
+ def write(name, contents = nil)
20
+ temp = Tempfile.new(File.basename(name))
21
+ temp.write contents || yield
22
+ temp.close
23
+
24
+ begin
25
+ # Get original file permissions
26
+ stat = File.stat(name)
27
+ rescue Errno::ENOENT
28
+ # No old permissions, write a temp file to determine the defaults
29
+ stat_check = File.join(File.dirname(name), ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}")
30
+ File.open(stat_check, "w") { }
31
+ stat = File.stat(stat_check)
32
+ File.unlink stat_check
33
+ end
34
+
35
+ # Overwrite original file with temp file (tries rename, if that fails, copies).
36
+ FileUtils.mv temp.path, name
37
+
38
+ # Set correct permissions on new file
39
+ File.chown stat.uid, stat.gid, name
40
+ File.chmod stat.mode, name
25
41
  end
26
- end
27
42
 
28
- # Updates a file: read contents, substitue and write it back.
29
- # Takes two arguments for substitution, or yields to block.
30
- # These two are equivalent:
31
- # update "/etc/memcached.conf", /^-l 127.0.0.1/, "-l 0.0.0.0"
32
- # update("/etc/memcached.conf") { |s| s.sub(/^-l 127.0.0.1/, "-l 0.0.0.0") }
33
- def update(name, from = nil, to = nil)
34
- contents = File.read(name)
35
- if from && to
36
- contents = contents.sub(from, to)
37
- else
38
- contents = yield(contents)
43
+ # Append contents to a file, creating it if necessary. Takes string as
44
+ # second argument, or yields to block. For example:
45
+ # append "/etc/fstab", "/dev/sdh /vol xfs\n" unless read("/etc/fstab")["/dev/sdh "]
46
+ def append(name, contents = nil)
47
+ contents ||= yield
48
+ File.open name, "a" do |f|
49
+ f.write contents
50
+ end
39
51
  end
40
- File.open name, "w" do |f|
41
- f.write contents
52
+
53
+ # Updates a file: read contents, substitue and write it back. Takes two
54
+ # arguments for substitution, or yields to block. These two are equivalent:
55
+ # update "/etc/memcached.conf", /^-l 127.0.0.1/, "-l 0.0.0.0"
56
+ # update("/etc/memcached.conf") { |s| s.sub(/^-l 127.0.0.1/, "-l 0.0.0.0") }
57
+ def update(name, from = nil, to = nil)
58
+ contents = File.read(name)
59
+ if from && to
60
+ contents = contents.sub(from, to)
61
+ else
62
+ contents = yield(contents)
63
+ end
64
+ write name, contents
42
65
  end
43
66
  end
44
67
  end
data/lib/necktie/gems.rb CHANGED
@@ -1,20 +1,22 @@
1
1
  require "rubygems/dependency_installer"
2
2
 
3
- module Necktie::Gems
4
- # Installs the specified gem, if not already installed. First argument is the
5
- # name of the gem, or file containing the gem. Second argument is version requirement.
6
- # For example:
7
- # install_gem "unicorn", "~>0.93"
8
- #
9
- # Dir["gems/*.gem"].each do |gem|
10
- # install_gem gem
11
- # end
12
- def install_gem(name, version = nil)
13
- installer = Gem::DependencyInstaller.new
14
- spec = installer.find_spec_by_name_and_version(name, version).first.first
15
- if Gem::SourceIndex.from_installed_gems.find_name(spec.name, spec.version).empty?
16
- puts " ** Installing the gem #{spec.name} #{spec.version}"
17
- installer.install name, version
3
+ module Necktie
4
+ module Gems
5
+ # Installs the specified gem, if not already installed. First argument is the
6
+ # name of the gem, or file containing the gem. Second argument is version requirement.
7
+ # For example:
8
+ # install_gem "unicorn", "~>0.93"
9
+ #
10
+ # Dir["gems/*.gem"].each do |gem|
11
+ # install_gem gem
12
+ # end
13
+ def install_gem(name, version = nil)
14
+ installer = Gem::DependencyInstaller.new
15
+ spec = installer.find_spec_by_name_and_version(name, version).first.first
16
+ if Gem::SourceIndex.from_installed_gems.find_name(spec.name, spec.version).empty?
17
+ puts " ** Installing the gem #{spec.name} #{spec.version}"
18
+ installer.install name, version
19
+ end
18
20
  end
19
21
  end
20
22
  end
data/necktie.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "necktie"
3
- spec.version = "1.0.4"
3
+ spec.version = "1.0.5"
4
4
  spec.author = "Assaf Arkin"
5
5
  spec.email = "assaf@labnotes.org"
6
6
  spec.homepage = "http://github.com/assaf/necktie"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: necktie
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Assaf Arkin
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-04 00:00:00 -08:00
12
+ date: 2009-11-13 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -26,6 +26,7 @@ files:
26
26
  - bin/necktie
27
27
  - lib/necktie/application.rb
28
28
  - lib/necktie/capistrano.rb
29
+ - lib/necktie/erb.rb
29
30
  - lib/necktie/files.rb
30
31
  - lib/necktie/gems.rb
31
32
  - lib/necktie/rush.rb
@@ -255,7 +256,7 @@ licenses: []
255
256
  post_install_message:
256
257
  rdoc_options:
257
258
  - --title
258
- - Necktie 1.0.4
259
+ - Necktie 1.0.5
259
260
  - --main
260
261
  - README.rdoc
261
262
  - --webcvs