taketo 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,7 +4,15 @@ Take Me To
4
4
  [![Build Status](https://secure.travis-ci.org/v-yarotsky/taketo.png)](http://travis-ci.org/v-yarotsky/taketo)
5
5
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/v-yarotsky/taketo)
6
6
 
7
- A tiny helper utility to make access to servers eaiser for different projects and environments.
7
+ A tiny helper utility to make access to servers easier for different projects and environments.
8
+
9
+ Important note:
10
+ ---------------
11
+
12
+ The project is currently actively developed, thus it may contain bugs;
13
+ backward compatibility is not guaranteed.
14
+
15
+ Suggestions and contributions are highly appreciated :)
8
16
 
9
17
  Usage:
10
18
  ------
@@ -28,9 +36,42 @@ puts a config into ```~/.taketo.rc.rb```:
28
36
  end
29
37
  ```
30
38
 
31
- Then execute ```taketo my_project staging server -c console``` to execute the "rails c" with corresponding environment variables set on desired server
32
- or just ```taketo my_project staging server``` to open bash
39
+ Then execute ```taketo my_project:staging:server -c console``` to execute the "rails c" with corresponding environment variables set on desired server
40
+ or just ```taketo my_project:staging:server``` to open bash
41
+
42
+ Destination resolving works intelligently. Given the following config:
43
+
44
+ ```ruby
45
+ default_destination "my_project2:staging:s2"
33
46
 
47
+ project :my_project do
48
+ environment :staging do
49
+ server :s1 do
50
+ host "1.2.3.4"
51
+ end
52
+ end
53
+ environment :production do
54
+ server :ps1 do
55
+ host "3.4.5.6"
56
+ end
57
+ end
58
+ end
59
+
60
+ project :my_project2 do
61
+ environment :staging do
62
+ server :s2 do
63
+ host "2.3.4.5"
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ ```taketo my_project:staging``` will ssh to s1 with host = 1.2.3.4
70
+ ```taketo my_project2``` will ssh to s2 with host = 2.3.4.5
71
+
72
+ Note that default destination can be specified via ```default_destination``` config option
73
+
74
+
34
75
  To-Do:
35
76
  ------
36
77
 
@@ -40,6 +81,10 @@ To-Do:
40
81
  The Changelog:
41
82
  --------------
42
83
 
84
+ ### v0.0.3 (22.07.2012) ###
85
+ * Add default_destination config option
86
+ * Add intelligent destination resolving
87
+
43
88
  ### v0.0.2 (21.07.2012) ###
44
89
  * Add ability to define environment variables
45
90
  * Add support for server commands
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
data/bin/taketo CHANGED
@@ -6,7 +6,7 @@ begin
6
6
  require 'taketo'
7
7
  require 'taketo/constructs_factory'
8
8
  require 'taketo/commands'
9
- require 'taketo/taketo_argv_parser'
9
+ require 'taketo/destination_resolver'
10
10
  require 'optparse'
11
11
  rescue LoadError => e #development
12
12
  raise if $loaded
@@ -62,7 +62,7 @@ end
62
62
 
63
63
  def remote_command(server, options)
64
64
  command_name = options[:command] or return default_command(options)
65
- server.command_by_name(command_name.to_sym) { default_command(options) }
65
+ server.find(:command, command_name.to_sym) { default_command(options) }
66
66
  end
67
67
 
68
68
  def default_command(options)
@@ -75,7 +75,8 @@ begin
75
75
  options = parse_options
76
76
  config = parse_config(options[:config])
77
77
 
78
- server = TaketoArgvParser.new(config, ARGV).parse
78
+ destination_path = ARGV.shift.to_s
79
+ server = DestinationResolver.new(config, destination_path).resolve
79
80
 
80
81
  server_command = remote_command(server, options)
81
82
  command_to_execute = Commands::SSHCommand.new(server).render(server_command)
@@ -91,3 +92,4 @@ rescue Exception => e
91
92
  STDERR.puts "An error occurred: #{e.message}"
92
93
  raise if options && options[:debug]
93
94
  end
95
+
@@ -16,7 +16,7 @@ Feature:
16
16
  end
17
17
  end
18
18
  """
19
- And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command "TERM=xterm-256color bash" slots staging s1`
19
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command "TERM=xterm-256color bash"`
20
20
  Then the output should contain
21
21
  """
22
22
  ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging TERM=xterm-256color bash"
@@ -37,7 +37,7 @@ Feature:
37
37
  end
38
38
  end
39
39
  """
40
- And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command console slots staging s1`
40
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command console slots:staging:s1`
41
41
  Then the output should contain
42
42
  """
43
43
  ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging rails c"
@@ -14,11 +14,15 @@ Feature:
14
14
  user "deployer"
15
15
  location "/var/apps/slots"
16
16
  end
17
+
18
+ server :s2 do
19
+ host "2.3.4.5"
20
+ end
17
21
  end
18
22
  end
19
23
 
20
24
  """
21
- And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run slots staging s1`
25
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run slots:staging:s1`
22
26
  Then the output should contain
23
27
  """
24
28
  ssh -t deployer@1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging bash"
@@ -65,3 +69,44 @@ Feature:
65
69
  FOO=the\ value
66
70
  """
67
71
 
72
+ Scenario: Reopen config scopes
73
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
74
+ """
75
+ project :slots do
76
+ environment :staging do
77
+ server :s1 do
78
+ host "1.2.3.4"
79
+ end
80
+ end
81
+ end
82
+
83
+ project :slots do
84
+ environment :staging do
85
+ server :s1 do
86
+ env :FOO => "bar"
87
+ end
88
+ end
89
+ end
90
+ """
91
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb slots:staging:s1 --dry-run`
92
+ Then the output should match /ssh -t 1\.2\.3\.4 "(RAILS_ENV=staging FOO=bar|FOO=bar RAILS_ENV=staging) bash"/
93
+
94
+ Scenario: Default destination
95
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
96
+ """
97
+ default_destination "slots:staging:s2"
98
+ project :slots do
99
+ environment :staging do
100
+ server :s1 do
101
+ end
102
+ server :s2 do
103
+ host "2.3.4.5"
104
+ end
105
+ end
106
+ end
107
+ """
108
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run`
109
+ Then the output should contain
110
+ """
111
+ ssh -t 2.3.4.5 "RAILS_ENV=staging bash"
112
+ """
@@ -0,0 +1,24 @@
1
+ require 'taketo/support'
2
+
3
+ module Taketo
4
+ module Constructs
5
+ class BaseConstruct
6
+ attr_reader :name
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ end
11
+
12
+ def find(scope, name)
13
+ send("find_#{scope}", name) or
14
+ if block_given?
15
+ yield
16
+ else
17
+ raise KeyError, "#{scope.to_s.capitalize} #{name} not found for #{self.class.name} #{name}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+
@@ -1,19 +1,15 @@
1
+ require 'taketo/constructs/base_construct'
1
2
  require 'shellwords'
2
3
 
3
4
  module Taketo
4
5
  module Constructs
5
- class Command
6
+ class Command < BaseConstruct
6
7
  include Shellwords
7
8
 
8
- attr_reader :name
9
9
  attr_accessor :command
10
10
 
11
- def initialize(name)
12
- @name = name
13
- end
14
-
15
11
  def render(server)
16
- %Q[#{location(server)} #{environment_variables(server)} #{command}].squeeze(" ")
12
+ %Q[#{location(server)} #{environment_variables(server)} #{command}].strip.squeeze(" ")
17
13
  end
18
14
 
19
15
  private
@@ -1,9 +1,11 @@
1
+ require 'taketo/constructs/base_construct'
1
2
  require 'taketo/support'
2
3
 
3
4
  module Taketo
4
5
  module Constructs
5
- class Config
6
+ class Config < BaseConstruct
6
7
  attr_reader :projects
8
+ attr_accessor :default_destination
7
9
 
8
10
  def initialize
9
11
  @projects = Taketo::Support::NamedNodesCollection.new
@@ -12,6 +14,10 @@ module Taketo
12
14
  def append_project(project)
13
15
  @projects << project
14
16
  end
17
+
18
+ def find_project(name)
19
+ @projects.find_by_name(name)
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -1,12 +1,13 @@
1
+ require 'taketo/constructs/base_construct'
1
2
  require 'taketo/support'
2
3
 
3
4
  module Taketo
4
5
  module Constructs
5
- class Environment
6
- attr_reader :name, :servers
6
+ class Environment < BaseConstruct
7
+ attr_reader :servers
7
8
 
8
9
  def initialize(name)
9
- @name = name
10
+ super
10
11
  @servers = Taketo::Support::NamedNodesCollection.new
11
12
  end
12
13
 
@@ -14,6 +15,10 @@ module Taketo
14
15
  server.environment = self
15
16
  @servers << server
16
17
  end
18
+
19
+ def find_server(name)
20
+ @servers.find_by_name(name)
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -1,18 +1,23 @@
1
+ require 'taketo/constructs/base_construct'
1
2
  require 'taketo/support'
2
3
 
3
4
  module Taketo
4
5
  module Constructs
5
- class Project
6
- attr_reader :name, :environments
6
+ class Project < BaseConstruct
7
+ attr_reader :environments
7
8
 
8
9
  def initialize(name)
9
- @name = name
10
+ super
10
11
  @environments = Taketo::Support::NamedNodesCollection.new
11
12
  end
12
13
 
13
14
  def append_environment(environment)
14
15
  @environments << environment
15
16
  end
17
+
18
+ def find_environment(name)
19
+ @environments.find_by_name(name)
20
+ end
16
21
  end
17
22
  end
18
23
  end
@@ -1,15 +1,18 @@
1
+ require 'taketo/constructs/base_construct'
2
+ require 'taketo/support'
3
+
1
4
  module Taketo
2
5
  module Constructs
3
- class Server
6
+ class Server < BaseConstruct
4
7
  class CommandNotFoundError < StandardError; end
5
8
 
6
- attr_reader :name, :environment_variables, :commands
9
+ attr_reader :environment_variables, :commands
7
10
  attr_accessor :host, :port, :username, :default_location, :environment
8
11
 
9
12
  def initialize(name)
10
- @name = name
13
+ super
11
14
  @environment_variables = {}
12
- @commands = []
15
+ @commands = Taketo::Support::NamedNodesCollection.new
13
16
  end
14
17
 
15
18
  def env(env_variables)
@@ -20,20 +23,14 @@ module Taketo
20
23
  @commands << command
21
24
  end
22
25
 
26
+ def find_command(name)
27
+ @commands.find_by_name(name)
28
+ end
29
+
23
30
  def environment=(environment)
24
31
  env(:RAILS_ENV => environment.name.to_s)
25
32
  @environment = environment
26
33
  end
27
-
28
- def command_by_name(command_name)
29
- @commands.detect { |c| c.name == command_name } or
30
- if block_given?
31
- yield
32
- else
33
- raise CommandNotFoundError, "Command #{command_name} not found for server #{name}"
34
- end
35
- end
36
-
37
34
  end
38
35
  end
39
36
  end
@@ -0,0 +1,118 @@
1
+ module Taketo
2
+ class ConfigError < StandardError; end
3
+ class AmbiguousDestinationError < StandardError; end
4
+ class NonExistentDestinationError < StandardError; end
5
+
6
+ class DestinationResolver
7
+ def initialize(config, path)
8
+ @config = config
9
+ set_destination(path)
10
+ end
11
+
12
+ def resolve
13
+ case @path.size
14
+ when 3 then resolve_by_three_segments
15
+ when 2 then resolve_by_two_segments
16
+ when 1 then resolve_by_one_segment
17
+ when 0 then resolve_with_no_segments
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def set_destination(path)
24
+ @path_str = path
25
+ @path = String(path).split(":").map(&:to_sym)
26
+ end
27
+
28
+ def resolve_by_three_segments
29
+ get_server(*@path)
30
+ end
31
+
32
+ def resolve_by_two_segments
33
+ project, environment = get_project_and_environment(*@path)
34
+ get_only_server_for_project_and_environment(project, environment)
35
+ end
36
+
37
+ def resolve_by_one_segment
38
+ project = get_project(*@path)
39
+ get_only_server_for_project(project)
40
+ end
41
+
42
+ def resolve_with_no_segments
43
+ unless @config.default_destination.nil?
44
+ set_destination(@config.default_destination.to_s)
45
+ return resolve
46
+ end
47
+ get_only_server
48
+ end
49
+
50
+ def get_server(project_name, environment_name, server_name)
51
+ handling_failure("There is no such project, environment or server for destination: #@path_str") do
52
+ project, environment = get_project_and_environment(project_name, environment_name)
53
+ environment.servers[server_name]
54
+ end
55
+ end
56
+
57
+ def get_project_and_environment(project_name, environment_name)
58
+ handling_failure("There is no such project - environment pair: #@path_str") do
59
+ project = get_project(project_name)
60
+ environment = project.environments[environment_name]
61
+ [project, environment]
62
+ end
63
+ end
64
+
65
+ def get_only_server_for_project_and_environment(project, environment)
66
+ if environment.servers.one?
67
+ return environment.servers.first
68
+ else
69
+ raise_server_ambiguous!(project, environment)
70
+ end
71
+ end
72
+
73
+ def get_project(project_name)
74
+ handling_failure("There is no such project: #@path_str") do
75
+ @config.projects[project_name]
76
+ end
77
+ end
78
+
79
+ def get_only_server_for_project(project)
80
+ if project.environments.one?
81
+ environment = project.environments.first
82
+ get_only_server_for_project_and_environment(project, environment)
83
+ else
84
+ raise_environment_ambiguous!(project)
85
+ end
86
+ end
87
+
88
+ def get_only_server
89
+ if @config.projects.one?
90
+ project = @config.projects.first
91
+ get_only_server_for_project(project)
92
+ else
93
+ raise_project_ambiguous!
94
+ end
95
+ end
96
+
97
+ def raise_project_ambiguous!
98
+ raise AmbiguousDestinationError, "There are multiple projects #{@config.projects.map(&:name).join(", ")}"
99
+ end
100
+
101
+ def raise_environment_ambiguous!(project)
102
+ raise AmbiguousDestinationError, "There are multiple environments for project #{project.name}: " \
103
+ "#{project.environments.map(&:name).join(", ")}"
104
+ end
105
+
106
+ def raise_server_ambiguous!(project, environment)
107
+ raise AmbiguousDestinationError, "There are multiple servers for project #{project.name} " \
108
+ "in environment #{environment.name}: #{environment.servers.map(&:name).join(", ")}"
109
+ end
110
+
111
+ def handling_failure(message)
112
+ yield
113
+ rescue KeyError, NonExistentDestinationError
114
+ raise NonExistentDestinationError, message
115
+ end
116
+ end
117
+ end
118
+
data/lib/taketo/dsl.rb CHANGED
@@ -11,7 +11,7 @@ module Taketo
11
11
  raise ScopeError,
12
12
  "#{scope} can't be defined in #{current_scope} scope"
13
13
  end
14
- scope_object = @factory.create(scope, name)
14
+ scope_object = current_scope_object.find(scope, name) { @factory.create(scope, name) }
15
15
  in_scope(scope, scope_object) do
16
16
  block.call
17
17
  end
@@ -55,12 +55,13 @@ module Taketo
55
55
  define_scope :server, :environment
56
56
  define_scope :command, :server
57
57
 
58
- define_attribute(:host, :server) { |hostname| current_scope_object.host = hostname }
59
- define_attribute(:port, :server) { |port_number| current_scope_object.port = port_number }
60
- define_attribute(:user, :server) { |username| current_scope_object.username = username }
61
- define_attribute(:location, :server) { |path| current_scope_object.default_location = path }
62
- define_attribute(:env, :server) { |env| current_scope_object.env(env) }
63
- define_attribute(:execute, :command) { |command| current_scope_object.command = command }
58
+ define_attribute(:default_destination, :config) { |destination| current_scope_object.default_destination = destination }
59
+ define_attribute(:host, :server) { |hostname| current_scope_object.host = hostname }
60
+ define_attribute(:port, :server) { |port_number| current_scope_object.port = port_number }
61
+ define_attribute(:user, :server) { |username| current_scope_object.username = username }
62
+ define_attribute(:location, :server) { |path| current_scope_object.default_location = path }
63
+ define_attribute(:env, :server) { |env| current_scope_object.env(env) }
64
+ define_attribute(:execute, :command) { |command| current_scope_object.command = command }
64
65
 
65
66
  private
66
67
 
@@ -78,7 +79,7 @@ module Taketo
78
79
 
79
80
  def in_scope(scope, new_scope_object)
80
81
  parent_scope_object, @current_scope_object = @current_scope_object, new_scope_object
81
- @scope << scope
82
+ @scope.push(scope)
82
83
  yield
83
84
  parent_scope_object.send("append_#{scope}", current_scope_object)
84
85
  @scope.pop
@@ -7,19 +7,28 @@ module Taketo
7
7
  include Enumerable
8
8
  extend Forwardable
9
9
 
10
- def_delegators :@nodes, :each, :<<, :push, :length, :size, :empty?
10
+ def_delegators :@nodes, :each, :length, :size, :empty?
11
11
 
12
12
  def initialize(nodes = [])
13
13
  @nodes = nodes
14
14
  end
15
15
 
16
+ def push(node)
17
+ @nodes << node unless find_by_name(node.name)
18
+ end
19
+ alias :<< :push
20
+
16
21
  def [](index)
17
22
  if index.is_a?(Symbol)
18
- node = @nodes.detect { |n| n.name == index } or raise KeyError, "Element with name #{index} not found"
23
+ node = find_by_name(index) or raise KeyError, "Element with name #{index} not found"
19
24
  return node
20
25
  end
21
26
  @nodes[index] or raise KeyError, "Element ##{index} not found"
22
27
  end
28
+
29
+ def find_by_name(name)
30
+ @nodes.detect { |n| n.name == name }
31
+ end
23
32
  end
24
33
  end
25
34
  end
@@ -11,6 +11,10 @@ describe "Taketo DSL" do
11
11
  host "127.0.0.2"
12
12
  user "deployer"
13
13
  location "/var/app"
14
+ env :FOO => "bar"
15
+ command :console do
16
+ execute "rails c"
17
+ end
14
18
  end
15
19
  end
16
20
 
@@ -40,6 +44,9 @@ describe "Taketo DSL" do
40
44
  staging_server.host.should == "127.0.0.2"
41
45
  staging_server.username.should == "deployer"
42
46
  staging_server.default_location.should == "/var/app"
47
+ staging_server.environment_variables.should == { :RAILS_ENV => "staging", :FOO => "bar" }
48
+ staging_server.commands.length.should == 1
49
+ staging_server.commands[:console].command.should == "rails c"
43
50
 
44
51
  production = project.environments[:production]
45
52
  production.servers.length.should == 2
@@ -0,0 +1,45 @@
1
+ require File.expand_path('../../../../spec_helper', __FILE__)
2
+ require 'taketo/constructs/base_construct'
3
+
4
+ include Taketo
5
+
6
+ describe "BaseConstruct" do
7
+ class TestConstruct < Constructs::BaseConstruct
8
+ attr_reader :foos
9
+
10
+ def initialize(name, foos)
11
+ super(name)
12
+ @foos = foos
13
+ end
14
+
15
+ def find_foo(name)
16
+ @foos[name]
17
+ end
18
+ end
19
+
20
+ let(:foos) { mock(:NamedNodesCollection) }
21
+ let(:construct) { TestConstruct.new(:construct, foos) }
22
+
23
+ describe "#find" do
24
+ context "if there is such node" do
25
+ it "should return node object with corresponding name" do
26
+ foos.should_receive(:[]).with(:bar).and_return(:qux)
27
+ construct.find(:foo, :bar).should == :qux
28
+ end
29
+ end
30
+
31
+ context "if there is no such command" do
32
+ before(:each) { foos.should_receive(:[]).and_return(nil) }
33
+
34
+ it "should yield if block given" do
35
+ expect { |b| construct.find(:foo, :bar, &b) }.to yield_control
36
+ end
37
+
38
+ it "should raise KeyError if no block given" do
39
+ expect { construct.find(:foo, :bar) }.to raise_error(KeyError)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
@@ -13,5 +13,18 @@ describe "Config" do
13
13
  config.projects.should include(project)
14
14
  end
15
15
  end
16
+
17
+ describe "#find_project" do
18
+ it "should find project by name" do
19
+ config.projects.should_receive(:find_by_name).with(:foo).and_return(:bar)
20
+ config.find_project(:foo).should == :bar
21
+ end
22
+ end
23
+
24
+ it "should set default_destination" do
25
+ config.default_destination.should be_nil
26
+ config.default_destination = "foo:bar:baz"
27
+ config.default_destination.should == "foo:bar:baz"
28
+ end
16
29
  end
17
30
 
@@ -23,6 +23,13 @@ describe "Environment" do
23
23
  environment.append_server(server)
24
24
  end
25
25
  end
26
+
27
+ describe "#find_server" do
28
+ it "should find server by name" do
29
+ environment.servers.should_receive(:find_by_name).with(:foo).and_return(:bar)
30
+ environment.find_server(:foo).should == :bar
31
+ end
32
+ end
26
33
  end
27
34
 
28
35
 
@@ -17,5 +17,12 @@ describe "Project" do
17
17
  project.environments.should include(environment)
18
18
  end
19
19
  end
20
+
21
+ describe "#find_environment" do
22
+ it "should find environment by name" do
23
+ project.environments.should_receive(:find_by_name).with(:foo).and_return(:bar)
24
+ project.find_environment(:foo).should == :bar
25
+ end
26
+ end
20
27
  end
21
28
 
@@ -60,22 +60,10 @@ describe "Server" do
60
60
  end
61
61
  end
62
62
 
63
- describe "#command_by_name" do
64
- context "if there is such command" do
65
- it "should return command object with corresponding name" do
66
- server.append_command(command)
67
- server.command_by_name(:foo).should == command
68
- end
69
- end
70
-
71
- context "if there is no such command" do
72
- it "should yield if block given" do
73
- expect { |b| server.command_by_name(:bar, &b) }.to yield_control
74
- end
75
-
76
- it "should raise CommandNotFoundError if no block given" do
77
- expect { server.command_by_name(:bar) }.to raise_error(Taketo::Constructs::Server::CommandNotFoundError)
78
- end
63
+ describe "#find_command" do
64
+ it "should find command by name" do
65
+ server.commands.should_receive(:find_by_name).with(:foo).and_return(:bar)
66
+ server.find_command(:foo).should == :bar
79
67
  end
80
68
  end
81
69
  end
@@ -6,7 +6,7 @@ include Taketo
6
6
  describe "ConstructsFactory" do
7
7
  let(:factory) { Taketo::ConstructsFactory.new }
8
8
 
9
- specify "#create should delegate to appripriate method according to type" do
9
+ specify "#create should delegate to appropriate method according to the type" do
10
10
  factory.should_receive(:create_config)
11
11
  factory.create(:config)
12
12
 
@@ -0,0 +1,170 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'taketo/destination_resolver'
3
+ require 'taketo/support/named_nodes_collection'
4
+
5
+ include Taketo
6
+
7
+ describe "DestinationResolver" do
8
+ let(:server1) { stub(:Server, :name => :s1) }
9
+ let(:server2) { stub(:Server, :name => :s2) }
10
+ let(:server3) { stub(:Server, :name => :s3) }
11
+ let(:server4) { stub(:Server, :name => :s4) }
12
+ let(:config) do
13
+ stub(:Config, :projects => list(
14
+ stub(:name => :foo, :environments => list(
15
+ stub(:name => :bar, :servers => list(server1))
16
+ )),
17
+ stub(:name => :baz, :environments => list(
18
+ stub(:name => :qux, :servers => list(server2))
19
+ )),
20
+ stub(:name => :quux, :environments => list(
21
+ stub(:name => :corge, :servers => list(server3, server4))
22
+ )),
23
+ stub(:name => :grault, :environments => list(
24
+ stub(:name => :garply, :servers => list(anything, anything)),
25
+ stub(:name => :waldo, :servers => list(anything))
26
+ ))
27
+ ))
28
+ end
29
+
30
+ context "when project, environment and server specified" do
31
+ it "should return server if it exists" do
32
+ resolver(config, "foo:bar:s1").resolve.should == server1
33
+ end
34
+
35
+ it "should raise error if server does not exist" do
36
+ config = stub(:Config, :projects => list())
37
+ expect { resolver(config, "foo:bar:noserver").resolve }.to raise_error(NonExistentDestinationError, /no such/i)
38
+ end
39
+ end
40
+
41
+ context "when there are 2 segments in path" do
42
+ context "when there is matching project - environment pair" do
43
+ it "should return server if only one specified" do
44
+ resolver(config, "foo:bar").resolve.should == server1
45
+ resolver(config, "baz:qux").resolve.should == server2
46
+ end
47
+
48
+ it "should raise error if there are multiple servers" do
49
+ expect { resolver(config, "quux:corge").resolve }.to raise_error(AmbiguousDestinationError, /servers/i)
50
+ end
51
+ end
52
+
53
+ context "when there is no matching project - environment pair" do
54
+ it "should raise error if no such project - environment pair exist" do
55
+ expect { resolver(config, "chunky:bacon").resolve }.to raise_error(NonExistentDestinationError, /project.*environment/i)
56
+ end
57
+ end
58
+ end
59
+
60
+ context "when there is only one segment in the path" do
61
+ context "when project with given name exists" do
62
+ context "when there's one environment" do
63
+ context "when there's one server" do
64
+ it "should return the server" do
65
+ resolver(config, "foo").resolve.should == server1
66
+ end
67
+ end
68
+
69
+ context "when there are multiple servers" do
70
+ it "should raise error" do
71
+ expect { resolver(config, "quux").resolve }.to raise_error(AmbiguousDestinationError, /servers/i)
72
+ end
73
+ end
74
+ end
75
+
76
+ context "when there are multiple environments" do
77
+ it "should raise error" do
78
+ expect { resolver(config, "grault").resolve }.to raise_error(AmbiguousDestinationError, /environments/i)
79
+ end
80
+ end
81
+ end
82
+
83
+ context "when there is no such project" do
84
+ it "should raise error" do
85
+ expect { resolver(config, "chunky").resolve }.to raise_error(NonExistentDestinationError, /project/i)
86
+ end
87
+ end
88
+ end
89
+
90
+ context "when passed path is empty" do
91
+ context "when there is no default destination set" do
92
+ context "when there's one project" do
93
+ context "when there's one environment" do
94
+ context "when there's one server" do
95
+ it "should execute command without asking project/environment/server" do
96
+ config = stub(:Config, :default_destination => nil, :projects => list(
97
+ stub(:name => :foo, :environments => list(
98
+ stub(:name => :bar, :servers => list(
99
+ server1
100
+ ))
101
+ ))
102
+ ))
103
+
104
+ resolver(config, "").resolve.should == server1
105
+ end
106
+ end
107
+
108
+ context "when there are multiple servers" do
109
+ let(:server1) { stub(:Server, :name => :s1) }
110
+ let(:server2) { stub(:Server, :name => :s2) }
111
+
112
+ it "should ask for server" do
113
+ config = stub(:Config, :default_destination => nil, :projects => list(
114
+ stub(:name => :foo, :environments => list(
115
+ stub(:name => :bar, :servers => list(
116
+ server1, server2
117
+ ))
118
+ ))
119
+ ))
120
+
121
+ expect { resolver(config, "").resolve }.to raise_error AmbiguousDestinationError, /server/i
122
+ end
123
+ end
124
+ end
125
+
126
+ context "when there are multiple environments" do
127
+ it "should ask for environment" do
128
+ config = stub(:Config, :default_destination => nil, :projects => list(
129
+ stub(:name => :foo, :environments => list(
130
+ stub(:name => :bar, :servers => [anything]),
131
+ stub(:name => :baz, :servers => [anything])
132
+ ))
133
+ ))
134
+
135
+ expect { resolver(config, "").resolve }.to raise_error AmbiguousDestinationError, /environment/i
136
+ end
137
+ end
138
+ end
139
+
140
+ context "when there are multiple projects" do
141
+ it "should ask for project" do
142
+ config = stub(:Config, :default_destination => nil, :projects => list(
143
+ stub(:name => :foo, :environments => [anything]),
144
+ stub(:name => :bar, :environments => [anything])
145
+ ))
146
+
147
+ expect { resolver(config, "").resolve }.to raise_error AmbiguousDestinationError, /projects/i
148
+ end
149
+ end
150
+ end
151
+
152
+ context "when there is default destination" do
153
+ it "should resolve by default destination" do
154
+ config.stub(:default_destination => "foo:bar:s1")
155
+ resolver(config, "").resolve.should == server1
156
+ end
157
+ end
158
+ end
159
+
160
+
161
+ def resolver(*args)
162
+ DestinationResolver.new(*args)
163
+ end
164
+
165
+ def list(*items)
166
+ Taketo::Support::NamedNodesCollection.new(items)
167
+ end
168
+
169
+ end
170
+
@@ -18,28 +18,36 @@ describe "DSL" do
18
18
  it { should_not be_appropriate_construct(scope_name, :foo).under(inappropriate_scope) }
19
19
  end
20
20
 
21
- it "should create a #{scope_name} and set it as current scope object" do
22
- dsl(parent_scope, factory.create(parent_scope_name)) do |c|
23
- factory.should_receive(:create).with(scope_name, :bar)
24
- c.send(scope_name, :bar) do
25
- c.current_scope_object.should == factory.send(scope_name)
26
- end
27
- end
28
- end
29
-
30
- it "should not leak #{scope_name} as current scope object" do
31
- dsl(parent_scope, factory.create(parent_scope_name)) do |c|
32
- c.send(scope_name, :bar) {}
33
- c.current_scope_object.should_not == factory.send(scope_name)
34
- end
35
- end
36
-
37
- it "should add a #{scope_name} to the #{parent_scope_name}'s #{scope_name}s collection" do
38
- dsl(parent_scope, factory.create(parent_scope_name)) do |c|
39
- c.current_scope_object.should_receive("append_#{scope_name}").with(factory.send("create_#{scope_name}", :bar))
40
- c.send(scope_name, :bar) {}
41
- end
42
- end
21
+ it "should create a #{scope_name} and set it as current scope object" do # it "should create project and set it as current scope object"
22
+ dsl(parent_scope, factory.create(parent_scope_name)) do |c| # dsl([:config], factory.create(:config)) do |c|
23
+ c.current_scope_object.should_receive(:find).with(scope_name, :bar). # c.current_scope_object.should_receive(:find).with(:project, :bar).
24
+ and_yield.and_return(:bacon) # and_yield.and_return(:bacon)
25
+ factory.should_receive(:create).with(scope_name, :bar) # factory.should_receive(:create).with(:project, :bar)
26
+ c.send(scope_name, :bar) do # c.project(:bar) do
27
+ c.current_scope_object.should_not be_nil # c.current_scope_object.should_not be_nil
28
+ c.current_scope_object.should == :bacon # c.current_scope_object.should == factory.project
29
+ end # end
30
+ end # end
31
+ end # end
32
+
33
+ it "should not leak #{scope_name} as current scope object" do # it "should not leak project as current scope object"
34
+ dsl(parent_scope, factory.create(parent_scope_name)) do |c| # dsl([:config], factory.create(:config)) do |c|
35
+ c.current_scope_object.stub(:find => factory.create(scope_name, :bar)) # c.current_scope.stub(:find => factory.create(:project, :bar))
36
+ c.send(scope_name, :bar) do # c.project(:bar) do
37
+ c.current_scope_object.should == factory.send(scope_name) # c.current_scope_object.should == factory.project
38
+ end # end
39
+ c.current_scope_object.should_not == factory.send(scope_name) # c.current_scope_object.should_not == factory.project
40
+ end # end
41
+ end # end
42
+
43
+ it "should add a #{scope_name} to the #{parent_scope_name}'s #{scope_name}s collection" do # it "should add a project to the config's projects collection" do
44
+ dsl(parent_scope, factory.create(parent_scope_name)) do |c| # dsl([:config], factory.create(:config)) do |c|
45
+ c.current_scope_object.stub(:find => factory.create(scope_name, :bar)) # c.current_scope_object.stub(:find => factory.create(:project, :bar))
46
+ c.current_scope_object.should_receive("append_#{scope_name}"). # c.current_scope_object.should_receive(:append_project).
47
+ with(factory.send(scope_name)) # with(factory.project)
48
+ c.send(scope_name, :bar) {} # c.project(:bar) {}
49
+ end # end
50
+ end # end
43
51
  end
44
52
 
45
53
  shared_examples "an attribute" do |attribute_name, parent_scope_name, parent_scope_method, example_value|
@@ -51,14 +59,18 @@ describe "DSL" do
51
59
  it { should_not be_appropriate_construct(attribute_name, example_value).under(inaproppriate_scope) }
52
60
  end
53
61
 
54
- it "should call #{parent_scope_method} on current #{parent_scope_name}" do
55
- dsl(parent_scope, factory.create(parent_scope_name, :foo)) do |c|
56
- factory.send(parent_scope_name).should_receive(parent_scope_method).with(example_value)
57
- c.send(attribute_name, example_value)
58
- end
62
+ it "should call #{parent_scope_method} on current #{parent_scope_name}" do # it "should call default_location= on current server" do
63
+ dsl(parent_scope, factory.create(parent_scope_name, :foo)) do |c| # dsl([:config, :project, :environment, :server], factory.create(:server, :foo)) do |c|
64
+ factory.send(parent_scope_name).should_receive(parent_scope_method).with(example_value) # factory.server.should_receive(:default_location=).with('/var/app/')
65
+ c.send(attribute_name, example_value) # c.location "/var/app"
66
+ end # end
59
67
  end
60
68
  end
61
69
 
70
+ describe "#default_destination" do
71
+ it_behaves_like "an attribute", :default_destination, :config, :default_destination=, "foo:bar:baz"
72
+ end
73
+
62
74
  describe "#project" do
63
75
  it_behaves_like "a scope", :project, :config
64
76
 
@@ -26,7 +26,13 @@ describe Taketo::Support::EvalDelegator do
26
26
  end.not_to raise_error
27
27
  end
28
28
 
29
- it "should pass local variables through the scopes" do
29
+ it "should raise if external context does not respond" do
30
+ expect do
31
+ EvalDelegatorContext.new.evaluate { qux }
32
+ end.to raise_error(NameError)
33
+ end
34
+
35
+ it "should pass local variabnes through the scopes" do
30
36
  baz = :foo
31
37
  expect do
32
38
  EvalDelegatorContext.new.evaluate { baz }
@@ -24,6 +24,13 @@ describe "NamedNodesCollection" do
24
24
  expect { collection[3] }.to raise_error KeyError, /#3/i
25
25
  expect { collection[:quux] }.to raise_error KeyError, /name/i
26
26
  end
27
+
28
+ it "should not add if node with same name exists" do
29
+ collection << node1
30
+ collection.push node1
31
+ collection.length.should == 1
32
+ collection.first.should == node1
33
+ end
27
34
  end
28
35
 
29
36
 
@@ -6,7 +6,7 @@ module DSLSpec
6
6
  send("create_#{type}", *args)
7
7
  end
8
8
 
9
- def create_config
9
+ def create_config(*args)
10
10
  @config ||= RSpec::Mocks::Mock.new(:Config).as_null_object
11
11
  end
12
12
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taketo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
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-07-21 00:00:00.000000000 Z
12
+ date: 2012-07-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -86,6 +86,7 @@ files:
86
86
  - lib/taketo/commands/ssh_command.rb
87
87
  - lib/taketo/commands.rb
88
88
  - lib/taketo/config_validator.rb
89
+ - lib/taketo/constructs/base_construct.rb
89
90
  - lib/taketo/constructs/command.rb
90
91
  - lib/taketo/constructs/config.rb
91
92
  - lib/taketo/constructs/environment.rb
@@ -93,26 +94,27 @@ files:
93
94
  - lib/taketo/constructs/server.rb
94
95
  - lib/taketo/constructs.rb
95
96
  - lib/taketo/constructs_factory.rb
97
+ - lib/taketo/destination_resolver.rb
96
98
  - lib/taketo/dsl.rb
97
99
  - lib/taketo/support/eval_delegator.rb
98
100
  - lib/taketo/support/key_error.rb
99
101
  - lib/taketo/support/named_nodes_collection.rb
100
102
  - lib/taketo/support.rb
101
- - lib/taketo/taketo_argv_parser.rb
102
103
  - lib/taketo.rb
103
104
  - spec/integration/dsl_integration_spec.rb
104
105
  - spec/lib/taketo/commands/ssh_command_spec.rb
105
106
  - spec/lib/taketo/config_validator_spec.rb
107
+ - spec/lib/taketo/constructs/base_construct_spec.rb
106
108
  - spec/lib/taketo/constructs/command_spec.rb
107
109
  - spec/lib/taketo/constructs/config_spec.rb
108
110
  - spec/lib/taketo/constructs/environment_spec.rb
109
111
  - spec/lib/taketo/constructs/project_spec.rb
110
112
  - spec/lib/taketo/constructs/server_spec.rb
111
113
  - spec/lib/taketo/constructs_factory_spec.rb
114
+ - spec/lib/taketo/destination_resolver_spec.rb
112
115
  - spec/lib/taketo/dsl_spec.rb
113
116
  - spec/lib/taketo/support/eval_delegator_spec.rb
114
117
  - spec/lib/taketo/support/named_nodes_collection_spec.rb
115
- - spec/lib/taketo/taketo_argv_parser_spec.rb
116
118
  - spec/spec_helper.rb
117
119
  - spec/support/helpers/dsl_spec_helper.rb
118
120
  - spec/support/matchers/be_appropriate_construct_matcher.rb
@@ -1,48 +0,0 @@
1
- module Taketo
2
- class ConfigError < StandardError; end
3
-
4
- class TaketoArgvParser
5
- def initialize(config, argv)
6
- @config = config
7
- @argv = argv
8
- end
9
-
10
- def parse
11
- if all_specified?
12
- project_name, environment_name, server_name = @argv.shift(3).map(&:to_sym)
13
-
14
- begin
15
- return @config.projects[project_name].environments[environment_name].servers[server_name]
16
- rescue KeyError
17
- raise ArgumentError,
18
- "Server #{server_name} in environment #{environment_name} for project #{project_name} does not exist"
19
- end
20
- end
21
-
22
- if @config.projects.one?
23
- project = @config.projects.first
24
- if project.environments.one?
25
- environment = project.environments.first
26
- if environment.servers.one?
27
- return environment.servers.first
28
- elsif environment.servers.length > 1
29
- if server_name = @argv.shift
30
- return environment.servers[server_name.to_sym]
31
- else
32
- raise ArgumentError, "There are multiple servers for project #{project.name} " \
33
- "in environment #{environment.name}: #{environment.servers.map(&:name).join(", ")}"
34
- end
35
- end
36
- end
37
- end
38
- end
39
-
40
- private
41
-
42
- def all_specified?
43
- @argv.size >= 3
44
- end
45
-
46
- end
47
- end
48
-
@@ -1,92 +0,0 @@
1
- require File.expand_path('../../../spec_helper', __FILE__)
2
- require 'taketo/taketo_argv_parser'
3
- require 'taketo/support/named_nodes_collection'
4
-
5
- include Taketo
6
-
7
- describe "TaketoArgvParser" do
8
- context "when project, environment and server specified" do
9
- let(:server) { stub(:Server, :name => :baz) }
10
-
11
- it "should return server if it exists" do
12
- config = stub(:Config, :projects => list(
13
- stub(:name => :foo, :environments => list(
14
- stub(:name => :bar, :servers => list(
15
- server
16
- ))
17
- ))
18
- ))
19
-
20
- parser(config, %w(foo bar baz)).parse.should == server
21
- end
22
-
23
- it "should raise error if server does not exist" do
24
- config = stub(:Config, :projects => list())
25
- expect do
26
- parser(config, %w(foo bar baz)).parse
27
- end.to raise_error ArgumentError, /exist/i
28
- end
29
- end
30
-
31
- context "when there are two arguments" do
32
- end
33
-
34
- context "when there are no args" do
35
- context "when there's one project" do
36
- context "when there's one environment" do
37
- context "when there's one server" do
38
- let(:server) { stub(:Server) }
39
-
40
- it "should execute command without asking project/environment/server" do
41
- config = stub(:Config, :projects => list(
42
- stub(:name => :foo, :environments => list(
43
- stub(:name => :bar, :servers => list(
44
- server
45
- ))
46
- ))
47
- ))
48
-
49
- parser(config, []).parse.should == server
50
- end
51
- end
52
-
53
- context "when there are multiple servers" do
54
- let(:server1) { stub(:Server, :name => :s1) }
55
- let(:server2) { stub(:Server, :name => :s2) }
56
-
57
- it "should ask for server if it's not specified" do
58
- config = stub(:Config, :projects => list(
59
- stub(:name => :foo, :environments => list(
60
- stub(:name => :bar, :servers => list(
61
- server1, server2
62
- ))
63
- ))
64
- ))
65
-
66
- expect do
67
- parser(config, []).parse
68
- end.to raise_error ArgumentError, /server/i
69
-
70
- parser(config, ["s1"]).parse.should == server1
71
- end
72
- end
73
- end
74
-
75
- context "when there are multiple environments" do
76
- context "when environment not specified" do
77
- it "should ask for environment"
78
- end
79
- end
80
- end
81
- end
82
-
83
- def parser(*args)
84
- TaketoArgvParser.new(*args)
85
- end
86
-
87
- def list(*items)
88
- Taketo::Support::NamedNodesCollection.new(items)
89
- end
90
-
91
- end
92
-