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