logical-construct 0.0.5 → 0.1.0
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/bin/flight-deck +3 -0
- data/doc/DESIGN +48 -0
- data/doc/EC2-baking-notes +70 -0
- data/doc/ExampleStartupRakefile +152 -0
- data/doc/ExampleTargetRakefile +4 -0
- data/doc/TODO +148 -0
- data/doc/Vb-EC2-translation-notes +96 -0
- data/doc/hating-chef +32 -0
- data/lib/logical-construct/archive-tasks.rb +307 -0
- data/lib/logical-construct/ground-control.rb +4 -1
- data/lib/logical-construct/ground-control/build-plan.rb +95 -0
- data/lib/logical-construct/ground-control/core.rb +1 -1
- data/lib/logical-construct/ground-control/generate-manifest.rb +67 -0
- data/lib/logical-construct/ground-control/provision.rb +73 -168
- data/lib/logical-construct/ground-control/run-on-target.rb +1 -1
- data/lib/logical-construct/ground-control/setup.rb +1 -4
- data/lib/logical-construct/ground-control/setup/copy-files.rb +2 -2
- data/lib/logical-construct/ground-control/tools.rb +66 -0
- data/lib/logical-construct/node-client.rb +112 -0
- data/lib/logical-construct/plan.rb +2 -0
- data/lib/logical-construct/plan/core.rb +45 -0
- data/lib/logical-construct/plan/standalone-bundle.rb +80 -0
- data/lib/logical-construct/port-open-check.rb +41 -0
- data/lib/logical-construct/protocol.rb +2 -0
- data/lib/logical-construct/protocol/plan-validation.rb +46 -0
- data/lib/logical-construct/protocol/ssh-tunnel.rb +127 -0
- data/lib/logical-construct/protocol/vocabulary.rb +8 -0
- data/lib/logical-construct/target/Implement.rake +8 -0
- data/lib/logical-construct/target/command-line.rb +90 -0
- data/lib/logical-construct/target/flight-deck.rb +341 -0
- data/lib/logical-construct/target/implementation.rb +33 -0
- data/lib/logical-construct/target/plan-records.rb +317 -0
- data/lib/logical-construct/target/resolution-server.rb +153 -0
- data/lib/logical-construct/target/{unpack-cookbook.rb → unpack-plan.rb} +8 -4
- data/lib/logical-construct/template-file.rb +41 -0
- data/lib/templates/Rakefile.erb +8 -0
- data/spec/ground-control/smoke-test.rb +8 -7
- data/spec/node_resolution.rb +62 -0
- data/spec/target/plan-records.rb +142 -0
- data/spec/target/provisioning.rb +21 -0
- data/spec_help/file-sandbox.rb +12 -6
- data/spec_help/fixtures/Manifest +1 -0
- data/spec_help/fixtures/source/one.tbz +1 -0
- data/spec_help/fixtures/source/three.tbz +1 -0
- data/spec_help/fixtures/source/two.tbz +1 -0
- data/spec_help/spec_helper.rb +5 -7
- metadata +165 -72
- data/lib/logical-construct/ground-control/setup/build-files.rb +0 -93
- data/lib/logical-construct/ground-control/setup/create-construct-directory.rb +0 -22
- data/lib/logical-construct/ground-control/setup/install-init.rb +0 -32
- data/lib/logical-construct/resolving-task.rb +0 -141
- data/lib/logical-construct/satisfiable-task.rb +0 -87
- data/lib/logical-construct/target.rb +0 -4
- data/lib/logical-construct/target/chef-solo.rb +0 -37
- data/lib/logical-construct/target/platforms.rb +0 -51
- data/lib/logical-construct/target/platforms/aws.rb +0 -8
- data/lib/logical-construct/target/platforms/default/chef-config.rb +0 -134
- data/lib/logical-construct/target/platforms/default/resolve-configuration.rb +0 -44
- data/lib/logical-construct/target/platforms/default/volume.rb +0 -11
- data/lib/logical-construct/target/platforms/virtualbox.rb +0 -8
- data/lib/logical-construct/target/platforms/virtualbox/volume.rb +0 -15
- data/lib/logical-construct/target/provision.rb +0 -36
- data/lib/logical-construct/target/sinatra-resolver.rb +0 -99
- data/lib/logical-construct/testing/resolve-configuration.rb +0 -32
- data/lib/logical-construct/testing/resolving-task.rb +0 -15
- data/lib/templates/chef.rb.erb +0 -9
- data/lib/templates/construct.init.d.erb +0 -18
- data/lib/templates/resolver/finished.html.erb +0 -1
- data/lib/templates/resolver/index.html.erb +0 -17
- data/lib/templates/resolver/task-file-form.html.erb +0 -6
- data/lib/templates/resolver/task-form.html.erb +0 -6
- data/spec/resolution.rb +0 -147
- data/spec/target/chef-config.rb +0 -67
- data/spec/target/chef-solo.rb +0 -55
- data/spec/target/platforms.rb +0 -36
- data/spec/target/smoke-test.rb +0 -45
- data/spec_help/ungemmer.rb +0 -36
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'logical-construct/node-client'
|
2
|
+
|
3
|
+
module LogicalConstruct
|
4
|
+
class GenerateManifest < Mattock::Tasklib
|
5
|
+
default_namespace :manifest
|
6
|
+
|
7
|
+
setting :plan_archives, []
|
8
|
+
setting :graph_format, :jsonld
|
9
|
+
setting :target_address, 'localhost'
|
10
|
+
setting :target_port, 51076
|
11
|
+
|
12
|
+
def default_configuration(provision)
|
13
|
+
super
|
14
|
+
self.plan_archives = provision.proxy_value.plan_archives
|
15
|
+
self.target_address = provision.proxy_value.target_address
|
16
|
+
self.target_port = provision.proxy_value.local_target_port
|
17
|
+
end
|
18
|
+
|
19
|
+
def node_url
|
20
|
+
"http://#{target_address}:#{target_port}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def define
|
24
|
+
in_namespace do
|
25
|
+
desc "Dump manifest (mostly for debugging)"
|
26
|
+
task :dump, [:format] do |task, args|
|
27
|
+
require 'rdf/turtle'
|
28
|
+
format = args[:format] || graph_format
|
29
|
+
format = format.to_sym
|
30
|
+
|
31
|
+
base_url = "urn:manifest"
|
32
|
+
|
33
|
+
graph = ::RDF::Graph.new
|
34
|
+
focus = RoadForest::RDF::GraphFocus.new(base_url, graph)
|
35
|
+
builder = NodeClient::ManifestBuilder.new(focus)
|
36
|
+
|
37
|
+
plan_archives.each do |archive|
|
38
|
+
builder.add(archive)
|
39
|
+
end
|
40
|
+
|
41
|
+
puts(RDF::Writer.for(format).buffer(:base_uri => base_url) do |writer|
|
42
|
+
focus.relevant_prefixes.each do |prefix, uri|
|
43
|
+
writer.prefix(prefix, uri)
|
44
|
+
end
|
45
|
+
writer.insert(graph)
|
46
|
+
end)
|
47
|
+
end
|
48
|
+
|
49
|
+
task :deliver do |task|
|
50
|
+
client = NodeClient.new
|
51
|
+
client.node_url = node_url
|
52
|
+
client.plan_archives = plan_archives
|
53
|
+
client.deliver_manifest
|
54
|
+
end
|
55
|
+
|
56
|
+
task :fulfill do |task|
|
57
|
+
client = NodeClient.new
|
58
|
+
client.node_url = node_url
|
59
|
+
client.plan_archives = plan_archives
|
60
|
+
client.deliver_plans
|
61
|
+
end
|
62
|
+
end
|
63
|
+
task self[:dump] => root_task
|
64
|
+
task self[:deliver] => root_task
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,204 +1,109 @@
|
|
1
1
|
require 'mattock'
|
2
|
-
|
3
|
-
require 'logical-construct/
|
2
|
+
|
3
|
+
require 'logical-construct/ground-control/generate-manifest'
|
4
|
+
require 'logical-construct/ground-control/build-plan'
|
5
|
+
require 'logical-construct/protocol/ssh-tunnel'
|
4
6
|
|
5
7
|
module LogicalConstruct
|
6
8
|
module GroundControl
|
7
9
|
class Provision < Mattock::Tasklib
|
8
|
-
class WebConfigure < Mattock::Task
|
9
|
-
include ResolutionProtocol
|
10
|
-
|
11
|
-
setting :target_protocol, "http"
|
12
|
-
setting :target_address, nil
|
13
|
-
setting :target_port, 51076
|
14
|
-
runtime_setting :target_url
|
15
|
-
setting :resolutions
|
16
|
-
runtime_setting :web_resolutions
|
17
|
-
|
18
|
-
def resolve_runtime_configuration
|
19
|
-
super
|
20
|
-
self.target_url ||= "#{target_protocol}://#{target_address}:#{target_port}/"
|
21
|
-
self.web_resolutions = Hash[resolutions.map do |name, value|
|
22
|
-
[web_path(name), value]
|
23
|
-
end]
|
24
|
-
end
|
25
|
-
|
26
|
-
def resolve(path)
|
27
|
-
resolved = web_resolutions.fetch(path)
|
28
|
-
if resolved.respond_to? :call
|
29
|
-
resolved = resolved.call
|
30
|
-
end
|
31
|
-
return resolved
|
32
|
-
rescue KeyError
|
33
|
-
puts "Can't find a resolution for #{path} in #{web_resolutions.keys.inspect} (ex #{resolutions.keys})"
|
34
|
-
raise
|
35
|
-
end
|
36
|
-
|
37
|
-
def uri(options)
|
38
|
-
uri_class = URI.scheme_list[target_protocol.upcase]
|
39
|
-
uri_hash = {:host => target_address, :port => target_port}
|
40
|
-
return uri_class.build(uri_hash.merge(options)).to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
def resolution_needed
|
44
|
-
index = RestClient.get(uri(:path => '/'))
|
45
|
-
body = Nokogiri::HTML(index.body)
|
46
|
-
return body.xpath("//a[@rel='requirement']")
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
#XXX I would like this to become an actual RESTful client at some
|
51
|
-
#point, but seems it would mean building it from scratch
|
52
|
-
def action
|
53
|
-
require 'uri'
|
54
|
-
require 'rest-client'
|
55
|
-
require 'nokogiri'
|
56
|
-
|
57
|
-
until (link = resolution_needed.first).nil?
|
58
|
-
href = link['href']
|
59
|
-
begin
|
60
|
-
response = RestClient.post(uri(:path => href), :data => resolve(href))
|
61
|
-
rescue RestClient::InternalServerError => ex
|
62
|
-
require 'tempfile'
|
63
|
-
file = Tempfile.open('provision-error.html')
|
64
|
-
path = file.path
|
65
|
-
file.close!
|
66
|
-
|
67
|
-
File::open(path, "w") do |file|
|
68
|
-
file.write ex.http_body
|
69
|
-
end
|
70
|
-
puts "Written error response to #{path}"
|
71
|
-
puts "Try: chromium #{path}"
|
72
|
-
fail "Unsuccessful response from server!"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
10
|
default_namespace :provision
|
79
11
|
|
80
|
-
setting :valise
|
81
12
|
setting :target_protocol, "http"
|
82
13
|
setting(:target_address, nil).isnt(:copiable)
|
83
|
-
setting :
|
84
|
-
setting :
|
85
|
-
setting :marshalling_path, "marshall"
|
86
|
-
|
87
|
-
setting(:secret_data, nested {
|
88
|
-
setting :path
|
89
|
-
setting :tarball_path
|
90
|
-
setting :file_list
|
91
|
-
})
|
92
|
-
|
93
|
-
setting(:normal_data, nested {
|
94
|
-
setting :path
|
95
|
-
setting :tarball_path
|
96
|
-
setting :file_list
|
97
|
-
})
|
98
|
-
|
99
|
-
setting(:cookbooks, nested {
|
100
|
-
setting :path
|
101
|
-
setting :tarball_path
|
102
|
-
setting :file_list
|
103
|
-
})
|
104
|
-
|
105
|
-
setting :json_attribs_path
|
106
|
-
setting :roles
|
107
|
-
setting :node_attribs
|
108
|
-
setting :json_attribs, ""
|
109
|
-
|
110
|
-
def default_configuration(core)
|
111
|
-
core.copy_settings_to(self)
|
112
|
-
super
|
113
|
-
self.cookbooks.path = "cookbooks"
|
114
|
-
self.secret_data.path = "data-bags/secret"
|
115
|
-
self.normal_data.path = "data-bags"
|
116
|
-
self.resolutions = {}
|
117
|
-
self.roles = {}
|
118
|
-
self.node_attribs = { "run_list" => [] }
|
119
|
-
end
|
120
|
-
|
121
|
-
def resolve_configuration
|
122
|
-
super
|
123
|
-
self.json_attribs_path ||= File::join(marshalling_path, "node.json")
|
14
|
+
setting :local_target_port, 51076
|
15
|
+
setting :remote_target_port, 30712
|
124
16
|
|
125
|
-
|
126
|
-
self.secret_data.file_list ||= Rake::FileList[secret_data.path + "/**/*"].exclude(%r{[.]sw[.]$})
|
127
|
-
self.normal_data.file_list ||=
|
128
|
-
Rake::FileList[normal_data.path + "/**/*"].exclude(%r{^#{secret_data.path}}).exclude(%r{[.]sw[.]$})
|
17
|
+
setting :plan_archives, []
|
129
18
|
|
130
|
-
|
131
|
-
|
132
|
-
self.normal_data.tarball_path ||= File::join(marshalling_path, "normal_data_bags.tgz")
|
19
|
+
dir(:marshalling, "marshall")
|
20
|
+
dir(:plan_source, "plans")
|
133
21
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
resolutions["chef_config:secret_data_tarball"] ||= proc do
|
139
|
-
File::open(secret_data.tarball_path, "rb")
|
140
|
-
end
|
141
|
-
|
142
|
-
resolutions["chef_config:normal_data_tarball"] ||= proc do
|
143
|
-
File::open(normal_data.tarball_path, "rb")
|
144
|
-
end
|
22
|
+
def resolve_configuration
|
23
|
+
resolve_paths
|
24
|
+
super
|
145
25
|
end
|
146
26
|
|
147
|
-
include Mattock::CommandLineDSL
|
148
27
|
def define
|
28
|
+
manifest = nil
|
149
29
|
in_namespace do
|
150
|
-
directory
|
30
|
+
directory marshalling.absolute_path
|
151
31
|
|
152
32
|
task :collect, [:ipaddr] do |task, args|
|
153
33
|
self.target_address = args[:ipaddr]
|
154
34
|
end
|
155
35
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
self.json_attribs = JSON.pretty_generate(node_attribs)
|
161
|
-
resolutions["chef_config:json_attribs"] ||= json_attribs
|
36
|
+
tunnel = LogicalConstruct::SSHTunnel.new do |tunnel|
|
37
|
+
tunnel.target_address = proxy_value.target_address
|
38
|
+
copy_settings_to(tunnel)
|
39
|
+
self.local_target_port = tunnel.proxy_value.local_target_port
|
162
40
|
end
|
163
41
|
|
164
|
-
|
165
|
-
|
166
|
-
|
42
|
+
manifest = LogicalConstruct::GenerateManifest.new(self)
|
43
|
+
|
44
|
+
start_flight = Mattock::Rake::RemoteCommandTask.define_task(:start_flight => :collect) do |start_flight|
|
45
|
+
start_flight.remote_server.address = proxy_value.target_address
|
46
|
+
start_flight.command =
|
47
|
+
Mattock::CommandLine.new("nohup",
|
48
|
+
"/opt/logical-construct/bin/flight-deck",
|
49
|
+
"-C start_server")
|
50
|
+
start_flight.command.redirect_stdin("/dev/null")
|
51
|
+
start_flight.command.redirect_stdout("/opt/logical-construct/flight_deck_server.log")
|
52
|
+
start_flight.command.copy_stream_to(2, 1)
|
53
|
+
start_flight.command.redirections << "&"
|
167
54
|
end
|
168
55
|
|
169
|
-
|
170
|
-
|
56
|
+
start_resolution = Mattock::Rake::RemoteCommandTask.define_task(:start_resolution => :deliver_manifest) do |start_flight|
|
57
|
+
start_flight.remote_server.address = proxy_value.target_address
|
58
|
+
start_flight.verbose = 3
|
59
|
+
start_flight.command =
|
60
|
+
Mattock::CommandLine.new("nohup",
|
61
|
+
"/opt/logical-construct/bin/flight-deck")
|
62
|
+
#TODO: Mattock CommandLine needs a "background"
|
63
|
+
#TODO: RemoteCommandTask should wrap the whole
|
64
|
+
#nohup-redirect-background thing
|
65
|
+
start_flight.command.redirect_stdin("/dev/null")
|
66
|
+
start_flight.command.redirect_stdout("/opt/logical-construct/flight_deck.log")
|
67
|
+
start_flight.command.copy_stream_to(2, 1)
|
68
|
+
start_flight.command.redirections << "&"
|
171
69
|
end
|
172
70
|
|
173
|
-
|
174
|
-
cmd("tar",
|
175
|
-
"--exclude **/*.sw?",
|
176
|
-
"--exclude #{secret_data.path}",
|
177
|
-
"-czf", normal_data.tarball_path, normal_data.path).must_succeed!
|
178
|
-
end
|
71
|
+
task manifest.root_task => :collect
|
179
72
|
|
180
|
-
|
181
|
-
|
182
|
-
|
73
|
+
task_spine(:start_flight, :deliver_manifest, :fulfill_manifest, :start_resolution, :complete_provision)
|
74
|
+
task :deliver_manifest => manifest[:deliver]
|
75
|
+
task :fulfill_manifest => manifest[:fulfill]
|
183
76
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
77
|
+
tunnel.wrap(manifest[:deliver])
|
78
|
+
tunnel.wrap(manifest[:fulfill])
|
79
|
+
task :complete_provision => tunnel[:close_tunnel]
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "Provision :ipaddr with specified configs"
|
83
|
+
task root_task, [:ipaddr] => self[:complete_provision]
|
84
|
+
end
|
85
|
+
|
86
|
+
def plan_task(name, &block)
|
87
|
+
plan = BuildPlan.new(self) do |build|
|
88
|
+
build.basename = name
|
89
|
+
yield build if block_given?
|
90
|
+
end
|
91
|
+
task self[:manifest] => plan.archive_path
|
193
92
|
|
194
|
-
|
195
|
-
|
196
|
-
|
93
|
+
in_namespace do
|
94
|
+
namespace :package do
|
95
|
+
desc "Compile and archive plan #{name.inspect}"
|
96
|
+
task name => plan.archive_path
|
197
97
|
end
|
198
98
|
end
|
199
99
|
|
200
|
-
|
201
|
-
|
100
|
+
plan_archives << plan.archive_path
|
101
|
+
end
|
102
|
+
|
103
|
+
def plans(*names)
|
104
|
+
names.each do |name|
|
105
|
+
plan_task(name)
|
106
|
+
end
|
202
107
|
end
|
203
108
|
end
|
204
109
|
end
|
@@ -18,7 +18,7 @@ module LogicalConstruct
|
|
18
18
|
def remote_task(name, comment = nil)
|
19
19
|
in_namespace do
|
20
20
|
desc comment unless comment.nil?
|
21
|
-
Mattock::RemoteCommandTask.
|
21
|
+
Mattock::Rake::RemoteCommandTask.define_task(name) do |task|
|
22
22
|
task.ssh_options += SSH_OPTIONS
|
23
23
|
|
24
24
|
task.runtime_definition do |task|
|
@@ -6,7 +6,7 @@ module LogicalConstruct
|
|
6
6
|
|
7
7
|
settings(
|
8
8
|
:remote_server => nested( :address => nil, :user => "root"),
|
9
|
-
:construct_dir => "/
|
9
|
+
:construct_dir => "/opt/logical-construct"
|
10
10
|
)
|
11
11
|
nil_fields :valise, :platform
|
12
12
|
|
@@ -47,7 +47,4 @@ module LogicalConstruct
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
require 'logical-construct/ground-control/setup/build-files'
|
51
|
-
require 'logical-construct/ground-control/setup/create-construct-directory'
|
52
50
|
require 'logical-construct/ground-control/setup/copy-files'
|
53
|
-
require 'logical-construct/ground-control/setup/install-init'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'mattock/command-task'
|
2
2
|
|
3
3
|
module LogicalConstruct
|
4
|
-
class RemoteCopyFile < Mattock::CommandTask
|
4
|
+
class RemoteCopyFile < Mattock::Rake::CommandTask
|
5
5
|
nil_fields :destination_address
|
6
6
|
nil_fields :source_dir, :destination_dir, :basename
|
7
7
|
required_fields :source_path, :destination_path
|
@@ -58,7 +58,7 @@ module LogicalConstruct
|
|
58
58
|
|
59
59
|
def define
|
60
60
|
in_namespace do
|
61
|
-
RemoteCopyFile.
|
61
|
+
RemoteCopyFile.define_task(self, :construct_dir) do |task|
|
62
62
|
task.runtime_definition do
|
63
63
|
task.remote_server = remote_server
|
64
64
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'mattock'
|
2
|
+
|
3
|
+
module LogicalConstruct
|
4
|
+
module GroundControl
|
5
|
+
class Tools < ::Mattock::Tasklib
|
6
|
+
include Mattock::CommandLineDSL
|
7
|
+
|
8
|
+
default_namespace :tools
|
9
|
+
|
10
|
+
dir(:plans, "plans")
|
11
|
+
|
12
|
+
def resolve_configuration
|
13
|
+
resolve_paths
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def plans_dir
|
18
|
+
plans.absolute_path.sub(%r{/$},'')
|
19
|
+
end
|
20
|
+
|
21
|
+
def define
|
22
|
+
in_namespace do
|
23
|
+
namespace :create_plan do
|
24
|
+
rule %r{\A#{plans_dir}/[^/]*\Z} do |task, args|
|
25
|
+
cmd("mkdir", "-p", task.name).must_succeed! #ok
|
26
|
+
end
|
27
|
+
|
28
|
+
rule(%r{\A#{plans_dir}/[^/]*/plan\.rake\Z}, [:name] => ['%d']) do |task, args|
|
29
|
+
require 'logical-construct/target/implementation'
|
30
|
+
|
31
|
+
File::open(task.name, "w") do |file|
|
32
|
+
indent = 16
|
33
|
+
file.write(<<-EOR.gsub(/^#{" "*indent}/,''))
|
34
|
+
require 'logical-construct/plan'
|
35
|
+
include LogicalConstruct::Plan
|
36
|
+
|
37
|
+
core = Core.new do |core|
|
38
|
+
core.namespace_name = :#{args[:name]}
|
39
|
+
core.plan_rakefile.absolute_path = __FILE__
|
40
|
+
end
|
41
|
+
|
42
|
+
core.in_namespace do
|
43
|
+
#Plan tasks go here
|
44
|
+
#
|
45
|
+
#Important tasks to make dependencies to:
|
46
|
+
|
47
|
+
EOR
|
48
|
+
|
49
|
+
Target::Implementation.task_list.each do |task_name|
|
50
|
+
file.puts(" #task :#{task_name}")
|
51
|
+
end
|
52
|
+
|
53
|
+
file.puts("end")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Create a new plan to be part of a provisioning"
|
59
|
+
task :create_plan, [:name] do |task, args|
|
60
|
+
Rake::Task[File::join(plans_dir, args[:name], "plan.rake")].invoke(args[:name])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|