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