imagemaster3000 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +38 -0
  5. data/.travis.yml +22 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +13 -0
  9. data/README.md +60 -0
  10. data/Rakefile +17 -0
  11. data/bin/imagemaster3000 +4 -0
  12. data/config/definitions/centos-7.json +29 -0
  13. data/config/definitions/debian-8.json +33 -0
  14. data/config/definitions/files/.gitkeep +0 -0
  15. data/config/definitions/files/centos-cloud.cfg +95 -0
  16. data/config/definitions/files/debian-cloud.cfg +101 -0
  17. data/config/definitions/files/serial-getty@ttyS0.service +35 -0
  18. data/config/definitions/files/ttyS0.conf +22 -0
  19. data/config/definitions/files/ubuntu-cloud.cfg +111 -0
  20. data/config/definitions/ubuntu-14.04.json +30 -0
  21. data/config/definitions/ubuntu-16.04.json +30 -0
  22. data/config/imagemaster3000.yml +15 -0
  23. data/imagemaster3000.gemspec +42 -0
  24. data/lib/imagemaster3000/actions/copy.rb +45 -0
  25. data/lib/imagemaster3000/actions/remove.rb +24 -0
  26. data/lib/imagemaster3000/actions.rb +6 -0
  27. data/lib/imagemaster3000/cli.rb +115 -0
  28. data/lib/imagemaster3000/definitions/parser.rb +45 -0
  29. data/lib/imagemaster3000/definitions/schemas/imagemaster3000-definition-schema.json +238 -0
  30. data/lib/imagemaster3000/definitions.rb +5 -0
  31. data/lib/imagemaster3000/entities/downloadable.rb +47 -0
  32. data/lib/imagemaster3000/entities/image.rb +92 -0
  33. data/lib/imagemaster3000/entities.rb +6 -0
  34. data/lib/imagemaster3000/errors/action_error.rb +5 -0
  35. data/lib/imagemaster3000/errors/argument_error.rb +5 -0
  36. data/lib/imagemaster3000/errors/command_execution_error.rb +5 -0
  37. data/lib/imagemaster3000/errors/download_error.rb +5 -0
  38. data/lib/imagemaster3000/errors/parsing_error.rb +5 -0
  39. data/lib/imagemaster3000/errors/standard_error.rb +5 -0
  40. data/lib/imagemaster3000/errors/verification_error.rb +5 -0
  41. data/lib/imagemaster3000/errors.rb +11 -0
  42. data/lib/imagemaster3000/image_list/generator.rb +25 -0
  43. data/lib/imagemaster3000/image_list/signer.rb +18 -0
  44. data/lib/imagemaster3000/image_list/templates/image_list.erb +41 -0
  45. data/lib/imagemaster3000/image_list.rb +6 -0
  46. data/lib/imagemaster3000/main_process.rb +22 -0
  47. data/lib/imagemaster3000/settings.rb +19 -0
  48. data/lib/imagemaster3000/utils/command_executioner.rb +22 -0
  49. data/lib/imagemaster3000/utils/crypto.rb +28 -0
  50. data/lib/imagemaster3000/utils/tmp.rb +24 -0
  51. data/lib/imagemaster3000/utils.rb +7 -0
  52. data/lib/imagemaster3000/verification/hash.rb +45 -0
  53. data/lib/imagemaster3000/verification/signatures/clearsign.rb +16 -0
  54. data/lib/imagemaster3000/verification/signatures/detached.rb +18 -0
  55. data/lib/imagemaster3000/verification/signatures.rb +8 -0
  56. data/lib/imagemaster3000/verification/verifiable.rb +10 -0
  57. data/lib/imagemaster3000/verification.rb +7 -0
  58. data/lib/imagemaster3000/version.rb +3 -0
  59. data/lib/imagemaster3000.rb +18 -0
  60. metadata +347 -0
@@ -0,0 +1,45 @@
1
+ require 'json'
2
+ require 'json-schema'
3
+
4
+ module Imagemaster3000
5
+ module Definitions
6
+ class Parser
7
+ SCHEMA = File.join File.dirname(__FILE__), 'schemas', 'imagemaster3000-definition-schema.json'
8
+
9
+ class << self
10
+ def parse_image_definitions
11
+ definition_files = Dir.glob(File.join(Imagemaster3000::Settings[:'definitions-dir'], '*.json')).sort
12
+ logger.debug "Found definition files: #{definition_files.inspect}"
13
+ definition_files.map { |file| parse file }.compact
14
+ end
15
+
16
+ private
17
+
18
+ def parse(file)
19
+ logger.debug "Parsing file #{file.inspect}"
20
+ json = File.read file
21
+ validate json
22
+
23
+ hash = JSON.parse json, symbolize_names: true
24
+ Imagemaster3000::Entities::Image.new hash
25
+ rescue Imagemaster3000::Errors::ArgumentError, Imagemaster3000::Errors::ParsingError => ex
26
+ logger.error "Parsing error occured while reading definition #{file.inspect}: #{ex.message}. Skipping."
27
+ nil
28
+ end
29
+
30
+ def validate(json)
31
+ logger.debug "Validating json\n#{json}"
32
+ JSON::Validator.schema_reader = JSON::Schema::Reader.new(accept_uri: false, accept_file: true)
33
+ JSON::Validator.validate!(SCHEMA, json, json: true)
34
+ rescue JSON::Schema::JsonParseError => ex
35
+ raise Imagemaster3000::Errors::ParsingError, ex
36
+ rescue JSON::Schema::ValidationError => ex
37
+ raise Imagemaster3000::Errors::ParsingError, "JSON is not valid according to JSON schema: #{ex.message}"
38
+ end
39
+ end
40
+
41
+ private_class_method :parse
42
+ private_class_method :validate
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,238 @@
1
+ {
2
+ "$schema":"http://json-schema.org/draft-04/schema#",
3
+ "additionalProperties":false,
4
+ "definitions":{
5
+ "source":{
6
+ "description":"Source file",
7
+ "id":"#source",
8
+ "title":"Source",
9
+ "type":"string"
10
+ },
11
+ "target":{
12
+ "description":"Target directory",
13
+ "id":"#target",
14
+ "title":"Target",
15
+ "type":"string"
16
+ },
17
+ "name":{
18
+ "description":"New name of the copied file",
19
+ "id":"#name",
20
+ "title":"Name",
21
+ "type":"string"
22
+ },
23
+ "copy":{
24
+ "additionalItems":false,
25
+ "id":"#copy",
26
+ "items":{
27
+ "additionalProperties":false,
28
+ "properties":{
29
+ "source":{
30
+ "$ref":"#/definitions/source"
31
+ },
32
+ "target":{
33
+ "$ref":"#/definitions/target"
34
+ },
35
+ "name":{
36
+ "$ref":"#/definitions/name"
37
+ }
38
+ },
39
+ "required":[
40
+ "source",
41
+ "target"
42
+ ],
43
+ "type":"object"
44
+ },
45
+ "type":"array"
46
+ },
47
+ "remove":{
48
+ "additionalItems":false,
49
+ "id":"#remove",
50
+ "items":{
51
+ "description":"File to remove",
52
+ "title":"File",
53
+ "type":"string"
54
+ },
55
+ "type":"array"
56
+ },
57
+ "actions":{
58
+ "additionalProperties":false,
59
+ "id":"#actions",
60
+ "properties":{
61
+ "copy":{
62
+ "$ref":"#/definitions/copy"
63
+ },
64
+ "remove":{
65
+ "$ref":"#/definitions/remove"
66
+ }
67
+ },
68
+ "type":"object"
69
+ },
70
+ "cpu":{
71
+ "description":"Minimum required CPU",
72
+ "id":"#cpu",
73
+ "title":"CPU",
74
+ "type":"integer"
75
+ },
76
+ "distribution":{
77
+ "description":"Distribution name",
78
+ "id":"#distribution",
79
+ "title":"Distribution",
80
+ "type":"string"
81
+ },
82
+ "name":{
83
+ "description":"Image name",
84
+ "id":"#name",
85
+ "title":"Name",
86
+ "type":"string"
87
+ },
88
+ "ram":{
89
+ "description":"Minimum required RAM",
90
+ "id":"#ram",
91
+ "title":"RAM",
92
+ "type":"integer"
93
+ },
94
+ "url":{
95
+ "description":"Image URL",
96
+ "id":"#url",
97
+ "title":"URL",
98
+ "type":"string"
99
+ },
100
+ "function":{
101
+ "description":"Hash function",
102
+ "id":"#function",
103
+ "title":"Function",
104
+ "type":"string"
105
+ },
106
+ "hash":{
107
+ "additionalProperties":false,
108
+ "id":"#hash",
109
+ "properties":{
110
+ "function":{
111
+ "$ref":"#/definitions/function"
112
+ }
113
+ },
114
+ "required":[
115
+ "function"
116
+ ],
117
+ "type":"object"
118
+ },
119
+ "file":{
120
+ "description":"URL to file with both signature and data",
121
+ "id":"#file",
122
+ "title":"File",
123
+ "type":"string"
124
+ },
125
+ "clearsign":{
126
+ "additionalProperties":false,
127
+ "id":"#clearsign",
128
+ "properties":{
129
+ "file":{
130
+ "$ref":"#/definitions/file"
131
+ }
132
+ },
133
+ "required":[
134
+ "file"
135
+ ],
136
+ "type":"object"
137
+ },
138
+ "data":{
139
+ "description":"URL to file with signed data",
140
+ "id":"#data",
141
+ "title":"Data",
142
+ "type":"string"
143
+ },
144
+ "signature_file":{
145
+ "description":"URL to file with signature",
146
+ "id":"#signature_file",
147
+ "title":"Signature",
148
+ "type":"string"
149
+ },
150
+ "detached":{
151
+ "additionalProperties":false,
152
+ "id":"#detached",
153
+ "properties":{
154
+ "data":{
155
+ "$ref":"#/definitions/data"
156
+ },
157
+ "signature":{
158
+ "$ref":"#/definitions/signature_file"
159
+ }
160
+ },
161
+ "required":[
162
+ "data",
163
+ "signature"
164
+ ],
165
+ "type":"object"
166
+ },
167
+ "signature":{
168
+ "additionalProperties":false,
169
+ "id":"#signature",
170
+ "properties":{
171
+ "clearsign":{
172
+ "$ref":"#/definitions/clearsign"
173
+ },
174
+ "detached":{
175
+ "$ref":"#/definitions/detached"
176
+ }
177
+ },
178
+ "type":"object"
179
+ },
180
+ "verification":{
181
+ "additionalProperties":false,
182
+ "id":"#verification",
183
+ "properties":{
184
+ "hash":{
185
+ "$ref":"#/definitions/hash"
186
+ },
187
+ "signature":{
188
+ "$ref":"#/definitions/signature"
189
+ }
190
+ },
191
+ "required":[
192
+ "hash",
193
+ "signature"
194
+ ],
195
+ "type":"object"
196
+ },
197
+ "version":{
198
+ "description":"Image version",
199
+ "id":"#version",
200
+ "title":"Version",
201
+ "type":"string"
202
+ }
203
+ },
204
+ "id":"imagemaster3000-definition-schema.json",
205
+ "properties":{
206
+ "actions":{
207
+ "$ref":"#/definitions/actions"
208
+ },
209
+ "cpu":{
210
+ "$ref":"#/definitions/cpu"
211
+ },
212
+ "distribution":{
213
+ "$ref":"#/definitions/distribution"
214
+ },
215
+ "name":{
216
+ "$ref":"#/definitions/name"
217
+ },
218
+ "ram":{
219
+ "$ref":"#/definitions/ram"
220
+ },
221
+ "url":{
222
+ "$ref":"#/definitions/url"
223
+ },
224
+ "verification":{
225
+ "$ref":"#/definitions/verification"
226
+ },
227
+ "version":{
228
+ "$ref":"#/definitions/version"
229
+ }
230
+ },
231
+ "required":[
232
+ "name",
233
+ "url",
234
+ "version",
235
+ "distribution"
236
+ ],
237
+ "type":"object"
238
+ }
@@ -0,0 +1,5 @@
1
+ module Imagemaster3000
2
+ module Definitions
3
+ autoload :Parser, 'imagemaster3000/definitions/parser'
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ require 'net/https'
2
+
3
+ module Imagemaster3000
4
+ module Entities
5
+ module Downloadable
6
+ def download
7
+ logger.debug "Downloading image from #{url.inspect}"
8
+
9
+ uri = URI.parse url
10
+ filename = generate_filename(uri)
11
+ retrieve_image(uri, filename)
12
+
13
+ logger.debug "Image from #{url.inspect} was saved as #{filename.inspect}"
14
+ @file = filename
15
+ @size = File.size filename
16
+ rescue ::URI::InvalidURIError, ::IOError => ex
17
+ raise Imagemaster3000::Errors::DownloadError, ex
18
+ end
19
+
20
+ private
21
+
22
+ def retrieve_image(uri, filename)
23
+ use_ssl = uri.scheme == 'https'
24
+ Net::HTTP.start(uri.host, uri.port, use_ssl: use_ssl) do |http|
25
+ request = Net::HTTP::Get.new(uri)
26
+
27
+ http.request(request) do |response|
28
+ if response.is_a? Net::HTTPRedirection
29
+ retrieve_image URI.join(uri, response.header['location']), filename
30
+ break
31
+ end
32
+
33
+ response.value
34
+ open(filename, 'w') { |file| response.read_body { |chunk| file.write(chunk) } }
35
+ end
36
+ end
37
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::HTTPBadResponse,
38
+ Net::HTTPHeaderSyntaxError, EOFError, Net::HTTPServerException, Net::HTTPRetriableError => ex
39
+ raise Imagemaster3000::Errors::DownloadError, ex
40
+ end
41
+
42
+ def generate_filename(uri)
43
+ File.join(Imagemaster3000::Settings[:'image-dir'], File.basename(uri.path))
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,92 @@
1
+ module Imagemaster3000
2
+ module Entities
3
+ class Image
4
+ include Downloadable
5
+
6
+ attr_accessor :name, :url, :distribution, :version, :ram, :cpu, :actions, :verification, :file, :size
7
+
8
+ def initialize(name: nil, url: nil, distribution: nil, version: nil, ram: nil, cpu: nil, actions: nil, verification: nil)
9
+ raise Imagemaster3000::Errors::ArgumentError, 'name, url, distribution or version cannot be nil' \
10
+ if name.blank? || url.blank? || distribution.blank? || version.blank?
11
+
12
+ @name = name
13
+ @url = url
14
+ @distribution = distribution
15
+ @version = version
16
+ @ram = ram
17
+ @cpu = cpu
18
+ @actions = prepare_actions actions
19
+ @verification = prepare_verification verification
20
+
21
+ logger.debug "Created image #{inspect}"
22
+ end
23
+
24
+ private
25
+
26
+ def prepare_actions(actions_hash)
27
+ return nil if actions_hash.blank?
28
+ logger.debug 'Preparing actions'
29
+
30
+ namespace = Imagemaster3000::Actions
31
+ prepared_actions = []
32
+
33
+ actions_hash.each_pair do |action_name, array|
34
+ action_const_name = action_name.capitalize.to_sym
35
+ raise Imagemaster3000::Errors::ActionError, "No such action #{action_name.inspect}" \
36
+ unless namespace.constants.include? action_const_name
37
+
38
+ logger.debug "Recognized action #{action_name.inspect}"
39
+ action_const = namespace.const_get action_const_name
40
+ prepared_actions += array.map { |object| action_const.new(object) }
41
+ end
42
+
43
+ prepared_actions
44
+ end
45
+
46
+ def prepare_verification(verification_hash)
47
+ return nil if verification_hash.blank?
48
+ logger.debug 'Preparing verification'
49
+
50
+ extend Imagemaster3000::Verification::Verifiable
51
+ prepared_verification = {}
52
+
53
+ prepared_verification[:signature] = prepare_signature_verification verification_hash[:signature]
54
+ prepared_verification[:hash] = prepare_hash_verification verification_hash[:hash]
55
+
56
+ prepared_verification
57
+ end
58
+
59
+ def prepare_signature_verification(signature_hash)
60
+ logger.debug 'Preparing signature verification'
61
+ namespace = Imagemaster3000::Verification::Signatures
62
+ signature_name = signature_hash.keys.first
63
+ signature_const_name = signature_name.capitalize.to_sym
64
+
65
+ raise Imagemaster3000::Errors::VerificationError, "No such signature type #{signature_name}" \
66
+ unless namespace.constants.include? signature_const_name
67
+
68
+ logger.debug "Recognized signature #{signature_name}"
69
+ extend namespace.const_get(signature_const_name)
70
+
71
+ signature_hash[signature_name]
72
+ end
73
+
74
+ def prepare_hash_verification(hash_hash)
75
+ logger.debug 'Preparing hash verification'
76
+ extend Imagemaster3000::Verification::Hash
77
+
78
+ hash_function_name = hash_hash[:function]
79
+ hash_function_const_name = hash_function_name.upcase.to_sym
80
+
81
+ begin
82
+ Digest(hash_function_const_name)
83
+ rescue LoadError
84
+ raise Imagemaster3000::Errors::VerificationError, "No such hash function #{hash_function_name}"
85
+ end
86
+
87
+ logger.debug "Recognized hash function #{hash_function_name}"
88
+ { function: Digest.const_get(hash_function_const_name) }
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,6 @@
1
+ module Imagemaster3000
2
+ module Entities
3
+ autoload :Image, 'imagemaster3000/entities/image'
4
+ autoload :Downloadable, 'imagemaster3000/entities/downloadable'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Imagemaster3000
2
+ module Errors
3
+ class ActionError < StandardError; end
4
+ end
5
+ end