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.
Files changed (77) hide show
  1. data/bin/flight-deck +3 -0
  2. data/doc/DESIGN +48 -0
  3. data/doc/EC2-baking-notes +70 -0
  4. data/doc/ExampleStartupRakefile +152 -0
  5. data/doc/ExampleTargetRakefile +4 -0
  6. data/doc/TODO +148 -0
  7. data/doc/Vb-EC2-translation-notes +96 -0
  8. data/doc/hating-chef +32 -0
  9. data/lib/logical-construct/archive-tasks.rb +307 -0
  10. data/lib/logical-construct/ground-control.rb +4 -1
  11. data/lib/logical-construct/ground-control/build-plan.rb +95 -0
  12. data/lib/logical-construct/ground-control/core.rb +1 -1
  13. data/lib/logical-construct/ground-control/generate-manifest.rb +67 -0
  14. data/lib/logical-construct/ground-control/provision.rb +73 -168
  15. data/lib/logical-construct/ground-control/run-on-target.rb +1 -1
  16. data/lib/logical-construct/ground-control/setup.rb +1 -4
  17. data/lib/logical-construct/ground-control/setup/copy-files.rb +2 -2
  18. data/lib/logical-construct/ground-control/tools.rb +66 -0
  19. data/lib/logical-construct/node-client.rb +112 -0
  20. data/lib/logical-construct/plan.rb +2 -0
  21. data/lib/logical-construct/plan/core.rb +45 -0
  22. data/lib/logical-construct/plan/standalone-bundle.rb +80 -0
  23. data/lib/logical-construct/port-open-check.rb +41 -0
  24. data/lib/logical-construct/protocol.rb +2 -0
  25. data/lib/logical-construct/protocol/plan-validation.rb +46 -0
  26. data/lib/logical-construct/protocol/ssh-tunnel.rb +127 -0
  27. data/lib/logical-construct/protocol/vocabulary.rb +8 -0
  28. data/lib/logical-construct/target/Implement.rake +8 -0
  29. data/lib/logical-construct/target/command-line.rb +90 -0
  30. data/lib/logical-construct/target/flight-deck.rb +341 -0
  31. data/lib/logical-construct/target/implementation.rb +33 -0
  32. data/lib/logical-construct/target/plan-records.rb +317 -0
  33. data/lib/logical-construct/target/resolution-server.rb +153 -0
  34. data/lib/logical-construct/target/{unpack-cookbook.rb → unpack-plan.rb} +8 -4
  35. data/lib/logical-construct/template-file.rb +41 -0
  36. data/lib/templates/Rakefile.erb +8 -0
  37. data/spec/ground-control/smoke-test.rb +8 -7
  38. data/spec/node_resolution.rb +62 -0
  39. data/spec/target/plan-records.rb +142 -0
  40. data/spec/target/provisioning.rb +21 -0
  41. data/spec_help/file-sandbox.rb +12 -6
  42. data/spec_help/fixtures/Manifest +1 -0
  43. data/spec_help/fixtures/source/one.tbz +1 -0
  44. data/spec_help/fixtures/source/three.tbz +1 -0
  45. data/spec_help/fixtures/source/two.tbz +1 -0
  46. data/spec_help/spec_helper.rb +5 -7
  47. metadata +165 -72
  48. data/lib/logical-construct/ground-control/setup/build-files.rb +0 -93
  49. data/lib/logical-construct/ground-control/setup/create-construct-directory.rb +0 -22
  50. data/lib/logical-construct/ground-control/setup/install-init.rb +0 -32
  51. data/lib/logical-construct/resolving-task.rb +0 -141
  52. data/lib/logical-construct/satisfiable-task.rb +0 -87
  53. data/lib/logical-construct/target.rb +0 -4
  54. data/lib/logical-construct/target/chef-solo.rb +0 -37
  55. data/lib/logical-construct/target/platforms.rb +0 -51
  56. data/lib/logical-construct/target/platforms/aws.rb +0 -8
  57. data/lib/logical-construct/target/platforms/default/chef-config.rb +0 -134
  58. data/lib/logical-construct/target/platforms/default/resolve-configuration.rb +0 -44
  59. data/lib/logical-construct/target/platforms/default/volume.rb +0 -11
  60. data/lib/logical-construct/target/platforms/virtualbox.rb +0 -8
  61. data/lib/logical-construct/target/platforms/virtualbox/volume.rb +0 -15
  62. data/lib/logical-construct/target/provision.rb +0 -36
  63. data/lib/logical-construct/target/sinatra-resolver.rb +0 -99
  64. data/lib/logical-construct/testing/resolve-configuration.rb +0 -32
  65. data/lib/logical-construct/testing/resolving-task.rb +0 -15
  66. data/lib/templates/chef.rb.erb +0 -9
  67. data/lib/templates/construct.init.d.erb +0 -18
  68. data/lib/templates/resolver/finished.html.erb +0 -1
  69. data/lib/templates/resolver/index.html.erb +0 -17
  70. data/lib/templates/resolver/task-file-form.html.erb +0 -6
  71. data/lib/templates/resolver/task-form.html.erb +0 -6
  72. data/spec/resolution.rb +0 -147
  73. data/spec/target/chef-config.rb +0 -67
  74. data/spec/target/chef-solo.rb +0 -55
  75. data/spec/target/platforms.rb +0 -36
  76. data/spec/target/smoke-test.rb +0 -45
  77. 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,2 @@
1
+ require 'logical-construct/plan/core'
2
+ require 'logical-construct/plan/standalone-bundle'
@@ -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,2 @@
1
+ require 'logical-construct/protocol/plan-validation'
2
+ require 'logical-construct/protocol/vocabulary'
@@ -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