taketo 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -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