linecook-gem 0.6.10 → 0.7.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.
@@ -1,11 +0,0 @@
1
- module Linecook
2
- module LinuxBuilder
3
- extend self
4
-
5
- def backend
6
- config = Linecook.config[:builder]
7
- images = Linecook.config[:image][:images]
8
- Linecook::Lxc::Container.new(name: config[:name], home: config[:home], image: config[:image], bridge: true, tmp: Linecook.config[:tmp])
9
- end
10
- end
11
- end
@@ -1,286 +0,0 @@
1
- require 'linecook-gem/image/manager'
2
- require 'linecook-gem/util/ssh'
3
- require 'linecook-gem/util/config'
4
- require 'linecook-gem/util/executor'
5
- require 'tempfile'
6
- require 'ipaddress'
7
-
8
- module Linecook
9
- module Lxc
10
- class Container
11
-
12
- include Executor
13
- MAX_WAIT = 60
14
- attr_reader :config, :root
15
- def initialize(name: 'linecook', home: '/u/lxc', image: nil, remote: :local, bridge: false, tmp: nil)
16
- @name = name
17
- @home = home
18
- @bridge = bridge
19
- @remote = remote == :local ? false : remote
20
- @root = File.join(@home, @name, 'rootfs')
21
- @tmp = tmp
22
- @tmpmount = "linecook_#{name}"
23
- config = { utsname: name, rootfs: @root, mount: {} }
24
- config[:mount][:entry] ||= []
25
- config[:mount][:entry] << '/sys/fs/cgroup sys/fs/cgroup none bind 0 0'
26
- config[:mount][:entry] << "#{capture("mktemp -d --tmpdir=/#{@tmp} #{@tmpmount}XXXXXXX").strip} #{@tmp} none bind,create=dir 0 0" if @tmp
27
-
28
- @config = Linecook::Lxc::Config.generate(config) # FIXME read link from config
29
- @source_image = image || :base_image
30
- end
31
-
32
- def start
33
- return if running?
34
- setup_image
35
- setup_dirs
36
- mount_all
37
- write_config
38
- execute("lxc-start #{container_str} -d")
39
- wait_running
40
- setup_bridge if @bridge
41
- wait_ssh
42
- unmount unless running?
43
- end
44
-
45
- def resume
46
- execute("lxc-start #{container_str} -d") unless running?
47
- end
48
-
49
- def stop(clean: false, destroy: true)
50
- setup_dirs
51
- if running?
52
- cexec("sudo userdel -r -f #{Linecook::Build::USERNAME}") if @remote
53
- execute("lxc-stop #{container_str} -k") if running?
54
- end
55
- unmount(clean: clean) if destroy
56
- end
57
-
58
- def ip
59
- attempts = 10
60
- attempt = 0
61
- until info[:ip] || attempt >= attempts
62
- attempt += 1
63
- sleep(1)
64
- end
65
- info[:ip].is_a?(Array) ? info[:ip].find { |ip| bridge_network.include?(IPAddress(ip)) } : info[:ip]
66
- end
67
-
68
- def running?
69
- info[:state] == 'RUNNING'
70
- end
71
-
72
- def pid
73
- info[:pid]
74
- end
75
-
76
- def info
77
- @info = {}
78
- capture("lxc-info #{container_str} || true").lines.each do |line|
79
- k, v = line.strip.split(/:\s+/)
80
- key = k.downcase.to_sym
81
- @info[key] = @info[key] ? [@info[key]].flatten << v : v
82
- end
83
- @info
84
- end
85
-
86
- private
87
-
88
- def cexec(command)
89
- execute("lxc-attach #{container_str} -- #{command}")
90
- end
91
-
92
- def wait_running
93
- wait_for { running? }
94
- end
95
-
96
- def wait_ssh
97
- if @remote
98
- user = Linecook::Build::USERNAME
99
- cexec("useradd -m -G sudo #{user} || true")
100
- cexec("mkdir -p /home/#{user}/.ssh")
101
- Linecook::Builder.ssh.upload(Linecook::SSH.public_key, "/tmp/#{@name}-pubkey")
102
- Linecook::Builder.ssh.run("sudo mv /tmp/#{@name}-pubkey #{@root}/home/#{user}/.ssh/authorized_keys")
103
- cexec("chown -R #{user} /home/#{user}/.ssh")
104
- end
105
- wait_for { capture("lxc-attach -n #{@name} -P #{@home} status ssh || true") =~ /running/ }
106
- end
107
-
108
- def wait_for
109
- attempts = 0
110
- until attempts > MAX_WAIT
111
- break if yield
112
- attempts += 1
113
- sleep(1)
114
- end
115
- end
116
-
117
- def lxc?
118
- namespaces = capture('cat /proc/1/cgroup').lines.map{ |l| l.strip.split(':').last }.uniq
119
- namespaces.length != 1 || namespaces.first != '/'
120
- end
121
-
122
- def setup_dirs
123
- @lower_dir = tmpdir(label: 'lower')
124
- @upper_base = tmpdir(label: 'upper')
125
- @upper_dir = File.join(@upper_base, '/upper')
126
- @work_dir = File.join(@upper_base, '/work')
127
- @socket_dirs = []
128
- (Linecook.config[:socket_dirs] ||[]).each{ |sock| @socket_dirs << File.join(@root, sock) }
129
- end
130
-
131
- def write_config
132
- path = if @remote
133
- file = "/tmp/lxc-config-#{SecureRandom.hex(4)}"
134
- @remote.upload(@config, file)
135
- file
136
- else
137
- file = Tempfile.new('lxc-config')
138
- file.write(@config)
139
- file.close
140
- file.path
141
- end
142
- execute("mv #{path} #{File.join(@home, @name, 'config')}")
143
- end
144
-
145
- def mount_all
146
- # Prepare an overlayfs
147
- execute("mkdir -p #{@root}")
148
- execute("mkdir -p #{@lower_dir}")
149
- execute("mkdir -p #{@upper_base}")
150
- mount(@image_path, @lower_dir, options: '-o loop')
151
- mount('tmpfs', @upper_base, type: '-t tmpfs', options:'-o noatime') # FIXME: - don't always be tmpfs
152
- execute("mkdir -p #{@work_dir}")
153
- execute("mkdir -p #{@upper_dir}")
154
- mount('overlay', @root, type: '-t overlay', options: "-o lowerdir=#{@lower_dir},upperdir=#{@upper_dir},workdir=#{@work_dir}")
155
- # Overlayfs doesn't support unix domain sockets
156
- @socket_dirs.each do |sock|
157
- execute("mkdir -p #{sock}")
158
- mount('tmpfs', sock, type: '-t tmpfs', options:'-o noatime')
159
- end
160
- end
161
-
162
- def mount(source, dest, type: '', options:'')
163
- execute("grep -q #{dest} /etc/mtab || sudo mount #{type} #{options} #{source} #{dest}")
164
- end
165
-
166
- def unmount(clean: false)
167
- @socket_dirs.each { |sock| execute("umount #{sock}") }
168
- source = capture("mount | grep #{@lower_dir} | grep squashfs | awk '{print $1}'") if clean
169
- execute("umount #{@root}")
170
- execute("umount #{@upper_base}")
171
- execute("umount #{@lower_dir}")
172
- execute("rmdir #{@lower_dir}")
173
- execute("rmdir #{@upper_base}")
174
- execute("rm -rf /#{@tmp}/#{@tmpmount}*") if @tmp && !@tmp.empty? && @tmp != '/'
175
-
176
- # Clean up the source image, but only if it's not mounted elsewhre
177
- FileUtils.rm_f(source) if clean && capture("mount | grep #{source} || true").strip.empty?
178
- end
179
-
180
- def bridge_network
181
- broad_ip = Socket.getifaddrs.find do |a|
182
- a.name == 'lxcbr0' && a.broadaddr && a.broadaddr.afamily == 2 && !a.broadaddr.inspect_sockaddr.start_with?('169.254')
183
- end.broadaddr.inspect_sockaddr
184
- IPAddress("#{broad_ip.to_s}/24")
185
- end
186
-
187
- def setup_bridge
188
- bridge_config = <<-eos
189
- auto lo
190
- iface lo inet loopback
191
-
192
- auto lxcbr0
193
- iface lxcbr0 inet dhcp
194
- bridge_ports eth0
195
- bridge_fd 0
196
- bridge_maxwait 0
197
- eos
198
- interfaces = Tempfile.new('interfaces')
199
- interfaces.write(bridge_config)
200
- interfaces.close
201
- execute("mv #{interfaces.path} #{File.join(@root, 'etc', 'network', 'interfaces')}")
202
-
203
- execute("lxc-attach -n #{@name} -P #{@home} ifup lxcbr0")
204
- end
205
-
206
- def setup_image
207
- @source_path = Linecook::ImageManager.fetch(@source_image, profile: :public)
208
- if @remote
209
- name = File.basename(@source_path)
210
- dest = "/u/linecook/images/#{name}"
211
- unless test("[ -f #{dest} ]") && capture("shasum #{dest}").split.first == `shasum #{@source_path}`.split.first
212
- tmp = "/tmp/#{name}-#{SecureRandom.hex(4)}"
213
- @remote.run("sudo mkdir -p #{File.dirname(dest)}")
214
- @remote.upload(@source_path, tmp)
215
- @remote.run("sudo mv #{tmp} #{dest}")
216
- end
217
- @image_path = dest
218
- else
219
- @image_path = @source_path
220
- end
221
- end
222
-
223
- def tmpdir(label: 'tmp')
224
- "/tmp/#{@name}-#{label}"
225
- end
226
-
227
- def container_str
228
- "-n #{@name} -P #{@home}"
229
- end
230
-
231
- end
232
-
233
- module Config
234
- extend self
235
- DEFAULT_LXC_CONFIG = {
236
- include: '/usr/share/lxc/config/ubuntu.common.conf',
237
- aa_profile: 'unconfined', #'lxc-container-default-with-nesting',
238
- arch: 'x86.64',
239
- utsname: 'linecook',
240
- rootfs: '/u/lxc/linecook/rootfs',
241
- network: {
242
- type: 'veth',
243
- flags: 'up',
244
- link: 'lxcbr0'
245
- },
246
- mount: {
247
- auto: ['cgroup', 'proc:rw', 'sys:rw']
248
- },
249
- cap: {
250
- drop: ''
251
- },
252
- cgroup: {
253
- devices: {
254
- allow: 'a'
255
- }
256
- }
257
- }.freeze
258
-
259
- def generate(**kwargs)
260
- cfg = []
261
- flatten(DEFAULT_LXC_CONFIG.deep_merge(kwargs || {})).each do |k, v|
262
- [v].flatten.each do |val|
263
- cfg << "lxc.#{k}=#{val}"
264
- end
265
- end
266
- cfg.join("\n")
267
- end
268
-
269
- private
270
-
271
- def flatten(hash)
272
- flattened = {}
273
- hash.each do |k, v|
274
- if v.is_a?(Hash)
275
- flatten(v).each do |key, val|
276
- flattened["#{k}.#{key}"] = val
277
- end
278
- else
279
- flattened[k] = v
280
- end
281
- end
282
- flattened
283
- end
284
- end
285
- end
286
- end
@@ -1,79 +0,0 @@
1
- require 'forwardable'
2
- require 'sshkey'
3
-
4
- require 'linecook-gem/builder/lxc'
5
- require 'linecook-gem/builder/darwin_backend'
6
- require 'linecook-gem/builder/linux_backend'
7
- require 'linecook-gem/builder/build'
8
-
9
- module Linecook
10
- module Builder
11
- extend self
12
- extend Forwardable
13
- BUILD_HOME = '/u/lxc'
14
-
15
- def_instance_delegators :backend, :stop, :ip, :info, :running?
16
-
17
- def backend
18
- @backend ||= backend_for_platform
19
- end
20
-
21
- def start
22
- return if running?
23
- backend.start
24
- increase_loop_devices
25
- end
26
-
27
- def ssh
28
- config = Linecook.config[:builder]
29
- @ssh ||= SSH.new(ip, username: config[:username], password: config[:password], keyfile: Linecook::SSH.private_key)
30
- end
31
-
32
- def builds
33
- ssh.test("[ -d #{BUILD_HOME} ]") ? ssh.capture("find #{BUILD_HOME} -maxdepth 1 -mindepth 1 -type d -printf \"%f\n\"").delete(';').lines : []
34
- end
35
-
36
- def build_info
37
- info = {}
38
- builds.each do |build|
39
- info[build] = Linecook::Build.new(build, nil).info
40
- end
41
- info
42
- end
43
-
44
- private
45
-
46
- def backend_for_platform
47
- case Config.platform
48
- when 'linux'
49
- LinuxBuilder.backend
50
- when 'darwin'
51
- OSXBuilder.backend
52
- else
53
- fail "Cannot find supported backend for #{Config.platform}"
54
- end
55
- end
56
-
57
- def increase_loop_devices
58
- kparams = {}
59
-
60
- ssh.capture('cat /proc/cmdline').split(/\s+/).each do |param|
61
- k,v = param.split('=')
62
- kparams[k] = v
63
- end
64
-
65
- if loops = kparams['max_loop']
66
- current = ssh.capture('ls -1 /dev | grep loop')
67
- last_loop = current.lines.length
68
- to_create = loops.to_i - last_loop
69
-
70
- to_create.times do |count|
71
- index = last_loop + count
72
- ssh.run("[ ! -e /dev/loop#{index} ] && sudo mknod -m660 /dev/loop#{index} b 7 #{index}")
73
- ssh.run("sudo chown root:disk /dev/loop#{index}")
74
- end
75
- end
76
- end
77
-
78
- end
79
- end
@@ -1,27 +0,0 @@
1
- require 'octokit'
2
-
3
- require 'linecook-gem/util/config'
4
-
5
- module Linecook
6
- module GithubManager
7
- extend self
8
-
9
- def url(name)
10
- latest[:assets].find { |a| a[:name] =~ /#{name}/ }[:browser_download_url]
11
- end
12
-
13
- private
14
-
15
- def client
16
- @client ||= Octokit::Client.new
17
- end
18
-
19
- def source
20
- @source ||= (Linecook.config['source_repo'] || 'dalehamel/lxb')
21
- end
22
-
23
- def latest
24
- client.releases(source).sort_by { |r| r[:published_at] }.last
25
- end
26
- end
27
- end
@@ -1,73 +0,0 @@
1
- require 'fileutils'
2
-
3
- require 'linecook-gem/image/s3'
4
- require 'linecook-gem/image/github'
5
- require 'linecook-gem/image/crypt'
6
-
7
- module Linecook
8
- module ImageManager
9
- IMAGE_PATH = File.join(Config::LINECOOK_HOME, 'images').freeze
10
- extend self
11
-
12
- def fetch(image, upgrade:false, profile: :private, type: nil, encrypted: false)
13
- url_path = if image.is_a?(Symbol)
14
- image_name = Linecook.config[:image][:images][image][:name]
15
- path = File.join(IMAGE_PATH, image_name)
16
- provider(profile).url(image_name) unless File.exist?(path) || upgrade
17
- elsif image.is_a?(Hash)
18
- profile = :private
19
- encrypted = true
20
- name = image[:name] == :latest ? File.basename(latest(image[:type])) : image[:name]
21
- path = File.join([IMAGE_PATH, image[:type], name].compact)
22
- provider(profile).url(name, type: image[:type])
23
- else
24
- puts "#{image} is invalid"
25
- end
26
-
27
- Linecook::Downloader.download(url_path, path, encrypted: encrypted) unless File.exist?(path) || upgrade
28
- path
29
- end
30
-
31
- def clean(type: nil)
32
- Dir["#{File.join([IMAGE_PATH, type].compact)}/**/*"].each do |image|
33
- FileUtils.rm_f(image) unless `mount`.index(image)
34
- end
35
- end
36
-
37
- def upload(image, profile: :private, type: nil)
38
- path = File.join(IMAGE_PATH, File.basename(image))
39
- puts "Encrypting and uploading image #{path}"
40
- encrypted = Linecook::Crypto.new.encrypt_file(path)
41
- provider(profile).upload(encrypted, type: type)
42
- FileUtils.rm_f(encrypted)
43
- end
44
-
45
- def url(image, profile: :private, type: nil)
46
- provider(profile).url(image, type: type)
47
- end
48
-
49
- def list(type: nil, profile: :private)
50
- profile = profile.to_sym
51
- provider(profile).list(type: type)
52
- end
53
-
54
- def latest(type, profile: :private)
55
- profile = profile.to_sym
56
- provider(profile).latest(type)
57
- end
58
-
59
- private
60
-
61
- def provider(image_profile)
62
- profile = Linecook.config[:image][:provider][image_profile]
63
- case profile
64
- when :s3
65
- S3Manager
66
- when :github
67
- GithubManager
68
- else
69
- fail "No provider implemented for for #{profile}"
70
- end
71
- end
72
- end
73
- end