logical-construct 0.0.1.localtesting → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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