alphonse 0.0.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.
- 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
|