admiral-check 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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ ## 0.0.1 - 2017-06-26
5
+
6
+ - Implementation of core features
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ Author:: Gattsu Blackswordsman (<gattsu.blackswordsman@gmail.com>)
2
+
3
+ Copyright (C) 2014, Gattsu Blackswordsman
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Admiral-Check
2
+
3
+ A tool for deploying and testing under docker
4
+
5
+
6
+
7
+
8
+
9
+ ## Author
10
+
11
+ Created by [Gattsu][author]
12
+
13
+ ## License
14
+ Apache 2.0 (see [LICENSE][license])
15
+
16
+
17
+
18
+ [author]: https://github.com/gattsublackswordsman
19
+ [license]: https://github.com/gattsublackswordsman/admiral-check/blob/master/LICENSE
20
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'admiral/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'admiral-check'
8
+ spec.version = Admiral::ADMIRAL__VERSION
9
+ spec.authors = ['Gattsu']
10
+ spec.email = ['gattsu.blackswordsman@gmail.com']
11
+ spec.description = %q{Deployment and testing tool}
12
+ spec.summary = spec.description
13
+ spec.homepage = 'https://github.com/gattsublackswordsman/admiral-check'
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = %w(admiral)
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_runtime_dependency 'mixlib-shellout', '~> 1.6.1'
21
+ spec.add_runtime_dependency 'net-scp', '~> 1.1'
22
+ spec.add_runtime_dependency 'net-ssh', '~> 2.7'
23
+ spec.add_runtime_dependency 'safe_yaml', '~> 1.0'
24
+ spec.add_runtime_dependency 'thor', '~> 0.18'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.3'
27
+ spec.add_development_dependency 'rake'
28
+ end
data/bin/admiral ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
4
+ $:.unshift File.join(File.dirname(__FILE__), %w{.. lib})
5
+ require 'rubygems'
6
+
7
+ require 'admiral/core'
8
+
9
+
10
+ Admiral::Core.start
@@ -0,0 +1,96 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+
4
+ require 'yaml'
5
+
6
+ module Admiral
7
+ class Config
8
+ attr_reader :platforms
9
+
10
+ def initialize
11
+
12
+ local_config_file = ".admiral.yml"
13
+ global_config_file = File.expand_path("~/.admiral.yml")
14
+
15
+ if not File.exist?(local_config_file)
16
+ STDERR.puts "File .admiral.yml must be present"
17
+ exit!
18
+ end
19
+
20
+ local_config = YAML.load_file(local_config_file)
21
+
22
+ if File.exist?(global_config_file)
23
+ global_config = YAML.load_file(File.expand_path("~/.admiral.yml"))
24
+
25
+ common_global_config = global_config['common']
26
+ common_local_config = local_config['common']
27
+
28
+ if common_global_config.nil?
29
+ if common_local_config.nil?
30
+ STDERR.puts "No common config defined"
31
+ exit!
32
+ else
33
+ common_config = common_local_config
34
+ end
35
+ else
36
+ if common_local_config.nil?
37
+ common_config = common_global_config
38
+ else
39
+ common_config = common_global_config.merge(common_local_config)
40
+ end
41
+ end
42
+ else
43
+ common_config = local_config['common']
44
+
45
+ if common_config.nil?
46
+ STDERR.puts "No common config defined"
47
+ exit!
48
+ end
49
+ end
50
+
51
+ @platforms = Hash.new
52
+ @platforms_name = []
53
+ @platforms_config = Hash.new
54
+
55
+ common_applications = common_config['applications']
56
+ common_applications = [] if not common_applications
57
+
58
+ if local_config['platforms'].nil?
59
+ STDERR.puts "No platforms defined"
60
+ exit!
61
+ end
62
+
63
+ local_config['platforms'].each do | platform |
64
+ platform_name = platform['name']
65
+ @platforms[platform_name] = platform
66
+ @platforms_name << platform_name
67
+
68
+ @platforms_config[platform_name] = common_config.clone
69
+ @platforms_config[platform_name].merge!(platform)
70
+
71
+ platform_applications = @platforms_config[platform_name]['applications']
72
+ platform_applications = [] if not platform_applications
73
+ platform_applications_codes = []
74
+
75
+ platform_applications.each do |application|
76
+ platform_applications_codes << application['code']
77
+ end
78
+
79
+ common_applications.each do |application|
80
+ if not platform_applications_codes.include?(application['code'])
81
+ @platforms_config[platform_name]['applications'].push(application)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def [](platform)
88
+ return @platforms_config[platform]
89
+ end
90
+
91
+ def platform?(platform)
92
+ return @platforms_config.key?(platform)
93
+ end
94
+ end
95
+ end
96
+
@@ -0,0 +1,154 @@
1
+ require "thor"
2
+ require "admiral/docker"
3
+
4
+ module Admiral
5
+
6
+ class Core < Thor
7
+
8
+ def initialize(*args)
9
+ super
10
+ @config = Admiral::Config.new()
11
+ end
12
+
13
+ desc "list", "List available platforms"
14
+ def list()
15
+ platforms = @config.platforms
16
+ platforms.keys.each do | platform |
17
+ puts "#{platform}"
18
+ end
19
+ end
20
+
21
+ desc "create NAME", "Create a container for a platform"
22
+ def create(platform_name)
23
+
24
+ if @config.platform?(platform_name)
25
+ Admiral::Docker::verify((@config[platform_name]))
26
+ Admiral::Docker::create(@config[platform_name])
27
+ else
28
+ STDERR.puts "Platform #{platform_name} don't exist"
29
+ exit!
30
+ end
31
+ end
32
+
33
+ desc "apply-layer PLATFORM LAYER", "Apply a layer in an existing container"
34
+ def apply_layer(platform_name, layer_uid)
35
+ Admiral::Docker::verify((@config[platform_name]))
36
+
37
+ if @config.platform?(platform_name)
38
+ container_id = Admiral::Docker::get_container_id(platform_name)
39
+ docker = @config[platform_name]['docker']
40
+ if container_id
41
+ ip_address = Admiral::Docker::get_ip_address(docker, container_id)
42
+ if ip_address
43
+ success = Admiral::Docker::apply_layer(@config[platform_name], layer_uid, ip_address)
44
+ if not success
45
+ STDERR.puts "failed to run the layer"
46
+ exit!
47
+ end
48
+ else
49
+ STDERR.puts "Failed to get IP address"
50
+ exit!
51
+ end
52
+ else
53
+ STDERR.puts "Failed to get container ID"
54
+ exit!
55
+ end
56
+ else
57
+ STDERR.puts "Platform #{platform_name} don't exist"
58
+ exit!
59
+ end
60
+ end
61
+
62
+ desc "login NAME", "Log in the container"
63
+ def login(platform_name)
64
+ Admiral::Docker::create(@config[platform_name])
65
+ if @config.platform?(platform_name)
66
+ Admiral::Docker::login(@config[platform_name])
67
+ else
68
+ STDERR.puts "Platform #{platform_name} don't exist"
69
+ exit!
70
+ end
71
+ end
72
+
73
+ desc "test NAME", "Run the tests suite"
74
+ def test(platform_name)
75
+ if @config.platform?(platform_name)
76
+ Admiral::Docker::create(@config[platform_name])
77
+ Admiral::Docker::test(@config[platform_name])
78
+ Admiral::Docker::destroy(@config[platform_name])
79
+ else
80
+ STDERR.puts "Platform #{platform_name} don't exist"
81
+ exit!
82
+ end
83
+ end
84
+
85
+ desc "destroy NAME", "Destroy a container"
86
+ def destroy(platform_name)
87
+ if @config.platform?(platform_name)
88
+ Admiral::Docker::destroy(@config[platform_name])
89
+ else
90
+ STDERR.puts "Platform #{platform_name} don't exist"
91
+ exit!
92
+ end
93
+ end
94
+
95
+ desc "layer-info NAME", "Show informations about a layer"
96
+ def layer_info(layer_uid)
97
+ begin
98
+ require "admiral/layers/#{layer_uid}.rb"
99
+ rescue LoadError
100
+ STDERR.puts "Layer #{layer_uid} not found"
101
+ return false
102
+ end
103
+
104
+ begin
105
+ kclass = ::Admiral::Layers.const_get(Admiral::Layer.uid_to_name(layer_uid))
106
+ rescue NameError
107
+ STDERR.puts "Layer #{layer_uid} has a mistake"
108
+ return false
109
+ end
110
+ layer = kclass.new(nil, nil)
111
+ layer.show_information
112
+ end
113
+
114
+
115
+ desc "config-help", "Show configuration help"
116
+ def config_help()
117
+ puts <<-EOF
118
+
119
+ The configuration is located in .admiral.yml files.
120
+ The global configuration is in ~/ and the local configuration is in any other directory.
121
+
122
+ common: # Configuration for all plaforms, in global or local configuration
123
+ docker: unix:///var/run/docker.sock # Docker socket
124
+ registry: 127.0.0.1:5000 # Docker registry
125
+ username: admiral # Username for Admiral user in the container
126
+ password: admiral # Password for Admiral
127
+ keyfile: docker_id_rsa # Private Key for Admiral connection in the container
128
+ pubkeyfile: docker_id_rsa.pub # Associated public Key
129
+ volumes: # Optional, list of volumes to export in the container
130
+ - guest: /path/in/the/guest # Path of the volume in the container
131
+ host: /path/in/the/host # Optional, path of the real directory
132
+ layers: # Layers for the configuration
133
+ - admiral.svn.puppet.manifest
134
+ - admiral.svn.puppet.cookbook
135
+ - admiral.puppet.apply
136
+ tests: # Layers for the tests
137
+ - admiral.test.chef.install
138
+ - admiral.test.serverspec.install
139
+ - admiral.test.serverspec.upload
140
+ - admiral.test.serverspec.run
141
+
142
+ platforms: # List of platforms' configuration, only in local configuration
143
+ - name: my-server
144
+ image: ubuntu16 # Docker image
145
+ hostname: web.domain.lan
146
+ lsp: false # A layer parameter
147
+ ...
148
+
149
+ EOF
150
+
151
+ end
152
+ end
153
+ end
154
+
@@ -0,0 +1,368 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+
4
+ require 'admiral/config'
5
+ require 'admiral/shell'
6
+ require 'admiral/layer'
7
+
8
+ module Admiral
9
+ module Docker
10
+
11
+ @@core_parameters = ['docker', 'image', 'username', 'password', 'keyfile', 'pubkeyfile', 'registry', 'layers', 'tests', 'hostname']
12
+
13
+ def self.verify (platform)
14
+ @@core_parameters.each do | parameter |
15
+ if not platform.key?(parameter)
16
+ STDERR.puts "Parameter #{parameter} not found"
17
+ exit!
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.create (platform)
23
+
24
+ platform_name = platform['name']
25
+ image = platform['image']
26
+ docker = platform['docker']
27
+ hostname = platform['hostname']
28
+ ssh_key_file = platform['keyfile']
29
+ username = platform['username']
30
+ password = platform['password']
31
+ volumes = platform['volumes']
32
+
33
+ last_container_id = get_container_id(platform_name)
34
+ if last_container_id
35
+ puts "Container exist : #{last_container_id}"
36
+ else
37
+ dockerfile = generate_dockerfile(platform)
38
+
39
+ volumes_cmd = ''
40
+
41
+ if volumes.kind_of?(Array)
42
+ volumes.each do | volume |
43
+ if not volume['guest']
44
+ STDERR.puts "ERROR: Volume must have 'guest' parameter"
45
+ exit!
46
+ else
47
+ if volume['host']
48
+ volumes_cmd << "-v #{volume['host']}:#{volume['guest']} "
49
+ else
50
+ volumes_cmd << "-v #{volume['guest']} "
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ puts "=== Create image ==="
57
+
58
+ begin
59
+ output = Admiral::Shell.local("docker -H #{docker} build --build-arg=USERNAME='#{username}' --build-arg=PASSWORD='#{password}' --no-cache -", {:input => dockerfile}, true)
60
+ rescue Interrupt
61
+ STDERR.puts "Creation interrupted"
62
+ exit!
63
+ end
64
+
65
+ if output
66
+
67
+ image_id = output.gsub(/.* /m, "")
68
+ puts "Image ID : #{image_id}"
69
+
70
+ Dir.mkdir(".states") unless File.exists?(".states")
71
+ f = File.open(".states/#{platform_name}.image", "w")
72
+ f.write("#{image_id}")
73
+ f.close
74
+
75
+ puts "=== Create container ==="
76
+ container_id = Admiral::Shell.local("docker -H #{docker} run -d -p 22 -h #{hostname} --privileged --cap-add ALL #{volumes_cmd} #{image_id}")
77
+
78
+ if container_id
79
+ puts "Container ID : #{container_id}"
80
+ Dir.mkdir(".states") unless File.exists?(".states")
81
+ f = File.open(".states/#{platform_name}.container", "w")
82
+ f.write("#{container_id}")
83
+ f.close
84
+
85
+ output = Admiral::Shell.local("docker -H #{docker} inspect #{container_id}")
86
+ if output
87
+ ipaddress = extract_ipaddress(output)
88
+ puts "=== Configuring container ==="
89
+ success = self.apply_layers(platform, ipaddress)
90
+ if not success
91
+ STDERR.puts "Failed to apply configuration layers, run destroy"
92
+ destroy(platform)
93
+ exit!
94
+ end
95
+ else
96
+ STDERR.puts "Failed to log in container, run destroy"
97
+ destroy(platform)
98
+ exit!
99
+ end
100
+ else
101
+ STDERR.puts "Failed to create container, run destroy"
102
+ destroy(platform)
103
+ exit!
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def self.test (platform)
110
+ platform_name = platform['name']
111
+ testdir = "test"
112
+ if Dir.exists?(testdir)
113
+ puts "=== Run tests ==="
114
+ container_id = get_container_id(platform['name'])
115
+ docker = platform['docker']
116
+
117
+ if container_id
118
+ output = Admiral::Shell.local("docker -H #{docker} inspect #{container_id}")
119
+ if output
120
+ ipaddress = extract_ipaddress(output)
121
+ success = self.apply_test_layers(platform, ipaddress)
122
+ if not success
123
+ STDERR.puts "One or more tests failed, run destroy"
124
+ destroy(platform)
125
+ exit!
126
+ end
127
+ else
128
+ STDERR.puts "Failed to get IP address"
129
+ destroy(platform)
130
+ exit!
131
+ end
132
+ else
133
+ STDERR.puts "Failed to container ID"
134
+ destroy(platform)
135
+ exit!
136
+ end
137
+ else
138
+ STDERR.puts "Test directory not found"
139
+ destroy(platform)
140
+ exit!
141
+ end
142
+ end
143
+
144
+ def self.destroy (platform)
145
+ platform_name = platform['name']
146
+ docker = platform['docker']
147
+
148
+ last_container_id = get_container_id(platform_name)
149
+ if last_container_id
150
+ puts "Remove container #{last_container_id}"
151
+ Admiral::Shell.local("docker -H #{docker} rm -f #{last_container_id}")
152
+ File.delete(".states/#{platform_name}.container")
153
+ else
154
+ puts "No container"
155
+ end
156
+
157
+ last_image_id = get_image_id(platform_name)
158
+
159
+ if last_container_id
160
+ puts "Remove image #{last_image_id}"
161
+ Admiral::Shell.local("docker -H #{docker} rmi -f #{last_image_id}")
162
+ File.delete(".states/#{platform_name}.image")
163
+ else
164
+ puts "No image"
165
+ end
166
+ end
167
+
168
+ def self.login(platform)
169
+
170
+ container_id = get_container_id(platform['name'])
171
+ docker = platform['docker']
172
+
173
+ if container_id
174
+ output = Admiral::Shell.local("docker -H #{docker} inspect #{container_id}")
175
+ if output
176
+ ipaddress = extract_ipaddress(output)
177
+ username = platform['username']
178
+ keyfile = platform['keyfile']
179
+ cmd = "/bin/bash"
180
+
181
+ puts "Log in to #{ipaddress}"
182
+ Admiral::Shell.remote(ipaddress, username, keyfile, cmd)
183
+ else
184
+ puts "Failed to get ip address"
185
+ end
186
+ else
187
+ puts "No container"
188
+ end
189
+ end
190
+
191
+ def self.get_image_id(platform_name)
192
+ if File.exists?(".states/#{platform_name}.image")
193
+ f = File.open(".states/#{platform_name}.image", "r")
194
+ image_id = f.read()
195
+ f.close
196
+ return image_id
197
+ else
198
+ return nil
199
+ end
200
+ end
201
+
202
+ def self.get_container_id(platform_name)
203
+ if File.exists?(".states/#{platform_name}.container")
204
+ f = File.open(".states/#{platform_name}.container", "r")
205
+ container_id = f.read()
206
+ f.close
207
+ return container_id
208
+ else
209
+ return nil
210
+ end
211
+ end
212
+
213
+ def self.apply_layers(platform, ipaddress)
214
+
215
+ layers = platform['layers']
216
+
217
+ layers.each do | layer_uid |
218
+ begin
219
+ require "admiral/layers/#{layer_uid}.rb"
220
+ rescue LoadError
221
+ STDERR.puts "Layer #{layer_uid} not found"
222
+ return false
223
+ end
224
+
225
+ begin
226
+ kclass = ::Admiral::Layers.const_get(Admiral::Layer.uid_to_name(layer_uid))
227
+ rescue NameError
228
+ STDERR.puts "Layer #{layer_uid} has a mistake"
229
+ return false
230
+ end
231
+ layer = kclass.new(platform,ipaddress)
232
+
233
+ valid = layer.verify()
234
+ if not valid
235
+ return false
236
+ end
237
+
238
+ success = layer.run()
239
+ if not success
240
+ return false
241
+ end
242
+
243
+ end
244
+ return true
245
+ end
246
+
247
+ def self.apply_test_layers(platform, ipaddress)
248
+
249
+ layers = platform['tests']
250
+
251
+ layers.each do | layer_uid |
252
+ begin
253
+ require "admiral/layers/#{layer_uid}.rb"
254
+ rescue LoadError
255
+ STDERR.puts "Layer #{layer_uid} not found"
256
+ return false
257
+ end
258
+
259
+ begin
260
+ kclass = ::Admiral::Layers.const_get(Admiral::Layer.uid_to_name(layer_uid))
261
+ rescue NameError
262
+ STDERR.puts "Layer #{layer_uid} has a mistake"
263
+ return false
264
+ end
265
+ layer = kclass.new(platform,ipaddress)
266
+
267
+ valid = layer.verify()
268
+ if not valid
269
+ return false
270
+ end
271
+
272
+ success = layer.run()
273
+ if not success
274
+ return false
275
+ end
276
+
277
+ end
278
+ return true
279
+ end
280
+
281
+
282
+ def self.apply_layer(platform, layer_uid, ipaddress)
283
+
284
+ begin
285
+ require "admiral/layers/#{layer_uid}.rb"
286
+ rescue LoadError
287
+ STDERR.puts "Layer #{layer_uid} not found"
288
+ return false
289
+ end
290
+
291
+ begin
292
+ kclass = ::Admiral::Layers.const_get(Admiral::Layer.uid_to_name(layer_uid))
293
+ rescue NameError
294
+ STDERR.puts "Layer #{layer_uid} has a mistake"
295
+ return false
296
+ end
297
+ layer = kclass.new(platform,ipaddress)
298
+
299
+ valid = layer.verify()
300
+ if not valid
301
+ return false
302
+ end
303
+
304
+ return layer.run()
305
+ end
306
+
307
+
308
+ def self.generate_dockerfile(platform)
309
+ image = platform["image"]
310
+ username = platform['username']
311
+ password = platform['password']
312
+ pubkeyfile = platform['pubkeyfile']
313
+ registry = platform['registry']
314
+
315
+ begin
316
+ f = File.open(pubkeyfile, 'r')
317
+ public_key = f.read().chomp()
318
+ f.close
319
+ rescue Errno::ENOENT => e
320
+ STDERR.puts "Error with public key : #{e.message}"
321
+ exit!
322
+ end
323
+
324
+ from = "FROM #{registry}/#{image}\n"
325
+
326
+ user = <<-eos
327
+ ARG USERNAME
328
+ ARG PASSWORD
329
+ RUN useradd -d /home/${USERNAME} -m -s /bin/bash ${USERNAME}
330
+ RUN echo ${USERNAME}:${PASSWORD} | chpasswd
331
+ RUN echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
332
+ eos
333
+
334
+ key = <<-eos
335
+ RUN mkdir /home/${USERNAME}/.ssh
336
+ RUN echo '#{public_key}' >> /home/${USERNAME}/.ssh/authorized_keys
337
+ eos
338
+
339
+ tmpdir = <<-eos
340
+ RUN mkdir /tmp/${USERNAME}/
341
+ RUN chown ${USERNAME}:${USERNAME} /tmp/${USERNAME}
342
+ eos
343
+
344
+ ssh_env = <<-eos
345
+ RUN echo "AcceptEnv *" >> /etc/ssh/sshd_config
346
+ eos
347
+
348
+ [from, user, key, tmpdir, ssh_env].join("\n")
349
+ end
350
+
351
+ def self.get_ip_address(docker, container_id)
352
+ container_info = Admiral::Shell.local("docker -H #{docker} inspect #{container_id}")
353
+ if container_info
354
+ data = YAML.load(container_info).first
355
+ return data['NetworkSettings']['IPAddress']
356
+ else
357
+ return nil
358
+ end
359
+ end
360
+
361
+ def self.extract_ipaddress(container_info)
362
+ data = YAML.load(container_info).first
363
+ return data['NetworkSettings']['IPAddress']
364
+ end
365
+
366
+ end
367
+ end
368
+