marionetta 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -59,7 +59,7 @@ of servers:
59
59
  ``` ruby
60
60
  require 'marionetta/group'
61
61
 
62
- servers = Marionetta::Group.new
62
+ servers = Marionetta::Group.new(:production)
63
63
 
64
64
  servers.add_server do |s|
65
65
  s[:hostname] = 'ubuntu@example.com'
@@ -102,7 +102,7 @@ Marionetta to orchestrate a number instances.
102
102
  ``` ruby
103
103
  require 'marionetta/group'
104
104
 
105
- servers = Marionetta::Group.new
105
+ servers = Marionetta::Group.new(:production)
106
106
 
107
107
  servers.add_server do |s|
108
108
  s[:hostname] = 'ubuntu@example.com'
data/Rakefile CHANGED
@@ -34,11 +34,11 @@ task(:publish => :gem) do
34
34
  system(cmd.join(' && '))
35
35
  end
36
36
 
37
- task(:docs) do
37
+ task(:doc) do
38
38
  docs_cmd = [
39
39
  'rm -rf docs',
40
40
  'cd lib',
41
- 'rocco -o ../docs -l ruby marionetta.rb',
41
+ 'rocco -o ../docs -l ruby {*,*/*,*/*/*}.rb',
42
42
  ]
43
43
  system(docs_cmd.join(' && '))
44
44
  end
@@ -1,14 +1,40 @@
1
+ # `CommandRunner` is the beast behind Marionetta. It has a
2
+ # number of methods for executing commands both locally and
3
+ # remotely.
4
+ #
5
+ # The external requirement for this file is `open4` so that
6
+ # we can easily capture output of commands executed.
7
+ #
1
8
  require 'open4'
2
9
 
3
10
  module Marionetta
4
11
  class CommandRunner
5
- attr_reader :server
6
- attr_reader :last
7
12
 
13
+ ### Server hash requirements
14
+
15
+ # The most important requirement is `:logger`. All methods
16
+ # depend upon this property being set since `.system()`
17
+ # (the local command execution method) logs commands,
18
+ # outputs and fatal exceptions using it. It should
19
+ # implement the ruby stdlib `Logger` interface.
20
+ #
21
+ # Other requirements will be listed with their appropriate
22
+ # methods.
23
+ #
8
24
  def initialize(server)
9
25
  @server = server
10
26
  end
11
27
 
28
+ ### Local execution
29
+
30
+ # Local commands are executed with `.system()`. You can
31
+ # optionally pass in a block which receives `stdout` and
32
+ # `stderr` as arguments:
33
+ #
34
+ # cmd.system('ls ~') do |out, err|
35
+ # puts out
36
+ # end
37
+ #
12
38
  def system(*args)
13
39
  @last = args.join(' ')
14
40
  server[:logger].info(last)
@@ -31,26 +57,32 @@ module Marionetta
31
57
  return status.exitstatus == 0
32
58
  end
33
59
 
34
- def rsync(from, to)
35
- rsync_cmd = [server[:rsync][:command]]
36
-
37
- if server[:rsync].has_key?(:flags)
38
- rsync_cmd << server[:rsync][:flags]
39
- end
40
-
41
- rsync_cmd << [from, to]
42
-
43
- system(*rsync_cmd.flatten)
44
- end
45
-
46
- def get(file_path, save_to = File.dirname(file_path))
47
- rsync("#{server[:hostname]}:#{file_path}", save_to)
48
- end
49
-
50
- def put(file_path, save_to = File.dirname(file_path))
51
- rsync(file_path, "#{server[:hostname]}:#{save_to}")
52
- end
60
+ # The last command run by `.system()` is accessible via
61
+ # the `.last` attribute.
62
+ #
63
+ attr_reader :last
53
64
 
65
+ ### Remote execution
66
+
67
+ # Requirements for remote executions `server[:hostname]`
68
+ # must be set along with `server[:ssh][:command]`.
69
+ # Optionally `server[:ssh][:flags]` can be used to pass in
70
+ # flags such as `-i` for setting SSH keys.
71
+ #
72
+ # A block can be called against this method just like
73
+ # `.system()` in order to get `stdout` and `stderr`.
74
+ #
75
+ # An example:
76
+ #
77
+ # server = Marionetta.default_server
78
+ # server[:hostname] = 'example.com'
79
+ # server[:ssh][:flags] << ['-i', 'keys/private.key']
80
+ #
81
+ # cmd = Marionetta::CommandRunner.new(server)
82
+ # cmd.ssh('ls -l') do |out, err|
83
+ # puts out
84
+ # end
85
+ #
54
86
  def ssh(command, &block)
55
87
  ssh_cmd = [server[:ssh][:command]]
56
88
 
@@ -99,5 +131,29 @@ module Marionetta
99
131
 
100
132
  ssh(cmds.join(' && '))
101
133
  end
134
+
135
+ def rsync(from, to)
136
+ rsync_cmd = [server[:rsync][:command]]
137
+
138
+ if server[:rsync].has_key?(:flags)
139
+ rsync_cmd << server[:rsync][:flags]
140
+ end
141
+
142
+ rsync_cmd << [from, to]
143
+
144
+ system(*rsync_cmd.flatten)
145
+ end
146
+
147
+ def get(file_path, save_to = File.dirname(file_path))
148
+ rsync("#{server[:hostname]}:#{file_path}", save_to)
149
+ end
150
+
151
+ def put(file_path, save_to = File.dirname(file_path))
152
+ rsync(file_path, "#{server[:hostname]}:#{save_to}")
153
+ end
154
+
155
+ private
156
+
157
+ attr_reader :server
102
158
  end
103
159
  end
@@ -1,3 +1,11 @@
1
+ # `Debloyer` was a way of deploying your application using
2
+ # .deb files. However I quickly realised using the .deb format
3
+ # was limiting since you can only install them on debian and
4
+ # ubuntu. Also it's an inefficient way of copying a folder to
5
+ # another system!
6
+ #
7
+ # **This class is deprecated please take a look at `Deployer`.**
8
+ #
1
9
  require 'marionetta/command_runner'
2
10
 
3
11
  module Marionetta
@@ -1,18 +1,44 @@
1
+ # `Deployer` is a class for rsyncing your application to a
2
+ # remote machine.
3
+ #
4
+ # Using a directory structure similar to capistrano `Deployer`
5
+ # maintains a folder of releases so you may rollback quickly.
6
+ #
1
7
  require 'marionetta/command_runner'
2
8
 
3
9
  module Marionetta
4
10
  module Manipulators
5
11
  class Deployer
12
+
13
+ ### RakeHelper tasks
14
+
15
+ # `Deployer` provides two rake tasks when used with
16
+ # `RakeHelper` namely `:deploy` and `:rollback`. When
17
+ # applied through `RakeHelper` they will appear
18
+ # namespaced under `:deployer` and your group name.
19
+ #
20
+ # With a group name of `:staging` would appear as:
21
+ #
22
+ # deployer:staging:deploy
23
+ # deployer:staging:rollback
24
+ #
6
25
  def self.tasks()
7
26
  [:deploy, :rollback]
8
27
  end
9
-
10
- attr_writer :cmd
11
28
 
29
+ ### Server hash requirements
30
+
31
+ # The keys `[:deployer][:from]` and `[:deployer][:to]`
32
+ # must be set in your `server` hash in order for
33
+ # `Deployer` to work.
34
+ #
12
35
  def initialize(server)
13
36
  @server = server
14
37
  end
15
38
 
39
+ # Call `.can?()` to check if the correct keys have be
40
+ # passed in as the server.
41
+ #
16
42
  def can?()
17
43
  d = server[:deployer]
18
44
 
@@ -23,11 +49,29 @@ module Marionetta
23
49
  end
24
50
  end
25
51
 
52
+ ### Deploying
53
+
54
+ # Call `.deploy()` to run a deploy to your remote
55
+ # server. The process involves:
56
+ #
57
+ # - `:from` directory copied to temporary directory
58
+ # - `:exclude` files are removed
59
+ # - rsync'd to a releases directory
60
+ # - `:before_script` run
61
+ # - release directory symlinked to a current directory
62
+ # - `:after_script` run
63
+ #
64
+ # The directory structure under `server[:deployer][:to]`
65
+ # looks something like this:
66
+ #
67
+ # current/ -> ./releases/2012-09-20_14:04:39
68
+ # releases/
69
+ # 2012-09-20_13:59:15
70
+ # 2012-09-20_14:04:39
71
+ #
26
72
  def deploy()
27
73
  release = timestamp
28
-
29
74
  create_tmp_release_dir(release)
30
-
31
75
  cmd.ssh("mkdir -p #{release_dir}")
32
76
 
33
77
  unless cmd.put(tmp_release_dir(release), release_dir)
@@ -39,6 +83,10 @@ module Marionetta
39
83
  run_script(:after, release)
40
84
  end
41
85
 
86
+ # To get an array of all releases call `.releases()`.
87
+ # Any release that is subsequently rolled back will not
88
+ # be listed.
89
+ #
42
90
  def releases()
43
91
  releases = []
44
92
 
@@ -51,6 +99,10 @@ module Marionetta
51
99
  return releases
52
100
  end
53
101
 
102
+ # If you push out and need to rollback to the previous
103
+ # version you can use `.rollback()` to do just that.
104
+ # Currently you can only rollback once at a time.
105
+ #
54
106
  def rollback()
55
107
  rollback_to_release = releases[-2]
56
108
 
@@ -63,6 +115,13 @@ module Marionetta
63
115
  symlink_release_dir(rollback_to_release)
64
116
  end
65
117
  end
118
+
119
+ ### Dependency Injection
120
+
121
+ # To use your own alternative to `CommandRunner` you can
122
+ # set an object of your choice via the `.cmd=` method.
123
+ #
124
+ attr_writer :cmd
66
125
 
67
126
  private
68
127
 
@@ -77,7 +136,7 @@ module Marionetta
77
136
  end
78
137
 
79
138
  def tmp_release_dir(release)
80
- "/tmp/#{release}"
139
+ "/tmp/#{server[:hostname]}/#{release}"
81
140
  end
82
141
 
83
142
  def to_dir()
@@ -113,12 +172,19 @@ module Marionetta
113
172
 
114
173
  def create_tmp_release_dir(release)
115
174
  tmp_release_dir = tmp_release_dir(release)
116
- cmd.system("cp -r #{from_dir} #{tmp_release_dir}")
175
+
176
+ create_tmp_dir_cmds = [
177
+ "mkdir -p #{File.dirname(tmp_release_dir)}",
178
+ "cp -rf #{from_dir} #{tmp_release_dir}",
179
+ ]
180
+ cmd.system(create_tmp_dir_cmds.join(' && '))
117
181
 
118
182
  if server[:deployer].has_key?(:exclude)
119
183
  exclude_files = server[:deployer][:exclude]
120
184
  exclude_files.map! {|f| Dir["#{tmp_release_dir}/#{f}"]}
121
- cmd.system("rm -rf #{exclude_files.flatten.join(' ')}")
185
+ exclude_files.flatten!
186
+
187
+ cmd.system("rm -rf #{exclude_files.join(' ')}") unless exclude_files.empty?
122
188
  end
123
189
  end
124
190
 
@@ -1,37 +1,83 @@
1
+ # `PuppetManipulator` copies a puppet manifest and optionally
2
+ # modules to a remote machine and applies them.
3
+ #
4
+ # You could do this with a puppet master instance, and that
5
+ # could (and most likely is) the right option for you. However
6
+ # if you do not want to host an additional node as your puppet
7
+ # master or want to push changes from your machine directly to
8
+ # nodes them this class maybe what you're looking for.
9
+ #
1
10
  require 'marionetta/command_runner'
2
11
 
3
12
  module Marionetta
4
13
  module Manipulators
5
14
  class PuppetManipulator
15
+
16
+ ### RakeHelper tasks
17
+
18
+ # `PupperManipulator` provides two rake tasks when used
19
+ # with `RakeHelper` namely `:install` and `:update`.
20
+ # When applied through `RakeHelper` they will appear
21
+ # namespaced under `:puppet` and your group name.
22
+ #
23
+ # With a group name of `:staging` would appear as:
24
+ #
25
+ # puppet:staging:install
26
+ # puppet:staging:update
27
+ #
6
28
  def self.tasks()
7
29
  [:install, :update]
8
30
  end
9
31
 
10
- attr_writer :cmd
11
-
32
+ ### Server hash requirements
33
+
34
+ # The key `[:puppet][:manifest]` must be set in your
35
+ # `server` hash in order for `PuppetManipulator` to
36
+ # function correctly.
37
+ #
12
38
  def initialize(server)
13
39
  @server = server
14
40
  end
15
41
 
42
+ # Call `.can?()` to check if the `:manifest` key has
43
+ # been set in the `server[:puppet]`.
44
+ #
16
45
  def can?()
17
46
  server[:puppet].has_key?(:manifest)
18
47
  end
19
48
 
49
+ ### Installing puppet
50
+
51
+ # `PuppetManipulator` provides the `.install()` method
52
+ # to install puppet on debian or ubuntu servers.
53
+ #
20
54
  def install()
21
55
  install_deb_repo
22
56
  install_deb
23
57
  end
24
58
 
25
- def installed?()
26
- cmd.ssh('which puppet')
27
- end
59
+ ### Updating puppet
28
60
 
61
+ # Use `.update()` to package up your manifest and
62
+ # optionally modules and send them to your remote
63
+ # machine. Once there they will be applied.
64
+ #
65
+ # If puppet is not installed, we attempt to install it
66
+ # before applying the manifest.
67
+ #
29
68
  def update()
30
69
  install unless installed?
31
70
  archive_files
32
71
  send_archive
33
72
  apply_archive
34
73
  end
74
+
75
+ ### Dependency Injection
76
+
77
+ # To use your own alternative to `CommandRunner` you can
78
+ # set an object of your choice via the `.cmd=` method.
79
+ #
80
+ attr_writer :cmd
35
81
 
36
82
  private
37
83
 
@@ -41,6 +87,10 @@ module Marionetta
41
87
  @cmd ||= CommandRunner.new(server)
42
88
  end
43
89
 
90
+ def installed?()
91
+ cmd.ssh('which puppet')
92
+ end
93
+
44
94
  def install_deb_repo()
45
95
  deb_file = 'puppetlabs-release-stable.deb'
46
96
 
@@ -64,9 +114,11 @@ module Marionetta
64
114
  cmd.ssh("which puppet || { #{install_cmd}; }")
65
115
  end
66
116
 
67
- def archive_files()
68
- puppet_tmp = '/tmp/puppet'
117
+ def puppet_tmp()
118
+ "/tmp/puppet_#{server[:hostname]}"
119
+ end
69
120
 
121
+ def archive_files()
70
122
  cmds = [
71
123
  "rm -rf #{puppet_tmp}",
72
124
  "mkdir #{puppet_tmp}",
@@ -82,12 +134,12 @@ module Marionetta
82
134
  end
83
135
 
84
136
  def send_archive()
85
- cmd.put('/tmp/puppet.tar.gz')
137
+ cmd.put("#{puppet_tmp}.tar.gz")
86
138
  end
87
139
 
88
140
  def apply_archive()
89
- cmd.ssh_extract('/tmp/puppet.tar.gz')
90
- cmds = ['cd /tmp/puppet']
141
+ cmd.ssh_extract("#{puppet_tmp}.tar.gz")
142
+ cmds = ["cd #{puppet_tmp}"]
91
143
 
92
144
  puppet_cmd = ['sudo puppet apply']
93
145
 
@@ -4,9 +4,9 @@ require 'rake'
4
4
 
5
5
  module Marionetta
6
6
  module RakeHelper
7
- extend self
8
-
9
7
  include ::Rake::DSL if defined?(::Rake::DSL)
8
+
9
+ extend self
10
10
 
11
11
  def install_group_tasks(group)
12
12
  install_group_tasks_for(group)
data/lib/marionetta.rb CHANGED
@@ -9,12 +9,12 @@
9
9
  # Installing the gem is the best way to start using
10
10
  # Marionetta. You can do this from command line:
11
11
  #
12
- # gem install marionetta
12
+ # gem install marionetta
13
13
  #
14
14
  # Or – better yet – in your Gemfile:
15
15
  #
16
- # source 'http://rubygems.org'
17
- # gem 'marionetta'
16
+ # source 'http://rubygems.org'
17
+ # gem 'marionetta'
18
18
  #
19
19
  # Marionetta is written by [Luke Morton][author] and licensed
20
20
  # under the MIT license. The project is [hosted on github][github]
@@ -23,14 +23,15 @@
23
23
  #
24
24
  # [author]: http://lukemorton.co.uk
25
25
  # [github]: https://github.com/DrPheltRight/marionetta
26
+ #
26
27
  module Marionetta
27
28
 
28
- VERSION = '0.4.0'
29
+ VERSION = '0.4.1'
29
30
 
30
31
  ### Defining Servers
31
32
 
32
33
  # In order to connect to servers you must define configs for
33
- # each. This method provides a default map describing some
34
+ # each. This method provides a default hash describing some
34
35
  # common settings including command binaries, default flags
35
36
  # and more.
36
37
  #
@@ -41,6 +42,10 @@ module Marionetta
41
42
  # elsewhere in Marionetta where you can better define your
42
43
  # servers. You should consult this method in order to see
43
44
  # the defaults.
45
+ #
46
+ # Any place in this library you see a variable called
47
+ # `server` you can be certain it is a server hash.
48
+ #
44
49
  def self.default_server()
45
50
  {
46
51
  :logger => Logger.new($stdout),
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marionetta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-20 00:00:00.000000000 Z
12
+ date: 2012-09-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: open4