Class: LxDev::Main

Inherits:
Object
  • Object
show all
Defined in:
lib/lxdev/main.rb

Constant Summary collapse

REQUIRED_COMMANDS =
["lxc", "redir", "kill"]
SHELLS =
["bash", "zsh", "sh", "csh", "tcsh", "ash"]
BOOT_TIMEOUT =
30
VERSION =
'0.2.0'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_file, state_file, lxc_command) ⇒ Main

Returns a new instance of Main.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/lxdev/main.rb', line 15

def initialize(config_file, state_file, lxc_command)
  @state_file = format(".lxdev/%s", state_file)
  @uid    = System.exec("id -u").output.chomp
  @gid    = System.exec("id -g").output.chomp
  @config = YAML.load_file(config_file)
  @name   = @config['box']['name']
  @image  = @config['box']['image']
  @user   = @config['box']['user']
  @ports  = @config['box']['ports'] || {}
  @lxc_command = lxc_command
  Dir.mkdir('.lxdev') unless File.directory?('.lxdev')
  begin
    @state = YAML.load_file(@state_file)
  rescue
    @state = Hash.new
  end
rescue Errno::ENOENT
  puts "#{config_file} not found"
  exit 1
end

Class Method Details

.setup(config_file = 'lxdev.yml', state_file = 'state') ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/lxdev/main.rb', line 36

def self.setup(config_file = 'lxdev.yml', state_file = 'state')
  self.check_requirements
  unless lxd_initialized?
    puts "Please run 'lxd init' and configure LXD first"
    return false
  end
  user_in_group = Main.user_in_lxd_group?
  lxc_command = (user_in_group ? "lxc" : "sudo lxc")
  unless(user_in_group)
    puts "Add yourself to the 'lxd' group to avoid entering the sudo password :"
    puts "(You need to login again afterwards, or start a new login shell)"
    puts " sudo usermod -a -G lxd #{Etc.getlogin}"
  end
  lxdev = Main.new(config_file, state_file, lxc_command)
  unless lxdev.set_ssh_keys
    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."
    return false
  end
  return lxdev
end

.user_in_lxd_group?Boolean

Returns:

  • (Boolean)


57
58
59
60
61
62
63
64
# File 'lib/lxdev/main.rb', line 57

def self.user_in_lxd_group?
  begin
    Etc.getgrnam("lxd").mem.include?(Etc.getlogin)
  rescue ArgumentError
    puts "There seems to be no 'lxd' group. Is LXD installed?"
    false
  end
end

Instance Method Details

#destroyObject



140
141
142
143
# File 'lib/lxdev/main.rb', line 140

def destroy
  ensure_container_created
  System.exec("#{@lxc_command} delete #{@name}")
end

#execute(command, interactive: false) ⇒ Object



156
157
158
159
160
161
162
163
164
165
# File 'lib/lxdev/main.rb', line 156

def execute(command, interactive: false)
  if interactive
    exec("#{@lxc_command} exec #{@name} #{command}") # execution stops here and gives control to exec
  end
  IO.popen("#{@lxc_command} exec #{@name} -- /bin/sh -c '#{command}'", err: [:child, :out]) do |cmd_output|
    cmd_output.each do |line|
      puts line
    end
  end
end

#haltObject



133
134
135
136
137
138
# File 'lib/lxdev/main.rb', line 133

def halt
  ensure_container_created
  System.exec("#{@lxc_command} stop #{@name}")
  cleanup_forwarded_ports
  remove_state
end

#provisionObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/lxdev/main.rb', line 167

def provision
  ensure_container_created
  if get_container_status.first['status'] != 'Running'
    puts "#{@name} is not running!"
    exit 1
  end
  provisioning = @config['box']['provisioning']
  if provisioning.nil?
    puts "Nothing to do"
    return
  end
  if @config['box']['auto_snapshots']
    snapshot_name = "provision_#{Time.now.to_i}"
    snapshot(snapshot_name)
  end
  puts "Provisioning #{@name}..."
  STDOUT.sync = true
  provisioning.each do |cmd|
    execute cmd
  end
  STDOUT.sync = false
end

#restore(snapshot_name) ⇒ Object



195
196
197
198
199
# File 'lib/lxdev/main.rb', line 195

def restore(snapshot_name)
  puts "Restoring snapshot #{snapshot_name}"
  exitstatus = System.exec("#{@lxc_command} restore #{@name} #{snapshot_name}").exitstatus
  exitstatus == 0
end

#revertObject



207
208
209
210
211
212
213
214
215
# File 'lib/lxdev/main.rb', line 207

def revert
  snapshot      = get_container_status.first['snapshots'].last
  snapshot_name = snapshot['name'].partition('/').last
  if restore(snapshot_name)
    puts "Reverted to snapshot #{snapshot_name}"
    puts "Deleting snapshot"
    rmsnapshot(snapshot_name)
  end
end

#rmsnapshot(snapshot_name) ⇒ Object



201
202
203
204
205
# File 'lib/lxdev/main.rb', line 201

def rmsnapshot(snapshot_name)
  puts "Deleting snapshot #{snapshot_name}"
  exitstatus = System.exec("#{@lxc_command} delete #{@name}/#{snapshot_name}").exitstatus
  exitstatus == 0
end

#save_stateObject



66
67
68
# File 'lib/lxdev/main.rb', line 66

def save_state
  File.open(@state_file, 'w') {|f| f.write @state.to_yaml} unless @state.empty?
end

#set_ssh_keysObject



70
71
72
73
74
75
76
77
# File 'lib/lxdev/main.rb', line 70

def set_ssh_keys
  ssh_keys = System.exec("ssh-add -L").output
  if ssh_keys[0..3] == 'ssh-'
    @ssh_keys = ssh_keys
  else
    nil
  end
end

#snapshot(snapshot_name) ⇒ Object



190
191
192
193
# File 'lib/lxdev/main.rb', line 190

def snapshot(snapshot_name)
  puts "Creating snapshot #{snapshot_name}"
  System.exec("#{@lxc_command} snapshot #{@name} #{snapshot_name}")
end

#ssh(args) ⇒ Object



145
146
147
148
149
150
151
152
153
154
# File 'lib/lxdev/main.rb', line 145

def ssh(args)
  ensure_container_created
  host = get_container_ip
  if host.nil?
    puts "#{@name} doesn't seem to be running."
    exit 1
  end
  ssh_command = "ssh -o StrictHostKeyChecking=no -t #{@user}@#{get_container_ip} #{args.empty? ? '' : "'#{args.join(' ')}'"}"
  exec ssh_command
end

#statusObject



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/lxdev/main.rb', line 79

def status
  ensure_container_created

  container_status = get_container_status
  folders          = container_status.first['devices'].map {|name, folders| [name, "#{folders['source']} => #{folders['path']}"] if folders['source']}.compact
  table            = Terminal::Table.new do |t|
    t.add_row ['Name', container_status.first['name']]
    t.add_row ['Status', container_status.first['status']]
    t.add_row ['IP', get_container_ip]
    t.add_row ['Image', @image]
    t.add_separator
    folders.each do |folder|
      t.add_row folder
    end
    t.add_separator
    @ports.each do |guest, host|
      t.add_row ['Forwarded port', "guest: #{guest} host: #{host}"]
    end
    if container_status.first['snapshots'].any?
      t.add_separator
      t.add_row ['Snapshots', '']
    end
    container_status.first['snapshots'].each do |snapshot|
      t.add_row [snapshot['name'].partition('/').last, snapshot['created_at']]
    end
  end
  puts table
end

#upObject



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/lxdev/main.rb', line 108

def up
  do_provision = false
  unless @state.empty?
    puts "Container state #{@state_file} exists, is it running? If not it might have stopped unexpectedly. Please remove the file before starting."
    exit 1
  end
  if get_container_status.empty?
    create_container
    do_provision = true
  else
    if get_container_status.first['status'] == 'Running'
      puts "#{@name} is already running!"
      exit 1
    else
      start_container
    end
  end
  puts "Waiting for boot..."
  wait_for_boot
  @state['status'] = 'running'
  puts "Forwarding ports..."
  forward_ports(@ports)
  provision if do_provision
end