taketo 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -3
- data/README.md +20 -7
- data/VERSION +1 -1
- data/bin/taketo +30 -31
- data/features/config.feature +33 -3
- data/features/config_validation.feature +29 -4
- data/features/connect_to_server.feature +2 -1
- data/features/error_handling.feature +1 -0
- data/features/help.feature +5 -3
- data/features/support/env.rb +2 -0
- data/lib/taketo/associated_nodes.rb +90 -0
- data/lib/taketo/commands/ssh_command.rb +0 -4
- data/lib/taketo/config_printer_visitor.rb +68 -0
- data/lib/taketo/config_traverser.rb +55 -0
- data/lib/taketo/config_validator.rb +33 -34
- data/lib/taketo/config_visitor.rb +29 -0
- data/lib/taketo/constructs/base_construct.rb +9 -54
- data/lib/taketo/constructs/command.rb +9 -1
- data/lib/taketo/constructs/environment.rb +11 -1
- data/lib/taketo/constructs/project.rb +5 -0
- data/lib/taketo/constructs/server.rb +7 -4
- data/lib/taketo/dsl.rb +13 -2
- data/lib/taketo/support/named_nodes_collection.rb +10 -0
- data/lib/taketo/support.rb +0 -1
- data/lib/taketo.rb +2 -1
- data/spec/integration/dsl_integration_spec.rb +1 -1
- data/spec/lib/taketo/associated_nodes_spec.rb +101 -0
- data/spec/lib/taketo/commands/ssh_command_spec.rb +5 -15
- data/spec/lib/taketo/config_printer_visitor_spec.rb +112 -0
- data/spec/lib/taketo/config_traverser_spec.rb +63 -0
- data/spec/lib/taketo/config_validator_spec.rb +53 -39
- data/spec/lib/taketo/config_visitor_spec.rb +51 -0
- data/spec/lib/taketo/constructs/base_construct_spec.rb +7 -70
- data/spec/lib/taketo/constructs/command_spec.rb +20 -8
- data/spec/lib/taketo/constructs/config_spec.rb +2 -6
- data/spec/lib/taketo/constructs/environment_spec.rb +12 -7
- data/spec/lib/taketo/constructs/project_spec.rb +10 -4
- data/spec/lib/taketo/constructs/server_spec.rb +25 -16
- data/spec/lib/taketo/constructs_factory_spec.rb +12 -12
- data/spec/lib/taketo/destination_resolver_spec.rb +32 -32
- data/spec/lib/taketo/dsl_spec.rb +115 -98
- data/spec/lib/taketo/support/named_nodes_collection_spec.rb +49 -21
- data/spec/support/helpers/construct_spec_helper.rb +5 -5
- data/spec/support/matchers/be_appropriate_construct_matcher.rb +9 -1
- data/spec/support/matchers/have_accessor_matcher.rb +6 -3
- metadata +18 -7
- data/lib/taketo/config_printer.rb +0 -84
- data/lib/taketo/support/eval_delegator.rb +0 -25
- data/spec/lib/taketo/config_printer_spec.rb +0 -116
- data/spec/lib/taketo/support/eval_delegator_spec.rb +0 -43
data/Gemfile
CHANGED
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.
|
29
|
+
rspec-expectations (2.11.3)
|
30
30
|
diff-lcs (~> 1.1.3)
|
31
|
-
rspec-mocks (2.11.
|
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.
|
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.
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
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
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
data/features/config.feature
CHANGED
@@ -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 :
|
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)
|
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
|
16
|
-
|
|
17
|
-
| project(:foo) {}
|
18
|
-
| project(:foo) { environment(: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"
|
data/features/help.feature
CHANGED
@@ -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
|
"""
|
data/features/support/env.rb
CHANGED
@@ -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
|
5
|
-
def initialize
|
6
|
-
@
|
7
|
+
class ConfigValidatorVisitor < ConfigVisitor
|
8
|
+
def initialize
|
9
|
+
@global_server_aliases = {}
|
7
10
|
end
|
8
11
|
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
44
|
-
|
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
|
|