alphonse 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +12 -0
- data/alphonse.gemspec +28 -0
- data/bin/alphonse +9 -0
- data/lib/alphonse/cli.rb +73 -0
- data/lib/alphonse/config.rb +70 -0
- data/lib/alphonse/configs/bundler.rb +28 -0
- data/lib/alphonse/configs/operation.rb +90 -0
- data/lib/alphonse/configs/setting.rb +48 -0
- data/lib/alphonse/connection.rb +65 -0
- data/lib/alphonse/default/fonzfile.rb +33 -0
- data/lib/alphonse/logger.rb +91 -0
- data/lib/alphonse/operator.rb +78 -0
- data/lib/alphonse/version.rb +9 -0
- data/lib/alphonse.rb +46 -0
- data/spec/alphonse/cli_spec.rb +49 -0
- data/spec/alphonse/config_spec.rb +62 -0
- data/spec/alphonse/configs/bundler_spec.rb +82 -0
- data/spec/alphonse/configs/operation_spec.rb +58 -0
- data/spec/alphonse/configs/setting_spec.rb +52 -0
- data/spec/alphonse/connection_spec.rb +58 -0
- data/spec/alphonse/operator_spec.rb +36 -0
- data/spec/fonzfiles/default +21 -0
- data/spec/spec_helper.rb +37 -0
- metadata +153 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'alphonse/connection'
|
2
|
+
require 'alphonse/configs/operation'
|
3
|
+
require 'alphonse/configs/bundler'
|
4
|
+
|
5
|
+
module Alphonse
|
6
|
+
class Operator
|
7
|
+
include Alphonse::Configs::Tasks
|
8
|
+
include Alphonse::Configs::Bundler
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def initialize(config = {})
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(command)
|
16
|
+
hosts.each do |host_connection|
|
17
|
+
begin
|
18
|
+
Alphonse.logger.info "\e[4mRunning on #{host_connection.connection_string}\e[0m"
|
19
|
+
|
20
|
+
host_connection.send_and_execute send(command)
|
21
|
+
|
22
|
+
Alphonse.logger.success "Aaaaaay! All Actions Completed"
|
23
|
+
rescue Exception => e
|
24
|
+
Alphonse.logger.error "#{e.class.to_s} : #{e.message}\n\n#{e.backtrace.join("\n")}"
|
25
|
+
break
|
26
|
+
ensure
|
27
|
+
host_connection.close
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Create and setup folder on remote server
|
35
|
+
def setup
|
36
|
+
Alphonse.logger.operation config[:setup_description]
|
37
|
+
prerequisite.push config[:setup].map { |task| send task }
|
38
|
+
end
|
39
|
+
|
40
|
+
# First time deploy of app to remote server
|
41
|
+
def deploy
|
42
|
+
Alphonse.logger.operation config[:deploy_description]
|
43
|
+
prerequisite.push config[:deploy].map { |task| send(task) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Issue bundle command on remote server
|
47
|
+
def bundle_update
|
48
|
+
Alphonse.logger.operation config[:bundle_description]
|
49
|
+
prerequisite.push config[:bundle_update].map { |task| send(task) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Pull latest on remote server
|
53
|
+
def update
|
54
|
+
Alphonse.logger.operation config[:update_description]
|
55
|
+
prerequisite.push config[:update].map { |task| send(task) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Restart the app
|
59
|
+
def restart
|
60
|
+
Alphonse.logger.operation config[:restart_description]
|
61
|
+
prerequisite.push config[:restart].map { |task| send(task) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def prerequisite
|
65
|
+
[cd_to_path, 'ls -al', 'which ruby']
|
66
|
+
end
|
67
|
+
|
68
|
+
def hosts
|
69
|
+
@hosts ||= begin
|
70
|
+
host_connections, hosts, user = [], (config[:hosts] rescue nil), (config[:user] rescue nil)
|
71
|
+
hosts.each {|host| host_connections << Connection.new(host, user, config) } if hosts && user;
|
72
|
+
host_connections
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/alphonse.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "alphonse/version"
|
2
|
+
require "alphonse/logger"
|
3
|
+
|
4
|
+
module Alphonse
|
5
|
+
|
6
|
+
# Base class for all Alphonse exceptions
|
7
|
+
class AlphonseError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# When a command(s) return a stderr stream
|
11
|
+
class StdError < AlphonseError
|
12
|
+
end
|
13
|
+
|
14
|
+
# When a configuration file is missing
|
15
|
+
class NoFonzfileError < AlphonseError
|
16
|
+
end
|
17
|
+
|
18
|
+
# When a host is not defined
|
19
|
+
class MissingUserError < AlphonseError
|
20
|
+
end
|
21
|
+
|
22
|
+
# When a host is not defined
|
23
|
+
class MissingHostError < AlphonseError
|
24
|
+
end
|
25
|
+
|
26
|
+
# When an operation is not defined
|
27
|
+
class MissingOperationError < AlphonseError
|
28
|
+
end
|
29
|
+
|
30
|
+
# When an operation is not defined
|
31
|
+
class MissingCommandError < AlphonseError
|
32
|
+
end
|
33
|
+
|
34
|
+
# When an operation is invalid
|
35
|
+
class InvalidOperationError < AlphonseError
|
36
|
+
end
|
37
|
+
|
38
|
+
# When a command is not present or formed corrently
|
39
|
+
class CliCommandError < AlphonseError
|
40
|
+
end
|
41
|
+
|
42
|
+
# When an argument is not present or well formed
|
43
|
+
class CliArgumentError < AlphonseError
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/cli'
|
5
|
+
|
6
|
+
describe "Cli" do
|
7
|
+
|
8
|
+
describe "with an operation passed" do
|
9
|
+
|
10
|
+
before do
|
11
|
+
@command = 'deploy'
|
12
|
+
@cli = Alphonse::Cli.new(@command.split(' '))
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should determine the operation to run" do
|
16
|
+
@cli.command.should == :deploy
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should match the default options" do
|
20
|
+
@cli.options.should == { :environment => :production }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "with an operation and environment passed" do
|
26
|
+
|
27
|
+
before do
|
28
|
+
@command = 'deploy -e staging'
|
29
|
+
@cli = Alphonse::Cli.new(@command.split(' '))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should determine the operation to run" do
|
33
|
+
@cli.command.should == :deploy
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should match the passed options" do
|
37
|
+
@cli.options.should == { :environment => :staging }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
describe "with no arguments" do
|
44
|
+
it 'should raise an error' do
|
45
|
+
lambda { @cli = Alphonse::Cli.new }.should raise_error(Alphonse::CliArgumentError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/config'
|
5
|
+
|
6
|
+
describe "Config" do
|
7
|
+
|
8
|
+
describe "with specified Fonzfile" do
|
9
|
+
before do
|
10
|
+
@config = Alphonse::Config.new(:file_name => 'spec/fonzfiles/default', :test_variable => 1)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should load the config file' do
|
14
|
+
@config.config_loaded?.should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should have attributes set' do
|
18
|
+
@config[:user].should_not be_nil
|
19
|
+
@config[:hosts].should_not be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be able to access given attributes' do
|
23
|
+
@config[:test_variable].should equal(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should set new attributes' do
|
27
|
+
@config.set_attr :new_variable => 'new value'
|
28
|
+
@config[:new_variable].should == 'new value'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should re set an attribute' do
|
32
|
+
@config.set_attr :test_variable => 2
|
33
|
+
@config[:test_variable].should equal(2)
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'calling operator' do
|
37
|
+
it 'should initialise and return new operator' do
|
38
|
+
@config.operator.should_not be_nil
|
39
|
+
@config.operator.class.should equal(Alphonse::Operator)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
describe 'without specified Fonzfile' do
|
46
|
+
before do
|
47
|
+
@config = Alphonse::Config.new
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should use default Fonzfile' do
|
51
|
+
@config.config_loaded?.should be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should have attributes set' do
|
55
|
+
@config[:user].should_not be_nil
|
56
|
+
@config[:hosts].should_not be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/configs/bundler'
|
5
|
+
|
6
|
+
describe "Configs::Bundler" do
|
7
|
+
before do
|
8
|
+
|
9
|
+
@klass = Class.new do
|
10
|
+
include Alphonse::Configs::Bundler
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@config = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def config=(hash)
|
18
|
+
@config = hash
|
19
|
+
end
|
20
|
+
|
21
|
+
end.new
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
@klass = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "with a Gemfile" do
|
30
|
+
before do
|
31
|
+
@klass.should_receive(:using_bundler?).and_return(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "with ruby_bin_path" do
|
35
|
+
before do
|
36
|
+
@klass.config = {:ruby_bin_path => "path/to/ruby/"}
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "when calling bundle_install" do
|
40
|
+
it "should return command prepended with ruby_bin_path and bundle exec" do
|
41
|
+
|
42
|
+
@klass.bundle_install.should == "path/to/ruby/bundle install --deployment --without development test"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when calling rake command" do
|
47
|
+
it "should return rake command prepended with ruby_bin_path and bundle exec" do
|
48
|
+
@klass.rake("--tasks").should == "path/to/ruby/bundle exec rake --tasks"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "without a Gemfile" do
|
57
|
+
before do
|
58
|
+
@klass.should_receive(:using_bundler?).and_return(false)
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "without ruby_bin_path" do
|
62
|
+
before do
|
63
|
+
@klass.config = {}
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "when calling bundle_install" do
|
67
|
+
it "should return command without prepends" do
|
68
|
+
@klass.bundle_install.should be_nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "when calling rake command" do
|
73
|
+
it "should return rake command without prepends" do
|
74
|
+
@klass.rake("--tasks").should == "rake --tasks"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/configs/operation'
|
5
|
+
|
6
|
+
describe "Configs::Operation" do
|
7
|
+
before do
|
8
|
+
@klass = Class.new do
|
9
|
+
include Alphonse::Configs::Operation
|
10
|
+
include Alphonse::Configs::Tasks
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@config = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def config=(hash)
|
18
|
+
@config = hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_attr(hash)
|
22
|
+
config.merge! hash
|
23
|
+
end
|
24
|
+
|
25
|
+
end.new
|
26
|
+
|
27
|
+
@test_tasks = [:source_shell_rc, :git_pull]
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
@klass = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "Given operation and tasks" do
|
35
|
+
before do
|
36
|
+
@klass.operation :test_operation, "Test Operation Deascription"
|
37
|
+
@klass.tasks(@test_tasks)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should set given tasks in operation key of config" do
|
41
|
+
@klass.config[:test_operation].should == @test_tasks
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should set given description in config" do
|
45
|
+
@klass.config[:test_operation_description].should_not be_nil
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Calling tasks" do
|
49
|
+
it "should return commands as strings" do
|
50
|
+
task = @klass.config[:test_operation].first
|
51
|
+
@klass.send(task).should == "source ~/.bashrc"
|
52
|
+
|
53
|
+
task = @klass.config[:test_operation].last
|
54
|
+
@klass.send(task).should == ["git checkout master -q", "git pull origin master -q", "git gc --aggressive"]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/configs/setting'
|
5
|
+
|
6
|
+
describe "Configs::Setting" do
|
7
|
+
before do
|
8
|
+
@klass = Class.new do
|
9
|
+
include Alphonse::Configs::Setting
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@config = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def config=(hash)
|
17
|
+
@config = hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_attr(hash)
|
21
|
+
config.merge! hash
|
22
|
+
end
|
23
|
+
|
24
|
+
end.new
|
25
|
+
end
|
26
|
+
|
27
|
+
after do
|
28
|
+
@klass = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "with a single given value for an attribute" do
|
32
|
+
before do
|
33
|
+
@klass.user("user_name")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set correct attribute" do
|
37
|
+
@klass.config[:user].should == "user_name"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "with an array of values given for an attribute" do
|
42
|
+
before do
|
43
|
+
@klass.hosts(["host.address", "alt_host.address"])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should set attribute with correct values" do
|
47
|
+
@klass.config[:hosts].should == ["host.address", "alt_host.address"]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/connection'
|
5
|
+
|
6
|
+
describe "Connection" do
|
7
|
+
describe "with host and user" do
|
8
|
+
before do
|
9
|
+
mock_net_ssh_session!
|
10
|
+
@connection = Alphonse::Connection.new("localhost", "username", {})
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "with given commands" do
|
14
|
+
it "should send and excute and return array" do
|
15
|
+
session.should_receive(:run).and_return(@result)
|
16
|
+
|
17
|
+
results = @connection.send_and_execute("date")
|
18
|
+
results.class.should equal(Array)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set host and username" do
|
22
|
+
@connection.connection_string.should == "username@localhost"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be able to close session" do
|
26
|
+
@connection.close.should equal(true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "without given commands" do
|
31
|
+
it "should return an empty array" do
|
32
|
+
result = @connection.send_and_execute()
|
33
|
+
result.class.should equal(Array)
|
34
|
+
result.empty?.should be_true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "given invalid command" do
|
39
|
+
it "shoould raise Alphonse::MalformedCommandError" do
|
40
|
+
lambda { @connection.send_and_execute(12) }.should raise_error(Alphonse::MissingCommandError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "without" do
|
46
|
+
describe "host" do
|
47
|
+
it "should raise Alphonse::MissingHostError" do
|
48
|
+
lambda { Alphonse::Connection.new(nil, "username", {}) }.should raise_error(Alphonse::MissingHostError)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe do
|
53
|
+
it "should raise Alphonse::MissingUserError" do
|
54
|
+
lambda { Alphonse::Connection.new("localhost", nil, {}) }.should raise_error(Alphonse::MissingUserError)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'alphonse/config'
|
5
|
+
require 'alphonse/operator'
|
6
|
+
|
7
|
+
describe "Operator" do
|
8
|
+
|
9
|
+
describe "initialised with config" do
|
10
|
+
before do
|
11
|
+
@config = Alphonse::Config.new(:file_name => 'spec/fonzfiles/default')
|
12
|
+
@operator = Alphonse::Operator.new(@config)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have array of Connections" do
|
16
|
+
@operator.send(:hosts).class.should equal(Array)
|
17
|
+
@operator.send(:hosts).first.class.should equal(Alphonse::Connection)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "with hosts" do
|
21
|
+
before do
|
22
|
+
mock_connection!
|
23
|
+
@operator.stub(:hosts).and_return([@connection])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should execute setup operation" do
|
27
|
+
@operator.execute(:setup).class.should equal(Array)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should execute deploy operation" do
|
31
|
+
@operator.execute(:deploy).class.should equal(Array)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
user 'remote_user'
|
2
|
+
hosts 'stage.example.com'
|
3
|
+
path "/fullpath/to/folder"
|
4
|
+
git_repo 'git@gitaddress:repository.git'
|
5
|
+
branch 'master'
|
6
|
+
|
7
|
+
operation :setup, 'Setup server' do
|
8
|
+
tasks :clone_repository
|
9
|
+
end
|
10
|
+
|
11
|
+
operation :deploy, 'Deploy repository' do
|
12
|
+
tasks :install_gems, :setup_database, :restart_app
|
13
|
+
end
|
14
|
+
|
15
|
+
operation :update, 'Update the repository on the server' do
|
16
|
+
tasks :update_repository, :install_gems, :update_database, :restart_app
|
17
|
+
end
|
18
|
+
|
19
|
+
operation :restart, 'Restart application' do
|
20
|
+
tasks :restart_app
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
|
4
|
+
require 'alphonse'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
# config
|
9
|
+
end
|
10
|
+
|
11
|
+
def mock_connection!
|
12
|
+
@connection = double('connection')
|
13
|
+
@connection.stub(:send_and_execute).and_return([])
|
14
|
+
@connection.stub(:connection_string).and_return("connection_string")
|
15
|
+
@connection.stub(:close).and_return(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
def mock_net_ssh_session!
|
19
|
+
@result = double('result')
|
20
|
+
@result.stub(:failure?).and_return(:false)
|
21
|
+
@result.stub(:output).and_return("Output")
|
22
|
+
@result.stub(:duration).and_return(100)
|
23
|
+
@session = double("session")
|
24
|
+
@session.stub(:close).and_return(true)
|
25
|
+
@session.stub(:open).and_return(true)
|
26
|
+
@session.stub(:run).and_return(@result)
|
27
|
+
Net::SSH::Session.stub(:new).and_return(@session)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def connection
|
32
|
+
@connection
|
33
|
+
end
|
34
|
+
|
35
|
+
def session
|
36
|
+
@session
|
37
|
+
end
|