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.
- 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
|
|