cangallo 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.
- checksums.yaml +7 -0
- data/bin/canga +307 -0
- data/lib/cangallo.rb +120 -0
- data/lib/cangallo/cangafile.rb +187 -0
- data/lib/cangallo/config.rb +95 -0
- data/lib/cangallo/keybase.rb +39 -0
- data/lib/cangallo/libguestfs.rb +46 -0
- data/lib/cangallo/qcow2.rb +151 -0
- data/lib/cangallo/repo.rb +276 -0
- data/lib/cangallo/version.rb +20 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a5714dca973ec1755fc348bbef4494c7e1da8c41
|
4
|
+
data.tar.gz: 4abacf5c9616cb358befa15117d1682b26236a1b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f817d57b34e4758aa33886d34ca0e9d744385a47c497ca33764ffcf988658f2172085ca2f25c3f778a09e8ee74b74844571b86d65e0b4bc4f7e5c1e17fa3eaf4
|
7
|
+
data.tar.gz: d2877b68c3ed720db1163dcb0c352a0d97fefcff866501d5f54375f223b1b57f02f08b5acafea0f59a907f16f447ac67d6bb623f6aaf427c2d7db62c05031b6a
|
data/bin/canga
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# vim:ts=2:sw=2
|
4
|
+
|
5
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
8
|
+
# not use this file except in compliance with the License. You may obtain
|
9
|
+
# a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
$: << File.dirname(__FILE__) + "/../lib"
|
20
|
+
|
21
|
+
require "rubygems"
|
22
|
+
require "thor"
|
23
|
+
require "fileutils"
|
24
|
+
require "digest"
|
25
|
+
require "cangallo"
|
26
|
+
require "tempfile"
|
27
|
+
|
28
|
+
require "pp"
|
29
|
+
|
30
|
+
$cangallo = Cangallo.new
|
31
|
+
|
32
|
+
class Canga < Thor
|
33
|
+
map ['--version', '-V'] => :__print_version
|
34
|
+
|
35
|
+
desc "--version, -V", "show version"
|
36
|
+
def __print_version
|
37
|
+
puts Cangallo::VERSION
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "create FILE [SIZE]", "create a new qcow2 image"
|
41
|
+
option :parent, :desc => "id of the parent image"
|
42
|
+
def create(file, size = nil)
|
43
|
+
puts [file, size]
|
44
|
+
Cangallo::Qcow2.create(file, options[:parent], size)
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "add FILE [REPO]", "add a new file to the repository"
|
48
|
+
option :parent, :desc => "id of the parent image"
|
49
|
+
option :tag, :desc => "tag name of the new image"
|
50
|
+
option :copy, :desc => "do not process the image, make an exact copy",
|
51
|
+
:type => :boolean
|
52
|
+
def add(file, repo_name = nil)
|
53
|
+
repo = $cangallo.repo(repo_name)
|
54
|
+
sha256 = repo.add_image(file, "only_copy" => options[:copy])
|
55
|
+
|
56
|
+
repo.add_tag(options[:tag], sha256) if options[:tag]
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "del IMAGE", "delete an image from the repository"
|
60
|
+
def del(name)
|
61
|
+
image = $cangallo.get(name)
|
62
|
+
|
63
|
+
if !image
|
64
|
+
STDERR.puts %Q{Image "#{name}" not found}
|
65
|
+
exit(-1)
|
66
|
+
end
|
67
|
+
|
68
|
+
repo = $cangallo.repo(image["repo"])
|
69
|
+
repo.del_image(image["sha256"])
|
70
|
+
end
|
71
|
+
|
72
|
+
desc "tag TAGNAME IMAGE", "add a tag name to an existing image"
|
73
|
+
def tag(tag, name)
|
74
|
+
image = $cangallo.get(name)
|
75
|
+
|
76
|
+
if !image
|
77
|
+
STDERR.puts %Q{Image "#{name}" not found}
|
78
|
+
exit(-1)
|
79
|
+
end
|
80
|
+
|
81
|
+
repo = $cangallo.repo(image['repo'])
|
82
|
+
repo.add_tag(tag, image['sha256'])
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "deltag TAGNAME", "delete a tag"
|
86
|
+
def deltag(tag)
|
87
|
+
repo, name = $cangallo.parse_name(tag)
|
88
|
+
image = $cangallo.get(tag)
|
89
|
+
|
90
|
+
if !image
|
91
|
+
STDERR.puts %Q{Image "#{name}" not found}
|
92
|
+
exit(-1)
|
93
|
+
end
|
94
|
+
|
95
|
+
repo = $cangallo.repo(repo)
|
96
|
+
repo.del_tag(name)
|
97
|
+
end
|
98
|
+
|
99
|
+
desc "list [REPO]", "list images"
|
100
|
+
def list(repo_name = nil)
|
101
|
+
images = $cangallo.get_images(repo_name)
|
102
|
+
|
103
|
+
format = "%-50.50s %11.11s %8.8s"
|
104
|
+
|
105
|
+
print "\e[1;4m"
|
106
|
+
print format % %w{NAME SIZE DAYS_AGO}
|
107
|
+
puts "\e[0m"
|
108
|
+
|
109
|
+
images.each do |image|
|
110
|
+
parent = image["parent"] ? "^" : " "
|
111
|
+
available = image["available"] ? " " : "*"
|
112
|
+
name = "#{parent}#{available}#{image["name"]}"
|
113
|
+
|
114
|
+
size = image["size"].to_f / (1024 * 1024) # Mb
|
115
|
+
size = size.round(1)
|
116
|
+
|
117
|
+
if image["creation-time"]
|
118
|
+
days = ( Time.new - image["creation-time"] ) / (60*60*24)
|
119
|
+
days = days.round(1)
|
120
|
+
else
|
121
|
+
days = "N/A"
|
122
|
+
end
|
123
|
+
|
124
|
+
print "\e[1m"
|
125
|
+
print format % [name, "#{size} Mb", days]
|
126
|
+
puts "\e[0m"
|
127
|
+
|
128
|
+
puts " %-70.70s" % [image["description"]] if image["description"]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
desc "show IMAGE", "show information about an image"
|
133
|
+
def show(name)
|
134
|
+
image = $cangallo.get(name)
|
135
|
+
|
136
|
+
if image
|
137
|
+
puts image.to_yaml
|
138
|
+
else
|
139
|
+
STDERR.puts "No image found with name '#{name}'"
|
140
|
+
exit(-1)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
desc "overlay IMAGE FILE", "create a new image based on another one"
|
145
|
+
def overlay(name, file)
|
146
|
+
image = $cangallo.get(name)
|
147
|
+
|
148
|
+
if !image
|
149
|
+
STDERR.puts "Image not found"
|
150
|
+
exit(-1)
|
151
|
+
end
|
152
|
+
|
153
|
+
repo = $cangallo.repo(image['repo'])
|
154
|
+
path = File.expand_path(repo.image_path(image['sha256']))
|
155
|
+
|
156
|
+
Cangallo::Qcow2.create_from_base(path, file)
|
157
|
+
end
|
158
|
+
|
159
|
+
desc "build CANGAFILE", "create a new image using a Cangafile"
|
160
|
+
option :tag, :desc => "tag name of the new image"
|
161
|
+
def build(file)
|
162
|
+
cangafile = Cangallo::Cangafile.new(file)
|
163
|
+
|
164
|
+
commands, params = cangafile.render
|
165
|
+
puts commands
|
166
|
+
|
167
|
+
begin
|
168
|
+
image = $cangallo.get(cangafile.parent)
|
169
|
+
rescue => e
|
170
|
+
STDERR.puts e.message
|
171
|
+
exit(-1)
|
172
|
+
end
|
173
|
+
|
174
|
+
if !image
|
175
|
+
STDERR.puts "Image not found"
|
176
|
+
exit(-1)
|
177
|
+
end
|
178
|
+
|
179
|
+
sha256 = image['sha256']
|
180
|
+
repo = $cangallo.repo(image['repo'])
|
181
|
+
|
182
|
+
parent_path = File.expand_path(repo.image_path(sha256))
|
183
|
+
|
184
|
+
# temp image path
|
185
|
+
temp_image = Tempfile.new([File.basename(file), '.qcow2'], repo.path)
|
186
|
+
temp_image.close
|
187
|
+
|
188
|
+
Cangallo::Qcow2.create_from_base(parent_path, temp_image.path)
|
189
|
+
|
190
|
+
rc = Cangallo::LibGuestfs.virt_customize(temp_image.path, commands, params)
|
191
|
+
exit(-1) if !rc
|
192
|
+
|
193
|
+
rc = Cangallo::LibGuestfs.virt_sparsify(temp_image.path)
|
194
|
+
exit(-1) if !rc
|
195
|
+
|
196
|
+
data = {}
|
197
|
+
data["parent"] = sha256
|
198
|
+
data["description"] = cangafile.data["description"]
|
199
|
+
data["os"] = cangafile.data["os"]
|
200
|
+
cangafile.data.delete("description")
|
201
|
+
cangafile.data.delete("os")
|
202
|
+
|
203
|
+
data["cangafile"] = cangafile.data
|
204
|
+
|
205
|
+
new_image = repo.add_image(temp_image.path, data)
|
206
|
+
|
207
|
+
puts "Deleting temporary image"
|
208
|
+
temp_image.delete
|
209
|
+
|
210
|
+
tag = options[:tag] || cangafile.data['tag']
|
211
|
+
|
212
|
+
repo.add_tag(tag, new_image) if tag
|
213
|
+
end
|
214
|
+
|
215
|
+
desc "fetch [REPO]", "download the index of the repository"
|
216
|
+
def fetch(repo_name = nil)
|
217
|
+
repo = $cangallo.repo(repo_name)
|
218
|
+
|
219
|
+
repo.fetch
|
220
|
+
end
|
221
|
+
|
222
|
+
desc "sign [REPO]", "sign the index file with keybase"
|
223
|
+
def sign(repo_name = nil)
|
224
|
+
repo = $cangallo.repo(repo_name)
|
225
|
+
repo.sign
|
226
|
+
end
|
227
|
+
|
228
|
+
desc "verify [REPO]", "verify index signature with keybase"
|
229
|
+
def verify(repo_name = nil)
|
230
|
+
repo = $cangallo.repo(repo_name)
|
231
|
+
repo.verify
|
232
|
+
end
|
233
|
+
|
234
|
+
desc "pull NAME", "download an image from a remote repository"
|
235
|
+
def pull(name)
|
236
|
+
image = $cangallo.get(name)
|
237
|
+
|
238
|
+
if !image
|
239
|
+
STDERR.puts "Image not found"
|
240
|
+
exit(-1)
|
241
|
+
end
|
242
|
+
|
243
|
+
repo = $cangallo.repo(image["repo"])
|
244
|
+
images = repo.ancestors(image["sha256"])
|
245
|
+
|
246
|
+
images.reverse.each do |img|
|
247
|
+
name = $cangallo.short_name(img, image["repo"])
|
248
|
+
if File.exist?(repo.image_path(img))
|
249
|
+
STDERR.puts "Image #{name} already downloaded"
|
250
|
+
else
|
251
|
+
STDERR.puts "Downloading #{name}"
|
252
|
+
repo.pull(img)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
desc "export IMAGE OUTPUT", "export an image to a file"
|
258
|
+
option :format, :desc => "format for output image", :default => :qcow2,
|
259
|
+
:aliases => :f
|
260
|
+
option :compress, :desc => "compress output qcow2 image", :default => false,
|
261
|
+
:aliases => :c
|
262
|
+
def export(image, output)
|
263
|
+
image = $cangallo.get(image)
|
264
|
+
|
265
|
+
if !image
|
266
|
+
STDERR.puts "Image not found"
|
267
|
+
exit(-1)
|
268
|
+
end
|
269
|
+
|
270
|
+
repo = $cangallo.repo(image["repo"])
|
271
|
+
path = File.expand_path(repo.image_path(image["sha256"]))
|
272
|
+
|
273
|
+
Cangallo::Qcow2.new(path).convert(output,options)
|
274
|
+
end
|
275
|
+
|
276
|
+
desc "import IMAGE [REPO]", "import an image from a remote repository"
|
277
|
+
option :tag, :desc => "tag name of the new image"
|
278
|
+
def import(image_name, repo = nil)
|
279
|
+
image = $cangallo.get(image_name)
|
280
|
+
|
281
|
+
if !image
|
282
|
+
STDERR.puts "Image not found"
|
283
|
+
exit(-1)
|
284
|
+
end
|
285
|
+
|
286
|
+
sha256 = image["sha256"]
|
287
|
+
|
288
|
+
remote_repo = $cangallo.repo(image["repo"])
|
289
|
+
remote_path = remote_repo.image_path(sha256)
|
290
|
+
|
291
|
+
repository = $cangallo.repo(repo)
|
292
|
+
image_path = repository.image_path(sha256)
|
293
|
+
|
294
|
+
FileUtils.ln(remote_path, image_path, force: true)
|
295
|
+
|
296
|
+
image.delete("repo")
|
297
|
+
repository.add(sha256, image)
|
298
|
+
|
299
|
+
repository.add_tag(options[:tag], sha256) if options[:tag]
|
300
|
+
|
301
|
+
repository.write_index
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
Canga.start(ARGV)
|
306
|
+
|
307
|
+
|
data/lib/cangallo.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
# vim:ts=2:sw=2
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'cangallo/qcow2'
|
19
|
+
require 'cangallo/config'
|
20
|
+
require 'cangallo/repo'
|
21
|
+
require 'cangallo/cangafile'
|
22
|
+
require 'cangallo/libguestfs'
|
23
|
+
require 'cangallo/keybase'
|
24
|
+
require 'cangallo/version'
|
25
|
+
|
26
|
+
class Cangallo
|
27
|
+
def initialize
|
28
|
+
@config = Cangallo::Config.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def repo(name = nil)
|
32
|
+
@config.repo(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_images(repo_name = nil)
|
36
|
+
info = []
|
37
|
+
repos = []
|
38
|
+
|
39
|
+
if repo_name
|
40
|
+
repos = [repo_name]
|
41
|
+
else
|
42
|
+
repos = @config.repos
|
43
|
+
end
|
44
|
+
|
45
|
+
repos.each do |r|
|
46
|
+
repo = self.repo(r)
|
47
|
+
|
48
|
+
repo.images.each do |sha256, image|
|
49
|
+
name = repo.short_name(sha256)
|
50
|
+
|
51
|
+
info << {
|
52
|
+
"repo" => r,
|
53
|
+
"sha256" => sha256,
|
54
|
+
"name" => "#{r}:#{name}",
|
55
|
+
"size" => image["actual-size"],
|
56
|
+
"parent" => short_name(image["parent"], r),
|
57
|
+
"description" => image["description"],
|
58
|
+
"available" => File.exist?(repo.image_path(sha256)),
|
59
|
+
"creation-time" => image["creation-time"]
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
info
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_name(name)
|
68
|
+
slices = name.split(':')
|
69
|
+
|
70
|
+
repo = nil
|
71
|
+
name = name
|
72
|
+
|
73
|
+
if slices.length > 1
|
74
|
+
repo = slices[0]
|
75
|
+
name = slices[1]
|
76
|
+
end
|
77
|
+
|
78
|
+
return repo, name
|
79
|
+
end
|
80
|
+
|
81
|
+
def short_name(string, repo = nil)
|
82
|
+
return nil if !string
|
83
|
+
|
84
|
+
img_repo, img_name = parse_name(string)
|
85
|
+
img_repo ||= repo
|
86
|
+
|
87
|
+
image = self.repo(img_repo).find(img_name)
|
88
|
+
name = self.repo(img_repo).short_name(image)
|
89
|
+
|
90
|
+
"#{img_repo}:#{name}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def find(string)
|
94
|
+
repo, name = parse_name(string)
|
95
|
+
return "#{repo}:#{self.repo(repo).find(name)}" if repo
|
96
|
+
|
97
|
+
image = self.repo.find(name)
|
98
|
+
return "#{self.repo.name}:#{image}" if image
|
99
|
+
|
100
|
+
@config.repos.each do |r|
|
101
|
+
image = self.repo(r).find(name)
|
102
|
+
return "#{r}:#{image}" if image
|
103
|
+
end
|
104
|
+
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def get(string)
|
109
|
+
image = find(string)
|
110
|
+
return nil if !image
|
111
|
+
|
112
|
+
repo, name = parse_name(image)
|
113
|
+
|
114
|
+
img = self.repo(repo).get(name)
|
115
|
+
|
116
|
+
img["repo"] = repo if img
|
117
|
+
img
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
|
2
|
+
# vim:tabstop=2:sw=2:et:
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'json'
|
19
|
+
require 'systemu'
|
20
|
+
require 'tempfile'
|
21
|
+
require 'fileutils'
|
22
|
+
require 'yaml'
|
23
|
+
|
24
|
+
class Cangallo
|
25
|
+
|
26
|
+
class Cangafile
|
27
|
+
attr_accessor :data
|
28
|
+
|
29
|
+
ACTIONS = {
|
30
|
+
"copy" => {
|
31
|
+
"action" => lambda do |input|
|
32
|
+
add_head "copy-in #{input.gsub(" ", ":")}"
|
33
|
+
end
|
34
|
+
},
|
35
|
+
|
36
|
+
"run" => {
|
37
|
+
"type" => [Array, String],
|
38
|
+
"action" => lambda do |input|
|
39
|
+
if input.class == String
|
40
|
+
commands = input.split("\n").reject {|l| l.empty? }
|
41
|
+
else
|
42
|
+
commands = input
|
43
|
+
end
|
44
|
+
|
45
|
+
commands.each do |cmd|
|
46
|
+
add_head "run-command #{cmd}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
},
|
50
|
+
|
51
|
+
"change" => {
|
52
|
+
"type" => Hash,
|
53
|
+
"action" => lambda do |input|
|
54
|
+
regexp = input["regexp"].gsub("/", "\\/")
|
55
|
+
text = input["text"]
|
56
|
+
file = input["file"]
|
57
|
+
add_head "run-command sed -i 's/#{regexp}/#{text}/g' #{file}"
|
58
|
+
end
|
59
|
+
},
|
60
|
+
|
61
|
+
"delete" => {
|
62
|
+
"action" => lambda do |input|
|
63
|
+
add_head "run-command rm -rf #{input}"
|
64
|
+
end
|
65
|
+
},
|
66
|
+
|
67
|
+
"password" => {
|
68
|
+
"type" => Hash,
|
69
|
+
"action" => lambda do |input|
|
70
|
+
if input["disabled"]
|
71
|
+
password = "disabled"
|
72
|
+
else
|
73
|
+
password = "password:#{input["password"]}"
|
74
|
+
end
|
75
|
+
|
76
|
+
add_parameter "--password '#{input["user"]}:#{password}'"
|
77
|
+
end
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
def initialize(file)
|
82
|
+
text = File.read(file)
|
83
|
+
@data = YAML.load(text)
|
84
|
+
|
85
|
+
@params = []
|
86
|
+
@head = []
|
87
|
+
@tail = []
|
88
|
+
|
89
|
+
if !@data["tasks"] || @data["tasks"].class != Array
|
90
|
+
raise "No tasks defined or it's not an array"
|
91
|
+
end
|
92
|
+
|
93
|
+
@tasks = @data["tasks"]
|
94
|
+
end
|
95
|
+
|
96
|
+
def render
|
97
|
+
@tasks.each do |task|
|
98
|
+
raise %Q{Task "#{task.inspect}" malformed} if task.class != Hash
|
99
|
+
|
100
|
+
action_name = task.keys.first
|
101
|
+
action_data = task[action_name]
|
102
|
+
|
103
|
+
if !ACTIONS.keys.include?(action_name)
|
104
|
+
raise %Q{Invalid action "#{action_name}"}
|
105
|
+
end
|
106
|
+
|
107
|
+
action = ACTIONS[action_name]
|
108
|
+
|
109
|
+
if action["type"]
|
110
|
+
type = [action["type"]].flatten
|
111
|
+
else
|
112
|
+
type = [String]
|
113
|
+
end
|
114
|
+
|
115
|
+
# Check action value type
|
116
|
+
task_type = action_data.class
|
117
|
+
if !type.include?(task_type)
|
118
|
+
raise %Q{Action parameters for "#{action_name}" must be "#{type.inspect}"}
|
119
|
+
end
|
120
|
+
|
121
|
+
if action["action"]
|
122
|
+
instance_exec(action_data, &action["action"])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
return @head + @tail, @params
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_head(str)
|
130
|
+
@head << str
|
131
|
+
end
|
132
|
+
|
133
|
+
def add_tail(str)
|
134
|
+
@tail.unshift(str)
|
135
|
+
end
|
136
|
+
|
137
|
+
def add_parameter(str)
|
138
|
+
@params << str
|
139
|
+
end
|
140
|
+
|
141
|
+
def file_commands
|
142
|
+
text = ""
|
143
|
+
|
144
|
+
if @data["files"]
|
145
|
+
@data["files"].each do |line|
|
146
|
+
l = line.gsub(" ", ":")
|
147
|
+
text << "copy-in #{l}\n"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
return text
|
152
|
+
end
|
153
|
+
|
154
|
+
def run_commands
|
155
|
+
text = ""
|
156
|
+
|
157
|
+
if @data["run"]
|
158
|
+
@data["run"].each do |line|
|
159
|
+
text << "run-command #{line}\n"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
return text
|
164
|
+
end
|
165
|
+
|
166
|
+
def libguestfs_commands
|
167
|
+
@tasks.each do |task|
|
168
|
+
if task.class != Hash
|
169
|
+
raise "This task is not a hash: #{task}"
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def libguestfs_commands_old
|
177
|
+
text = file_commands
|
178
|
+
text << run_commands
|
179
|
+
text
|
180
|
+
end
|
181
|
+
|
182
|
+
def parent
|
183
|
+
@data["parent"]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
# vim:ts=2:sw=2
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'fileutils'
|
19
|
+
require 'yaml'
|
20
|
+
|
21
|
+
class Cangallo
|
22
|
+
class Config
|
23
|
+
CONFIG_DIR = '.cangallo'
|
24
|
+
CONFIG_FILE = 'config.yaml'
|
25
|
+
|
26
|
+
DEFAULT_CONFIG = <<EOT
|
27
|
+
default_repo: default
|
28
|
+
repos:
|
29
|
+
default:
|
30
|
+
type: local
|
31
|
+
path: ~/#{CONFIG_DIR}/default
|
32
|
+
EOT
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
create_config_dir
|
36
|
+
create_default_config
|
37
|
+
load_conf
|
38
|
+
end
|
39
|
+
|
40
|
+
def repo(name = nil)
|
41
|
+
repo_name = name || @conf['default_repo'] || 'default'
|
42
|
+
raise(%q{Configuration malformed, no 'repos'}) if !@conf['repos']
|
43
|
+
|
44
|
+
repo_conf = @conf['repos'][repo_name]
|
45
|
+
raise(%Q<No repo with name '#{repo_name}'>) if !repo_conf
|
46
|
+
raise(%Q<Repo path no defined for '#{repo_name}'>) if !repo_conf['path']
|
47
|
+
|
48
|
+
path = File.expand_path(repo_conf["path"])
|
49
|
+
repo_conf["path"] = path
|
50
|
+
repo_conf["name"] = repo_name
|
51
|
+
create_repo_dir(path)
|
52
|
+
Cangallo::Repo.new(repo_conf)
|
53
|
+
end
|
54
|
+
|
55
|
+
def load_conf
|
56
|
+
@conf = YAML.load_file(config_file)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_config_dir
|
60
|
+
if !File.exist?(config_dir)
|
61
|
+
FileUtils.mkdir_p(config_dir)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_default_config
|
66
|
+
if !File.exist?(config_file)
|
67
|
+
open(config_file, 'w') do |f|
|
68
|
+
f.write(DEFAULT_CONFIG)
|
69
|
+
end
|
70
|
+
|
71
|
+
load_conf
|
72
|
+
path = File.expand_path(@conf["repos"]["default"]["path"])
|
73
|
+
create_repo_dir(path)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_repo_dir(path)
|
78
|
+
if !File.exist?(path)
|
79
|
+
FileUtils.mkdir_p(path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def config_dir
|
84
|
+
File.join(ENV['HOME'], CONFIG_DIR)
|
85
|
+
end
|
86
|
+
|
87
|
+
def config_file
|
88
|
+
File.join(config_dir, CONFIG_FILE)
|
89
|
+
end
|
90
|
+
|
91
|
+
def repos
|
92
|
+
@conf["repos"].keys
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
# vim:tabstop=2:sw=2:et:
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
class Cangallo
|
19
|
+
|
20
|
+
module Keybase
|
21
|
+
def self.sign(file)
|
22
|
+
sig_file = "#{file}.sig"
|
23
|
+
cmd = "keybase pgp sign --detached --infile '#{file}' " \
|
24
|
+
"--outfile '#{sig_file}'"
|
25
|
+
rc = system(cmd)
|
26
|
+
raise "Error executing keybase sign command" if !rc
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.verify(file)
|
30
|
+
sig_file = "#{file}.sig"
|
31
|
+
cmd = "keybase pgp verify --detached '#{sig_file}' " \
|
32
|
+
"--infile '#{file}'"
|
33
|
+
rc = system(cmd)
|
34
|
+
raise "Error executing keybase verify command" if !rc
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
# vim:tabstop=2:sw=2:et:
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'json'
|
19
|
+
require 'systemu'
|
20
|
+
require 'tempfile'
|
21
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
class Cangallo
|
24
|
+
|
25
|
+
class LibGuestfs
|
26
|
+
def self.virt_customize(image, commands, params = "")
|
27
|
+
cmd_file = Tempfile.new("canga")
|
28
|
+
|
29
|
+
cmd_file.puts(commands)
|
30
|
+
cmd_file.close
|
31
|
+
|
32
|
+
#rc = system("virt-customize -v -x -a #{image} --commands-from-file #{cmd_file.path}")
|
33
|
+
rc = system("virt-customize -a #{image} #{params.join(" ")} " <<
|
34
|
+
"--commands-from-file #{cmd_file.path}")
|
35
|
+
cmd_file.unlink
|
36
|
+
|
37
|
+
return rc
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.virt_sparsify(image)
|
41
|
+
rc = system("virt-sparsify --in-place #{image}")
|
42
|
+
|
43
|
+
return rc
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
# vim:tabstop=2:sw=2:et:
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'json'
|
19
|
+
require 'systemu'
|
20
|
+
require 'tempfile'
|
21
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
class Cangallo
|
24
|
+
|
25
|
+
class Qcow2
|
26
|
+
attr_reader :path
|
27
|
+
|
28
|
+
def initialize(path=nil)
|
29
|
+
@path=path
|
30
|
+
end
|
31
|
+
|
32
|
+
def info
|
33
|
+
res = execute :info, '--output=json', @path
|
34
|
+
|
35
|
+
JSON.parse res
|
36
|
+
end
|
37
|
+
|
38
|
+
def compress(destination = nil, parent = nil)
|
39
|
+
copy(destination, :parent => parent, :compress => true)
|
40
|
+
end
|
41
|
+
|
42
|
+
def convert(destination, options = {})
|
43
|
+
command = [:convert]
|
44
|
+
command << '-c' if options[:compress]
|
45
|
+
command << "-O #{options[:format]}" if options[:format]
|
46
|
+
command += [@path, destination]
|
47
|
+
|
48
|
+
execute(*command)
|
49
|
+
end
|
50
|
+
|
51
|
+
def copy(destination = nil, options = {})
|
52
|
+
ops = {
|
53
|
+
:parent => nil,
|
54
|
+
:compress => true,
|
55
|
+
:only_copy => false
|
56
|
+
}.merge(options)
|
57
|
+
|
58
|
+
parent = ops[:parent]
|
59
|
+
|
60
|
+
new_path = destination || @path + '.compressed'
|
61
|
+
|
62
|
+
command = [:convert, "-p", "-O qcow2"]
|
63
|
+
#command = ["convert", "-p", "-O qcow2"]
|
64
|
+
command << '-c' if ops[:compress]
|
65
|
+
command << "-o backing_file=#{parent}" if parent
|
66
|
+
command += [@path, new_path]
|
67
|
+
|
68
|
+
if ops[:only_copy]
|
69
|
+
FileUtils.cp(@path, new_path)
|
70
|
+
else
|
71
|
+
execute *command
|
72
|
+
end
|
73
|
+
|
74
|
+
# pp command
|
75
|
+
# system(*command)
|
76
|
+
|
77
|
+
if !destination
|
78
|
+
begin
|
79
|
+
File.rm @path
|
80
|
+
File.mv new_path, @path
|
81
|
+
ensure
|
82
|
+
File.rm new_path if File.exist? new_path
|
83
|
+
end
|
84
|
+
else
|
85
|
+
@path = new_path
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def sparsify(destination)
|
90
|
+
parent = info['backing_file']
|
91
|
+
parent_options = ''
|
92
|
+
|
93
|
+
parent_options = "-o backing_file=#{parent}" if parent
|
94
|
+
|
95
|
+
command = "TMPDIR=#{File.dirname(destination)} virt-sparsify #{parent_options} #{@path} #{destination}"
|
96
|
+
status, stdout, stderr = systemu command
|
97
|
+
end
|
98
|
+
|
99
|
+
def sha(ver = 256)
|
100
|
+
command = "guestfish --progress-bars --ro -a #{@path} " <<
|
101
|
+
"run : checksum-device sha#{ver} /dev/sda"
|
102
|
+
%x{#{command}}.strip
|
103
|
+
end
|
104
|
+
|
105
|
+
def sha1
|
106
|
+
sha(1)
|
107
|
+
end
|
108
|
+
|
109
|
+
def sha256
|
110
|
+
sha(256)
|
111
|
+
end
|
112
|
+
|
113
|
+
def rebase(new_base)
|
114
|
+
execute :rebase, '-u', "-b #{new_base}", @path
|
115
|
+
end
|
116
|
+
|
117
|
+
def execute(command, *params)
|
118
|
+
self.class.execute(command, params)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.execute(command, *params)
|
122
|
+
command = "qemu-img #{command} #{params.join(' ')}"
|
123
|
+
STDERR.puts command
|
124
|
+
|
125
|
+
status, stdout, stderr = systemu command
|
126
|
+
|
127
|
+
if status.success?
|
128
|
+
stdout
|
129
|
+
else
|
130
|
+
raise stderr
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.create_from_base(origin, destination, size=nil)
|
135
|
+
cmd = [:create, '-f qcow2', "-o backing_file=#{origin}", destination]
|
136
|
+
cmd << size if size
|
137
|
+
|
138
|
+
execute(*cmd)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.create(image, parent=nil, size=nil)
|
142
|
+
cmd = [:create, '-f qcow2']
|
143
|
+
cmd << "-o backing_file=#{parent}" if parent
|
144
|
+
cmd << image
|
145
|
+
cmd << size if size
|
146
|
+
|
147
|
+
execute(*cmd)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
|
2
|
+
# vim:ts=2:sw=2
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require "yaml"
|
19
|
+
require "open-uri"
|
20
|
+
require "uri"
|
21
|
+
|
22
|
+
class Cangallo
|
23
|
+
|
24
|
+
class Repo
|
25
|
+
attr_reader :images, :tags, :path, :name
|
26
|
+
|
27
|
+
VERSION = 0
|
28
|
+
|
29
|
+
def initialize(conf)
|
30
|
+
@conf = conf
|
31
|
+
@path = File.expand_path(@conf["path"])
|
32
|
+
@name = @conf["name"]
|
33
|
+
@type = @conf["type"]
|
34
|
+
@url = @conf["url"]
|
35
|
+
|
36
|
+
if !@type && @url
|
37
|
+
@type = "remote"
|
38
|
+
else
|
39
|
+
@type = "local"
|
40
|
+
end
|
41
|
+
|
42
|
+
read_index
|
43
|
+
end
|
44
|
+
|
45
|
+
def index_data(images = {}, tags = {}, version = VERSION)
|
46
|
+
{
|
47
|
+
"version" => version,
|
48
|
+
"images" => images,
|
49
|
+
"tags" => tags
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def index_path
|
54
|
+
metadata_path("index")
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_index(index = nil)
|
58
|
+
if !index
|
59
|
+
if File.exist?(index_path)
|
60
|
+
data = YAML.load(File.read(index_path))
|
61
|
+
else
|
62
|
+
data = index_data()
|
63
|
+
end
|
64
|
+
else
|
65
|
+
data = YAML.load(index)
|
66
|
+
end
|
67
|
+
|
68
|
+
@images = data["images"]
|
69
|
+
@tags = data["tags"]
|
70
|
+
@reverse_tags = @tags.invert
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_index
|
74
|
+
data = index_data(@images, @tags)
|
75
|
+
|
76
|
+
open(metadata_path("index"), "w") do |f|
|
77
|
+
f.write(data.to_yaml)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def metadata_path(name)
|
82
|
+
File.join(@path, "#{name}.yaml")
|
83
|
+
end
|
84
|
+
|
85
|
+
def image_path(name)
|
86
|
+
File.join(@path, "#{name}.qcow2")
|
87
|
+
end
|
88
|
+
|
89
|
+
def remote_url(name)
|
90
|
+
URI.join(@url, name)
|
91
|
+
end
|
92
|
+
|
93
|
+
def remote_image_url(name)
|
94
|
+
remote_url("#{name}.qcow2")
|
95
|
+
end
|
96
|
+
|
97
|
+
def add(name, data)
|
98
|
+
data["creation-time"] = Time.now
|
99
|
+
data["sha256"] = name
|
100
|
+
@images[name] = data
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_image(file, data = {})
|
104
|
+
parent_sha256 = nil
|
105
|
+
parent = nil
|
106
|
+
parent_path = nil
|
107
|
+
|
108
|
+
only_copy = data.delete("only_copy")
|
109
|
+
|
110
|
+
if data["parent"]
|
111
|
+
parent_sha256 = data["parent"]
|
112
|
+
parent = self.images[parent_sha256]
|
113
|
+
|
114
|
+
if !parent
|
115
|
+
raise "Parent not found"
|
116
|
+
end
|
117
|
+
|
118
|
+
parent_path = File.expand_path(self.image_path(parent_sha256))
|
119
|
+
end
|
120
|
+
|
121
|
+
puts "Calculating image sha256 with libguestfs (it will take some time)"
|
122
|
+
qcow2 = Cangallo::Qcow2.new(file)
|
123
|
+
sha256 = qcow2.sha256
|
124
|
+
sha256.strip! if sha256
|
125
|
+
|
126
|
+
puts "Image SHA256: #{sha256}"
|
127
|
+
|
128
|
+
puts "Copying file to repository"
|
129
|
+
image_path = self.image_path(sha256)
|
130
|
+
qcow2.copy(image_path, parent: parent_path, only_copy: only_copy)
|
131
|
+
|
132
|
+
qcow2 = Cangallo::Qcow2.new(image_path)
|
133
|
+
info = qcow2.info
|
134
|
+
|
135
|
+
info_data = info.select do |k,v|
|
136
|
+
%w{virtual-size format actual-size format-specific}.include?(k)
|
137
|
+
end
|
138
|
+
|
139
|
+
data.merge!(info_data)
|
140
|
+
|
141
|
+
data["file-sha256"] = Digest::SHA256.file(file).hexdigest
|
142
|
+
|
143
|
+
if parent
|
144
|
+
qcow2.rebase("#{parent_sha256}.qcow2")
|
145
|
+
data["parent"] = parent_sha256
|
146
|
+
end
|
147
|
+
|
148
|
+
self.add(sha256, data)
|
149
|
+
self.write_index
|
150
|
+
|
151
|
+
sha256
|
152
|
+
end
|
153
|
+
|
154
|
+
def del_image(image)
|
155
|
+
sha256 = find(image)
|
156
|
+
|
157
|
+
raise %Q{Image "#{image}" does not exist} if !sha256
|
158
|
+
|
159
|
+
path = image_path(sha256)
|
160
|
+
File.delete(path)
|
161
|
+
|
162
|
+
@images.delete(sha256)
|
163
|
+
write_index
|
164
|
+
end
|
165
|
+
|
166
|
+
def add_tag(tag, image)
|
167
|
+
img = find(image)
|
168
|
+
@tags[tag] = img
|
169
|
+
write_index
|
170
|
+
end
|
171
|
+
|
172
|
+
def del_tag(tag)
|
173
|
+
@tags.delete(tag)
|
174
|
+
write_index
|
175
|
+
end
|
176
|
+
|
177
|
+
def find(name, search_tags = true)
|
178
|
+
length = name.length
|
179
|
+
found = @images.select do |sha256, data|
|
180
|
+
sha256[0, length] == name
|
181
|
+
end
|
182
|
+
|
183
|
+
if found && found.length > 0
|
184
|
+
return found.first.first
|
185
|
+
end
|
186
|
+
|
187
|
+
if search_tags
|
188
|
+
found = @tags.select do |tag, sha256|
|
189
|
+
tag == name
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
if found && found.length > 0
|
194
|
+
return found.first[1]
|
195
|
+
end
|
196
|
+
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
|
200
|
+
def get(name)
|
201
|
+
image = find(name)
|
202
|
+
|
203
|
+
return nil if !image
|
204
|
+
|
205
|
+
@images[image]
|
206
|
+
end
|
207
|
+
|
208
|
+
def ancestors(name)
|
209
|
+
ancestors = []
|
210
|
+
|
211
|
+
image = get(name)
|
212
|
+
ancestors << image["sha256"]
|
213
|
+
|
214
|
+
while image["parent"]
|
215
|
+
image = get(image["parent"])
|
216
|
+
ancestors << image["sha256"]
|
217
|
+
end
|
218
|
+
|
219
|
+
ancestors
|
220
|
+
end
|
221
|
+
|
222
|
+
def url
|
223
|
+
@conf["url"]
|
224
|
+
end
|
225
|
+
|
226
|
+
def fetch
|
227
|
+
return nil if @conf["type"] != "remote"
|
228
|
+
|
229
|
+
uri = remote_url("index.yaml")
|
230
|
+
|
231
|
+
open(uri, "r") do |f|
|
232
|
+
data = f.read
|
233
|
+
read_index(data)
|
234
|
+
end
|
235
|
+
|
236
|
+
write_index
|
237
|
+
end
|
238
|
+
|
239
|
+
def sign
|
240
|
+
Keybase.sign(index_path)
|
241
|
+
end
|
242
|
+
|
243
|
+
def verify
|
244
|
+
Keybase.verify(index_path)
|
245
|
+
end
|
246
|
+
|
247
|
+
def pull(name)
|
248
|
+
image = get(name)
|
249
|
+
|
250
|
+
raise "Image not found" if !image
|
251
|
+
|
252
|
+
sha256 = image["sha256"]
|
253
|
+
image_url = remote_image_url(sha256)
|
254
|
+
image_path = image_path(sha256)
|
255
|
+
cmd = "curl -o '#{image_path}' '#{image_url}'"
|
256
|
+
|
257
|
+
STDERR.puts(cmd)
|
258
|
+
|
259
|
+
system(cmd)
|
260
|
+
end
|
261
|
+
|
262
|
+
def short_name(sha256)
|
263
|
+
tag = @reverse_tags[sha256]
|
264
|
+
|
265
|
+
if tag
|
266
|
+
name = "#{tag}"
|
267
|
+
else
|
268
|
+
name = "#{sha256[0..15]}"
|
269
|
+
end
|
270
|
+
|
271
|
+
name
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# vim:ts=2:sw=2
|
3
|
+
|
4
|
+
# Copyright 2016, Javier Fontán Muiños <jfontan@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
7
|
+
# not use this file except in compliance with the License. You may obtain
|
8
|
+
# a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
class Cangallo
|
19
|
+
VERSION = '0.0.1'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cangallo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Javier Fontan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: systemu
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: VM Image manager
|
42
|
+
email: jfontan@gmail.com
|
43
|
+
executables:
|
44
|
+
- canga
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- bin/canga
|
49
|
+
- lib/cangallo.rb
|
50
|
+
- lib/cangallo/cangafile.rb
|
51
|
+
- lib/cangallo/config.rb
|
52
|
+
- lib/cangallo/keybase.rb
|
53
|
+
- lib/cangallo/libguestfs.rb
|
54
|
+
- lib/cangallo/qcow2.rb
|
55
|
+
- lib/cangallo/repo.rb
|
56
|
+
- lib/cangallo/version.rb
|
57
|
+
homepage: http://canga.io
|
58
|
+
licenses:
|
59
|
+
- Apache-2.0
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.5.1
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Cangallo!!
|
81
|
+
test_files: []
|
82
|
+
has_rdoc:
|