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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +38 -0
- data/.travis.yml +22 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +60 -0
- data/Rakefile +17 -0
- data/bin/imagemaster3000 +4 -0
- data/config/definitions/centos-7.json +29 -0
- data/config/definitions/debian-8.json +33 -0
- data/config/definitions/files/.gitkeep +0 -0
- data/config/definitions/files/centos-cloud.cfg +95 -0
- data/config/definitions/files/debian-cloud.cfg +101 -0
- data/config/definitions/files/serial-getty@ttyS0.service +35 -0
- data/config/definitions/files/ttyS0.conf +22 -0
- data/config/definitions/files/ubuntu-cloud.cfg +111 -0
- data/config/definitions/ubuntu-14.04.json +30 -0
- data/config/definitions/ubuntu-16.04.json +30 -0
- data/config/imagemaster3000.yml +15 -0
- data/imagemaster3000.gemspec +42 -0
- data/lib/imagemaster3000/actions/copy.rb +45 -0
- data/lib/imagemaster3000/actions/remove.rb +24 -0
- data/lib/imagemaster3000/actions.rb +6 -0
- data/lib/imagemaster3000/cli.rb +115 -0
- data/lib/imagemaster3000/definitions/parser.rb +45 -0
- data/lib/imagemaster3000/definitions/schemas/imagemaster3000-definition-schema.json +238 -0
- data/lib/imagemaster3000/definitions.rb +5 -0
- data/lib/imagemaster3000/entities/downloadable.rb +47 -0
- data/lib/imagemaster3000/entities/image.rb +92 -0
- data/lib/imagemaster3000/entities.rb +6 -0
- data/lib/imagemaster3000/errors/action_error.rb +5 -0
- data/lib/imagemaster3000/errors/argument_error.rb +5 -0
- data/lib/imagemaster3000/errors/command_execution_error.rb +5 -0
- data/lib/imagemaster3000/errors/download_error.rb +5 -0
- data/lib/imagemaster3000/errors/parsing_error.rb +5 -0
- data/lib/imagemaster3000/errors/standard_error.rb +5 -0
- data/lib/imagemaster3000/errors/verification_error.rb +5 -0
- data/lib/imagemaster3000/errors.rb +11 -0
- data/lib/imagemaster3000/image_list/generator.rb +25 -0
- data/lib/imagemaster3000/image_list/signer.rb +18 -0
- data/lib/imagemaster3000/image_list/templates/image_list.erb +41 -0
- data/lib/imagemaster3000/image_list.rb +6 -0
- data/lib/imagemaster3000/main_process.rb +22 -0
- data/lib/imagemaster3000/settings.rb +19 -0
- data/lib/imagemaster3000/utils/command_executioner.rb +22 -0
- data/lib/imagemaster3000/utils/crypto.rb +28 -0
- data/lib/imagemaster3000/utils/tmp.rb +24 -0
- data/lib/imagemaster3000/utils.rb +7 -0
- data/lib/imagemaster3000/verification/hash.rb +45 -0
- data/lib/imagemaster3000/verification/signatures/clearsign.rb +16 -0
- data/lib/imagemaster3000/verification/signatures/detached.rb +18 -0
- data/lib/imagemaster3000/verification/signatures.rb +8 -0
- data/lib/imagemaster3000/verification/verifiable.rb +10 -0
- data/lib/imagemaster3000/verification.rb +7 -0
- data/lib/imagemaster3000/version.rb +3 -0
- data/lib/imagemaster3000.rb +18 -0
- 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,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
|