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