logical-construct 0.0.1.localtesting → 0.0.1

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.
Files changed (29) hide show
  1. data/lib/logical-construct/ground-control/core.rb +0 -1
  2. data/lib/logical-construct/ground-control/provision.rb +146 -29
  3. data/lib/logical-construct/ground-control/run-on-target.rb +4 -4
  4. data/lib/logical-construct/ground-control/setup.rb +5 -14
  5. data/lib/logical-construct/ground-control/setup/build-files.rb +31 -10
  6. data/lib/logical-construct/ground-control/setup/copy-files.rb +31 -19
  7. data/lib/logical-construct/ground-control/setup/create-construct-directory.rb +1 -0
  8. data/lib/logical-construct/ground-control/setup/install-init.rb +32 -0
  9. data/lib/logical-construct/resolving-task.rb +108 -5
  10. data/lib/logical-construct/satisfiable-task.rb +25 -2
  11. data/lib/logical-construct/target/chef-solo.rb +12 -15
  12. data/lib/logical-construct/target/platforms/default/chef-config.rb +57 -12
  13. data/lib/logical-construct/target/platforms/default/resolve-configuration.rb +22 -3
  14. data/lib/logical-construct/target/provision.rb +10 -0
  15. data/lib/logical-construct/target/sinatra-resolver.rb +16 -19
  16. data/lib/logical-construct/testing/resolve-configuration.rb +8 -0
  17. data/lib/templates/Gemfile.erb +3 -1
  18. data/lib/templates/chef.rb.erb +6 -1
  19. data/lib/templates/construct.init.d.erb +15 -0
  20. data/lib/templates/resolver/index.html.erb +11 -2
  21. data/lib/templates/resolver/task-file-form.html.erb +6 -0
  22. data/spec/ground-control/smoke-test.rb +23 -0
  23. data/spec/resolution.rb +147 -0
  24. data/spec/target/chef-config.rb +3 -0
  25. data/spec/target/chef-solo.rb +8 -0
  26. data/spec/target/smoke-test.rb +45 -0
  27. metadata +44 -14
  28. data/lib/logical-construct/ground-control/setup/bundle-setup.rb +0 -36
  29. data/lib/logical-construct/ground-control/setup/ensure-env.rb +0 -15
@@ -1,4 +1,3 @@
1
- p :loading => __FILE__
2
1
  require 'mattock'
3
2
 
4
3
  module LogicalConstruct
@@ -1,69 +1,186 @@
1
1
  require 'mattock'
2
- require 'rake/packagetask'
2
+ require 'json'
3
+ require 'logical-construct/resolving-task'
3
4
 
4
5
  module LogicalConstruct
5
6
  module GroundControl
6
7
  class Provision < Mattock::Tasklib
7
8
  class WebConfigure < Mattock::Task
8
- setting :target_ipaddr, nil
9
- setting :web_port, 51076
9
+ include ResolutionProtocol
10
+
11
+ setting :target_protocol, "http"
12
+ setting :target_address, nil
13
+ setting :target_port, 51076
14
+ runtime_setting :target_url
10
15
  setting :resolutions, {}
16
+ runtime_setting :web_resolutions
11
17
 
12
- def action
13
- connection = Excon.new(target_address)
14
- index = connection.get
15
- body = Nokogiri::HTML(index.body)
16
- resolution_needed = body.xpath('//a[@href]')
17
- resolution_needed.each do |link|
18
- href = link['href']
19
- connection.post(href, {"data" => resolve(href)})
20
- end
18
+ def resolve_runtime_configuration
19
+ super
20
+ self.target_url ||= "#{target_protocol}://#{target_address}:#{target_port}/"
21
+ self.web_resolutions = Hash[resolutions.map do |name, value|
22
+ [web_path(name), value]
23
+ end]
21
24
  end
22
25
 
23
26
  def resolve(path)
24
- resolved = resolutions.fetch(path)
27
+ resolved = web_resolutions.fetch(path)
25
28
  if resolved.respond_to? :call
26
29
  resolved = resolved.call
27
30
  end
28
31
  return resolved
32
+ rescue KeyError
33
+ puts "Can't find a resolution for #{path} in #{web_resolutions.keys.inspect} (ex #{resolutions.keys})"
34
+ raise
35
+ end
36
+
37
+ def uri(options)
38
+ uri_class = URI.scheme_list[target_protocol.upcase]
39
+ uri_hash = {:host => target_address, :port => target_port}
40
+ return uri_class.build(uri_hash.merge(options)).to_s
41
+ end
42
+
43
+ def resolution_needed
44
+ index = RestClient.get(uri(:path => '/'))
45
+ body = Nokogiri::HTML(index.body)
46
+ return body.xpath("//a[@rel='requirement']")
47
+ end
48
+
49
+
50
+ #XXX I would like this to become an actual RESTful client at some
51
+ #point, but seems it would mean building it from scratch
52
+ def action
53
+ require 'uri'
54
+ require 'rest-client'
55
+ require 'nokogiri'
56
+
57
+ until (link = resolution_needed.first).nil?
58
+ href = link['href']
59
+ begin
60
+ response = RestClient.post(uri(:path => href), :data => resolve(href))
61
+ rescue RestClient::InternalServerError => ex
62
+ require 'tempfile'
63
+ file = Tempfile.open('provision-error.html')
64
+ path = file.path
65
+ file.close!
66
+
67
+ File::open(path, "w") do |file|
68
+ file.write ex.http_body
69
+ end
70
+ puts "Written error response to #{path}"
71
+ puts "Try: chromium #{path}"
72
+ fail "Unsuccessful response from server!"
73
+ end
74
+ end
29
75
  end
30
76
  end
31
77
 
32
78
  default_namespace :provision
33
79
 
34
80
  setting :valise
35
- setting :web_port, 51076
36
- setting :target_ipaddr, nil
81
+ setting :target_protocol, "http"
82
+ setting(:target_address, nil).isnt(:copiable)
83
+ setting :target_port, 51076
37
84
  setting :resolutions, {}
38
- setting :marshalling_path
39
- setting :cookbooks_path
85
+ setting :marshalling_path, "marshall"
86
+
87
+ setting(:secret_data, nested {
88
+ setting :path
89
+ setting :tarball_path
90
+ setting :file_list
91
+ })
92
+
93
+ setting(:normal_data, nested {
94
+ setting :path
95
+ setting :tarball_path
96
+ setting :file_list
97
+ })
98
+
99
+ setting(:cookbooks, nested {
100
+ setting :path
101
+ setting :tarball_path
102
+ setting :file_list
103
+ })
104
+
105
+ setting :json_attribs_path
106
+ setting :roles, {}
107
+ setting :node_attribs, { "run_list" => [] }
108
+ setting :json_attribs, ""
40
109
 
41
110
  def default_configuration(core)
42
111
  core.copy_settings_to(self)
43
112
  super
113
+ self.cookbooks.path = "cookbooks"
114
+ self.secret_data.path = "data-bags/secret"
115
+ self.normal_data.path = "data-bags"
116
+ end
117
+
118
+ def resolve_configuration
119
+ super
120
+ self.json_attribs_path ||= File::join(marshalling_path, "node.json")
121
+
122
+ self.cookbooks.file_list ||= Rake::FileList[cookbooks.path + "/**/*"].exclude(%r{/[.]git/}).exclude(%r{[.]sw[.]$})
123
+ self.secret_data.file_list ||= Rake::FileList[secret_data.path + "/**/*"].exclude(%r{[.]sw[.]$})
124
+ self.normal_data.file_list ||=
125
+ Rake::FileList[normal_data.path + "/**/*"].exclude(%r{^#{secret_data.path}}).exclude(%r{[.]sw[.]$})
126
+
127
+ self.cookbooks.tarball_path ||= File::join(marshalling_path, "cookbooks.tgz")
128
+ self.secret_data.tarball_path ||= File::join(marshalling_path, "secret_data_bags.tgz")
129
+ self.normal_data.tarball_path ||= File::join(marshalling_path, "normal_data_bags.tgz")
130
+
131
+ resolutions["chef_config:cookbook_tarball"] ||= proc do
132
+ File::open(cookbooks.tarball_path, "rb")
133
+ end
134
+
135
+ resolutions["chef_config:secret_data_tarball"] ||= proc do
136
+ File::open(secret_data.tarball_path, "rb")
137
+ end
138
+
139
+ resolutions["chef_config:normal_data_tarball"] ||= proc do
140
+ File::open(normal_data.tarball_path, "rb")
141
+ end
44
142
  end
45
143
 
144
+ include Mattock::CommandLineDSL
46
145
  def define
47
146
  in_namespace do
48
- task :collect, [:ipaddr] do |task, args|
49
- self.target_ipaddr = args[:ipaddr]
147
+ directory marshalling_path
148
+
149
+ task :collect, [:ipaddr, :role] do |task, args|
150
+ self.target_address = args[:ipaddr]
151
+ unless args[:role].nil?
152
+ self.node_attribs["run_list"] = roles[args[:role]]
153
+ end
154
+ self.json_attribs = JSON.pretty_generate(node_attribs)
155
+ resolutions["chef_config:json_attribs"] ||= json_attribs
50
156
  end
51
157
 
52
- WebConfigure.new(:web_configure => :collect) do |task|
53
- self.copy_settings_to(task)
158
+ file secret_data.tarball_path => [marshalling_path] + secret_data.file_list do
159
+ cmd("tar", "--exclude **/*.sw?", "-czf", secret_data.tarball_path, secret_data.path).must_succeed!
54
160
  end
55
161
 
56
- namespace :cookbook do
57
- Rake::PackageTask.new("cookbook", :noversion) do |task|
58
- task.need_tar_gz = true
59
- task.package_dir = marshalling_path
60
- task.package_files.include(cookbooks_path + "/**/*")
61
- end
162
+ file normal_data.tarball_path => [marshalling_path] + normal_data.file_list do
163
+ cmd("tar",
164
+ "--exclude **/*.sw?",
165
+ "--exclude #{secret_data.path}",
166
+ "-czf", normal_data.tarball_path, normal_data.path).must_succeed!
62
167
  end
63
- end
64
168
 
65
- task root_task, [:ipaddr] => :web_configure
169
+ file cookbooks.tarball_path => [marshalling_path] + cookbooks.file_list do
170
+ cmd("tar", "--exclude .git", "--exclude **/*.sw?", "-czf", cookbooks.tarball_path, cookbooks.path).must_succeed!
171
+ end
172
+
173
+ manifest = LogicalConstruct::GenerateManifest.new(self, :manifest => [cookbooks.tarball_path, :collect]) do |manifest|
174
+ manifest.receiving_name = "configuration:Manifest"
175
+ end
176
+
177
+ WebConfigure.new(:web_configure => [:collect, :manifest, cookbooks.tarball_path]) do |task|
178
+ self.proxy_settings_to(task)
179
+ task.target_address = proxy_value.target_address
180
+ end
181
+ end
66
182
 
183
+ task root_task, [:ipaddr] => self[:web_configure]
67
184
  end
68
185
  end
69
186
  end
@@ -5,6 +5,9 @@ module LogicalConstruct
5
5
  class RunOnTarget < Mattock::TaskLib
6
6
  include Mattock::CommandLineDSL
7
7
 
8
+ SSH_OPTIONS = [ "ControlMaster=auto", "ControlPersist=3600",
9
+ "StrictHostKeyChecking=no", "UserKnownHostsFile=/dev/null" ]
10
+
8
11
  runtime_setting(:remote_server)
9
12
 
10
13
  def default_configuration(setup)
@@ -16,10 +19,7 @@ module LogicalConstruct
16
19
  in_namespace do
17
20
  desc comment unless comment.nil?
18
21
  Mattock::RemoteCommandTask.new(name) do |task|
19
- task.ssh_options << "ControlMaster=auto"
20
- task.ssh_options << "ControlPersist=3600"
21
- task.ssh_options << "StrictHostKeyChecking=no"
22
- task.ssh_options << "UserKnownHostsFile=/dev/null"
22
+ task.ssh_options += SSH_OPTIONS
23
23
 
24
24
  task.runtime_definition do |task|
25
25
  copy_settings_to(task)
@@ -12,6 +12,7 @@ module LogicalConstruct
12
12
  nil_fields :valise
13
13
 
14
14
  def default_configuration(core)
15
+ super
15
16
  core.copy_settings_to(self)
16
17
  end
17
18
 
@@ -19,17 +20,9 @@ module LogicalConstruct
19
20
  in_namespace do
20
21
  task :collect, [:address] do |t, args|
21
22
  remote_server.address = args[:address]
22
- p :collect => remote_server
23
23
  end
24
24
 
25
- task :local_setup => [:collect]
26
-
27
- task :remote_groundwork => [:local_setup]
28
-
29
- task :remote_config => [:remote_groundwork]
30
-
31
- task :remote_setup => [:remote_config]
32
-
25
+ task_spine(:collect, :local_setup, :remote_groundwork, :remote_config, :remote_setup)
33
26
  task :complete => [:local_setup, :remote_setup]
34
27
  end
35
28
 
@@ -40,18 +33,16 @@ module LogicalConstruct
40
33
  def default_subtasks
41
34
  in_namespace do
42
35
  CreateConstructDirectory.new(self)
43
- EnsureEnv.new(self)
44
- BundleSetup.new(self)
45
36
  build_files = BuildFiles.new(self)
46
37
  CopyFiles.new(self, build_files)
38
+ InstallInit.new(self)
47
39
  end
48
40
  end
49
41
  end
50
42
  end
51
43
  end
52
44
 
53
- require 'logical-construct/ground-control/setup/bundle-setup'
45
+ require 'logical-construct/ground-control/setup/build-files'
54
46
  require 'logical-construct/ground-control/setup/create-construct-directory'
55
- require 'logical-construct/ground-control/setup/ensure-env'
56
47
  require 'logical-construct/ground-control/setup/copy-files'
57
- require 'logical-construct/ground-control/setup/build-files'
48
+ require 'logical-construct/ground-control/setup/install-init'
@@ -1,3 +1,6 @@
1
+ require 'mattock'
2
+ require 'mattock/bundle-command-task'
3
+
1
4
  module LogicalConstruct
2
5
  class ConfigBuilder < Mattock::TaskLib
3
6
  include Mattock::TemplateHost
@@ -28,36 +31,54 @@ module LogicalConstruct
28
31
  file.write render(source_path)
29
32
  end
30
33
  end
34
+ file target_path => target_dir
31
35
  task :local_setup => target_path
32
36
  end
33
37
  end
34
38
 
35
39
  class BuildFiles < Mattock::TaskLib
40
+ include Mattock::CommandLineDSL
41
+
36
42
  default_namespace :build_files
37
43
 
38
44
  setting(:target_dir, "target_configs")
39
- setting(:valise)
45
+ required_fields :valise, :construct_dir
40
46
 
41
47
  def default_configuration(parent)
42
48
  super
43
- self.valise = parent.valise
49
+ parent.copy_settings_to(self)
44
50
  end
45
51
 
46
- attr_reader :built_files
47
-
48
52
  def define
49
- file_tasks = []
53
+ rakefile = nil
50
54
  in_namespace do
51
55
  directory target_dir
52
56
 
53
- file_tasks = ["Rakefile", "Gemfile"].map do |path|
54
- ConfigBuilder.new(self) do |task|
55
- task.base_name = path
56
- end
57
+ gemfile = ConfigBuilder.new(self) do |task|
58
+ task.base_name = "Gemfile"
59
+ end
60
+
61
+ Mattock::BundleCommandTask.new(:standalone => gemfile.target_path) do |bundle_build|
62
+ bundle_build.command = (
63
+ cmd("cd", target_dir) &
64
+ cmd("pwd") &
65
+ cmd("bundle", "install"){|bundler|
66
+ bundler.options << "--standalone"
67
+ bundler.options << "--binstubs=bin"
68
+ })
69
+ end
70
+
71
+ rakefile = ConfigBuilder.new(self) do |task|
72
+ task.base_name = "Rakefile"
73
+ end
74
+
75
+ initd = ConfigBuilder.new(self) do |task|
76
+ task.base_name = "construct.init.d"
77
+ task.extra[:construct_dir] = construct_dir
57
78
  end
58
79
  end
59
80
  desc "Template files to be created on the remote server"
60
- task root_task => file_tasks.map{|t| t.target_path}
81
+ task root_task => [rakefile.target_path] + in_namespace(:standalone)
61
82
  task :local_setup => root_task
62
83
  end
63
84
  end
@@ -1,14 +1,16 @@
1
1
  require 'mattock/command-task'
2
2
 
3
3
  module LogicalConstruct
4
- class SecureCopyFile < Mattock::CommandTask
4
+ class RemoteCopyFile < Mattock::CommandTask
5
5
  nil_fields :destination_address
6
6
  nil_fields :source_dir, :destination_dir, :basename
7
7
  required_fields :source_path, :destination_path
8
8
  runtime_required_field :remote_server
9
9
  runtime_required_field :command
10
+ setting :exclude, []
10
11
 
11
12
  def default_configuration(copy)
13
+ super
12
14
  self.remote_server = copy.proxy_value.remote_server
13
15
  end
14
16
 
@@ -18,14 +20,27 @@ module LogicalConstruct
18
20
  self.destination_path ||= File::join(destination_dir, basename)
19
21
  end
20
22
 
23
+ def secure_shell_command
24
+ escaped_command("ssh") do |ssh|
25
+ ssh.options += RunOnTarget::SSH_OPTIONS.map{|opt| "-o #{opt}"}
26
+ ssh.options << "-l #{remote_server.user}" unless remote_server.user.nil?
27
+ end
28
+ end
29
+
21
30
  def resolve_runtime_configuration
22
31
  self.destination_address ||= [remote_server.address, destination_path].join(":")
23
- if remote_server.user
24
- self.destination_address = "#{remote_server.user}@#{destination_address}"
32
+
33
+ self.command = cmd("rsync") do |rsync|
34
+ rsync.options << "-a"
35
+ rsync.options << "--copy-unsafe-links"
36
+ rsync.options << "-e #{secure_shell_command}"
37
+ exclude.each do |pattern|
38
+ rsync.options << "--exclude #{pattern}"
39
+ end
40
+ rsync.options << source_path
41
+ rsync.options << destination_address
25
42
  end
26
- self.command = cmd("scp",
27
- "-o ControlMaster=auto",
28
- source_path, destination_address)
43
+ super
29
44
  end
30
45
  end
31
46
 
@@ -34,8 +49,6 @@ module LogicalConstruct
34
49
 
35
50
  required_fields :files_dir, :remote_server, :construct_dir
36
51
 
37
- setting :files, ["Rakefile", "Gemfile"]
38
-
39
52
  def default_configuration(setup, build_files)
40
53
  super()
41
54
  self.files_dir = build_files.target_dir
@@ -44,22 +57,21 @@ module LogicalConstruct
44
57
  end
45
58
 
46
59
  def define
47
- files.each do |file|
48
- in_namespace do
49
- SecureCopyFile.new(self, file) do |task|
50
- task.runtime_definition do
51
- task.remote_server = remote_server
52
- end
53
- task.source_dir = files_dir
54
- task.destination_dir = construct_dir
55
- task.basename = file
60
+ in_namespace do
61
+ RemoteCopyFile.new(self, :construct_dir) do |task|
62
+ task.runtime_definition do
63
+ task.remote_server = remote_server
56
64
  end
65
+ task.exclude << "*.so"
66
+ task.exclude << "*.dynlib"
67
+ task.source_path = File::join(files_dir, "*")
68
+ task.destination_path = construct_dir
57
69
  end
58
- bracket_task(:local_setup, file, :remote_config)
59
70
  end
71
+ bracket_task(:remote_groundwork, :construct_dir, :remote_config)
60
72
 
61
73
  desc "Copy locally generated files to the remote server"
62
- task root_task => in_namespace(*files)
74
+ task root_task => in_namespace(:construct_dir)
63
75
  task :remote_config => root_task
64
76
  end
65
77
  end