taketo 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -61,6 +61,7 @@ Destination resolving works intelligently. Given the following config:
61
61
  end
62
62
  environment :production do
63
63
  server :ps1 do
64
+ global_alias :mps1
64
65
  host "3.4.5.6"
65
66
  end
66
67
  end
@@ -77,6 +78,7 @@ Destination resolving works intelligently. Given the following config:
77
78
 
78
79
  ```taketo my_project:staging``` will ssh to s1 with host = 1.2.3.4
79
80
  ```taketo my_project2``` will ssh to s2 with host = 2.3.4.5
81
+ ```taketo mps2``` will ssh to s2 with host = 3.4.5.6 - note the use of global alias
80
82
 
81
83
  Note that default destination can be specified via ```default_destination``` config option
82
84
 
@@ -90,6 +92,10 @@ To-Do:
90
92
  The Changelog:
91
93
  --------------
92
94
 
95
+ ### v0.0.5 (24.07.2012) ###
96
+ * Add --directory option, which enables specifying directory on remote server upon launch
97
+ * Add global_alias config option for servers
98
+
93
99
  ### v0.0.4 (22.07.2012) ###
94
100
  * Add --view option. Now you can view your config quickly: ```taketo my_project:environment:server --view``` or just ```taketo --view```
95
101
  * Now commands can have description
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
data/bin/taketo CHANGED
@@ -45,6 +45,11 @@ def parse_options
45
45
  options[:command] = c
46
46
  end
47
47
 
48
+ opts.on("-d DIRECTORY", "--directory", "Directory on destination server to cd to") do |d|
49
+ raise OptionParser::MissingArgument if String(d).strip.empty?
50
+ options[:directory] = d
51
+ end
52
+
48
53
  opts.on("-v", "--view", "Show config contents and exit") do |v|
49
54
  options[:view] = v
50
55
  end
@@ -87,14 +92,14 @@ begin
87
92
  destination_path = ARGV.shift.to_s
88
93
  resolver = DestinationResolver.new(config, destination_path)
89
94
 
90
- if options[:view]
95
+ if options.delete(:view)
91
96
  node = resolver.get_node
92
97
  puts ConfigPrinter.new.render(node).chomp
93
98
  else
94
99
  server = resolver.resolve
95
100
 
96
101
  server_command = remote_command(server, options)
97
- command_to_execute = Commands::SSHCommand.new(server).render(server_command)
102
+ command_to_execute = Commands::SSHCommand.new(server).render(server_command.render(server, options))
98
103
 
99
104
  if options[:dry_run]
100
105
  puts command_to_execute
@@ -4,7 +4,7 @@ Feature:
4
4
  I want to have ability to create command shortcuts via config
5
5
  or specify command directly
6
6
 
7
- Scenario: Run explicit command
7
+ Background:
8
8
  When I have the following config in "/tmp/taketo_test_cfg.rb"
9
9
  """
10
10
  project :slots do
@@ -12,34 +12,31 @@ Feature:
12
12
  server do
13
13
  host "1.2.3.4"
14
14
  location "/var/apps/slots"
15
+ command :console do
16
+ execute "rails c"
17
+ end
15
18
  end
16
19
  end
17
20
  end
18
21
  """
19
- And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command "TERM=xterm-256color bash"`
22
+
23
+ Scenario: Run explicit command
24
+ When I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command "TERM=xterm-256color bash"`
20
25
  Then the output should contain
21
26
  """
22
27
  ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging TERM=xterm-256color bash"
23
28
  """
24
29
 
25
30
  Scenario: Run command defined in config
26
- When I have the following config in "/tmp/taketo_test_cfg.rb"
27
- """
28
- project :slots do
29
- environment :staging do
30
- server do
31
- host "1.2.3.4"
32
- location "/var/apps/slots"
33
- command :console do
34
- execute "rails c"
35
- end
36
- end
37
- end
38
- end
39
- """
40
- And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command console slots:staging`
31
+ When I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command console slots:staging`
41
32
  Then the output should contain
42
33
  """
43
34
  ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging rails c"
44
35
  """
45
36
 
37
+ Scenario: Override default location specified for server
38
+ When I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --directory /var/www slots:staging`
39
+ Then the output should contain
40
+ """
41
+ ssh -t 1.2.3.4 "cd /var/www; RAILS_ENV=staging bash"
42
+ """
@@ -110,3 +110,24 @@ Feature:
110
110
  """
111
111
  ssh -t 2.3.4.5 "RAILS_ENV=staging bash"
112
112
  """
113
+
114
+ Scenario: Unique server alias
115
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
116
+ """
117
+ project :slots do
118
+ environment :staging do
119
+ server :s1 do
120
+ host "1.2.3.4"
121
+ end
122
+ server :s2 do
123
+ global_alias :ss2
124
+ host "2.3.4.5"
125
+ end
126
+ end
127
+ end
128
+ """
129
+ And I successfully run `taketo ss2 --config=/tmp/taketo_test_cfg.rb --dry-run`
130
+ Then the output should contain
131
+ """
132
+ ssh -t 2.3.4.5 "RAILS_ENV=staging bash"
133
+ """
@@ -0,0 +1,28 @@
1
+ Feature:
2
+ To be able to troubleshoot my config issues quickly
3
+ As a user
4
+ I want to see meaningful errors when I accidentally specify bad location
5
+
6
+ Background:
7
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
8
+ """
9
+ default_destination "slots:staging:s2"
10
+ project :slots do
11
+ environment :staging do
12
+ server :s1 do
13
+ end
14
+ server :s2 do
15
+ host "2.3.4.5"
16
+ end
17
+ end
18
+ end
19
+ """
20
+
21
+ Scenario: Non-existent location
22
+ And I run `taketo slots:staging:qqq --config=/tmp/taketo_test_cfg.rb --dry-run`
23
+ Then the stderr should contain "server qqq not found for environment staging"
24
+
25
+ Scenario: Ambiguous location
26
+ And I run `taketo slots:staging --config=/tmp/taketo_test_cfg.rb --dry-run`
27
+ Then the stderr should contain "There are multiple servers for environment staging: s1, s2"
28
+
@@ -12,8 +12,8 @@ module Taketo
12
12
  @environment = @server.environment
13
13
  end
14
14
 
15
- def render(command)
16
- %Q[ssh -t #{port} #{username}#{host} "#{command.render(@server)}"].squeeze(" ")
15
+ def render(rendered_command)
16
+ %Q[ssh -t #{port} #{username}#{host} "#{rendered_command}"].squeeze(" ")
17
17
  end
18
18
 
19
19
  def host
@@ -10,7 +10,7 @@ module Taketo
10
10
  end
11
11
 
12
12
  def render(object)
13
- method = "render_#{object.class.name.gsub(/[\w:]*::/, '').downcase}"
13
+ method = "render_#{object.node_type}"
14
14
  send(method, object)
15
15
  end
16
16
 
@@ -10,6 +10,7 @@ module Taketo
10
10
  ensure_projects_exist
11
11
  ensure_environments_exist
12
12
  ensure_servers_exist
13
+ ensure_global_server_aliases_unique
13
14
  end
14
15
 
15
16
  private
@@ -38,6 +39,15 @@ module Taketo
38
39
  end
39
40
  end
40
41
  end
42
+
43
+ def ensure_global_server_aliases_unique
44
+ aliases = @config.projects.map { |p| p.environments.map { |e| e.servers.map(&:global_alias) } }.flatten
45
+ non_uniq_aliases = aliases.reject(&:nil?).group_by(&:to_sym).reject { |k, v| v.size == 1 }
46
+ unless non_uniq_aliases.empty?
47
+ raise ConfigError,
48
+ "There are duplicates among global server aliases: #{non_uniq_aliases.keys.join(", ")}"
49
+ end
50
+ end
41
51
  end
42
52
  end
43
53
 
@@ -1,22 +1,72 @@
1
1
  require 'taketo/support'
2
2
 
3
3
  module Taketo
4
+ class NodesNotDefinedError < StandardError; end
5
+
4
6
  module Constructs
5
7
  class BaseConstruct
6
8
  attr_reader :name
7
9
 
10
+ ##
11
+ # Adds nodes collections to the construct
12
+ #
13
+ # Example:
14
+ #
15
+ # class Bar < BaseConstruct
16
+ # has_nodes :foos, :foo
17
+ # end
18
+ #
19
+ # bar = Bar.new
20
+ # bar.foos # => foos collection
21
+ # bar.append_foo(foo) # adds node the collection
22
+ # bar.find_foo(:foo_name) # find foo in foos collection by name
23
+ #
24
+ def self.has_nodes(name_plural, name_singular)
25
+ self.node_types << name_plural
26
+
27
+ define_method "append_#{name_singular}" do |object|
28
+ nodes(name_plural) << object
29
+ end
30
+
31
+ define_method "find_#{name_singular}" do |name|
32
+ nodes(name_plural).find_by_name(name)
33
+ end
34
+
35
+ define_method name_plural do
36
+ nodes(name_plural)
37
+ end
38
+ end
39
+
40
+ def self.node_types
41
+ @node_types ||= []
42
+ end
43
+
8
44
  def initialize(name)
9
45
  @name = name
46
+ @nodes = {}
10
47
  end
11
48
 
12
- def find(scope, name)
13
- send("find_#{scope}", name) or
49
+ def find(singular_node_name, name)
50
+ send("find_#{singular_node_name}", name) or
14
51
  if block_given?
15
52
  yield
16
53
  else
17
- raise KeyError, "#{scope.to_s.capitalize} #{name} not found for #{self.class.name} #{name}"
54
+ raise KeyError, "#{singular_node_name} #{name} not found for #{node_type} #{self.name}"
18
55
  end
19
56
  end
57
+
58
+ def nodes(name_plural)
59
+ unless self.class.node_types.include?(name_plural)
60
+ raise NodesNotDefinedError, "#{name_plural} not defined for #{node_type}"
61
+ end
62
+ @nodes[name_plural] ||= Taketo::Support::NamedNodesCollection.new
63
+ end
64
+
65
+ def node_type
66
+ demodulized = self.class.name.gsub(/.*::/, '')
67
+ demodulized.gsub(/([a-z])([A-Z])/, '\\1_\\2').downcase.to_sym
68
+ end
69
+
20
70
  end
21
71
  end
22
72
  end
@@ -8,14 +8,15 @@ module Taketo
8
8
 
9
9
  attr_accessor :command, :description
10
10
 
11
- def render(server)
12
- %Q[#{location(server)} #{environment_variables(server)} #{command}].strip.squeeze(" ")
11
+ def render(server, options = {})
12
+ %Q[#{location(server, options)} #{environment_variables(server)} #{command}].strip.squeeze(" ")
13
13
  end
14
14
 
15
15
  private
16
16
 
17
- def location(server)
18
- %Q[cd #{shellescape server.default_location};] if server.default_location
17
+ def location(server, options = {})
18
+ directory = options.fetch(:directory) { server.default_location }
19
+ %Q[cd #{shellescape directory};] if directory
19
20
  end
20
21
 
21
22
  def environment_variables(server)
@@ -4,19 +4,12 @@ require 'taketo/support'
4
4
  module Taketo
5
5
  module Constructs
6
6
  class Config < BaseConstruct
7
- attr_reader :projects
8
- attr_accessor :default_destination
9
-
10
- def initialize
11
- @projects = Taketo::Support::NamedNodesCollection.new
12
- end
7
+ has_nodes :projects, :project
13
8
 
14
- def append_project(project)
15
- @projects << project
16
- end
9
+ attr_accessor :default_destination
17
10
 
18
- def find_project(name)
19
- @projects.find_by_name(name)
11
+ def initialize(*args)
12
+ super(nil)
20
13
  end
21
14
  end
22
15
  end
@@ -4,20 +4,11 @@ require 'taketo/support'
4
4
  module Taketo
5
5
  module Constructs
6
6
  class Environment < BaseConstruct
7
- attr_reader :servers
8
-
9
- def initialize(name)
10
- super
11
- @servers = Taketo::Support::NamedNodesCollection.new
12
- end
7
+ has_nodes :servers, :server
13
8
 
14
9
  def append_server(server)
15
10
  server.environment = self
16
- @servers << server
17
- end
18
-
19
- def find_server(name)
20
- @servers.find_by_name(name)
11
+ nodes(:servers) << server
21
12
  end
22
13
  end
23
14
  end
@@ -4,20 +4,7 @@ require 'taketo/support'
4
4
  module Taketo
5
5
  module Constructs
6
6
  class Project < BaseConstruct
7
- attr_reader :environments
8
-
9
- def initialize(name)
10
- super
11
- @environments = Taketo::Support::NamedNodesCollection.new
12
- end
13
-
14
- def append_environment(environment)
15
- @environments << environment
16
- end
17
-
18
- def find_environment(name)
19
- @environments.find_by_name(name)
20
- end
7
+ has_nodes :environments, :environment
21
8
  end
22
9
  end
23
10
  end
@@ -2,31 +2,24 @@ require 'taketo/constructs/base_construct'
2
2
  require 'taketo/support'
3
3
 
4
4
  module Taketo
5
+ class CommandNotFoundError < StandardError; end
6
+
5
7
  module Constructs
6
8
  class Server < BaseConstruct
7
- class CommandNotFoundError < StandardError; end
8
-
9
- attr_reader :environment_variables, :commands
10
- attr_accessor :host, :port, :username, :default_location, :environment
9
+ attr_reader :environment_variables
10
+ attr_accessor :host, :port, :username, :default_location, :environment, :global_alias
11
11
 
12
+ has_nodes :commands, :command
13
+
12
14
  def initialize(name)
13
15
  super
14
16
  @environment_variables = {}
15
- @commands = Taketo::Support::NamedNodesCollection.new
16
17
  end
17
18
 
18
19
  def env(env_variables)
19
20
  @environment_variables.merge!(env_variables)
20
21
  end
21
22
 
22
- def append_command(command)
23
- @commands << command
24
- end
25
-
26
- def find_command(name)
27
- @commands.find_by_name(name)
28
- end
29
-
30
23
  def environment=(environment)
31
24
  env(:RAILS_ENV => environment.name.to_s)
32
25
  @environment = environment
@@ -1,126 +1,81 @@
1
1
  module Taketo
2
- class ConfigError < StandardError; end
3
2
  class AmbiguousDestinationError < StandardError; end
4
3
  class NonExistentDestinationError < StandardError; end
5
4
 
6
5
  class DestinationResolver
7
- def initialize(config, path)
8
- @config = config
9
- set_destination(path)
10
- end
6
+ class Path
7
+ PATH = [[:project, :projects], [:environment, :environments], [:server, :servers]].freeze
11
8
 
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
9
+ class PathNode < Struct.new(:name, :singular, :plural); end
20
10
 
21
- def get_node
22
- case @path.size
23
- when 3 then get_server(*@path)
24
- when 2 then get_project_and_environment(*@path).last
25
- when 1 then get_project(*@path)
26
- when 0 then @config
11
+ def initialize(names)
12
+ names = names.split(":").map(&:to_sym)
13
+ @path = PATH.each_with_index.map { |n, i| PathNode.new(names[i], *n) }
27
14
  end
28
- end
29
-
30
- private
31
15
 
32
- def set_destination(path)
33
- @path_str = path
34
- @path = String(path).split(":").map(&:to_sym)
35
- end
36
-
37
- def resolve_by_three_segments
38
- get_server(*@path)
39
- end
40
-
41
- def resolve_by_two_segments
42
- project, environment = get_project_and_environment(*@path)
43
- get_only_server_for_project_and_environment(project, environment)
44
- end
45
-
46
- def resolve_by_one_segment
47
- project = get_project(*@path)
48
- get_only_server_for_project(project)
49
- end
50
-
51
- def resolve_with_no_segments
52
- unless @config.default_destination.nil?
53
- set_destination(@config.default_destination.to_s)
54
- return resolve
16
+ def specified
17
+ @path.reject { |n| n.name.nil? }
55
18
  end
56
- get_only_server
57
- end
58
19
 
59
- def get_server(project_name, environment_name, server_name)
60
- handling_failure("There is no such project, environment or server for destination: #@path_str") do
61
- project, environment = get_project_and_environment(project_name, environment_name)
62
- environment.servers[server_name]
20
+ def unspecified
21
+ @path - specified
63
22
  end
64
23
  end
65
24
 
66
- def get_project_and_environment(project_name, environment_name)
67
- handling_failure("There is no such project - environment pair: #@path_str") do
68
- project = get_project(project_name)
69
- environment = project.environments[environment_name]
70
- [project, environment]
71
- end
72
- end
73
-
74
- def get_only_server_for_project_and_environment(project, environment)
75
- if environment.servers.one?
76
- return environment.servers.first
77
- else
78
- raise_server_ambiguous!(project, environment)
25
+ def initialize(config, path)
26
+ @config = config
27
+ if String(path).empty? && !String(config.default_destination).empty?
28
+ path = config.default_destination
79
29
  end
30
+ @path_str = path.dup
31
+ @path = Path.new(path)
80
32
  end
81
33
 
82
- def get_project(project_name)
83
- handling_failure("There is no such project: #@path_str") do
84
- @config.projects[project_name]
85
- end
34
+ def resolve
35
+ resolve_by_global_alias || resolve_by_path
86
36
  end
87
37
 
88
- def get_only_server_for_project(project)
89
- if project.environments.one?
90
- environment = project.environments.first
91
- get_only_server_for_project_and_environment(project, environment)
92
- else
93
- raise_environment_ambiguous!(project)
38
+ def get_node(node = @config, remaining_path = @path.specified)
39
+ handling_failure do
40
+ return node if remaining_path.empty?
41
+ next_in_path = remaining_path.shift
42
+ node = node.find(next_in_path.singular, next_in_path.name)
43
+ get_node(node, remaining_path)
94
44
  end
95
45
  end
96
46
 
97
- def get_only_server
98
- if @config.projects.one?
99
- project = @config.projects.first
100
- get_only_server_for_project(project)
101
- else
102
- raise_project_ambiguous!
103
- end
104
- end
47
+ private
105
48
 
106
- def raise_project_ambiguous!
107
- raise AmbiguousDestinationError, "There are multiple projects #{@config.projects.map(&:name).join(", ")}"
49
+ def resolve_by_global_alias
50
+ aliases_and_servers = Hash[servers_with_aliases]
51
+ aliases_and_servers[@path_str.to_sym] unless String(@path_str).empty?
108
52
  end
109
53
 
110
- def raise_environment_ambiguous!(project)
111
- raise AmbiguousDestinationError, "There are multiple environments for project #{project.name}: " \
112
- "#{project.environments.map(&:name).join(", ")}"
54
+ def resolve_by_path
55
+ node = get_node
56
+ get_only_server(node)
113
57
  end
114
58
 
115
- def raise_server_ambiguous!(project, environment)
116
- raise AmbiguousDestinationError, "There are multiple servers for project #{project.name} " \
117
- "in environment #{environment.name}: #{environment.servers.map(&:name).join(", ")}"
59
+ def get_only_server(node, remaining_path = @path.unspecified)
60
+ return node if remaining_path.empty?
61
+ next_in_path = remaining_path.shift
62
+ nested_nodes = node.nodes(next_in_path.plural)
63
+ if nested_nodes.one?
64
+ node = nested_nodes.first
65
+ get_only_server(node, remaining_path)
66
+ else
67
+ raise AmbiguousDestinationError, "There are multiple #{next_in_path.plural} for #{node.node_type} #{node.name}: #{nested_nodes.map(&:name).join(", ")}"
68
+ end
118
69
  end
119
70
 
120
- def handling_failure(message)
71
+ def handling_failure(message = nil)
121
72
  yield
122
73
  rescue KeyError, NonExistentDestinationError
123
- raise NonExistentDestinationError, message
74
+ raise NonExistentDestinationError, message || $!.message
75
+ end
76
+
77
+ def servers_with_aliases
78
+ @config.nodes(:projects).map { |p| p.nodes(:environments).map { |e| e.nodes(:servers).reject { |s| s.global_alias.nil? }.map { |s| [s.global_alias, s] } } }.flatten(2)
124
79
  end
125
80
  end
126
81
  end
data/lib/taketo/dsl.rb CHANGED
@@ -61,6 +61,7 @@ module Taketo
61
61
  define_attribute(:port, :server) { |port_number| current_scope_object.port = port_number }
62
62
  define_attribute(:user, :server) { |username| current_scope_object.username = username }
63
63
  define_attribute(:location, :server) { |path| current_scope_object.default_location = path }
64
+ define_attribute(:global_alias,:server) { |alias_name| current_scope_object.global_alias = alias_name }
64
65
  define_attribute(:env, :server) { |env| current_scope_object.env(env) }
65
66
  define_attribute(:execute, :command) { |command| current_scope_object.command = command }
66
67
  define_attribute(:desc, :command) { |description| current_scope_object.description = description }
@@ -19,14 +19,13 @@ describe "SSH Command" do
19
19
  let(:ssh_command) { SSHCommand.new(server) }
20
20
 
21
21
  it "should compose command based on provided server object" do
22
- command.should_receive(:render).with(server).and_return("foobar")
23
- ssh_command.render(command).should == %q[ssh -t -p 22 deployer@1.2.3.4 "foobar"]
22
+ ssh_command.render("foobar").should == %q[ssh -t -p 22 deployer@1.2.3.4 "foobar"]
24
23
  end
25
24
 
26
25
  it "should ignore absent parts" do
27
26
  server.stub(:port => nil)
28
27
  server.stub(:username => nil)
29
- ssh_command.render(command).should == %q[ssh -t 1.2.3.4 "the_command"]
28
+ ssh_command.render("foobar").should == %q[ssh -t 1.2.3.4 "foobar"]
30
29
  end
31
30
 
32
31
  it "should require host" do
@@ -6,8 +6,9 @@ include Taketo
6
6
  describe "ConfigPrinter" do
7
7
  describe "#render" do
8
8
  it "should render based on node class name" do
9
- printer.should_receive(:render_fixnum)
10
- printer.render(1)
9
+ construct = stub(:Server, :node_type => :server)
10
+ printer.should_receive(:render_server)
11
+ printer.render(construct)
11
12
  end
12
13
  end
13
14
 
@@ -33,6 +33,26 @@ describe "ConfigValidator" do
33
33
  expect { validator(config).validate! }.not_to raise_error ConfigError, /environment_2/i
34
34
  end
35
35
 
36
+ describe "global server aliases" do
37
+ let(:server_1) { stub(:Server) }
38
+ let(:server_2) { stub(:Server) }
39
+ let(:environment) { stub(:Environment, :name => :environment, :servers => [server_1, server_2]) }
40
+ let(:project) { stub(:Project, :name => :project, :environments => [environment]) }
41
+ let(:config) { stub(:Config, :projects => [project]) }
42
+
43
+ it "should not raise error if unique" do
44
+ server_1.stub(:global_alias => :foo)
45
+ server_2.stub(:global_alias => :bar)
46
+ expect { validator(config).validate! }.not_to raise_error(ConfigError, /alias/i)
47
+ end
48
+
49
+ it "should raise error unless unique" do
50
+ server_1.stub(:global_alias => :foo)
51
+ server_2.stub(:global_alias => :foo)
52
+ expect { validator(config).validate! }.to raise_error(ConfigError, /alias/i)
53
+ end
54
+ end
55
+
36
56
  def validator(config)
37
57
  Taketo::ConfigValidator.new(config)
38
58
  end
@@ -40,6 +40,45 @@ describe "BaseConstruct" do
40
40
  end
41
41
  end
42
42
  end
43
+
44
+ describe "#node_type" do
45
+ it "should return demodulized snake-cased class name" do
46
+ construct.node_type.should == :test_construct
47
+ end
48
+ end
49
+
50
+ describe "nodes" do
51
+ class TestConstruct2 < Constructs::BaseConstruct
52
+ has_nodes :foos, :foo
53
+ end
54
+
55
+ let(:construct) { TestConstruct2.new(:node_name) }
56
+
57
+ let(:foo) { stub(:name => :bar) }
58
+
59
+ describe ".has_nodes" do
60
+ it "should define add node type to class's node_types" do
61
+ TestConstruct2.node_types.should include(:foos)
62
+ end
63
+
64
+ it "should make nodes accessible via added methods" do
65
+ construct.append_foo(foo)
66
+ construct.foos.should include(foo)
67
+ construct.find_foo(:bar).should == foo
68
+ end
69
+ end
70
+
71
+ describe "#nodes" do
72
+ it "should return nodes collection by plural name" do
73
+ construct.append_foo(foo)
74
+ construct.nodes(:foos).should include(foo)
75
+ end
76
+
77
+ it "should raise NodesNotDefinedError if non-specified node requested" do
78
+ expect { construct.nodes(:bars) }.to raise_error(NodesNotDefinedError, /bars/)
79
+ end
80
+ end
81
+ end
43
82
  end
44
83
 
45
84
 
@@ -4,23 +4,26 @@ require 'taketo/constructs/command'
4
4
  include Taketo
5
5
 
6
6
  describe "Command" do
7
- let(:cmd) { Taketo::Constructs::Command.new(:the_command) }
7
+ subject { Taketo::Constructs::Command.new(:the_command) }
8
8
 
9
- specify "#command= should set which command to execute" do
10
- cmd.command= "rails c"
11
- cmd.command.should == "rails c"
12
- end
13
-
14
- specify "#description= should set command description" do
15
- cmd.description= "Run rails console"
16
- cmd.description.should == "Run rails console"
17
- end
9
+ it { should have_accessor(:command) }
10
+ it { should have_accessor(:description) }
18
11
 
19
12
  describe "#render" do
13
+ let(:server) do
14
+ mock(:Server,
15
+ :environment_variables => { :FOO => "bar baz" },
16
+ :default_location => "/var/apps/the app")
17
+ end
18
+
20
19
  it "should pick up server's environment variables and location" do
21
- server = mock(:Server, :environment_variables => { :FOO => "bar baz" }, :default_location => "/var/apps/the app")
22
- cmd.command = "rails c"
23
- cmd.render(server).should == "cd /var/apps/the\\ app; FOO=bar\\ baz rails c"
20
+ subject.command = "rails c"
21
+ subject.render(server).should == "cd /var/apps/the\\ app; FOO=bar\\ baz rails c"
22
+ end
23
+
24
+ it "should let user override default directory" do
25
+ subject.command = "rails c"
26
+ subject.render(server, :directory => "/var/qux").should == "cd /var/qux; FOO=bar\\ baz rails c"
24
27
  end
25
28
  end
26
29
  end
@@ -1,30 +1,18 @@
1
1
  require File.expand_path('../../../../spec_helper', __FILE__)
2
+ require 'support/helpers/construct_spec_helper'
2
3
  require 'taketo/constructs/config'
3
4
 
4
5
  include Taketo
5
6
 
6
7
  describe "Config" do
7
- let(:config) { Taketo::Constructs::Config.new }
8
+ subject { Taketo::Constructs::Config.new }
8
9
 
9
- describe "#append_project" do
10
- it "should add project to config's projects collection" do
11
- project = stub(:name => :foo)
12
- config.append_project(project)
13
- config.projects.should include(project)
14
- end
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
10
+ it_behaves_like "a construct with nodes", :projects, :project
23
11
 
24
12
  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"
13
+ subject.default_destination.should be_nil
14
+ subject.default_destination = "foo:bar:baz"
15
+ subject.default_destination.should == "foo:bar:baz"
28
16
  end
29
17
  end
30
18
 
@@ -1,34 +1,22 @@
1
1
  require File.expand_path('../../../../spec_helper', __FILE__)
2
+ require 'support/helpers/construct_spec_helper'
2
3
  require 'taketo/constructs/environment'
3
4
 
4
5
  include Taketo
5
6
 
6
7
  describe "Environment" do
7
- let(:environment) { Taketo::Constructs::Environment.new(:foo) }
8
+ subject { Taketo::Constructs::Environment.new(:foo) }
8
9
 
9
10
  it "should have name" do
10
- environment.name.should == :foo
11
+ subject.name.should == :foo
11
12
  end
12
13
 
13
- describe "#append_server" do
14
- let(:server) { mock(:Server, :name => :foo).as_null_object }
14
+ it_behaves_like "a construct with nodes", :servers, :server
15
15
 
16
- it "should add a server to environment's servers collection" do
17
- environment.append_server(server)
18
- environment.servers.should include(server)
19
- end
20
-
21
- it "should set environment attribute on a server to self" do
22
- server.should_receive(:environment=).with(environment)
23
- environment.append_server(server)
24
- end
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
16
+ specify "#append_server should set environment attribute on a server to self" do
17
+ server = mock(:Server, :name => :foo)
18
+ server.should_receive(:environment=).with(subject)
19
+ subject.append_server(server)
32
20
  end
33
21
  end
34
22
 
@@ -1,28 +1,16 @@
1
1
  require File.expand_path('../../../../spec_helper', __FILE__)
2
+ require 'support/helpers/construct_spec_helper'
2
3
  require 'taketo/constructs/project'
3
4
 
4
5
  include Taketo
5
6
 
6
7
  describe "Project" do
7
- let(:project) { Taketo::Constructs::Project.new(:foo) }
8
+ subject { Taketo::Constructs::Project.new(:foo) }
8
9
 
9
10
  it "should have name" do
10
- project.name.should == :foo
11
- end
12
-
13
- describe "#append_environment" do
14
- it "should add an environment to project's environments collection" do
15
- environment = stub(:name => :foo)
16
- project.append_environment(environment)
17
- project.environments.should include(environment)
18
- end
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
11
+ subject.name.should == :foo
26
12
  end
13
+
14
+ it_behaves_like "a construct with nodes", :environments, :environment
27
15
  end
28
16
 
@@ -1,72 +1,44 @@
1
1
  require File.expand_path('../../../../spec_helper', __FILE__)
2
+ require 'support/helpers/construct_spec_helper'
2
3
  require 'taketo/constructs/server'
3
4
 
4
5
  include Taketo
5
6
 
6
7
  describe "Server" do
7
- let(:environment) { environment = stub(:Environment, :name => :the_environment) }
8
- let(:server) { Taketo::Constructs::Server.new(:foo) }
8
+ subject { Taketo::Constructs::Server.new(:foo) }
9
9
 
10
10
  it "should have name" do
11
- server.name.should == :foo
11
+ subject.name.should == :foo
12
12
  end
13
13
 
14
- it "should set host" do
15
- server.host = "foo"
16
- server.host.should == "foo"
17
- end
18
-
19
- it "should set port" do
20
- server.port = "foo"
21
- server.port.should == "foo"
22
- end
23
-
24
- it "should set username" do
25
- server.username = "foo"
26
- server.username.should == "foo"
27
- end
28
-
29
- it "should set default_location" do
30
- server.default_location = "foo"
31
- server.default_location.should == "foo"
32
- end
14
+ it { should have_accessor(:host) }
15
+ it { should have_accessor(:port) }
16
+ it { should have_accessor(:username) }
17
+ it { should have_accessor(:default_location) }
18
+ it { should have_accessor(:global_alias) }
33
19
 
34
20
  describe "#environment=" do
21
+ let(:environment) { environment = stub(:Environment, :name => :the_environment) }
22
+
35
23
  it "should set environment" do
36
- server.environment = environment
37
- server.environment.should == environment
24
+ subject.environment = environment
25
+ subject.environment.should == environment
38
26
  end
39
27
 
40
28
  it "should set RAILS_ENV environment variable" do
41
- server.environment_variables.should == {}
42
- server.environment = environment
43
- server.environment_variables[:RAILS_ENV].should == environment.name.to_s
29
+ subject.environment_variables.should == {}
30
+ subject.environment = environment
31
+ subject.environment_variables[:RAILS_ENV].should == environment.name.to_s
44
32
  end
45
33
  end
46
34
 
47
35
  it "should set environment variables" do
48
- server.env :FOO => "bar"
49
- server.env :BAR => "baz"
50
- server.environment_variables.should include(:FOO => "bar", :BAR => "baz")
36
+ subject.env :FOO => "bar"
37
+ subject.env :BAR => "baz"
38
+ subject.environment_variables.should include(:FOO => "bar", :BAR => "baz")
51
39
  end
52
40
 
53
- context "Commands" do
54
- let(:command) { mock(:Command, :name => :foo).as_null_object }
55
-
56
- describe "#append_command" do
57
- it "should add a command to servers's commands collection" do
58
- server.append_command(command)
59
- server.commands.should include(command)
60
- end
61
- end
62
-
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
67
- end
68
- end
69
- end
41
+ it_behaves_like "a construct with nodes", :commands, :command
70
42
  end
71
43
 
72
44
 
@@ -5,36 +5,57 @@ require 'taketo/support/named_nodes_collection'
5
5
  include Taketo
6
6
 
7
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
- ))
8
+ class TestNode
9
+ attr_reader :name, :node_type
10
+ def initialize(node_type, name, *nodes)
11
+ @node_type, @name, @nodes = node_type, name, nodes
12
+ end
13
+
14
+ def find(type, name)
15
+ @nodes.detect { |s| s.name == name } or raise KeyError, name.to_s
16
+ end
17
+
18
+ def nodes(type)
19
+ @nodes
20
+ end
21
+
22
+ def global_alias
23
+ :the_alias if name == :s1 && node_type == :server
24
+ end
28
25
  end
29
26
 
27
+ [:project, :environment, :server].each do |node_type|
28
+ define_method node_type do |name, *nodes|
29
+ TestNode.new(node_type, name, *nodes)
30
+ end
31
+ end
32
+
33
+ let(:server1) { server(:s1) }
34
+ let(:environment1) { environment(:bar, server1) }
35
+ let(:project1) { project(:foo, environment1) }
36
+
37
+ let(:server2) { server(:s2) }
38
+ let(:environment2) { environment(:qux, server2) }
39
+ let(:project2) { project(:baz, environment2) }
40
+
41
+ let(:server3) { server(:s3) }
42
+ let(:server4) { server(:s4) }
43
+ let(:environment3) { environment(:corge, server3, server4) }
44
+ let(:project3) { environment(:quux, environment3) }
45
+
46
+ let(:environment4) { environment(:garply, server(:s5), server(:s6)) }
47
+ let(:environment5) { environment(:waldo, server(:s7)) }
48
+ let(:project4) { project(:grault, environment4, environment5) }
49
+
50
+ let(:config) { TestNode.new(:config, nil, project1, project2, project3, project4) }
51
+
30
52
  context "when project, environment and server specified" do
31
53
  it "should return server if it exists" do
32
54
  resolver(config, "foo:bar:s1").resolve.should == server1
33
55
  end
34
56
 
35
57
  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)
58
+ expect { resolver(config, "foo:bar:noserver").resolve }.to raise_error(NonExistentDestinationError)
38
59
  end
39
60
  end
40
61
 
@@ -46,13 +67,13 @@ describe "DestinationResolver" do
46
67
  end
47
68
 
48
69
  it "should raise error if there are multiple servers" do
49
- expect { resolver(config, "quux:corge").resolve }.to raise_error(AmbiguousDestinationError, /servers/i)
70
+ expect { resolver(config, "quux:corge").resolve }.to raise_error(AmbiguousDestinationError)
50
71
  end
51
72
  end
52
73
 
53
74
  context "when there is no matching project - environment pair" do
54
75
  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)
76
+ expect { resolver(config, "chunky:bacon").resolve }.to raise_error(NonExistentDestinationError)
56
77
  end
57
78
  end
58
79
  end
@@ -68,21 +89,21 @@ describe "DestinationResolver" do
68
89
 
69
90
  context "when there are multiple servers" do
70
91
  it "should raise error" do
71
- expect { resolver(config, "quux").resolve }.to raise_error(AmbiguousDestinationError, /servers/i)
92
+ expect { resolver(config, "quux").resolve }.to raise_error(AmbiguousDestinationError)
72
93
  end
73
94
  end
74
95
  end
75
96
 
76
97
  context "when there are multiple environments" do
77
98
  it "should raise error" do
78
- expect { resolver(config, "grault").resolve }.to raise_error(AmbiguousDestinationError, /environments/i)
99
+ expect { resolver(config, "grault").resolve }.to raise_error(AmbiguousDestinationError)
79
100
  end
80
101
  end
81
102
  end
82
103
 
83
104
  context "when there is no such project" do
84
105
  it "should raise error" do
85
- expect { resolver(config, "chunky").resolve }.to raise_error(NonExistentDestinationError, /project/i)
106
+ expect { resolver(config, "chunky").resolve }.to raise_error(NonExistentDestinationError)
86
107
  end
87
108
  end
88
109
  end
@@ -93,31 +114,16 @@ describe "DestinationResolver" do
93
114
  context "when there's one environment" do
94
115
  context "when there's one server" do
95
116
  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
-
117
+ config = TestNode.new(:config, nil, project1)
118
+ config.stub(:default_destination => nil)
104
119
  resolver(config, "").resolve.should == server1
105
120
  end
106
121
  end
107
122
 
108
123
  context "when there are multiple servers" do
109
- let(:server1) { stub(:Server, :name => :s1) }
110
- let(:server2) { stub(:Server, :name => :s2) }
111
-
112
124
  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
-
125
+ config = TestNode.new(:config, nil, project3)
126
+ config.stub(:default_destination => nil)
121
127
  expect { resolver(config, "").resolve }.to raise_error AmbiguousDestinationError, /server/i
122
128
  end
123
129
  end
@@ -125,13 +131,8 @@ describe "DestinationResolver" do
125
131
 
126
132
  context "when there are multiple environments" do
127
133
  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
-
134
+ config = TestNode.new(:config, nil, project4)
135
+ config.stub(:default_destination => nil)
135
136
  expect { resolver(config, "").resolve }.to raise_error AmbiguousDestinationError, /environment/i
136
137
  end
137
138
  end
@@ -139,10 +140,8 @@ describe "DestinationResolver" do
139
140
 
140
141
  context "when there are multiple projects" do
141
142
  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
- ))
143
+ config = TestNode.new(:config, nil, project3, project4)
144
+ config.stub(:default_destination => nil)
146
145
 
147
146
  expect { resolver(config, "").resolve }.to raise_error AmbiguousDestinationError, /projects/i
148
147
  end
@@ -157,24 +156,32 @@ describe "DestinationResolver" do
157
156
  end
158
157
  end
159
158
 
159
+ context "when there is global matching server alias" do
160
+ it "should resolve by alias" do
161
+ resolver(config, "the_alias").resolve.should == server1
162
+ end
163
+ end
164
+
160
165
  describe "#get_node" do
161
166
  it "should return server when path has 3 segments and is correct" do
162
167
  resolver(config, "foo:bar:s1").get_node.should == server1
163
168
  end
164
169
 
165
170
  it "should return environment when path has 2 segments and is correct" do
166
- resolver(config, "foo:bar").get_node.name.should == :bar
171
+ resolver(config, "foo:bar").get_node.should == environment1
167
172
  end
168
173
 
169
174
  it "should return project when path has 1 segment and is correct" do
170
- resolver(config, "foo").get_node.name.should == :foo
175
+ resolver(config, "foo").get_node.should == project1
171
176
  end
172
177
 
173
- it "should return the config if path has is empty" do
178
+ it "should return the config if path has is empty and there's no default destination" do
179
+ config.stub(:default_destination => nil)
174
180
  resolver(config, "").get_node.should == config
175
181
  end
176
182
 
177
183
  it "should raise NonExistentDestinationError when path is not correct" do
184
+ config.should_receive(:find).with(:project, :i).and_raise(KeyError)
178
185
  expect { resolver(config, "i").get_node }.to raise_error(NonExistentDestinationError)
179
186
  end
180
187
  end
@@ -107,6 +107,10 @@ describe "DSL" do
107
107
  it_behaves_like "an attribute", :env, :server, :env, { :FOO => "bar" }
108
108
  end
109
109
 
110
+ describe "#global_alias" do
111
+ it_behaves_like "an attribute", :global_alias, :server, :global_alias=, "foobared"
112
+ end
113
+
110
114
  describe "#command" do
111
115
  it_behaves_like "a scope", :command, :server
112
116
 
@@ -0,0 +1,15 @@
1
+ shared_examples "a construct with nodes" do |name_plural, name_singular|
2
+ specify "#append_#{name_singular} should add a #{name_singular} to " + # specify "#append_server should add a server to " +
3
+ "#{subject.class.name}'s #{name_plural} collection" do # "environment's servers collection" do
4
+ node = mock(:name => :foo).as_null_object # server = mock(:name => :foo).as_null_object
5
+ subject.send("append_#{name_singular}", node) # environment.append_server(server)
6
+ subject.send(name_plural).should include(node) # environment.servers.should include(server)
7
+ end # end
8
+
9
+ specify "#find_#{name_singular} should find #{name_singular} by name" do # specify "#find_server should find server by name" do
10
+ subject.send(name_plural).should_receive(:find_by_name). # environment.servers.should_receive(:find_by_name).
11
+ with(:foo).and_return(:bar) # with(:foo).and_return(:bar)
12
+ subject.send("find_#{name_singular}", :foo).should == :bar # environment.find_server(:foo).should == :bar
13
+ end # end
14
+ end
15
+
@@ -0,0 +1,7 @@
1
+ RSpec::Matchers.define :have_accessor do |accessor|
2
+ match do |construct|
3
+ construct.send("#{accessor}=", :foo)
4
+ construct.send(accessor) == :foo
5
+ end
6
+ end
7
+
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.4
4
+ version: 0.0.5
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-22 00:00:00.000000000 Z
12
+ date: 2012-07-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -118,12 +118,15 @@ files:
118
118
  - spec/lib/taketo/support/eval_delegator_spec.rb
119
119
  - spec/lib/taketo/support/named_nodes_collection_spec.rb
120
120
  - spec/spec_helper.rb
121
+ - spec/support/helpers/construct_spec_helper.rb
121
122
  - spec/support/helpers/dsl_spec_helper.rb
122
123
  - spec/support/matchers/be_appropriate_construct_matcher.rb
123
124
  - spec/support/matchers/enclose_scope_matcher.rb
125
+ - spec/support/matchers/have_accessor_matcher.rb
124
126
  - features/commands.feature
125
127
  - features/config_validation.feature
126
128
  - features/connect_to_server.feature
129
+ - features/error_handling.feature
127
130
  - features/help.feature
128
131
  - features/step_definitions/main_steps.rb
129
132
  - features/support/env.rb