lxdev 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2120a1f663ad212a874571fd8bf7e8a9c786de30
4
- data.tar.gz: dc6fd44130b3fa99542d48e181222ee4706b80d1
3
+ metadata.gz: 0b31393a23f8385606e4eac0f3bce19b0d9c8d24
4
+ data.tar.gz: 36d736278c01df9698f937504d759ae0c8605773
5
5
  SHA512:
6
- metadata.gz: 1b0bfc74622c66e21f19b339ea2d3f6274ae674dfffd28acaa5156634d57097c5ea2b95da84ffab3e2620183498e8cbb844fe89ef9c4a39d702a992907304d6a
7
- data.tar.gz: 87079f38bd82eecc97c2ca3c40e28639fc4447c6556d87b57b9e619c81efaebb34523315ecf954eba8039e8b4a5b92dbecf470756256006a4927a552a806834c
6
+ metadata.gz: e36ccf0712ada1a0c1ac016405607d60ed1c8674a20425b4513a0ea07f46c46673531d51aeb5b2b5885f469fbb09ab2610795c37cf68b83d79820823aeeb176a
7
+ data.tar.gz: d364c34c12388a837b5700f8e6eb7f3947ffb714a0d6b1021114b08b33220078ce61d1aabf45565a42a0f41ad4f7caecd0b3c00da14e9891e4f99c1003d840e6
data/bin/lxdev CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- require 'lxdev'
2
+ require 'lxdev/main'
3
3
  require 'optparse'
4
4
 
5
5
  def option_parser
@@ -12,7 +12,7 @@ def option_parser
12
12
  end
13
13
 
14
14
  opts.on("-v", "--version", "Prints the version") do
15
- puts "Version #{LxDev::VERSION}"
15
+ puts "Version #{LxDev::Main::VERSION}"
16
16
  exit
17
17
  end
18
18
 
@@ -27,6 +27,10 @@ halt Stop the container
27
27
  destroy Destroy the container
28
28
  provision Run provisioning commands from YAML file
29
29
  exec Run command as root in container
30
+ snapshot Snapshots the container. Takes a snapshot name as parameter.
31
+ rmsnapshot Deletes a snapshot. Takes a snapshot name as parameter.
32
+ restore Restore container to a snapshot with a previous state. Takes a snapshot name as parameter.
33
+ revert Restore container to the last snapshot taken.
30
34
  sudoers Create a sudoers file, allowing you to use lxdev without entering the sudo password
31
35
  EOS
32
36
  opt_parser.parse!()
@@ -55,13 +59,36 @@ def execute_main_command(lxdev)
55
59
  lxdev.provision()
56
60
  when 'exec'
57
61
  command = ARGV[1..-1].join(" ")
58
- if LxDev::SHELLS.include?(command)
62
+ if LxDev::Main::SHELLS.include?(command)
59
63
  lxdev.execute(command, interactive: true)
60
64
  else
61
65
  lxdev.execute(command)
62
66
  end
63
67
  when 'sudoers'
64
- LxDev.create_sudoers_file()
68
+ LxDev::Main.create_sudoers_file()
69
+ when 'snapshot'
70
+ snapshot_name = ARGV[1]
71
+ if snapshot_name.nil?
72
+ puts "Needs a snapshot name!"
73
+ exit 1
74
+ end
75
+ lxdev.snapshot(snapshot_name)
76
+ when 'restore'
77
+ snapshot_name = ARGV[1]
78
+ if snapshot_name.nil?
79
+ puts "Needs a snapshot name!"
80
+ exit 1
81
+ end
82
+ lxdev.restore(snapshot_name)
83
+ when 'revert'
84
+ lxdev.revert()
85
+ when 'rmsnapshot'
86
+ snapshot_name = ARGV[1]
87
+ if snapshot_name.nil?
88
+ puts "Needs a snapshot name!"
89
+ exit 1
90
+ end
91
+ lxdev.rmsnapshot(snapshot_name)
65
92
  else
66
93
  puts "Unknown command.\nRun \"lxdev --help\" for info"
67
94
  end
@@ -70,7 +97,7 @@ end
70
97
 
71
98
 
72
99
  option_parser
73
- lxdev = LxDev.setup()
100
+ lxdev = LxDev::Main.setup()
74
101
  if lxdev
75
102
  execute_main_command(lxdev)
76
103
  lxdev.save_state
data/lib/lxdev/main.rb ADDED
@@ -0,0 +1,371 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'terminal-table'
4
+ require 'lxdev/system'
5
+
6
+ module LxDev
7
+ class Main
8
+ WHITELISTED_SUDO_COMMANDS = ["lxc", "redir", "kill"]
9
+ SHELLS = ["bash", "zsh", "sh", "csh", "tcsh", "ash"]
10
+ BOOT_TIMEOUT = 30
11
+ VERSION = '0.1.2'
12
+
13
+ def initialize
14
+ @uid = System.exec("id -u").output.chomp
15
+ @gid = System.exec("id -g").output.chomp
16
+ @config = YAML.load_file('lxdev.yml')
17
+ @name = @config['box']['name']
18
+ @image = @config['box']['image']
19
+ @user = @config['box']['user']
20
+ @ports = @config['box']['ports'] || {}
21
+ Dir.mkdir('.lxdev') unless File.directory?('.lxdev')
22
+ begin
23
+ @state = YAML.load_file('.lxdev/state')
24
+ rescue
25
+ @state = Hash.new
26
+ end
27
+ rescue Errno::ENOENT
28
+ puts "lxdev.yml not found"
29
+ exit 1
30
+ end
31
+
32
+ def self.setup
33
+ unless lxd_initialized?
34
+ puts "Please run 'lxd init' and configure LXD first"
35
+ return false
36
+ end
37
+ lxdev = Main.new
38
+ unless lxdev.set_ssh_keys
39
+ puts "No ssh keys detected. Make sure you have an ssh key, a running agent, and the key added to the agent, e.g. with ssh-add."
40
+ return false
41
+ end
42
+ return lxdev
43
+ end
44
+
45
+ def save_state
46
+ File.open('.lxdev/state', 'w') {|f| f.write @state.to_yaml} unless @state.empty?
47
+ end
48
+
49
+ def set_ssh_keys
50
+ ssh_keys = System.exec("ssh-add -L").output
51
+ if ssh_keys[0..3] == 'ssh-'
52
+ @ssh_keys = ssh_keys
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def status
59
+ ensure_container_created
60
+
61
+ container_status = get_container_status
62
+ folders = container_status.first['devices'].map {|name, folders| [name, "#{folders['source']} => #{folders['path']}"] if folders['source']}.compact
63
+ table = Terminal::Table.new do |t|
64
+ t.add_row ['Name', container_status.first['name']]
65
+ t.add_row ['Status', container_status.first['status']]
66
+ t.add_row ['IP', get_container_ip]
67
+ t.add_row ['Image', @image]
68
+ t.add_separator
69
+ folders.each do |folder|
70
+ t.add_row folder
71
+ end
72
+ t.add_separator
73
+ @ports.each do |guest, host|
74
+ t.add_row ['Forwarded port', "guest: #{guest} host: #{host}"]
75
+ end
76
+ if container_status.first['snapshots'].any?
77
+ t.add_separator
78
+ t.add_row ['Snapshots', '']
79
+ end
80
+ container_status.first['snapshots'].each do |snapshot|
81
+ t.add_row [snapshot['name'].partition('/').last, snapshot['created_at']]
82
+ end
83
+ end
84
+ puts table
85
+ end
86
+
87
+ def up
88
+ do_provision = false
89
+ unless @state.empty?
90
+ puts "Container state .lxdev/state exists, is it running? If not it might have stopped unexpectedly. Please remove the file before starting."
91
+ exit 1
92
+ end
93
+ if get_container_status.empty?
94
+ create_container
95
+ do_provision = true
96
+ else
97
+ if get_container_status.first['status'] == 'Running'
98
+ puts "#{@name} is already running!"
99
+ exit 1
100
+ else
101
+ start_container
102
+ end
103
+ end
104
+ puts "Waiting for boot..."
105
+ wait_for_boot
106
+ @state['status'] = 'running'
107
+ puts "Forwarding ports..."
108
+ forward_ports(@ports)
109
+ provision if do_provision
110
+ end
111
+
112
+ def halt
113
+ ensure_container_created
114
+ System.exec("sudo lxc stop #{@name}")
115
+ cleanup_forwarded_ports
116
+ remove_state
117
+ end
118
+
119
+ def destroy
120
+ ensure_container_created
121
+ System.exec("sudo lxc delete #{@name}")
122
+ end
123
+
124
+ def ssh(args)
125
+ ensure_container_created
126
+ host = get_container_ip
127
+ if host.nil?
128
+ puts "#{@name} doesn't seem to be running."
129
+ exit 1
130
+ end
131
+ ssh_command = "ssh -o StrictHostKeyChecking=no -t #{@user}@#{get_container_ip} #{args.empty? ? '' : "'#{args.join(' ')}'"}"
132
+ exec ssh_command
133
+ end
134
+
135
+ def execute(command, interactive: false)
136
+ if interactive
137
+ exec("sudo lxc exec #{@name} #{command}") # execution stops here and gives control to exec
138
+ end
139
+ IO.popen("sudo lxc exec #{@name} -- /bin/sh -c '#{command}'", err: [:child, :out]) do |cmd_output|
140
+ cmd_output.each do |line|
141
+ puts line
142
+ end
143
+ end
144
+ end
145
+
146
+ def provision
147
+ ensure_container_created
148
+ if get_container_status.first['status'] != 'Running'
149
+ puts "#{@name} is not running!"
150
+ exit 1
151
+ end
152
+ provisioning = @config['box']['provisioning']
153
+ if provisioning.nil?
154
+ puts "Nothing to do"
155
+ return
156
+ end
157
+ if @config['box']['auto_snapshots']
158
+ snapshot_name = "provision_#{Time.now.to_i}"
159
+ snapshot(snapshot_name)
160
+ end
161
+ puts "Provisioning #{@name}..."
162
+ STDOUT.sync = true
163
+ provisioning.each do |cmd|
164
+ execute cmd
165
+ end
166
+ STDOUT.sync = false
167
+ end
168
+
169
+ def snapshot(snapshot_name)
170
+ puts "Creating snapshot #{snapshot_name}"
171
+ System.exec("sudo lxc snapshot #{@name} #{snapshot_name}")
172
+ end
173
+
174
+ def restore(snapshot_name)
175
+ puts "Restoring snapshot #{snapshot_name}"
176
+ exitstatus = System.exec("sudo lxc restore #{@name} #{snapshot_name}").exitstatus
177
+ exitstatus == 0
178
+ end
179
+
180
+ def rmsnapshot(snapshot_name)
181
+ puts "Deleting snapshot #{snapshot_name}"
182
+ exitstatus = System.exec("sudo lxc delete #{@name}/#{snapshot_name}").exitstatus
183
+ exitstatus == 0
184
+ end
185
+
186
+ def revert
187
+ snapshot = get_container_status.first['snapshots'].last
188
+ snapshot_name = snapshot['name'].partition('/').last
189
+ if restore(snapshot_name)
190
+ puts "Reverted to snapshot #{snapshot_name}"
191
+ puts "Deleting snapshot"
192
+ rmsnapshot(snapshot_name)
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def self.lxd_initialized?
199
+ exitstatus = System.exec("sudo lxc info | grep 'lxd init'").exitstatus
200
+ exitstatus != 0
201
+ end
202
+
203
+ def ensure_container_created
204
+ container_status = get_container_status
205
+ unless container_status.size > 0
206
+ puts "Container not created yet. Run lxdev up"
207
+ exit(0)
208
+ end
209
+ end
210
+
211
+ def remove_state
212
+ File.delete('.lxdev/state') if File.exists?('.lxdev/state')
213
+ @state = {}
214
+ end
215
+
216
+ def create_container
217
+ add_subuid_and_subgid
218
+ puts "Launching #{@name}..."
219
+ System.exec("sudo lxc init #{@image} #{@name}")
220
+ System.exec(%{printf "uid #{@uid} 1001\ngid #{@gid} 1001"| sudo lxc config set #{@name} raw.idmap -})
221
+ System.exec("sudo lxc start #{@name}")
222
+ puts "Creating user #{@user}..."
223
+ create_container_user(@user)
224
+ puts "Mapping folders.."
225
+ map_folders(@config['box']['folders'])
226
+ end
227
+
228
+ def start_container
229
+ puts "Starting #{@name}..."
230
+ System.exec("sudo lxc start #{@name}")
231
+ end
232
+
233
+ def get_container_status
234
+ return @status unless @status.nil?
235
+ command_result = System.exec("sudo lxc list #{@name} --format=json")
236
+ @status = JSON.parse(command_result.output)
237
+ end
238
+
239
+ def get_container_ip
240
+ get_container_status.first['state']['network']['eth0']['addresses'].select {|addr| addr['family'] == 'inet'}.first['address']
241
+ rescue
242
+ nil
243
+ end
244
+
245
+ def add_subuid_and_subgid
246
+ need_restart = false
247
+ if System.exec("grep -q 'root:#{@uid}:1' /etc/subuid").exitstatus != 0
248
+ System.exec("echo 'root:#{@uid}:1' | sudo tee -a /etc/subuid")
249
+ need_restart = true
250
+ end
251
+ if System.exec("grep -q 'root:#{@gid}:1' /etc/subgid").exitstatus != 0
252
+ System.exec("echo 'root:#{@gid}:1' | sudo tee -a /etc/subgid")
253
+ need_restart = true
254
+ end
255
+ if need_restart
256
+ begin
257
+ System.exec("sudo systemctl restart #{lxd_service_name}")
258
+ rescue
259
+ puts "The LXD service needs to be restarted, but the service name cannot be detected. Please restart it manually."
260
+ end
261
+ end
262
+ end
263
+
264
+ def create_container_user(user)
265
+ System.exec("sudo lxc exec #{@name} -- groupadd --gid 1001 #{user}")
266
+ System.exec("sudo lxc exec #{@name} -- useradd --uid 1001 --gid 1001 -s /bin/bash -m #{user}")
267
+ System.exec("sudo lxc exec #{@name} -- mkdir /home/#{user}/.ssh")
268
+ System.exec("sudo lxc exec #{@name} -- chmod 0700 /home/#{user}/.ssh")
269
+ System.exec("printf '#{@ssh_keys}' | sudo lxc exec #{@name} tee /home/#{user}/.ssh/authorized_keys")
270
+ System.exec("sudo lxc exec #{@name} -- chown -R #{user} /home/#{user}/.ssh")
271
+ System.exec("sudo lxc exec #{@name} -- touch /home/#{@user}/.hushlogin")
272
+ System.exec("sudo lxc exec #{@name} -- chown #{user} /home/#{user}/.hushlogin")
273
+ System.exec(%{printf "#{user} ALL=(ALL) NOPASSWD: ALL\n" | sudo lxc exec #{@name} -- tee -a /etc/sudoers})
274
+ System.exec("sudo lxc exec #{@name} -- chmod 0440 /etc/sudoers")
275
+ end
276
+
277
+ def wait_for_boot
278
+ BOOT_TIMEOUT.times do |t|
279
+ @status = nil # reset status for each iteration to refresh IP
280
+ break if get_container_ip
281
+ abort_boot if t == (BOOT_TIMEOUT - 1)
282
+ sleep 1
283
+ end
284
+ end
285
+
286
+ def forward_ports(ports)
287
+ redir_pids = []
288
+ ports.each do |guest, host|
289
+ puts "Forwarding #{get_container_ip}:#{guest} to local port #{host}"
290
+ pid = System.spawn_exec("sudo redir --caddr=#{get_container_ip} --cport=#{guest} --lport=#{host}")
291
+ redir_pids << pid
292
+ Process.detach(pid)
293
+ end
294
+ @state['redir_pids'] = redir_pids
295
+ end
296
+
297
+ def cleanup_forwarded_ports
298
+ if @state.empty?
299
+ return
300
+ end
301
+ @state['redir_pids'].each do |pid|
302
+ System.exec("sudo kill #{pid}")
303
+ end
304
+ end
305
+
306
+ def map_folders(folders)
307
+ counter = 0
308
+ folders.each do |host, guest|
309
+ counter = counter + 1
310
+ puts "Mounting #{host} in #{guest}"
311
+ absolute_path = System.exec("readlink -f #{host}").output.chomp
312
+ System.exec("sudo lxc config device add #{@name} shared_folder_#{counter} disk source=#{absolute_path} path=#{guest}")
313
+ end
314
+ end
315
+
316
+ def get_snapshots
317
+ snapshots = []
318
+ get_container_status.first['snapshots'].each do |snapshot|
319
+ result = {}
320
+ result['name'] = snapshot['name']
321
+ result['date'] = snapshot['created_at']
322
+ snapshots << result
323
+ end
324
+ snapshots
325
+ end
326
+
327
+ def abort_boot
328
+ puts "Timeout waiting for container to boot"
329
+ exit 1
330
+ end
331
+
332
+ def lxd_service_name
333
+ if System.exec("sudo systemctl status lxd.service").exitstatus == 0
334
+ 'lxd.service'
335
+ elsif System.exec("sudo systemctl status snap.lxd.daemon.service").exitstatus == 0
336
+ 'snap.lxd.daemon.service'
337
+ else
338
+ raise 'There seems to be no LXD service on the system!'
339
+ end
340
+ end
341
+
342
+ def self.create_sudoers_file
343
+ user = System.exec("whoami").output.chomp
344
+ puts <<-EOS
345
+ !! WARNING !!
346
+ This will create a file, /etc/sudoers.d/lxdev,
347
+ which will give your user #{user} access to running
348
+ the following commands :
349
+ #{WHITELISTED_SUDO_COMMANDS.join(" ")}
350
+ with superuser privileges. If you do not know what you're
351
+ doing, this can be dangerous and insecure.
352
+
353
+ If you want to do this, type 'yesplease'
354
+ EOS
355
+ action = STDIN.gets.chomp
356
+ unless action == 'yesplease'
357
+ puts "Not creating sudoers file"
358
+ return
359
+ end
360
+ content = []
361
+ content << "# Created by lxdev #{Time.now}"
362
+ WHITELISTED_SUDO_COMMANDS.each do |cmd|
363
+ cmd_with_path = System.exec("which #{cmd}").output.chomp
364
+ content << "#{user} ALL=(root) NOPASSWD: #{cmd_with_path}"
365
+ end
366
+ System.exec(%{printf '#{content.join("\n")}\n' | sudo tee /etc/sudoers.d/lxdev})
367
+ System.exec("sudo chmod 0440 /etc/sudoers.d/lxdev")
368
+ puts "Created sudoers file."
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,19 @@
1
+ module LxDev
2
+ class System
3
+ class Result
4
+ attr_accessor :output
5
+ attr_accessor :exitstatus
6
+ end
7
+
8
+ def self.exec(cmd)
9
+ return_object = Result.new
10
+ return_object.output = %x{#{cmd}}
11
+ return_object.exitstatus = $?.exitstatus
12
+ return_object
13
+ end
14
+
15
+ def self.spawn_exec(cmd)
16
+ spawn(cmd)
17
+ end
18
+ end
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lxdev
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Lønaas
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-06-22 00:00:00.000000000 Z
12
+ date: 2018-08-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -47,7 +47,8 @@ extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
49
  - bin/lxdev
50
- - lib/lxdev.rb
50
+ - lib/lxdev/main.rb
51
+ - lib/lxdev/system.rb
51
52
  homepage: https://github.com/GyldendalDigital/lxdev
52
53
  licenses:
53
54
  - MIT
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  version: '0'
69
70
  requirements: []
70
71
  rubyforge_project:
71
- rubygems_version: 2.5.1
72
+ rubygems_version: 2.5.2.1
72
73
  signing_key:
73
74
  specification_version: 4
74
75
  summary: Automagic development environment with LXD
data/lib/lxdev.rb DELETED
@@ -1,292 +0,0 @@
1
- require 'yaml'
2
- require 'json'
3
- require 'terminal-table'
4
-
5
- class LxDev
6
- WHITELISTED_SUDO_COMMANDS = ["lxc", "redir", "kill"]
7
- SHELLS = ["bash", "zsh", "sh", "csh", "tcsh", "ash"]
8
- BOOT_TIMEOUT = 30
9
- VERSION = '0.1.1'
10
-
11
- def initialize
12
- @uid=%x{id -u}.chomp
13
- @gid=%x{id -g}.chomp
14
- @config = YAML.load_file('lxdev.yml')
15
- @name = @config['box']['name']
16
- @image = @config['box']['image']
17
- @user = @config['box']['user']
18
- @ports = @config['box']['ports'] || {}
19
- Dir.mkdir('.lxdev') unless File.directory?('.lxdev')
20
- begin
21
- @state = YAML.load_file('.lxdev/state')
22
- rescue
23
- @state = Hash.new
24
- end
25
- rescue Errno::ENOENT
26
- puts "lxdev.yml not found"
27
- exit 1
28
- end
29
-
30
- def self.setup
31
- unless lxd_initialized?
32
- puts "Please run 'lxd init' and configure LXD first"
33
- return false
34
- end
35
- return LxDev.new
36
- end
37
-
38
- def save_state
39
- File.open('.lxdev/state', 'w') {|f| f.write @state.to_yaml } unless @state.empty?
40
- end
41
-
42
- def status
43
- ensure_container_created
44
-
45
- container_status = get_container_status
46
- folders = container_status.first['devices'].map{|name, folders| [name,"#{folders['source']} => #{folders['path']}"] if folders['source']}.compact
47
- table = Terminal::Table.new do |t|
48
- t.add_row ['Name', container_status.first['name']]
49
- t.add_row ['Status', container_status.first['status']]
50
- t.add_row ['IP', get_container_ip]
51
- t.add_row ['Image', @image]
52
- t.add_separator
53
- folders.each do |folder|
54
- t.add_row folder
55
- end
56
- t.add_separator
57
- @ports.each do |guest,host|
58
- t.add_row ['Forwarded port', "guest: #{guest} host: #{host}"]
59
- end
60
- end
61
- puts table
62
- end
63
-
64
- def up
65
- do_provision = false
66
- unless @state.empty?
67
- puts "Container state .lxdev/state exists, is it running? If not it might have stopped unexpectedly. Please remove the file before starting."
68
- exit 1
69
- end
70
- if get_container_status.empty?
71
- create_container
72
- do_provision = true
73
- else
74
- if get_container_status.first['status'] == 'Running'
75
- puts "#{@name} is already running!"
76
- exit 1
77
- else
78
- start_container
79
- end
80
- end
81
- puts "Waiting for boot..."
82
- wait_for_boot
83
- @state['status'] = 'running'
84
- puts "Forwarding ports..."
85
- forward_ports(@ports)
86
- provision if do_provision
87
- end
88
-
89
- def halt
90
- ensure_container_created
91
- %x{sudo lxc stop #{@name}}
92
- cleanup_forwarded_ports
93
- remove_state
94
- end
95
-
96
- def destroy
97
- ensure_container_created
98
- %x{sudo lxc delete #{@name}}
99
- end
100
-
101
- def ssh(args)
102
- ensure_container_created
103
- host = get_container_ip
104
- if host.nil?
105
- puts "#{@name} doesn't seem to be running."
106
- exit 1
107
- end
108
- ssh_command = "ssh -o StrictHostKeyChecking=no -t #{@user}@#{get_container_ip} #{args.empty? ? '' : "'#{args.join(' ')}'"}"
109
- exec ssh_command
110
- end
111
-
112
- def execute(command, interactive: false)
113
- if interactive
114
- exec("sudo lxc exec #{@name} #{command}") # execution stops here and gives control to exec
115
- end
116
- IO.popen("sudo lxc exec #{@name} -- /bin/sh -c '#{command}'", err: [:child, :out]) do |cmd_output|
117
- cmd_output.each do |line|
118
- puts line
119
- end
120
- end
121
- end
122
-
123
- def provision
124
- ensure_container_created
125
- if get_container_status.first['status'] != 'Running'
126
- puts "#{@name} is not running!"
127
- exit 1
128
- end
129
- provisioning = @config['box']['provisioning']
130
- if provisioning.nil?
131
- puts "Nothing to do"
132
- return
133
- end
134
- puts "Provisioning #{@name}..."
135
- STDOUT.sync = true
136
- provisioning.each do |cmd|
137
- execute cmd
138
- end
139
- STDOUT.sync = false
140
- end
141
-
142
- private
143
- def self.lxd_initialized?
144
- %x{sudo lxc info | grep 'lxd init'}
145
- $?.exitstatus != 0
146
- end
147
-
148
- def ensure_container_created
149
- container_status = get_container_status
150
- unless container_status.size > 0
151
- puts "Container not created yet. Run lxdev up"
152
- exit(0)
153
- end
154
- end
155
-
156
- def remove_state
157
- File.delete('.lxdev/state') if File.exists?('.lxdev/state')
158
- @state = {}
159
- end
160
-
161
- def create_container
162
- add_subuid_and_subgid
163
- puts "Launching #{@name}..."
164
- %x{sudo lxc init #{@image} #{@name}}
165
- %x{printf "uid #{@uid} 1001\ngid #{@gid} 1001"| sudo lxc config set #{@name} raw.idmap -}
166
- %x{sudo lxc start #{@name}}
167
- puts "Creating user #{@user}..."
168
- create_container_user(@user)
169
- puts "Mapping folders.."
170
- map_folders(@config['box']['folders'])
171
- end
172
-
173
- def start_container
174
- puts "Starting #{@name}..."
175
- %x{sudo lxc start #{@name}}
176
- end
177
-
178
- def get_container_status
179
- return @status unless @status.nil?
180
- container_status = %x{sudo lxc list #{@name} --format=json}
181
- @status = JSON.parse(container_status)
182
- end
183
-
184
- def get_container_ip
185
- get_container_status.first['state']['network']['eth0']['addresses'].select{|addr| addr['family'] == 'inet'}.first['address']
186
- rescue
187
- nil
188
- end
189
-
190
- def add_subuid_and_subgid
191
- need_restart = false
192
- %x{grep -q 'root:#{@uid}:1' /etc/subuid}
193
- if $?.exitstatus != 0
194
- %x{echo 'root:#{@uid}:1' | sudo tee -a /etc/subuid}
195
- need_restart = true
196
- end
197
- %x{grep -q 'root:#{@gid}:1' /etc/subgid}
198
- if $?.exitstatus != 0
199
- %x{echo 'root:#{@gid}:1' | sudo tee -a /etc/subgid}
200
- need_restart = true
201
- end
202
- if need_restart
203
- %x{sudo systemctl restart lxd.service}
204
- end
205
- end
206
-
207
- def create_container_user(user)
208
- %x{sudo lxc exec #{@name} -- groupadd --gid 1001 #{user}}
209
- %x{sudo lxc exec #{@name} -- useradd --uid 1001 --gid 1001 -s /bin/bash -m #{user}}
210
- %x{sudo lxc exec #{@name} -- mkdir /home/#{user}/.ssh}
211
- %x{sudo lxc exec #{@name} -- chmod 0700 /home/#{user}/.ssh}
212
- %x{ssh-add -L | sudo lxc exec #{@name} tee /home/#{user}/.ssh/authorized_keys}
213
- %x{sudo lxc exec #{@name} -- chown -R #{user} /home/#{user}/.ssh}
214
- %x{sudo lxc exec #{@name} -- touch /home/#{@user}/.hushlogin}
215
- %x{sudo lxc exec #{@name} -- chown #{user} /home/#{user}/.hushlogin}
216
- %x{printf "#{user} ALL=(ALL) NOPASSWD: ALL\n" | sudo lxc exec #{@name} -- tee -a /etc/sudoers}
217
- %x{sudo lxc exec #{@name} -- chmod 0440 /etc/sudoers}
218
- end
219
-
220
- def wait_for_boot
221
- BOOT_TIMEOUT.times do |t|
222
- @status = nil # reset status for each iteration to refresh IP
223
- break if get_container_ip
224
- abort_boot if t == (BOOT_TIMEOUT-1)
225
- sleep 1
226
- end
227
- end
228
-
229
- def forward_ports(ports)
230
- redir_pids = []
231
- ports.each do |guest, host|
232
- puts "Forwarding #{get_container_ip}:#{guest} to local port #{host}"
233
- pid = spawn %{sudo redir --caddr=#{get_container_ip} --cport=#{guest} --lport=#{host}}
234
- redir_pids << pid
235
- Process.detach(pid)
236
- end
237
- @state['redir_pids'] = redir_pids
238
- end
239
-
240
- def cleanup_forwarded_ports
241
- if @state.empty?
242
- return
243
- end
244
- @state['redir_pids'].each do |pid|
245
- %x{sudo kill #{pid}}
246
- end
247
- end
248
-
249
- def map_folders(folders)
250
- counter = 0
251
- folders.each do |host, guest|
252
- counter = counter + 1
253
- puts "Mounting #{host} in #{guest}"
254
- absolute_path = %x{readlink -f #{host}}.chomp
255
- %x{sudo lxc config device add #{@name} shared_folder_#{counter} disk source=#{absolute_path} path=#{guest}}
256
- end
257
- end
258
-
259
- def abort_boot
260
- puts "Timeout waiting for container to boot"
261
- exit 1
262
- end
263
-
264
- def self.create_sudoers_file
265
- user=%x{whoami}.chomp
266
- puts <<-EOS
267
- !! WARNING !!
268
- This will create a file, /etc/sudoers.d/lxdev,
269
- which will give your user #{user} access to running
270
- the following commands :
271
- #{WHITELISTED_SUDO_COMMANDS.join(" ")}
272
- with superuser privileges. If you do not know what you're
273
- doing, this can be dangerous and insecure.
274
-
275
- If you want to do this, type 'yesplease'
276
- EOS
277
- action = STDIN.gets.chomp
278
- unless action == 'yesplease'
279
- puts "Not creating sudoers file"
280
- return
281
- end
282
- content = []
283
- content << "# Created by lxdev #{Time.now}"
284
- WHITELISTED_SUDO_COMMANDS.each do |cmd|
285
- cmd_with_path=%x{which #{cmd}}.chomp
286
- content << "#{user} ALL=(root) NOPASSWD: #{cmd_with_path}"
287
- end
288
- %x{printf '#{content.join("\n")}\n' | sudo tee /etc/sudoers.d/lxdev}
289
- %x{sudo chmod 0440 /etc/sudoers.d/lxdev}
290
- puts "Created sudoers file."
291
- end
292
- end