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