marionetta 0.4.0 → 0.4.1

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.
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