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 +2 -2
- data/Rakefile +2 -2
- data/lib/marionetta/command_runner.rb +77 -21
- data/lib/marionetta/manipulators/debloyer.rb +8 -0
- data/lib/marionetta/manipulators/deployer.rb +73 -7
- data/lib/marionetta/manipulators/puppet_manipulator.rb +62 -10
- data/lib/marionetta/rake_helper.rb +2 -2
- data/lib/marionetta.rb +10 -5
- metadata +2 -2
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(:
|
37
|
+
task(:doc) do
|
38
38
|
docs_cmd = [
|
39
39
|
'rm -rf docs',
|
40
40
|
'cd lib',
|
41
|
-
'rocco -o ../docs -l ruby
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
68
|
-
|
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(
|
137
|
+
cmd.put("#{puppet_tmp}.tar.gz")
|
86
138
|
end
|
87
139
|
|
88
140
|
def apply_archive()
|
89
|
-
cmd.ssh_extract(
|
90
|
-
cmds = [
|
141
|
+
cmd.ssh_extract("#{puppet_tmp}.tar.gz")
|
142
|
+
cmds = ["cd #{puppet_tmp}"]
|
91
143
|
|
92
144
|
puppet_cmd = ['sudo puppet apply']
|
93
145
|
|
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
|
-
#
|
12
|
+
# gem install marionetta
|
13
13
|
#
|
14
14
|
# Or – better yet – in your Gemfile:
|
15
15
|
#
|
16
|
-
#
|
17
|
-
#
|
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.
|
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
|
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.
|
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-
|
12
|
+
date: 2012-09-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: open4
|