marionetta 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -39,7 +39,9 @@ servers.each_server do |s|
39
39
  cmd = Marionetta::CommandRunner.new(s)
40
40
 
41
41
  # Send a command via SSH
42
- cmd.ssh('whoami')
42
+ cmd.ssh('whoami') do |out, err|
43
+ puts out.read
44
+ end
43
45
 
44
46
  # Get a file
45
47
  cmd.get('/var/backups/database')
@@ -70,10 +72,11 @@ end
70
72
  servers.manipulate_each_server(:puppet, :update)
71
73
  ```
72
74
 
73
- ## Using the debloyer
75
+ ## Using the deployer
74
76
 
75
- Also included is a .deb deploying manipulator. You can use
76
- this to deploy your application over SSH as a .deb.
77
+ Also included is a deployment mechanism similar to capistrano.
78
+ You can use this to deploy releases of folders from a local
79
+ machine to a remote one over SSH.
77
80
 
78
81
  ``` ruby
79
82
  require 'marionetta/group'
@@ -82,11 +85,25 @@ staging = Marionetta::Group.new(:staging)
82
85
 
83
86
  staging.add_server do |s|
84
87
  s[:hostname] = 'staging.example.com'
85
- s[:debloyer][:from] = '/my-app'
86
- s[:debloyer][:to] = '/home/staging/www'
88
+ s[:deployer][:from] = '/my-app'
89
+ s[:deployer][:to] = '/home/staging/www'
87
90
  end
88
91
 
89
- staging.manipulate_each_server(:debloyer, :deploy)
92
+ staging.manipulate_each_server(:deployer, :deploy)
93
+ ```
94
+
95
+ The deployer also supports listing releases:
96
+
97
+ ```
98
+ staging.manipulate_each_server(:deployer, :releases) do |server, releases|
99
+ puts server[:hostname], releases
100
+ end
101
+ ```
102
+
103
+ Oh and you can rollback to the last release too!
104
+
105
+ ```
106
+ staging.manipulate_each_server(:deployer, :rollback)
90
107
  ```
91
108
 
92
109
  ## Using Marionetta in your Rakefile
@@ -105,16 +122,16 @@ staging = Marionetta::Group.new(:staging)
105
122
  staging.add_server do |s|
106
123
  s[:hostname] = 'staging.example.com'
107
124
  s[:puppet][:manifest] = 'puppet/manifest.pp'
108
- s[:debloyer][:from] = '/my-app'
109
- s[:debloyer][:to] = '/home/staging/www'
125
+ s[:deployer][:from] = '/my-app'
126
+ s[:deployer][:to] = '/home/staging/www'
110
127
  end
111
128
 
112
129
  Marionetta::RakeHelper.new(staging).install_group_tasks
113
130
  ```
114
131
 
115
- The tasks `staging:puppet:install`, `staging:puppet:update`
116
- `staging:debloyer:deploy` will now be available in your
117
- Rakefile.
132
+ The tasks `puppet:staging:install`, `puppet:staging:update`,
133
+ `deployer:staging:deploy` and `deployer:staging:rollback`
134
+ will now be available in your Rakefile.
118
135
 
119
136
  **Groups must have names if you want to generate rake tasks.**
120
137
 
@@ -3,20 +3,31 @@ require 'open4'
3
3
  module Marionetta
4
4
  class CommandRunner
5
5
  attr_reader :server
6
+ attr_reader :last
6
7
 
7
8
  def initialize(server)
8
9
  @server = server
9
10
  end
10
11
 
11
12
  def system(*args)
12
- status = Open4::popen4(*args) do |pid, stdin, stdout, stderr|
13
- yield stdout, stderr if block_given?
13
+ @last = args.join(' ')
14
+ server[:logger].info(last)
14
15
 
15
- server[:logger].info(args.join(' '))
16
- server[:logger].debug(stdout.read)
17
- server[:logger].debug(stderr.read)
16
+ begin
17
+ status = Open4::popen4(*args) do |pid, stdin, stdout, stderr|
18
+ yield stdout, stderr if block_given?
19
+
20
+ [stdout, stderr].each do |io|
21
+ str = io.read
22
+ server[:logger].debug(str) unless str.empty?
23
+ end
24
+ end
25
+ rescue
26
+ server[:logger].fatal(args.join(' '))
27
+ server[:logger].fatal($!)
28
+ exit(1)
18
29
  end
19
-
30
+
20
31
  return status.exitstatus == 0
21
32
  end
22
33
 
@@ -52,5 +63,40 @@ module Marionetta
52
63
 
53
64
  system(*ssh_cmd.flatten, &block)
54
65
  end
66
+
67
+ def archive(directory, save_to = nil)
68
+ if save_to.nil?
69
+ save_to = "#{directory}.#{server[:archive][:ext]}"
70
+ elsif File.directory?(save_to)
71
+ dirname = File.basename(directory)
72
+ save_to = "#{save_to}/#{dirname}.#{server[:archive][:ext]}"
73
+ end
74
+
75
+ archive_cmd = [
76
+ server[:archive][:command],
77
+ server[:archive][:flags],
78
+ save_to,
79
+ directory,
80
+ ]
81
+
82
+ system(*archive_cmd.flatten)
83
+ end
84
+
85
+ def ssh_extract(archive_path, save_to = File.dirname(archive_path))
86
+ cmds = [
87
+ "mkdir -p #{save_to}",
88
+ "cd #{save_to}",
89
+ ]
90
+
91
+ extract_cmd = [
92
+ server[:extract][:command],
93
+ server[:extract][:flags],
94
+ archive_path,
95
+ ]
96
+
97
+ cmds << extract_cmd.flatten.join(' ')
98
+
99
+ ssh(cmds.join(' && '))
100
+ end
55
101
  end
56
102
  end
@@ -53,15 +53,19 @@ module Marionetta
53
53
  end
54
54
  end
55
55
 
56
+ return_values = []
57
+
56
58
  futures.each do |f|
57
- f.value
59
+ return_values << f.value
58
60
  end
61
+
62
+ return return_values
59
63
  end
60
64
 
61
65
  def manipulate_each_server(manipulator_name, method_name)
62
66
  each_server do |s|
63
67
  manipulator = Manipulators[manipulator_name].new(s)
64
- manipulator.method(method_name).call()
68
+ yield s, manipulator.method(method_name).call() if block_given?
65
69
  end
66
70
  end
67
71
  end
@@ -0,0 +1,99 @@
1
+ require 'marionetta/command_runner'
2
+
3
+ module Marionetta
4
+ module Manipulators
5
+ class Deployer
6
+ def self.tasks()
7
+ [:deploy, :rollback]
8
+ end
9
+
10
+ attr_writer :cmd
11
+
12
+ def initialize(server)
13
+ @server = server
14
+ end
15
+
16
+ def deploy()
17
+ release = timestamp
18
+ release_archive = "/tmp/#{release}.tar.gz"
19
+ cmd.archive(from_dir, release_archive)
20
+ cmd.put(release_archive)
21
+
22
+ release_dir = release_dir(release)
23
+
24
+ unless cmd.ssh_extract(release_archive, release_dir)
25
+ server[:logger].fatal(cmd.last)
26
+ server[:logger].fatal('Could not extract archive')
27
+ exit(1)
28
+ end
29
+
30
+ symlink_release(release)
31
+ end
32
+
33
+ def releases()
34
+ releases = []
35
+
36
+ cmd.ssh("ls -m #{release_dir}") do |stdout|
37
+ stdout.read.split(/[,\s]+/).each do |release|
38
+ releases << release unless release.index('skip-') == 0
39
+ end
40
+ end
41
+
42
+ return releases
43
+ end
44
+
45
+ def rollback()
46
+ rollback_to_release = releases[-2]
47
+
48
+ if rollback_to_release.nil?
49
+ server[:logger].warn('No release to rollback to')
50
+ else
51
+ current_release_dir = release_dir(releases.last)
52
+ skip_current_release_dir = release_dir("skip-#{releases.last}")
53
+ cmd.ssh("mv #{current_release_dir} #{skip_current_release_dir}")
54
+ symlink_release(rollback_to_release)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :server
61
+
62
+ def cmd()
63
+ @cmd ||= CommandRunner.new(server)
64
+ end
65
+
66
+ def from_dir()
67
+ server[:deployer][:from]
68
+ end
69
+
70
+ def to_dir()
71
+ server[:deployer][:to]
72
+ end
73
+
74
+ def release_dir(release=nil)
75
+ dir = "#{to_dir}/releases"
76
+ dir << "/#{release}" unless release.nil?
77
+ return dir
78
+ end
79
+
80
+ def current_dir()
81
+ "#{to_dir}/current"
82
+ end
83
+
84
+ def symlink_release(release)
85
+ release_dir = release_dir(release)
86
+
87
+ unless cmd.ssh("rm -rf #{current_dir} && ln -s #{release_dir} #{current_dir}")
88
+ server[:logger].fatal(cmd.last)
89
+ server[:logger].fatal('Could not symlink release as current')
90
+ exit(1)
91
+ end
92
+ end
93
+
94
+ def timestamp()
95
+ Time.new.strftime('%F_%T')
96
+ end
97
+ end
98
+ end
99
+ end
@@ -61,20 +61,19 @@ module Marionetta
61
61
  end
62
62
 
63
63
  def archive_files()
64
+ puppet_tmp = '/tmp/puppet'
65
+
64
66
  cmds = [
65
- 'rm -rf /tmp/puppet',
66
- 'mkdir /tmp/puppet',
67
- "cp #{server[:puppet][:manifest]} /tmp/puppet/manifest.pp",
67
+ "rm -rf #{puppet_tmp}",
68
+ "mkdir #{puppet_tmp}",
69
+ "cp #{server[:puppet][:manifest]} #{puppet_tmp}/manifest.pp",
68
70
  ]
69
71
 
70
72
  if server[:puppet].has_key?(:modules)
71
- cmds << "cp -r #{server[:puppet][:modules]} /tmp/puppet/modules"
73
+ cmds << "cp -r #{server[:puppet][:modules]} #{puppet_tmp}/modules"
72
74
  end
73
75
 
74
- cmds << 'cd /tmp'
75
- cmds << 'tar cvfz puppet.tar.gz puppet'
76
-
77
- cmd.system(cmds.join(' && '))
76
+ cmd.archive(puppet_tmp)
78
77
  end
79
78
 
80
79
  def send_archive()
@@ -82,11 +81,8 @@ module Marionetta
82
81
  end
83
82
 
84
83
  def apply_archive()
85
- cmds = [
86
- 'cd /tmp',
87
- 'tar xvfz puppet.tar.gz',
88
- 'cd puppet',
89
- ]
84
+ cmd.ssh_extract('/tmp/puppet.tar.gz')
85
+ cmds = ['cd /tmp/puppet']
90
86
 
91
87
  puppet_cmd = 'sudo puppet apply '
92
88
 
@@ -1,10 +1,12 @@
1
1
  module Marionetta
2
2
  module Manipulators
3
+ require_relative 'manipulators/deployer'
3
4
  require_relative 'manipulators/debloyer'
4
5
  require_relative 'manipulators/puppet_manipulator'
5
6
 
6
7
  def self.all()
7
8
  {
9
+ :deployer => Deployer,
8
10
  :debloyer => Debloyer,
9
11
  :puppet => PuppetManipulator,
10
12
  }
data/lib/marionetta.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Marionetta
2
- VERSION = '0.2.2'
2
+ VERSION = '0.3.0'
3
3
  DESCRIPTION = 'For lightweight puppet mastery. Organise
4
4
  multiple machines via rsync and SSH rather
5
5
  than using puppet master'
@@ -15,9 +15,22 @@ module Marionetta
15
15
 
16
16
  :rsync => {
17
17
  :command => 'rsync',
18
- :flags => ["-azP", "--delete"],
18
+ :flags => ['-azP', '--delete'],
19
19
  },
20
20
 
21
+ :archive => {
22
+ :command => 'tar',
23
+ :flags => ['-zvcf'],
24
+ :ext => 'tar.gz',
25
+ },
26
+
27
+ :extract => {
28
+ :command => 'tar',
29
+ :flags => ['-xvf'],
30
+ },
31
+
32
+ :deployer => {},
33
+
21
34
  :debloyer => {
22
35
  :name => 'debloyer',
23
36
  :fpm => {
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require "marionetta"
3
+ require "marionetta/manipulators/deployer"
4
+
5
+ def deployer()
6
+ Marionetta::Manipulators::Deployer.new(server)
7
+ end
8
+
9
+ describe Marionetta::Manipulators::Deployer do
10
+ it 'should deploy' do
11
+ deployer.deploy
12
+ end
13
+
14
+ it 'should list releases' do
15
+ deployer.releases.length.should > 0
16
+ end
17
+
18
+ it 'should rollback' do
19
+ deployer.rollback
20
+ end
21
+ end
@@ -84,6 +84,8 @@ describe Marionetta::Group do
84
84
  it 'should manipulate each server' do
85
85
  vagrant = Marionetta::Group.new
86
86
  vagrant.add_server(server)
87
- vagrant.manipulate_each_server(:puppet, :update)
87
+ vagrant.manipulate_each_server(:deployer, :releases) do |server, releases|
88
+ releases.length.should > 0
89
+ end
88
90
  end
89
91
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'marionetta/group'
2
3
  require 'marionetta/rake_helper'
3
4
 
4
5
  describe Marionetta::RakeHelper do
@@ -10,6 +11,7 @@ describe Marionetta::RakeHelper do
10
11
  Rake::Task.tasks.count.should > 0
11
12
 
12
13
  Rake::Task['puppet:vagrant:update'].invoke
13
- Rake::Task['debloyer:vagrant:deploy'].invoke
14
+ Rake::Task['deployer:vagrant:deploy'].invoke
15
+ Rake::Task['deployer:vagrant:rollback'].invoke
14
16
  end
15
17
  end
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,9 @@ def server()
15
15
  s[:ssh][:flags] = ['-i', ssh_key_path]
16
16
  s[:rsync][:flags] = ['-azP', '-e', "ssh -i #{ssh_key_path}", '--delete']
17
17
 
18
+ s[:deployer][:from] = File.dirname(__FILE__)+'/app'
19
+ s[:deployer][:to] = '/home/vagrant'
20
+
18
21
  s[:debloyer][:from] = File.dirname(__FILE__)+'/app'
19
22
  s[:debloyer][:to] = '/home/vagrant'
20
23
  s[:debloyer][:name] = 'test'
data/spec/ssh_spec.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require 'marionetta/command_runner'
3
+
4
+ def cmd()
5
+ Marionetta::CommandRunner.new(server)
6
+ end
7
+
8
+ describe Marionetta::CommandRunner do
9
+ it 'should provide stdout and stderr via block' do
10
+ cmd.system('whoami') do |stdout, stderr|
11
+ stdout.read.strip.should == ENV['USER']
12
+ end
13
+ end
14
+
15
+ it 'should get file' do
16
+ cmd.get('/etc/hostname', '/tmp/hosting')
17
+ File.open('/tmp/hosting', 'rb').read.should == "precise64\n"
18
+ end
19
+
20
+ it 'should put file' do
21
+ file_path = "#{LIB}/marionetta.rb"
22
+ cmd.put(file_path, '/tmp')
23
+ local = File.open(file_path, 'rb').read
24
+
25
+ tmp_path = '/tmp/marionetta.rb'
26
+ cmd.get(tmp_path)
27
+ remote = File.open(tmp_path, 'rb').read
28
+ remote.should == local
29
+ end
30
+
31
+ it 'should run commands' do
32
+ cmd.ssh('whoami') do |stdout, stderr|
33
+ stdout.read.strip.should == 'vagrant'
34
+ end
35
+ end
36
+
37
+ it 'should archive directory' do
38
+ cmd.archive(LIB)
39
+ File.exists?("#{LIB}.tar.gz").should == true
40
+ system('rm', "#{LIB}.tar.gz")
41
+ end
42
+
43
+ it 'should archive directory to specified path' do
44
+ cmd.archive(LIB, '/tmp')
45
+ File.exists?("/tmp/lib.tar.gz").should == true
46
+ end
47
+
48
+ it 'should archive directory to specified file' do
49
+ cmd.archive(LIB, '/tmp/custom.archive')
50
+ File.exists?('/tmp/custom.archive').should == true
51
+ end
52
+
53
+ it 'should extract an archive over ssh' do
54
+ cmd.archive(LIB, '/tmp')
55
+ cmd.put('/tmp/lib.tar.gz')
56
+ cmd.ssh_extract('/tmp/lib.tar.gz')
57
+ cmd.ssh("[ -d /tmp/lib ]").should == true
58
+ cmd.ssh("[ -d /tmp/lib/lib ]").should_not == true
59
+ end
60
+
61
+ it 'should extract an archive over ssh into specific folder' do
62
+ cmd.archive(LIB, '/tmp')
63
+ cmd.put('/tmp/lib.tar.gz')
64
+ cmd.ssh_extract('/tmp/lib.tar.gz', '/tmp/other')
65
+ cmd.ssh("[ -d /tmp/other ]").should == true
66
+ cmd.ssh("[ -d /tmp/other/lib ]").should == true
67
+ end
68
+ end
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.2.2
4
+ version: 0.3.0
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-18 00:00:00.000000000 Z
12
+ date: 2012-09-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: open4
@@ -124,20 +124,22 @@ files:
124
124
  - lib/marionetta/group.rb
125
125
  - lib/marionetta/manipulators.rb
126
126
  - lib/marionetta/manipulators/debloyer.rb
127
+ - lib/marionetta/manipulators/deployer.rb
127
128
  - lib/marionetta/manipulators/puppet_manipulator.rb
128
129
  - lib/marionetta/rake_helper.rb
129
130
  - lib/test.rb
130
131
  - marionetta.gemspec
131
132
  - spec/app/app.rb
132
- - spec/marionetta_debloyer_spec.rb
133
- - spec/marionetta_group_spec.rb
134
- - spec/marionetta_manipulators_spec.rb
135
- - spec/marionetta_puppet_manipulator_spec.rb
136
- - spec/marionetta_rake_helper_spec.rb
133
+ - spec/debloyer_spec.rb
134
+ - spec/deployer_spec.rb
135
+ - spec/group_spec.rb
136
+ - spec/manipulators_spec.rb
137
137
  - spec/marionetta_spec.rb
138
- - spec/marionetta_ssh_spec.rb
139
138
  - spec/puppet/manifest.pp
139
+ - spec/puppet_manipulator_spec.rb
140
+ - spec/rake_helper_spec.rb
140
141
  - spec/spec_helper.rb
142
+ - spec/ssh_spec.rb
141
143
  - spec/vagrant/Vagrantfile
142
144
  - spec/vagrant/key
143
145
  homepage: https://github.com/DrPheltRight/marionetta
@@ -167,14 +169,15 @@ summary: For lightweight puppet mastery. Organise multiple machines via rsync an
167
169
  SSH rather than using puppet master
168
170
  test_files:
169
171
  - spec/app/app.rb
170
- - spec/marionetta_debloyer_spec.rb
171
- - spec/marionetta_group_spec.rb
172
- - spec/marionetta_manipulators_spec.rb
173
- - spec/marionetta_puppet_manipulator_spec.rb
174
- - spec/marionetta_rake_helper_spec.rb
172
+ - spec/debloyer_spec.rb
173
+ - spec/deployer_spec.rb
174
+ - spec/group_spec.rb
175
+ - spec/manipulators_spec.rb
175
176
  - spec/marionetta_spec.rb
176
- - spec/marionetta_ssh_spec.rb
177
177
  - spec/puppet/manifest.pp
178
+ - spec/puppet_manipulator_spec.rb
179
+ - spec/rake_helper_spec.rb
178
180
  - spec/spec_helper.rb
181
+ - spec/ssh_spec.rb
179
182
  - spec/vagrant/Vagrantfile
180
183
  - spec/vagrant/key
@@ -1,30 +0,0 @@
1
- require 'spec_helper'
2
- require 'marionetta/command_runner'
3
-
4
- def cmd()
5
- Marionetta::CommandRunner.new(server)
6
- end
7
-
8
- describe Marionetta::CommandRunner do
9
- it 'should get file' do
10
- cmd.get('/etc/hostname', '/tmp/hosting')
11
- File.open('/tmp/hosting', 'rb').read.should == "precise64\n"
12
- end
13
-
14
- it 'should put file' do
15
- file_path = "#{LIB}/marionetta.rb"
16
- cmd.put(file_path, '/tmp')
17
- local = File.open(file_path, 'rb').read
18
-
19
- tmp_path = '/tmp/marionetta.rb'
20
- cmd.get(tmp_path)
21
- remote = File.open(tmp_path, 'rb').read
22
- remote.should == local
23
- end
24
-
25
- it 'should run commands' do
26
- cmd.ssh('whoami') do |stdout, stderr|
27
- stdout.read.should == "vagrant\n"
28
- end
29
- end
30
- end