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,112 @@
|
|
1
|
+
require 'roadforest/remote-host'
|
2
|
+
require 'logical-construct/protocol'
|
3
|
+
|
4
|
+
module LogicalConstruct
|
5
|
+
class NodeClient
|
6
|
+
class ManifestBuilder
|
7
|
+
include Protocol::PlanValidation
|
8
|
+
def initialize(graph_focus)
|
9
|
+
@graph_focus = graph_focus
|
10
|
+
end
|
11
|
+
|
12
|
+
def plans_list
|
13
|
+
@graph_focus.find_or_add([:lc, "plans"]).as_list
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_plan(plan_archive)
|
17
|
+
name = File::basename(plan_archive)
|
18
|
+
plans_list.append_node("##{name}") do |node|
|
19
|
+
node[[:rdf, "type" ]] = [:lc, "Need"]
|
20
|
+
node[[ :lc, "name" ]] = name
|
21
|
+
node[[ :lc, "digest"]] = file_checksum(plan_archive)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@plan_archives = []
|
28
|
+
@silent = false
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :plan_archives, :node_url, :server, :silent
|
32
|
+
|
33
|
+
def server
|
34
|
+
@server ||= RoadForest::RemoteHost.new(node_url).tap do |server|
|
35
|
+
#server.graph_transfer.trace = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def report(item)
|
40
|
+
puts item unless silent
|
41
|
+
end
|
42
|
+
|
43
|
+
def state
|
44
|
+
state = nil
|
45
|
+
server.getting do |root|
|
46
|
+
state = page_labeled("Current Status", root)[:lc, "node-state"]
|
47
|
+
end
|
48
|
+
state
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolved?
|
52
|
+
state.downcase == "resolved"
|
53
|
+
end
|
54
|
+
|
55
|
+
def page_labeled(label, focus)
|
56
|
+
concept = focus.all(:skos, "hasTopConcept").find do |concept|
|
57
|
+
concept.all(:skos, "prefLabel").include?(label) or concept.all(:skos, "altLabel").include?(label)
|
58
|
+
end
|
59
|
+
concept.first(:foaf, "page")
|
60
|
+
end
|
61
|
+
|
62
|
+
def deliver_manifest
|
63
|
+
report "Delivering manifest"
|
64
|
+
messages = []
|
65
|
+
server.putting do |root|
|
66
|
+
messages = []
|
67
|
+
needs = page_labeled("Server Manifest", root)
|
68
|
+
|
69
|
+
builder = ManifestBuilder.new(needs)
|
70
|
+
|
71
|
+
plan_archives.each do |archive|
|
72
|
+
messages << "Adding #{archive}"
|
73
|
+
builder.add_plan(archive)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
report messages
|
77
|
+
end
|
78
|
+
|
79
|
+
def deliver_plans
|
80
|
+
loop do
|
81
|
+
needs = []
|
82
|
+
server.getting do |root|
|
83
|
+
needs = []
|
84
|
+
unresolved = page_labeled("Unresolved Plans", root)
|
85
|
+
|
86
|
+
unresolved[:lc, "plans"].as_list.each do |need|
|
87
|
+
needs << [need[:lc, "name"], need[:lc, "contents"]]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
if needs.empty?
|
91
|
+
report "Target needs fulfilled"
|
92
|
+
break
|
93
|
+
end
|
94
|
+
|
95
|
+
report "Delivering plan archives"
|
96
|
+
needs.each do |need|
|
97
|
+
name, path = *need
|
98
|
+
plan = plan_archives.find do |plan|
|
99
|
+
File.basename(plan) == name
|
100
|
+
end
|
101
|
+
|
102
|
+
next if plan.nil?
|
103
|
+
|
104
|
+
File::open(plan, "r") do |file|
|
105
|
+
report " Delivering #{name}"
|
106
|
+
server.put_file(path, "application/x-gtar-compressed", file) #sorta like a ukulele
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'mattock'
|
2
|
+
require 'logical-construct/target/implementation'
|
3
|
+
|
4
|
+
module LogicalConstruct
|
5
|
+
module Plan
|
6
|
+
class Core < ::Mattock::Tasklib
|
7
|
+
path(:plan_rakefile, "plan.rake")
|
8
|
+
|
9
|
+
def resolve_configuration
|
10
|
+
self.absolute_path = plan_rakefile.pathname.dirname
|
11
|
+
|
12
|
+
resolve_paths
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def define
|
17
|
+
in_namespace do
|
18
|
+
task :compile => 'compile:finished'
|
19
|
+
namespace :compile do
|
20
|
+
task_spine :preflight, :perform, :finished
|
21
|
+
end
|
22
|
+
|
23
|
+
task :install => "install:finished"
|
24
|
+
namespace :install do
|
25
|
+
task_spine :preflight, :perform, :finished
|
26
|
+
end
|
27
|
+
|
28
|
+
Target::Implementation.task_list.each_cons(2) do |first, second|
|
29
|
+
task second => "construct:#{first}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :construct do
|
34
|
+
Target::Implementation.task_list.each do |name|
|
35
|
+
task name => self[name]
|
36
|
+
end
|
37
|
+
|
38
|
+
[:install, :compile].each do |task|
|
39
|
+
task task => self[task]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'mattock'
|
2
|
+
require 'mattock/bundle-command-task'
|
3
|
+
|
4
|
+
module LogicalConstruct
|
5
|
+
module Plan
|
6
|
+
class StandaloneBundle < Mattock::TaskLib
|
7
|
+
include Mattock::CommandLineDSL
|
8
|
+
default_namespace :bundler
|
9
|
+
|
10
|
+
dir(:target_dir,
|
11
|
+
path(:gemfile, "Gemfile"),
|
12
|
+
dir(:gems, "gems"))
|
13
|
+
|
14
|
+
def default_configuration(core)
|
15
|
+
super
|
16
|
+
target_dir.absolute_path = core.absolute_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolve_configuration
|
20
|
+
resolve_paths
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def define
|
25
|
+
directory gems.absolute_path
|
26
|
+
|
27
|
+
#TODO: Experiment with
|
28
|
+
#
|
29
|
+
#bundle install
|
30
|
+
#bundle package --all
|
31
|
+
#....
|
32
|
+
#bundle install --local
|
33
|
+
in_namespace do
|
34
|
+
#bundler unconditionally re-produces bundle/setup.rb, which means
|
35
|
+
#that plans that use this tasklib will always be re-packed, even when
|
36
|
+
#the contents haven't changed significantly.
|
37
|
+
#
|
38
|
+
#It's possible that a "hash difference" file task could be written
|
39
|
+
#for setup.rb? Kind of a hack. Or else tell bundler that it's config
|
40
|
+
#dir goes in a temp directory, and then we move things, or local
|
41
|
+
#rsync --checksum or something.
|
42
|
+
Mattock::BundleCommandTask.define_task(:standalone => gems.absolute_path) do |bundle_build|
|
43
|
+
bundle_build.command = (
|
44
|
+
cmd("cd", target_dir.absolute_path) &
|
45
|
+
cmd("bundle", "install"){|bundler|
|
46
|
+
bundler.options << "--gemfile=" + gemfile.absolute_path
|
47
|
+
bundler.options << "--path=" + gems.absolute_path
|
48
|
+
bundler.options << "--standalone"
|
49
|
+
bundler.options << "--binstubs=bin"
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
task :pristine => gems.absolute_path do
|
54
|
+
gem_scope = [Gem.ruby_engine, Gem::ConfigMap[:ruby_version]].join("/")
|
55
|
+
|
56
|
+
cmd_env = {
|
57
|
+
"RUBYOPT" => "",
|
58
|
+
"GEM_HOME" => gems.pathname.join(gem_scope).to_s #XXX update this to use gem_scope from flight-deck
|
59
|
+
}
|
60
|
+
|
61
|
+
cmd("gem pristine") do |gem|
|
62
|
+
gem.options << "--all"
|
63
|
+
gem.options << "--extensions"
|
64
|
+
gem.command_environment.merge! cmd_env
|
65
|
+
end.must_succeed!
|
66
|
+
end
|
67
|
+
|
68
|
+
task :load_setup do
|
69
|
+
require gems.pathname.join("bundler/setup")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
task 'install:perform' => self[:pristine]
|
74
|
+
task 'compile:perform' => self[:standalone]
|
75
|
+
task 'compile:preflight' => gemfile.absolute_path
|
76
|
+
task :preflight => self[:load_setup]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module LogicalConstruct
|
2
|
+
class TCPPortOpenCheck
|
3
|
+
def initialize(port)
|
4
|
+
@host = "localhost"
|
5
|
+
@port = port
|
6
|
+
@timeout = 0
|
7
|
+
@retry_delay = 0.5
|
8
|
+
yield self if block_given?
|
9
|
+
end
|
10
|
+
attr_accessor :host, :port
|
11
|
+
attr_accessor :timeout, :retry_delay
|
12
|
+
|
13
|
+
def open_socket
|
14
|
+
TCPSocket.new @host, @port
|
15
|
+
end
|
16
|
+
|
17
|
+
def open?(timeout = nil)
|
18
|
+
timeout ||= @timeout
|
19
|
+
start_time = Time.now
|
20
|
+
test_conn = open_socket()
|
21
|
+
return true
|
22
|
+
rescue Errno::ECONNREFUSED
|
23
|
+
if Time.now - start_time > timeout
|
24
|
+
return false
|
25
|
+
else
|
26
|
+
sleep @retry_delay
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
ensure
|
30
|
+
test_conn.close if test_conn.respond_to? :close
|
31
|
+
end
|
32
|
+
|
33
|
+
def fail_if_open!
|
34
|
+
raise "Port is open, should be closed: #{@host}:#{@port}" if open?(0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def fail_if_closed!
|
38
|
+
raise "Port is closed, should be open: #{@host}:#{@port}" unless open?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
module LogicalConstruct
|
3
|
+
module Protocol
|
4
|
+
module PlanValidation
|
5
|
+
class DigestFailure < ::StandardError; end
|
6
|
+
|
7
|
+
def digest
|
8
|
+
@digest ||= OpenSSL::Digest::SHA256.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def check_digest(checksum, path, target_path=nil)
|
12
|
+
if file_checksum(path) != checksum
|
13
|
+
raise DigestFailure, "Digest failure for #{target_path || path}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#The biggest digest-block-length-aligned chunk under 4MB
|
18
|
+
BIG_CHUNK = 4 * 1024 * 1024
|
19
|
+
def chunk_size
|
20
|
+
@chunk_size ||= (BIG_CHUNK / digest.block_length).floor * digest.block_length
|
21
|
+
end
|
22
|
+
|
23
|
+
def realpath(path)
|
24
|
+
File::readlink(path.to_s)
|
25
|
+
rescue Errno::EINVAL, Errno::ENOENT
|
26
|
+
path
|
27
|
+
end
|
28
|
+
|
29
|
+
def file_checksum(path)
|
30
|
+
digest.reset
|
31
|
+
File::open(realpath(path)) do |file|
|
32
|
+
while chunk = file.read(chunk_size)
|
33
|
+
digest.update(chunk)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
digest.hexdigest
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_checksum(data)
|
40
|
+
digest.reset
|
41
|
+
digest << data
|
42
|
+
digest.hexdigest
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'mattock'
|
3
|
+
require 'mattock/command-task'
|
4
|
+
|
5
|
+
module LogicalConstruct
|
6
|
+
class SSHTunnel < Mattock::Tasklib
|
7
|
+
class OpenPortTask < Mattock::Rake::Task
|
8
|
+
default_taskname :open_port
|
9
|
+
setting :base_port
|
10
|
+
setting :found_port
|
11
|
+
|
12
|
+
def resolve_configuration
|
13
|
+
self.found_port = base_port
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure_discovery(base_port)
|
18
|
+
self.base_port = base_port
|
19
|
+
return proxy_value.found_port
|
20
|
+
end
|
21
|
+
|
22
|
+
def action(args)
|
23
|
+
test_server = TCPServer.new("0.0.0.0", found_port)
|
24
|
+
rescue Errno::EADDRINUSE
|
25
|
+
self.found_port += 1
|
26
|
+
retry
|
27
|
+
ensure
|
28
|
+
test_server.close
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class CreateSSHMaster < Mattock::Rake::CommandTask
|
33
|
+
default_taskname :create_ssh_master
|
34
|
+
|
35
|
+
setting :target_address
|
36
|
+
|
37
|
+
def verify_command
|
38
|
+
cmd("ssh", "-o ControlMaster=auto", "-O check", target_address)
|
39
|
+
end
|
40
|
+
|
41
|
+
def command
|
42
|
+
cmd("ssh", "-Nf", "-o ControlMaster=auto", "-o ControlPersist=300", "-o ExitOnForwardFailure=yes", target_address)
|
43
|
+
end
|
44
|
+
|
45
|
+
def action(args)
|
46
|
+
super
|
47
|
+
rescue StandardError => se
|
48
|
+
puts "Attempting to recover from: #{se.message}"
|
49
|
+
retry
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class CreateTunnel < Mattock::Rake::CommandTask
|
54
|
+
default_taskname :create_tunnel
|
55
|
+
|
56
|
+
setting :target_address
|
57
|
+
setting :local_target_port
|
58
|
+
setting :remote_target_port
|
59
|
+
|
60
|
+
def command
|
61
|
+
cmd("ssh", "-O", "forward", "-L", "localhost:#{local_target_port}:localhost:#{remote_target_port}", target_address)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class CancelTask < Mattock::Rake::CommandTask
|
66
|
+
default_taskname :cancel_tunnel
|
67
|
+
|
68
|
+
setting :target_address
|
69
|
+
|
70
|
+
def verify_command
|
71
|
+
cmd("ssh", "-o ControlMaster=auto", "-O check", target_address)
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_verification_command
|
75
|
+
!super
|
76
|
+
end
|
77
|
+
|
78
|
+
def command
|
79
|
+
cmd("ssh", "-n", "-o ControlMaster=auto", "-O exit", target_address)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
default_namespace :ssh_tunnel
|
84
|
+
|
85
|
+
runtime_setting :target_address
|
86
|
+
setting :target_port, 10000
|
87
|
+
setting :local_target_port
|
88
|
+
setting :remote_target_port
|
89
|
+
|
90
|
+
def resolve_configuration
|
91
|
+
self.local_target_port ||= target_port
|
92
|
+
self.remote_target_port ||= target_port
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def wrap(task_name)
|
97
|
+
task task_name => self[:create_tunnel]
|
98
|
+
task self[:cancel_tunnel] => task_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def define
|
102
|
+
in_namespace do
|
103
|
+
open_port = OpenPortTask.define_task do |open_port|
|
104
|
+
self.local_target_port = open_port.configure_discovery(local_target_port)
|
105
|
+
end
|
106
|
+
|
107
|
+
master = CreateSSHMaster.define_task(self) do |master|
|
108
|
+
copy_settings_to(master)
|
109
|
+
end
|
110
|
+
|
111
|
+
create = CreateTunnel.define_task(self) do |create|
|
112
|
+
copy_settings_to(create)
|
113
|
+
end
|
114
|
+
|
115
|
+
cancel = CancelTask.define_task(self) do |cancel|
|
116
|
+
copy_settings_to(cancel)
|
117
|
+
end
|
118
|
+
|
119
|
+
task_spine create.task_name, :close_tunnel, :run
|
120
|
+
|
121
|
+
task master.task_name => open_port.task_name
|
122
|
+
task create.task_name => master.task_name
|
123
|
+
task :close_tunnel => cancel.task_name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|