marionetta 0.2.2 → 0.3.0

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