imagemaster3000 0.1.0

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 (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