taketo 0.0.6 → 0.0.7

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.
Files changed (51) hide show
  1. data/Gemfile +1 -1
  2. data/Gemfile.lock +3 -3
  3. data/README.md +20 -7
  4. data/VERSION +1 -1
  5. data/bin/taketo +30 -31
  6. data/features/config.feature +33 -3
  7. data/features/config_validation.feature +29 -4
  8. data/features/connect_to_server.feature +2 -1
  9. data/features/error_handling.feature +1 -0
  10. data/features/help.feature +5 -3
  11. data/features/support/env.rb +2 -0
  12. data/lib/taketo/associated_nodes.rb +90 -0
  13. data/lib/taketo/commands/ssh_command.rb +0 -4
  14. data/lib/taketo/config_printer_visitor.rb +68 -0
  15. data/lib/taketo/config_traverser.rb +55 -0
  16. data/lib/taketo/config_validator.rb +33 -34
  17. data/lib/taketo/config_visitor.rb +29 -0
  18. data/lib/taketo/constructs/base_construct.rb +9 -54
  19. data/lib/taketo/constructs/command.rb +9 -1
  20. data/lib/taketo/constructs/environment.rb +11 -1
  21. data/lib/taketo/constructs/project.rb +5 -0
  22. data/lib/taketo/constructs/server.rb +7 -4
  23. data/lib/taketo/dsl.rb +13 -2
  24. data/lib/taketo/support/named_nodes_collection.rb +10 -0
  25. data/lib/taketo/support.rb +0 -1
  26. data/lib/taketo.rb +2 -1
  27. data/spec/integration/dsl_integration_spec.rb +1 -1
  28. data/spec/lib/taketo/associated_nodes_spec.rb +101 -0
  29. data/spec/lib/taketo/commands/ssh_command_spec.rb +5 -15
  30. data/spec/lib/taketo/config_printer_visitor_spec.rb +112 -0
  31. data/spec/lib/taketo/config_traverser_spec.rb +63 -0
  32. data/spec/lib/taketo/config_validator_spec.rb +53 -39
  33. data/spec/lib/taketo/config_visitor_spec.rb +51 -0
  34. data/spec/lib/taketo/constructs/base_construct_spec.rb +7 -70
  35. data/spec/lib/taketo/constructs/command_spec.rb +20 -8
  36. data/spec/lib/taketo/constructs/config_spec.rb +2 -6
  37. data/spec/lib/taketo/constructs/environment_spec.rb +12 -7
  38. data/spec/lib/taketo/constructs/project_spec.rb +10 -4
  39. data/spec/lib/taketo/constructs/server_spec.rb +25 -16
  40. data/spec/lib/taketo/constructs_factory_spec.rb +12 -12
  41. data/spec/lib/taketo/destination_resolver_spec.rb +32 -32
  42. data/spec/lib/taketo/dsl_spec.rb +115 -98
  43. data/spec/lib/taketo/support/named_nodes_collection_spec.rb +49 -21
  44. data/spec/support/helpers/construct_spec_helper.rb +5 -5
  45. data/spec/support/matchers/be_appropriate_construct_matcher.rb +9 -1
  46. data/spec/support/matchers/have_accessor_matcher.rb +6 -3
  47. metadata +18 -7
  48. data/lib/taketo/config_printer.rb +0 -84
  49. data/lib/taketo/support/eval_delegator.rb +0 -25
  50. data/spec/lib/taketo/config_printer_spec.rb +0 -116
  51. data/spec/lib/taketo/support/eval_delegator_spec.rb +0 -43
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source :rubygems
2
2
 
3
3
  group :development do
4
- gem "rspec", "~> 2.10"
4
+ gem "rspec", "~> 2.11"
5
5
  gem "rake", "~> 0.9"
6
6
  gem "aruba", "~> 0.4"
7
7
  gem 'simplecov', '~> 0.6', :require => false
data/Gemfile.lock CHANGED
@@ -26,9 +26,9 @@ GEM
26
26
  rspec-expectations (~> 2.11.0)
27
27
  rspec-mocks (~> 2.11.0)
28
28
  rspec-core (2.11.1)
29
- rspec-expectations (2.11.1)
29
+ rspec-expectations (2.11.3)
30
30
  diff-lcs (~> 1.1.3)
31
- rspec-mocks (2.11.1)
31
+ rspec-mocks (2.11.3)
32
32
  simplecov (0.6.4)
33
33
  multi_json (~> 1.0)
34
34
  simplecov-html (~> 0.5.3)
@@ -40,5 +40,5 @@ PLATFORMS
40
40
  DEPENDENCIES
41
41
  aruba (~> 0.4)
42
42
  rake (~> 0.9)
43
- rspec (~> 2.10)
43
+ rspec (~> 2.11)
44
44
  simplecov (~> 0.6)
data/README.md CHANGED
@@ -92,6 +92,10 @@ You can use shared server configs to reduce duplication:
92
92
  end
93
93
  end
94
94
 
95
+ shared_server_config :some_other_shared_config do |folder|
96
+ location File.join("/var", folder)
97
+ end
98
+
95
99
  project :my_project do
96
100
  environment :staging do
97
101
  server :s1 do
@@ -101,7 +105,7 @@ You can use shared server configs to reduce duplication:
101
105
 
102
106
  server :s2 do
103
107
  host :s2 do
104
- include_shared_server_config(:my_staging)
108
+ include_shared_server_config(:my_staging => [], :some_other_shared_config => "qux")
105
109
  end
106
110
  end
107
111
  end
@@ -109,15 +113,24 @@ You can use shared server configs to reduce duplication:
109
113
 
110
114
  This will give you ```console``` commands available both on s1 and s2
111
115
 
112
- To-Do:
113
- ------
114
-
115
- * Add support for defaults
116
- * Add support for generating shortcuts
117
-
118
116
  The Changelog:
119
117
  --------------
120
118
 
119
+ ### v0.0.7 (08.10.2012) ###
120
+ * Add ability to include several shared server config at once
121
+ Use hash as include_shared_server_config parameter to include
122
+ multiple shared server configs with arguments, like:
123
+ ```ruby
124
+ include_shared_server_config(:foo => :some_arg, :bar => [:arg1, :arg2])
125
+ ```
126
+ or just enumerate them if no arguments needed:
127
+ ```ruby
128
+ include_shared_server_configs(:baz, :quux)
129
+ ```
130
+
131
+ NOTE: This change will break your config if you've used parametrized
132
+ shared server configs before; rewrite them using hash-form
133
+
121
134
  ### v0.0.6 (26.07.2012) ###
122
135
  * Add identity_file server config option
123
136
  * Add shared server config support
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
data/bin/taketo CHANGED
@@ -1,21 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ if ENV["TAKETO_DEV"]
4
+ $:.unshift(File.expand_path('../../lib', __FILE__))
5
+ end
6
+
3
7
  require 'rubygems'
4
8
 
5
- begin
6
- require 'taketo'
7
- require 'taketo/constructs_factory'
8
- require 'taketo/commands'
9
- require 'taketo/destination_resolver'
10
- require 'optparse'
11
- rescue LoadError => e #development
12
- raise if $loaded
13
- $:.unshift File.expand_path('../../lib', __FILE__)
14
- require 'bundler'
15
- Bundler.setup(:default)
16
- $loaded = true
17
- retry
18
- end
9
+ require 'taketo'
10
+ require 'taketo/constructs_factory'
11
+ require 'taketo/commands'
12
+ require 'taketo/destination_resolver'
13
+ require 'optparse'
19
14
 
20
15
  Signal.trap("SIGINT") do
21
16
  puts "Terminating"
@@ -39,7 +34,7 @@ def parse_options
39
34
  options[:config] = c
40
35
  end
41
36
 
42
- opts.on("-c COMMAND", "--command", "Command to execute on destination server",
37
+ opts.on("-c COMMAND", "--command", "Command to execute on destination server",
43
38
  " (COMMAND either declared in config or passed as an argument)") do |c|
44
39
  raise OptionParser::MissingArgument if String(c).strip.empty?
45
40
  options[:command] = c
@@ -71,21 +66,28 @@ end
71
66
 
72
67
  def parse_config(config)
73
68
  DSL.new.configure(config).tap do |config|
74
- ConfigValidator.new(config).validate!
69
+ traverser = ConfigTraverser.new(config)
70
+ ConfigValidator.new(traverser).validate!
75
71
  end
76
72
  end
77
73
 
78
74
  def remote_command(server, options)
79
- command_name = options[:command] or return default_command(options)
80
- server.find(:command, command_name.to_sym) { default_command(options) }
75
+ command = options[:command]
76
+ if String(command).empty?
77
+ server.default_command
78
+ else
79
+ server.find_command(command.to_sym) || Constructs::Command.explicit_command(command)
80
+ end
81
81
  end
82
82
 
83
- def default_command(options)
84
- cmd = Constructs::Command.new(:default)
85
- cmd.command = options.fetch(:command) { "bash" }
86
- cmd
83
+ def execute(shell_command, options)
84
+ if options[:dry_run]
85
+ puts shell_command
86
+ else
87
+ system shell_command
88
+ end
87
89
  end
88
-
90
+
89
91
  begin
90
92
  options = parse_options
91
93
  config = parse_config(options[:config])
@@ -94,18 +96,15 @@ begin
94
96
 
95
97
  if options.delete(:view)
96
98
  node = resolver.get_node
97
- puts ConfigPrinter.new.render(node).chomp
99
+ traverser = ConfigTraverser.new(node)
100
+ config_printer = ConfigPrinterVisitor.new
101
+ traverser.visit_depth_first(config_printer)
102
+ puts config_printer.result
98
103
  else
99
104
  server = resolver.resolve
100
-
101
105
  server_command = remote_command(server, options)
102
106
  command_to_execute = Commands::SSHCommand.new(server).render(server_command.render(server, options))
103
-
104
- if options[:dry_run]
105
- puts command_to_execute
106
- else
107
- system command_to_execute
108
- end
107
+ execute(command_to_execute, options)
109
108
  end
110
109
  rescue SystemExit
111
110
  # Do nothing
@@ -6,20 +6,50 @@ Feature: taketo config
6
6
  Scenario: Shared server configs
7
7
  When I have the following config in "/tmp/taketo_test_cfg.rb"
8
8
  """
9
- shared_server_config :shared_config_example do
9
+ shared_server_config :sc1 do
10
10
  port 9999 #can contain any server config options
11
11
  end
12
12
 
13
+ shared_server_config :sc2 do
14
+ location "/var/qux"
15
+ end
16
+
17
+ project :slots do
18
+ environment :staging do
19
+ server(:s1) { host "1.2.3.4"; include_shared_server_config(:sc1, :sc2) }
20
+ end
21
+ end
22
+ """
23
+ And I successfully run `taketo slots:staging:s1 --config=/tmp/taketo_test_cfg.rb --dry-run`
24
+ Then the output should contain
25
+ """
26
+ ssh -t -p 9999 1.2.3.4 "cd /var/qux; RAILS_ENV=staging bash"
27
+ """
28
+
29
+ Scenario: Shared server configs with arguments
30
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
31
+ """
32
+ shared_server_config :sc_args1 do |port_num|
33
+ port port_num
34
+ end
35
+
36
+ shared_server_config :sc_args2 do |projects_root_folder, project_folder|
37
+ location File.join(projects_root_folder, project_folder)
38
+ end
39
+
13
40
  project :slots do
14
41
  environment :staging do
15
- server(:s1) { host "1.2.3.4"; include_shared_server_config(:shared_config_example) }
42
+ server(:s1) do
43
+ host "1.2.3.4"
44
+ include_shared_server_config(:sc_args1 => 9999, :sc_args2 => ['/var', 'qux'])
45
+ end
16
46
  end
17
47
  end
18
48
  """
19
49
  And I successfully run `taketo slots:staging:s1 --config=/tmp/taketo_test_cfg.rb --dry-run`
20
50
  Then the output should contain
21
51
  """
22
- ssh -t -p 9999 1.2.3.4 "RAILS_ENV=staging bash"
52
+ ssh -t -p 9999 1.2.3.4 "cd /var/qux; RAILS_ENV=staging bash"
23
53
  """
24
54
 
25
55
  Scenario: Set environment variables
@@ -12,7 +12,32 @@ Feature:
12
12
  Then the stderr should contain "<error>"
13
13
 
14
14
  Examples:
15
- | config | error |
16
- | | There are no projects. Add some to your config (~/.taketo.rc.rb by default) |
17
- | project(:foo) {} | There is no environments for the following projects: foo |
18
- | project(:foo) { environment(:bar) {}} | There is no servers for the following environments in project foo: bar |
15
+ | config | error |
16
+ | | There are no projects. Add some to your config (~/.taketo.rc.rb by default) |
17
+ | project(:foo) {} | Project foo: no environments |
18
+ | project(:foo) { environment(:bar) {} } | Environment foo:bar: no servers |
19
+ | project(:foo) { environment(:bar) { server {} }} | Server foo:bar:default: host is not defined |
20
+
21
+ Scenario: Global server alias clash
22
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
23
+ """
24
+ project(:foo) { environment(:bar) {
25
+ server(:s1) { host '1.2.3.4'; global_alias :a1 }
26
+ server(:s2) { host '2.3.4.5'; global_alias :a1 }
27
+ }}
28
+ """
29
+ And I run `taketo --config=/tmp/taketo_test_cfg.rb`
30
+ Then the stderr should contain "Server foo:bar:s2: global alias 'a1' has already been taken by server foo:bar:s1"
31
+
32
+ Scenario: Bad command
33
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
34
+ """
35
+ project(:foo) { environment(:bar) {
36
+ server(:s1) { host '1.2.3.4'
37
+ command(:foo) {}
38
+ }
39
+ }}
40
+ """
41
+ And I run `taketo --config=/tmp/taketo_test_cfg.rb`
42
+ Then the stderr should contain "Don't know what to execute on command foo"
43
+
@@ -1,4 +1,4 @@
1
- Feature:
1
+ Feature:
2
2
  In order to be able to access my servers quickly
3
3
  As a developer
4
4
  I want to have a nifty little utility
@@ -53,6 +53,7 @@ Feature:
53
53
  project :slots do
54
54
  environment :staging do
55
55
  server :s1 do
56
+ host "1.2.3.4"
56
57
  end
57
58
  server :s2 do
58
59
  host "2.3.4.5"
@@ -10,6 +10,7 @@ Feature:
10
10
  project :slots do
11
11
  environment :staging do
12
12
  server :s1 do
13
+ host "1.2.3.4"
13
14
  end
14
15
  server :s2 do
15
16
  host "2.3.4.5"
@@ -37,6 +37,7 @@ Feature:
37
37
  When I run `taketo --config=/tmp/taketo_test_cfg.rb --view`
38
38
  Then the output should contain exactly:
39
39
  """
40
+
40
41
  Project: foo
41
42
  Environment: bar
42
43
  Server: default
@@ -45,16 +46,16 @@ Feature:
45
46
  User: pivo
46
47
  Default location: /var/apps/vodka
47
48
  Environment: RAILS_ENV=bar
48
-
49
+ (No commands)
50
+
49
51
  Project: baz
50
52
  Environment: qux
51
53
  Server: bart
52
54
  Host: 2.3.4.5
53
55
  Environment: RAILS_ENV=qux
54
- Commands:
55
56
  console
56
57
  killall - Kill ALL humans
57
-
58
+
58
59
  """
59
60
 
60
61
  Scenario: View particular server
@@ -67,5 +68,6 @@ Feature:
67
68
  User: pivo
68
69
  Default location: /var/apps/vodka
69
70
  Environment: RAILS_ENV=bar
71
+ (No commands)
70
72
 
71
73
  """
@@ -4,6 +4,8 @@ require 'aruba/cucumber'
4
4
  require 'fileutils'
5
5
 
6
6
  ENV['PATH'] = "#{File.expand_path('../../../bin', __FILE__)}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
7
+ ENV['RUBYOPT'] = '-Ilib'
8
+ ENV["TAKETO_DEV"] = 'true'
7
9
 
8
10
  After do
9
11
  if defined? @config_path and File.exist?(@config_path)
@@ -0,0 +1,90 @@
1
+ module Taketo
2
+ class NodesNotDefinedError < StandardError; end
3
+
4
+ module AssociatedNodes
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.send(:include, InstanceMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ ##
13
+ # Adds nodes collections to the construct
14
+ #
15
+ # Example:
16
+ #
17
+ # class Bar < BaseConstruct
18
+ # has_nodes :foos, :foo
19
+ # end
20
+ #
21
+ # bar = Bar.new
22
+ # bar.foos # => foos collection
23
+ # bar.append_foo(foo) # adds node the collection
24
+ # bar.find_foo(:foo_name) # find foo in foos collection by name
25
+ #
26
+ def has_nodes(name_plural, name_singular)
27
+ self.node_types << name_plural
28
+ includable = Module.new do
29
+ define_method "append_#{name_singular}" do |object|
30
+ nodes(name_plural) << object
31
+ end
32
+
33
+ define_method "find_#{name_singular}" do |name|
34
+ nodes(name_plural).find_by_name(name)
35
+ end
36
+
37
+ define_method name_plural do
38
+ nodes(name_plural)
39
+ end
40
+
41
+ define_method "has_#{name_plural}?" do
42
+ nodes(name_plural).any?
43
+ end
44
+ end
45
+ self.send(:include, includable)
46
+ end
47
+
48
+ def node_types
49
+ @node_types ||= []
50
+ end
51
+ end
52
+
53
+ module InstanceMethods
54
+ def initialize(*args)
55
+ @nodes = {}
56
+ end
57
+
58
+ def find(singular_node_name, name)
59
+ send("find_#{singular_node_name}", name) or
60
+ if block_given?
61
+ yield
62
+ else
63
+ raise KeyError, "#{singular_node_name} #{name} not found for #{qualified_name}"
64
+ end
65
+ end
66
+
67
+ def nodes(name_plural)
68
+ unless self.class.node_types.include?(name_plural)
69
+ raise NodesNotDefinedError, "#{name_plural} not defined for #{qualified_name}"
70
+ end
71
+ @nodes[name_plural] ||= Taketo::Support::NamedNodesCollection.new
72
+ end
73
+
74
+ def has_nodes?(name_plural)
75
+ unless self.class.node_types.include?(name_plural)
76
+ raise NodesNotDefinedError, "#{name_plural} not defined for #{qualified_name}"
77
+ end
78
+ @nodes.fetch(name_plural) { [] }.any?
79
+ end
80
+
81
+ ##
82
+ # Ovverride for better error messages
83
+ def qualified_name
84
+ self.class.name
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+
@@ -9,7 +9,6 @@ module Taketo
9
9
 
10
10
  def initialize(server, options = {})
11
11
  @server = server
12
- @environment = @server.environment
13
12
  end
14
13
 
15
14
  def render(rendered_command)
@@ -17,9 +16,6 @@ module Taketo
17
16
  end
18
17
 
19
18
  def host
20
- unless @server.host
21
- raise ArgumentError, "host for server #{@server.name} in #{@environment.name} is not defined!"
22
- end
23
19
  shellescape @server.host
24
20
  end
25
21
 
@@ -0,0 +1,68 @@
1
+ require 'taketo/config_visitor'
2
+
3
+ module Taketo
4
+ class ConfigPrinterVisitor < ConfigVisitor
5
+ def initialize
6
+ @indent_level = 0
7
+ @result = ""
8
+ end
9
+
10
+ visit Config do |config|
11
+ indent(0) do
12
+ if config.has_projects?
13
+ put "Default destination: #{config.default_destination}" if config.default_destination
14
+ else
15
+ put "There are no projects yet..."
16
+ end
17
+ end
18
+ end
19
+
20
+ visit Project do |project|
21
+ put
22
+ indent(0) do
23
+ put "Project: #{project.name}"
24
+ indent { put "(No environments)" unless project.has_environments? }
25
+ end
26
+ end
27
+
28
+ visit Environment do |environment|
29
+ indent(1) do
30
+ put "Environment: #{environment.name}"
31
+ indent { put "(No servers)" unless environment.has_servers? }
32
+ end
33
+ end
34
+
35
+ visit Server do |server|
36
+ indent(2) do
37
+ put "Server: #{server.name}"
38
+ indent do
39
+ put "Host: #{server.host}"
40
+ put "Port: #{server.port}" if server.port
41
+ put "User: #{server.username}" if server.username
42
+ put "Default location: #{server.default_location}" if server.default_location
43
+ put "Environment: " + server.environment_variables.map { |n, v| "#{n}=#{v}" }.join(" ")
44
+ indent { put "(No commands)" unless server.has_commands? }
45
+ end
46
+ end
47
+ end
48
+
49
+ visit Command do |command|
50
+ indent(4) { put command.name.to_s + (" - " + command.description if command.description).to_s }
51
+ end
52
+
53
+ def result
54
+ @result.chomp
55
+ end
56
+
57
+ def indent(level = nil)
58
+ @first_indent ||= level || @indent_level
59
+ level ? @indent_level = level - @first_indent : @indent_level += 1
60
+ yield
61
+ end
62
+
63
+ def put(str = nil)
64
+ @result += (" " * @indent_level + str.to_s).rstrip.chomp + "\n"
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,55 @@
1
+ require 'delegate'
2
+ require 'forwardable'
3
+
4
+ module Taketo
5
+ class ConfigTraverser
6
+ LEVELS = { :config => 1, :project => 2, :environment => 3, :server => 4, :command => 5 }.freeze
7
+ PLURALS = { :config => :configs, :project => :projects, :environment => :environments, :server => :servers, :command => :commands }.freeze
8
+
9
+ class NodeWithPath < SimpleDelegator
10
+ extend Forwardable
11
+ def_delegators :__getobj__, :class, :ancestors, :== # avoid ancestry tree troubles
12
+
13
+ attr_reader :path
14
+
15
+ def initialize(node, path_string)
16
+ super(node)
17
+ @path = path_string.freeze
18
+ end
19
+ end
20
+
21
+ def initialize(root)
22
+ @root = root
23
+ end
24
+
25
+ def visit_depth_first(visitor)
26
+ parents = []
27
+ path_stack = [wrap_node_with_path(parents, @root)]
28
+
29
+ while path_stack.any?
30
+ node = path_stack.pop
31
+ visitor.visit(node)
32
+
33
+ next_level_node_type = LEVELS.respond_to?(:key) ? LEVELS.key(LEVELS[node.node_type] + 1) : LEVELS.index(LEVELS[node.node_type] + 1)
34
+ if next_level_node_type && node.has_nodes?(PLURALS[next_level_node_type])
35
+ parents << node
36
+ node.nodes(PLURALS[next_level_node_type]).reverse_each do |n|
37
+ n = wrap_node_with_path(parents, n)
38
+ path_stack.push(n)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def wrap_node_with_path(parents, node)
47
+ NodeWithPath.new(node, path(parents, node))
48
+ end
49
+
50
+ def path(parents, node)
51
+ parents.map(&:name).reject(&:nil?).concat([node.name]).join(":")
52
+ end
53
+ end
54
+ end
55
+
@@ -1,53 +1,52 @@
1
+ require 'taketo/config_traverser'
2
+ require 'taketo/config_visitor'
3
+
1
4
  module Taketo
2
5
  class ConfigError < StandardError; end
3
6
 
4
- class ConfigValidator
5
- def initialize(config)
6
- @config = config
7
+ class ConfigValidatorVisitor < ConfigVisitor
8
+ def initialize
9
+ @global_server_aliases = {}
7
10
  end
8
11
 
9
- def validate!
10
- ensure_projects_exist
11
- ensure_environments_exist
12
- ensure_servers_exist
13
- ensure_global_server_aliases_unique
12
+ visit Config do |c|
13
+ raise ConfigError, "There are no projects. Add some to your config (~/.taketo.rc.rb by default)" unless c.has_projects?
14
14
  end
15
15
 
16
- private
17
-
18
- def ensure_projects_exist
19
- if @config.projects.empty?
20
- raise ConfigError,
21
- "There are no projects. Add some to your config (~/.taketo.rc.rb by default)"
22
- end
16
+ visit Project do |p|
17
+ raise ConfigError, "Project #{p.path}: no environments" unless p.has_environments?
23
18
  end
24
19
 
25
- def ensure_environments_exist
26
- projects_without_environments = @config.projects.select { |project| project.environments.empty? }
27
- if projects_without_environments.any?
28
- raise ConfigError,
29
- "There is no environments for the following projects: #{projects_without_environments.map(&:name).join(", ")}"
30
- end
20
+ visit Environment do |e|
21
+ raise ConfigError, "Environment #{e.path}: no servers" unless e.has_servers?
31
22
  end
32
23
 
33
- def ensure_servers_exist
34
- @config.projects.each do |project|
35
- environments_without_servers = project.environments.select { |environment| environment.servers.empty? }
36
- if environments_without_servers.any?
37
- raise ConfigError,
38
- "There is no servers for the following environments in project #{project.name}: #{environments_without_servers.map(&:name).join(", ")}"
24
+ visit Server do |s|
25
+ if !String(s.global_alias).empty?
26
+ if @global_server_aliases.key?(s.global_alias)
27
+ raise ConfigError, "Server #{s.path}: global alias '#{s.global_alias}' has already been taken by server #{@global_server_aliases[s.global_alias].path}"
28
+ else
29
+ @global_server_aliases[s.global_alias] = s
39
30
  end
40
31
  end
32
+
33
+ raise ConfigError, "Server #{s.path}: host is not defined" if String(s.host).empty?
41
34
  end
42
35
 
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
36
+ visit Command do |c|
37
+ raise ConfigError, "Don't know what to execute on command #{c.name}" if String(c.command).empty?
50
38
  end
51
39
  end
40
+
41
+ class ConfigValidator
42
+ def initialize(traverser)
43
+ @traverser = traverser
44
+ end
45
+
46
+ def validate!
47
+ @traverser.visit_depth_first(ConfigValidatorVisitor.new)
48
+ end
49
+ end
50
+
52
51
  end
53
52