logical-construct 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,33 @@
|
|
1
|
+
require 'mattock/tasklib'
|
2
|
+
|
3
|
+
module LogicalConstruct
|
4
|
+
module Target
|
5
|
+
class Implementation < ::Mattock::Tasklib
|
6
|
+
def self.task_list
|
7
|
+
[
|
8
|
+
:preflight, #Is this node acceptable?
|
9
|
+
:settings, #Shared in-memory configuration for the overall setup
|
10
|
+
:setup, #write configuration to disk for implementation tools (e.g. Chef, Puppet, apt-get)
|
11
|
+
:files, #deliver files from plan to filesystem
|
12
|
+
:execute, #run implementation tools
|
13
|
+
:configure, #install application configuration files (e.g. /etc/apache/http.conf)
|
14
|
+
:complete #All done - depends on everything.
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
default_namespace :construct
|
19
|
+
|
20
|
+
def define
|
21
|
+
in_namespace do
|
22
|
+
task_spine( *self.class.task_list )
|
23
|
+
|
24
|
+
self.class.task_list.each do |taskname|
|
25
|
+
task taskname do
|
26
|
+
puts "*** Implementation stage complete: #{taskname}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'thread'
|
3
|
+
require 'logical-construct/protocol'
|
4
|
+
|
5
|
+
module LogicalConstruct
|
6
|
+
module ResolutionServer
|
7
|
+
class PlanRecords
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
Record = Struct.new(:name, :filehash)
|
11
|
+
Directories = Struct.new(:delivered, :current, :stored)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@record_lock = Mutex.new
|
15
|
+
@records = []
|
16
|
+
@directories = Directories.new(nil, nil, nil)
|
17
|
+
end
|
18
|
+
attr_reader :directories
|
19
|
+
|
20
|
+
def reset!
|
21
|
+
each do |record|
|
22
|
+
record.cancel!
|
23
|
+
end
|
24
|
+
@records.clear
|
25
|
+
clear_files(directories.delivered)
|
26
|
+
clear_files(directories.current)
|
27
|
+
end
|
28
|
+
alias reset reset!
|
29
|
+
|
30
|
+
def clear_files(directory)
|
31
|
+
dirpath = Pathname.new(directory)
|
32
|
+
dirpath.mkpath
|
33
|
+
dirpath.each_child do |delivered|
|
34
|
+
delivered.delete
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_state
|
39
|
+
return "no-plans-yet" if @records.empty?
|
40
|
+
return "resolved" if @records.all?(&:resolved?)
|
41
|
+
return "unresolved"
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(name)
|
45
|
+
record = @records.find{|record| record.name == name}
|
46
|
+
end
|
47
|
+
|
48
|
+
def each(&block)
|
49
|
+
@records.each(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def add(name, hash)
|
53
|
+
record = nil
|
54
|
+
@record_lock.synchronize do
|
55
|
+
if @records.any?{|record| record.name == name}
|
56
|
+
raise "Cannot add a second plan requirement for #{name}"
|
57
|
+
end
|
58
|
+
record = States::Unresolved.new(self, Record.new(name, hash))
|
59
|
+
@records << record
|
60
|
+
end
|
61
|
+
record.resolve
|
62
|
+
|
63
|
+
return find(name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def change(old_state, new_state_class)
|
67
|
+
unless old_state.alive?
|
68
|
+
raise "Tried to change from old invalid state: #{old_state}"
|
69
|
+
end
|
70
|
+
new_state = new_state_class.new(self, old_state.record)
|
71
|
+
|
72
|
+
@record_lock.synchronize do
|
73
|
+
@records.delete(old_state)
|
74
|
+
@records << new_state
|
75
|
+
old_state.cancel!
|
76
|
+
end
|
77
|
+
new_state.enter
|
78
|
+
|
79
|
+
return new_state
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module States
|
84
|
+
class PlanState
|
85
|
+
include Protocol::PlanValidation
|
86
|
+
include FileTest
|
87
|
+
|
88
|
+
def initialize(records, record)
|
89
|
+
@records, @record = records, record
|
90
|
+
end
|
91
|
+
attr_reader :record
|
92
|
+
|
93
|
+
def inspect
|
94
|
+
"#<#{self.class.name}:#{"0x%0x"%object_id} #{name||"dead"}:#{filehash}>"
|
95
|
+
end
|
96
|
+
|
97
|
+
def ==(other)
|
98
|
+
return true if self.equal?(other)
|
99
|
+
return false if !self.alive? or !other.alive?
|
100
|
+
return (other.class.equal?(self.class) and
|
101
|
+
other.name.equal?(self.name) and
|
102
|
+
other.filehash.equal?(self.filehash))
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def name
|
107
|
+
return nil unless alive?
|
108
|
+
@record.name
|
109
|
+
end
|
110
|
+
|
111
|
+
def filehash
|
112
|
+
return nil unless alive?
|
113
|
+
@record.filehash
|
114
|
+
end
|
115
|
+
|
116
|
+
def enter
|
117
|
+
end
|
118
|
+
|
119
|
+
def cancel!
|
120
|
+
@record = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def alive?
|
124
|
+
!@record.nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
def exists?(path)
|
128
|
+
super(realpath(path))
|
129
|
+
end
|
130
|
+
|
131
|
+
def delivered_plans_dir
|
132
|
+
@records.directories.delivered
|
133
|
+
end
|
134
|
+
|
135
|
+
def current_plans_dir
|
136
|
+
@records.directories.current
|
137
|
+
end
|
138
|
+
|
139
|
+
def stored_plans_dir
|
140
|
+
@records.directories.stored
|
141
|
+
end
|
142
|
+
|
143
|
+
def resolved?
|
144
|
+
false
|
145
|
+
end
|
146
|
+
|
147
|
+
def can_receive?
|
148
|
+
false
|
149
|
+
end
|
150
|
+
|
151
|
+
def change(next_state)
|
152
|
+
@records.change(self, next_state)
|
153
|
+
end
|
154
|
+
|
155
|
+
def received_path
|
156
|
+
@received_path ||= Pathname.new(delivered_plans_dir) + name
|
157
|
+
end
|
158
|
+
|
159
|
+
def storage_path_for(actual_hash)
|
160
|
+
@stored_path ||= Pathname.new(stored_plans_dir) + [name, actual_hash].join(".")
|
161
|
+
end
|
162
|
+
|
163
|
+
def current_path
|
164
|
+
@current_path ||= Pathname.new(current_plans_dir) + name
|
165
|
+
end
|
166
|
+
|
167
|
+
def resolve
|
168
|
+
warn "Cannot resolve plan in current state: #{state}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def receive
|
172
|
+
warn "Cannot receive file in current state: #{state}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def state
|
176
|
+
self.class.name.sub(/.*::/,'').downcase
|
177
|
+
end
|
178
|
+
|
179
|
+
def join
|
180
|
+
return
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class Unresolved < PlanState
|
185
|
+
def can_receive?
|
186
|
+
true
|
187
|
+
end
|
188
|
+
|
189
|
+
def store_received_file(actual_hash)
|
190
|
+
stored_path = storage_path_for(actual_hash)
|
191
|
+
FileUtils.mkdir_p(stored_plans_dir)
|
192
|
+
unless exists?(stored_path)
|
193
|
+
FileUtils.mv(received_path, stored_path)
|
194
|
+
FileUtils.symlink(stored_path, received_path)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def enter
|
199
|
+
unless alive?
|
200
|
+
super
|
201
|
+
return false
|
202
|
+
end
|
203
|
+
|
204
|
+
unless exists?(received_path)
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
|
208
|
+
actual_hash = file_checksum(received_path)
|
209
|
+
|
210
|
+
store_received_file(actual_hash)
|
211
|
+
|
212
|
+
if actual_hash == filehash
|
213
|
+
FileUtils.cp(received_path.readlink, current_path.to_s)
|
214
|
+
change(Resolved)
|
215
|
+
else
|
216
|
+
received_path.delete
|
217
|
+
return false
|
218
|
+
end
|
219
|
+
end
|
220
|
+
alias receive enter
|
221
|
+
|
222
|
+
def resolve
|
223
|
+
unless alive?
|
224
|
+
super
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
change(Resolving)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class Resolving < PlanState
|
232
|
+
def cancel!
|
233
|
+
unless @thread.nil?
|
234
|
+
if @working
|
235
|
+
@thread.kill
|
236
|
+
end
|
237
|
+
end
|
238
|
+
super
|
239
|
+
end
|
240
|
+
|
241
|
+
def join
|
242
|
+
super if @thread.nil?
|
243
|
+
@thread.join
|
244
|
+
end
|
245
|
+
|
246
|
+
def enter
|
247
|
+
@working = true
|
248
|
+
@thread = Thread.new do
|
249
|
+
ResolutionMethods.run_all(self)
|
250
|
+
@working = false
|
251
|
+
change(Unresolved)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class Resolved < PlanState
|
257
|
+
def resolved?
|
258
|
+
true
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
module ResolutionMethods
|
264
|
+
class << self
|
265
|
+
def resolution_methods
|
266
|
+
@methods ||= []
|
267
|
+
end
|
268
|
+
|
269
|
+
def add_method(name, klass)
|
270
|
+
resolution_methods << [name, klass]
|
271
|
+
end
|
272
|
+
|
273
|
+
def run_all(state)
|
274
|
+
resolution_methods.each do |name, klass|
|
275
|
+
klass.new(state).run
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
class ResolutionMethod
|
281
|
+
include Protocol::PlanValidation
|
282
|
+
include FileTest
|
283
|
+
|
284
|
+
def self.register(name)
|
285
|
+
ResolutionMethods.add_method(name, self)
|
286
|
+
end
|
287
|
+
|
288
|
+
def exists?(path)
|
289
|
+
super(realpath(path))
|
290
|
+
end
|
291
|
+
|
292
|
+
def initialize(state)
|
293
|
+
@state = state
|
294
|
+
end
|
295
|
+
attr_reader :state
|
296
|
+
|
297
|
+
def run
|
298
|
+
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class LocalStorage < ResolutionMethod
|
303
|
+
register :local_storage
|
304
|
+
|
305
|
+
def run
|
306
|
+
stored_path = state.storage_path_for(state.record.filehash)
|
307
|
+
received_path = state.received_path
|
308
|
+
|
309
|
+
if exists?(stored_path) and not (exists?(received_path) or symlink?(received_path))
|
310
|
+
FileUtils.symlink(stored_path, received_path)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'rdf/vocab/skos'
|
2
|
+
require 'roadforest/server'
|
3
|
+
require 'logical-construct/protocol'
|
4
|
+
require 'logical-construct/target/plan-records'
|
5
|
+
|
6
|
+
module LogicalConstruct
|
7
|
+
module ResolutionServer
|
8
|
+
class Application < ::RoadForest::Application
|
9
|
+
def setup
|
10
|
+
router.add :root, [], :read_only, Models::Navigation
|
11
|
+
router.add :status, ["status"], :read_only, Models::ServerStatus
|
12
|
+
router.add :manifest, ["manifest"], :leaf, Models::ServerManifest
|
13
|
+
router.add :unresolved_plans, ["unresolved_plans"], :parent, Models::UnresolvedPlansList
|
14
|
+
router.add :full_plans, ["full_plans"], :parent, Models::FullPlansList
|
15
|
+
router.add :plan, ["plans",'*'], :leaf, Models::Plan
|
16
|
+
router.add :file_content, ["files","*"], :leaf, Models::PlanContent
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ServicesHost < ::RoadForest::Application::ServicesHost
|
21
|
+
attr_accessor :plan_records
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@plan_records = PlanRecords.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def destination_dir
|
28
|
+
plan_records.directories.delivered
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Models
|
33
|
+
class Navigation < RoadForest::RDFModel
|
34
|
+
def exists?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(graph)
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
def nav_entry(graph, name, path)
|
43
|
+
graph.add_node([:skos, :hasTopConcept], "#" + name) do |entry|
|
44
|
+
entry[:rdf, :type] = [:skos, "Concept"]
|
45
|
+
entry[:skos, :prefLabel] = name
|
46
|
+
entry[:foaf, "page"] = path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fill_graph(graph)
|
51
|
+
graph[:rdf, "type"] = [:skos, "ConceptScheme"]
|
52
|
+
nav_entry(graph, "Server Manifest", path_for(:manifest))
|
53
|
+
nav_entry(graph, "Unresolved Plans", path_for(:unresolved_plans))
|
54
|
+
nav_entry(graph, "All Plans", path_for(:full_plans))
|
55
|
+
nav_entry(graph, "Current Status", path_for(:status))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ServerStatus < RoadForest::RDFModel
|
60
|
+
def data
|
61
|
+
services.plan_records.total_state
|
62
|
+
end
|
63
|
+
|
64
|
+
def fill_graph(graph)
|
65
|
+
graph[[:lc, "node-state"]] = data
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class ServerManifest < RoadForest::RDFModel
|
70
|
+
def fill_graph(graph)
|
71
|
+
graph.add_list(:lc, "plans") do |list|
|
72
|
+
services.plan_records.each do |record|
|
73
|
+
list << path_for(:plan, '*' => record.name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def graph_update(graph)
|
79
|
+
services.plan_records.reset!
|
80
|
+
|
81
|
+
graph[:lc, "plans"].as_list.each do |plan|
|
82
|
+
services.plan_records.add(plan.first(:lc, "name"), plan.first(:lc, "digest"))
|
83
|
+
end
|
84
|
+
|
85
|
+
new_graph
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class FullPlansList < RoadForest::RDFModel
|
90
|
+
def exists?
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
def update(graph)
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_child(graph)
|
98
|
+
services.plan_records.add(graph.first(:lc, "name"), graph.first(:lc, "digest"))
|
99
|
+
end
|
100
|
+
|
101
|
+
def plan_records
|
102
|
+
services.plan_records
|
103
|
+
end
|
104
|
+
|
105
|
+
def fill_graph(graph)
|
106
|
+
graph.add_list(:lc, "plans") do |list|
|
107
|
+
plan_records.each do |record|
|
108
|
+
list << path_for(:plan, '*' => record.name)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class UnresolvedPlansList < FullPlansList
|
115
|
+
def plan_records
|
116
|
+
#recheck resolution?
|
117
|
+
services.plan_records.find_all{|record| !record.resolved?}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class PlanContent < RoadForest::BlobModel
|
122
|
+
add_type "text/plain", TypeHandlers::Handler.new
|
123
|
+
add_type "application/octet-stream", TypeHandlers::Handler.new
|
124
|
+
add_type "application/x-gtar-compressed", TypeHandlers::Handler.new
|
125
|
+
|
126
|
+
def update(file)
|
127
|
+
name = params.remainder
|
128
|
+
record = services.plan_records.find(name)
|
129
|
+
raise "Unexpected file: #{name}" if record.nil?
|
130
|
+
raise "Plan already resolved: #{name}" unless record.can_receive?
|
131
|
+
|
132
|
+
super(file)
|
133
|
+
|
134
|
+
record.receive
|
135
|
+
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Plan < RoadForest::RDFModel
|
141
|
+
def data
|
142
|
+
@data = services.plan_records.find(params.remainder)
|
143
|
+
end
|
144
|
+
|
145
|
+
def fill_graph(graph)
|
146
|
+
graph[[:lc, "name"]] = data.name
|
147
|
+
graph[[:lc, "digest"]] = data.filehash
|
148
|
+
graph[[:lc, "contents"]] = path_for(:file_content)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|