admiral-check 0.0.1

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