linecook-gem 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/linecook +16 -0
- data/lib/linecook/bake.rb +49 -0
- data/lib/linecook/build.rb +21 -0
- data/lib/linecook/builder.rb +81 -0
- data/lib/linecook/cli.rb +55 -0
- data/lib/linecook/config.rb +68 -0
- data/lib/linecook/darwin_backend.rb +96 -0
- data/lib/linecook/image.rb +51 -0
- data/lib/linecook/linux_backend.rb +21 -0
- data/lib/linecook/lxc.rb +261 -0
- data/lib/linecook/ssh.rb +128 -0
- data/lib/linecook/version.rb +3 -0
- data/lib/linecook.rb +8 -0
- data/man/LINECOOK.1 +59 -0
- metadata +242 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: daf691a1760137d1407850c46df82f52f30ab117
|
4
|
+
data.tar.gz: 830e47b2c9d7267c1f362a62ed214b78512e2185
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5cbe11357a3fa8ea4bae785803da3548e2aebfc30d2a5616ea4403ffb6a3d9b5b349cf38b63129ee4698fa9f6fdaa3bf32a5ee4b431ee63dca6c0906c2b14cd
|
7
|
+
data.tar.gz: d51d2a394fdd2af2a07e9ba70aef43102f305dfadc9843da66f2bdc2bd5aed650673eb79a833a6b63a55ea8d79cab418e23bbb16a3e6bbd75c34549c9c816dbe
|
data/bin/linecook
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
3
|
+
require 'linecook/cli'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
begin
|
7
|
+
ENV['THOR_DEBUG'] = '1'
|
8
|
+
etime = Benchmark.realtime { Linecook::CLI.start(ARGV) }
|
9
|
+
$stderr.puts "Completed in #{etime}s"
|
10
|
+
rescue Thor::UndefinedCommandError, Thor::UnknownArgumentError, Thor::AmbiguousCommandError, Thor::InvocationError => e
|
11
|
+
$stderr.puts(e.message)
|
12
|
+
exit(64)
|
13
|
+
rescue Thor::Error => e
|
14
|
+
$stderr.puts(e.message)
|
15
|
+
exit(1)
|
16
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
require 'chef-provisioner'
|
4
|
+
require 'chefdepartie'
|
5
|
+
|
6
|
+
require 'linecook/build'
|
7
|
+
|
8
|
+
module Linecook
|
9
|
+
module Baker
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def bake
|
13
|
+
chef_config = setup
|
14
|
+
script = ChefProvisioner::Bootstrap.generate(
|
15
|
+
node_name: chef_config[:node_name],
|
16
|
+
chef_version: chef_config[:version] || nil,
|
17
|
+
first_boot: {
|
18
|
+
run_list: chef_config[:run_list]
|
19
|
+
}
|
20
|
+
)
|
21
|
+
|
22
|
+
puts "Establishing connection to build..."
|
23
|
+
build = Linecook::Build.new('test', 'ubuntu-base.squashfs')
|
24
|
+
build.start
|
25
|
+
build.ssh.forward(chef_port)
|
26
|
+
build.ssh.upload(script, '/tmp/chef_bootstrap')
|
27
|
+
build.ssh.run('sudo bash /tmp/chef_bootstrap')
|
28
|
+
build.ssh.stop_forwarding
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def setup
|
34
|
+
ChefProvisioner::Config.setup(client: 'linecook', listen: 'localhost')
|
35
|
+
config = Linecook::Config.load_config
|
36
|
+
|
37
|
+
chef_config = config[:chef]
|
38
|
+
chef_config.merge!(node_name: "linecook-#{SecureRandom.uuid}",
|
39
|
+
chef_server_url: ChefProvisioner::Config.server)
|
40
|
+
# FIXME: sort out cache copying here for concurrent builds of different refs
|
41
|
+
Chefdepartie.run(background: true, config: chef_config, cache: '/tmp/linecook-cache')
|
42
|
+
chef_config
|
43
|
+
end
|
44
|
+
|
45
|
+
def chef_port
|
46
|
+
ChefProvisioner::Config.server.split(':')[-1].to_i
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'linecook/builder'
|
3
|
+
|
4
|
+
module Linecook
|
5
|
+
class Build
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_instance_delegators :@container, :stop, :start, :ip, :info
|
9
|
+
|
10
|
+
def initialize(name, image)
|
11
|
+
Linecook::Builder.start
|
12
|
+
@name = name
|
13
|
+
@image = image
|
14
|
+
@container = Linecook::Lxc::Container.new(name: @name, image: @image, remote: Linecook::Builder.ssh)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ssh
|
18
|
+
@ssh ||= Linecook::SSH.new(@container.ip, username: 'ubuntu', password: 'ubuntu', proxy: Linecook::Builder.ssh)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'sshkey'
|
3
|
+
|
4
|
+
require 'linecook/lxc'
|
5
|
+
require 'linecook/darwin_backend'
|
6
|
+
require 'linecook/linux_backend'
|
7
|
+
#
|
8
|
+
# Linux builder:
|
9
|
+
# - just checks for a bridge interface
|
10
|
+
# - download live image, if not already
|
11
|
+
# - sets up lxc container using local lxc config
|
12
|
+
# - copies base image into builder container
|
13
|
+
|
14
|
+
# OS X builder:
|
15
|
+
# - download live ISO
|
16
|
+
# - hdiutil
|
17
|
+
# - create cache loopback image
|
18
|
+
# - dd, based on config file.
|
19
|
+
# - start xhyve using gem
|
20
|
+
# - keep track of PID and IP
|
21
|
+
# - copy base image into xhyve cache
|
22
|
+
|
23
|
+
# One linecook instance per build, but many linecook instances can share a single builder
|
24
|
+
# FIXME: How to deal with concurrent builds on different branches / revisions?
|
25
|
+
|
26
|
+
module Linecook
|
27
|
+
module Builder
|
28
|
+
extend self
|
29
|
+
extend Forwardable
|
30
|
+
BUILD_HOME = '/u/lxc'
|
31
|
+
|
32
|
+
def_instance_delegators :backend, :stop, :ip, :info, :running?
|
33
|
+
|
34
|
+
def backend
|
35
|
+
@backend ||= backend_for_platform
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
return if running?
|
40
|
+
backend.start
|
41
|
+
setup_ssh
|
42
|
+
end
|
43
|
+
|
44
|
+
def ssh
|
45
|
+
config = Linecook::Config.load_config[:builder]
|
46
|
+
@ssh ||= SSH.new(ip, username: config[:username], password: config[:password])
|
47
|
+
end
|
48
|
+
|
49
|
+
def builds
|
50
|
+
ssh.test("[ -d #{BUILD_HOME} ]") ? ssh.capture("find #{BUILD_HOME} -maxdepth 1 -mindepth 1 -type d -printf \"%f\n\"").delete(';').lines : []
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_info
|
54
|
+
info = {}
|
55
|
+
builds.each do |build|
|
56
|
+
info[build] = Linecook::Build.new(build, nil).info
|
57
|
+
end
|
58
|
+
info
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def setup_ssh
|
64
|
+
pubkey = SSHKey.new(File.read(File.expand_path("~/.ssh/id_rsa"))).ssh_public_key
|
65
|
+
config = Linecook::Config.load_config[:builder]
|
66
|
+
ssh.run("mkdir -p /home/#{config[:username]}/.ssh")
|
67
|
+
ssh.upload(pubkey, "/home/#{config[:username]}/.ssh/authorized_keys")
|
68
|
+
end
|
69
|
+
|
70
|
+
def backend_for_platform
|
71
|
+
case Config.platform
|
72
|
+
when 'linux'
|
73
|
+
LinuxBuilder.backend
|
74
|
+
when 'darwin'
|
75
|
+
OSXBuilder.backend
|
76
|
+
else
|
77
|
+
fail "Cannot find supported backend for #{Config.platform}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/linecook/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'linecook'
|
3
|
+
|
4
|
+
class Builder < Thor
|
5
|
+
desc 'info', 'Show builder info'
|
6
|
+
def info
|
7
|
+
puts Linecook::Builder.info
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'start', 'Start the builder'
|
11
|
+
def start
|
12
|
+
Linecook::Builder.start
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'stop', 'Stop the builder'
|
16
|
+
def stop
|
17
|
+
Linecook::Builder.stop
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'ip', 'Show the external ip address of the builder'
|
21
|
+
def ip
|
22
|
+
puts Linecook::Builder.ip
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Build < Thor
|
27
|
+
desc 'list', 'Show all builds'
|
28
|
+
def list
|
29
|
+
puts Linecook::Builder.builds
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'info', 'Show build info' # FIXME: accept the build name
|
33
|
+
def info
|
34
|
+
puts Linecook::Builder.build_info
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Linecook::CLI < Thor
|
39
|
+
desc 'builder SUBCOMMAND', 'Manage builders'
|
40
|
+
subcommand 'builder', Builder
|
41
|
+
|
42
|
+
desc 'build SUBCOMMAND', 'Manage builds'
|
43
|
+
subcommand 'build', Build
|
44
|
+
|
45
|
+
desc 'bake', 'Bake a new image'
|
46
|
+
def bake
|
47
|
+
Linecook::Baker.bake
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'fetch IMAGE_NAME', 'Fetch an image by name'
|
51
|
+
method_options name: :string
|
52
|
+
def fetch(name)
|
53
|
+
Linecook::ImageFetcher.fetch(name)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
require 'xhyve'
|
5
|
+
|
6
|
+
module Linecook
|
7
|
+
module Config
|
8
|
+
extend self
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
CONFIG_PATH = File.join(Dir.pwd, 'linecook.yml').freeze # File.expand_path('../../../config/config.yml', __FILE__)
|
12
|
+
LINECOOK_HOME = File.expand_path('~/.linecook').freeze
|
13
|
+
DEFAULT_CONFIG_PATH = File.join(LINECOOK_HOME, 'config.yml').freeze
|
14
|
+
DEFAULT_CONFIG = {
|
15
|
+
builder: {
|
16
|
+
image: :live_image,
|
17
|
+
name: 'builder',
|
18
|
+
home: '/u/lxc',
|
19
|
+
username: 'ubuntu',
|
20
|
+
password: 'ubuntu'
|
21
|
+
},
|
22
|
+
images: {
|
23
|
+
live_iso: 'livesys.iso',
|
24
|
+
live_image: 'livesys.squashfs',
|
25
|
+
base_image: 'ubuntu-base.squashfs'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
def setup
|
30
|
+
FileUtils.mkdir_p(LINECOOK_HOME)
|
31
|
+
File.write(DEFAULT_CONFIG_PATH, YAML.dump(DEFAULT_CONFIG)) unless File.exist?(DEFAULT_CONFIG_PATH)
|
32
|
+
check_perms if platform == 'darwin'
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_perms
|
36
|
+
fix_perms if (File.stat(Xhyve::BINARY_PATH).uid != 0 || !File.stat(Xhyve::BINARY_PATH).setuid?)
|
37
|
+
end
|
38
|
+
|
39
|
+
def fix_perms
|
40
|
+
puts "Xhyve requires root until https://github.com/mist64/xhyve/issues/60 is resolved\nPlease enter your sudo password to setuid on the xhyve binary"
|
41
|
+
system("sudo chown root #{Xhyve::BINARY_PATH}")
|
42
|
+
system("sudo chmod +s #{Xhyve::BINARY_PATH}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_config
|
46
|
+
@config ||= begin
|
47
|
+
config = YAML.load(File.read(DEFAULT_CONFIG_PATH)) if File.exist?(DEFAULT_CONFIG_PATH)
|
48
|
+
config.merge!(YAML.load(File.read(CONFIG_PATH))) if File.exist?(CONFIG_PATH)
|
49
|
+
# fail "Cookbook path not provided or doesn't exist" unless (config[:chef][:cookbook_path] && Dir.exists?(config[:chef][:cookbook_path]))
|
50
|
+
# fail "Databag secret not provided or doesn't exist" unless (config[:chef][:encrypted_data_bag_secret] && File.exists?(config[:chef][:encrypted_data_bag_secret]))
|
51
|
+
(config || {}).deep_symbolize_keys
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def platform
|
56
|
+
case RbConfig::CONFIG['host_os'].downcase
|
57
|
+
when /linux/
|
58
|
+
'linux'
|
59
|
+
when /darwin/
|
60
|
+
'darwin'
|
61
|
+
else
|
62
|
+
fail 'Linux and OS X are the only supported systems'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
setup
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'linecook/image'
|
3
|
+
# FIXME: read config values from config file
|
4
|
+
|
5
|
+
module Linecook
|
6
|
+
module OSXBuilder
|
7
|
+
extend self
|
8
|
+
def backend
|
9
|
+
XhyveBackend.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class XhyveBackend
|
14
|
+
RUN_SPEC_PATH = File.join(Linecook::Config::LINECOOK_HOME, 'xhyve.yml').freeze
|
15
|
+
LINUX_CMDLINE = 'boot=live root=/dev/ram0 live-media=initramfs console=ttyS1,115200 console=tty0 net.ifnames=0 biosdevname=0'.freeze
|
16
|
+
|
17
|
+
attr_reader :ip
|
18
|
+
def initialize
|
19
|
+
spec = load_run_spec
|
20
|
+
@pid = spec[:pid]
|
21
|
+
@ip = spec[:ip]
|
22
|
+
@uuid = spec[:uuid]
|
23
|
+
end
|
24
|
+
|
25
|
+
def info
|
26
|
+
{ip: @ip, pid: @pid, uuid: @uuid}
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
launch_guest unless running?
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
return false unless @pid
|
35
|
+
Process.kill('KILL', @pid)
|
36
|
+
@ip = nil
|
37
|
+
@pid = nil
|
38
|
+
save_run_spec
|
39
|
+
end
|
40
|
+
|
41
|
+
def running?
|
42
|
+
return false unless @pid
|
43
|
+
(true if Process.kill(0, @pid) rescue false)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def launch_guest
|
49
|
+
get_iso
|
50
|
+
boot_path = File.join(mount_iso, 'BOOT')
|
51
|
+
puts "Starting xhyve guest..."
|
52
|
+
@uuid ||= SecureRandom.uuid
|
53
|
+
guest = Xhyve::Guest.new(
|
54
|
+
kernel: File.join(boot_path, 'VMLINUZ'),
|
55
|
+
initrd: File.join(boot_path, 'INITRD'),
|
56
|
+
cmdline: LINUX_CMDLINE, # boot flags to linux
|
57
|
+
#blockdevs: 'loop.img', # path to img files to use as block devs # FIXME
|
58
|
+
uuid: @uuid,
|
59
|
+
serial: 'com2',
|
60
|
+
memory: '4G', # FIXME
|
61
|
+
processors: 1, # number of processors #FIXME
|
62
|
+
networking: true, # Enable networking? (requires sudo)
|
63
|
+
acpi: true, # set up acpi? (required for clean shutdown)
|
64
|
+
)
|
65
|
+
guest.start
|
66
|
+
puts "Started with #{guest.mac}... waiting for network"
|
67
|
+
@ip = guest.ip
|
68
|
+
@pid = guest.pid
|
69
|
+
unless @ip
|
70
|
+
@guest.kill
|
71
|
+
fail 'Could not acquire ip'
|
72
|
+
end
|
73
|
+
puts "Network acquired, IP is #{@ip}"
|
74
|
+
save_run_spec
|
75
|
+
# wait_ssh # FIXME: we need to wait until SSH is actually up or we will sometimes timeout
|
76
|
+
end
|
77
|
+
|
78
|
+
# get and mount the iso
|
79
|
+
def get_iso
|
80
|
+
image = Linecook::Config.load_config[:images][:live_iso]
|
81
|
+
@image_path = Linecook::ImageFetcher.fetch(image)
|
82
|
+
end
|
83
|
+
|
84
|
+
def mount_iso
|
85
|
+
`hdiutil mount #{@image_path}`.strip.split(/\s+/).last
|
86
|
+
end
|
87
|
+
|
88
|
+
def save_run_spec
|
89
|
+
File.write(RUN_SPEC_PATH, YAML.dump(info))
|
90
|
+
end
|
91
|
+
|
92
|
+
def load_run_spec
|
93
|
+
File.exists?(RUN_SPEC_PATH) ? YAML.load(File.read(RUN_SPEC_PATH)) : {}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
require 'octokit'
|
6
|
+
require 'ruby-progressbar'
|
7
|
+
|
8
|
+
module Linecook
|
9
|
+
module ImageFetcher
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def fetch(name, upgrade:false)
|
13
|
+
dir = File.join(Config::LINECOOK_HOME, 'images')
|
14
|
+
path = File.join(dir, name)
|
15
|
+
download(image(name)[:browser_download_url], path) unless File.exist?(path) || upgrade
|
16
|
+
path
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def client
|
22
|
+
@client ||= Octokit::Client.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def source
|
26
|
+
@source ||= (Config.load_config['source_repo'] || 'dalehamel/lxb')
|
27
|
+
end
|
28
|
+
|
29
|
+
def latest
|
30
|
+
client.releases(source).sort_by { |r| r[:published_at] }.last
|
31
|
+
end
|
32
|
+
|
33
|
+
def image(name)
|
34
|
+
latest[:assets].find { |a| a[:name] =~ /#{name}/ }
|
35
|
+
end
|
36
|
+
|
37
|
+
def download(url, path)
|
38
|
+
FileUtils.mkdir_p(File.dirname(path))
|
39
|
+
File.open(path, 'w') do |f|
|
40
|
+
pbar = ProgressBar.create(title: File.basename(path), total: nil)
|
41
|
+
IO.copy_stream(open(url,
|
42
|
+
content_length_proc: lambda do|t|
|
43
|
+
pbar.total = t if t && 0 < t
|
44
|
+
end,
|
45
|
+
progress_proc: lambda do|s|
|
46
|
+
pbar.progress = s
|
47
|
+
end), f)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Linecook
|
2
|
+
module LinuxBuilder
|
3
|
+
extend self
|
4
|
+
LXC_MIN_VERSION = '1.0.7'
|
5
|
+
|
6
|
+
def backend
|
7
|
+
check_lxc_version
|
8
|
+
config = Linecook::Config.load_config[:builder]
|
9
|
+
images = Linecook::Config.load_config[:images]
|
10
|
+
Linecook::Lxc::Container.new(name: config[:name], home: config[:home], image: images[config[:image]])
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# FIXME: move to dependency check during initial setup if on linux
|
16
|
+
def check_lxc_version
|
17
|
+
version = `lxc-info --version`
|
18
|
+
fail "lxc too old (<#{LXC_MIN_VERSION}) or not present" unless Gem::Version.new(version) >= Gem::Version.new(LXC_MIN_VERSION)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/linecook/lxc.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'linecook/ssh'
|
2
|
+
require 'linecook/image'
|
3
|
+
require 'linecook/config'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'ipaddress'
|
6
|
+
|
7
|
+
module Linecook
|
8
|
+
module Lxc
|
9
|
+
class Container
|
10
|
+
attr_reader :config
|
11
|
+
def initialize(name: 'linecook', home: '/u/lxc', image: nil, remote: :local)
|
12
|
+
@remote = remote == :local ? false : remote
|
13
|
+
config = { utsname: name, rootfs: File.join(home, name, 'rootfs') }
|
14
|
+
config.merge!(network: { type: 'veth', flags: 'up', link: 'lxcbr0' }) # FIXME
|
15
|
+
@config = Linecook::Lxc::Config.generate(config) # FIXME read link from config
|
16
|
+
@source_image = image || Linecook::Config.load_config[:images][:base_image]
|
17
|
+
@name = name
|
18
|
+
@home = home
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
setup_image
|
23
|
+
setup_dirs
|
24
|
+
mount_all
|
25
|
+
write_config
|
26
|
+
execute("lxc-start #{container_str} -d")
|
27
|
+
# wait_running # FIXME - poll until it's running, or we can reach a race condition
|
28
|
+
# Don't start a cgmanager if we're already in a container
|
29
|
+
execute('[ -f /etc/init/cgmanager.conf ] && sudo status cgmanager | grep -q running && sudo stop cgmanager || true') if lxc?
|
30
|
+
setup_bridge unless @remote
|
31
|
+
wait_ssh
|
32
|
+
unmount unless running?
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
setup_dirs
|
37
|
+
execute("lxc-stop #{container_str} -k")
|
38
|
+
unmount
|
39
|
+
end
|
40
|
+
|
41
|
+
def ip
|
42
|
+
attempts = 10
|
43
|
+
attempt = 0
|
44
|
+
until info[:ip] || attempt >= attempts
|
45
|
+
attempt += 1
|
46
|
+
sleep(1)
|
47
|
+
end
|
48
|
+
info[:ip].is_a?(Array) ? info[:ip].find { |ip| bridge_network.include?(IPAddress(ip)) } : info[:ip]
|
49
|
+
end
|
50
|
+
|
51
|
+
def running?
|
52
|
+
info[:state] == 'RUNNING'
|
53
|
+
end
|
54
|
+
|
55
|
+
def pid
|
56
|
+
info[:pid]
|
57
|
+
end
|
58
|
+
|
59
|
+
def info
|
60
|
+
@info = {}
|
61
|
+
capture("lxc-info #{container_str}").lines.each do |line|
|
62
|
+
k, v = line.strip.split(/:\s+/)
|
63
|
+
key = k.downcase.to_sym
|
64
|
+
@info[key] = @info[key] ? [@info[key]].flatten << v : v
|
65
|
+
end
|
66
|
+
@info
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def wait_ssh
|
72
|
+
running = false
|
73
|
+
max_attempts = 60
|
74
|
+
attempts = 0
|
75
|
+
until running || attempts > max_attempts
|
76
|
+
break if capture("lxc-attach -n #{@name} -P #{@home} status ssh") =~ /running/
|
77
|
+
attempts += 1
|
78
|
+
sleep(1)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def lxc?
|
83
|
+
namespaces = capture('cat /proc/1/cgroup').lines.map{ |l| l.strip.split(':').last }.uniq
|
84
|
+
namespaces.length != 1 || namespaces.first != '/'
|
85
|
+
end
|
86
|
+
|
87
|
+
def setup_dirs
|
88
|
+
@lower_dir = tmpdir(label: 'lower')
|
89
|
+
@upper_base = tmpdir(label: 'upper')
|
90
|
+
@upper_dir = File.join(@upper_base, '/upper')
|
91
|
+
@work_dir = File.join(@upper_base, '/work')
|
92
|
+
@overlay = File.join(@home, @name, '/rootfs')
|
93
|
+
@socket_dirs = []
|
94
|
+
(Linecook::Config.load_config[:socket_dirs] ||[]).each{ |sock| @socket_dirs << File.join(@overlay, sock) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def write_config
|
98
|
+
path = if @remote
|
99
|
+
@remote.upload(@config, '/tmp/lxc-config')
|
100
|
+
'/tmp/lxc-config'
|
101
|
+
else
|
102
|
+
file = Tempfile.new('lxc-config')
|
103
|
+
file.write(@config)
|
104
|
+
file.close
|
105
|
+
file.path
|
106
|
+
end
|
107
|
+
execute("mv #{path} #{File.join(@home, @name, 'config')}")
|
108
|
+
end
|
109
|
+
|
110
|
+
def mount_all
|
111
|
+
# Prepare an overlayfs
|
112
|
+
execute("mkdir -p #{@overlay}")
|
113
|
+
execute("mkdir -p #{@lower_dir}")
|
114
|
+
execute("mkdir -p #{@upper_base}")
|
115
|
+
mount(@image_path, @lower_dir, options: '-o loop')
|
116
|
+
mount('tmpfs', @upper_base, type: '-t tmpfs', options:'-o noatime') # FIXME: - don't always be tmpfs
|
117
|
+
execute("mkdir -p #{@work_dir}")
|
118
|
+
execute("mkdir -p #{@upper_dir}")
|
119
|
+
mount('overlay', @overlay, type: '-t overlay', options: "-o lowerdir=#{@lower_dir},upperdir=#{@upper_dir},workdir=#{@work_dir}")
|
120
|
+
# Overlayfs doesn't support unix domain sockets
|
121
|
+
@socket_dirs.each do |sock|
|
122
|
+
execute("mkdir -p #{sock}")
|
123
|
+
mount('tmpfs', sock, type: '-t tmpfs', options:'-o noatime')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def mount(source, dest, type: '', options:'')
|
128
|
+
execute("grep -q #{dest} /etc/mtab || sudo mount #{type} #{options} #{source} #{dest}")
|
129
|
+
end
|
130
|
+
|
131
|
+
def unmount
|
132
|
+
@socket_dirs.each { |sock| execute("umount #{sock}") }
|
133
|
+
execute("umount #{@overlay}")
|
134
|
+
execute("umount #{@upper_base}")
|
135
|
+
execute("umount #{@lower_dir}")
|
136
|
+
execute("rmdir #{@lower_dir}")
|
137
|
+
execute("rmdir #{@upper_base}")
|
138
|
+
end
|
139
|
+
|
140
|
+
def bridge_network
|
141
|
+
broad_ip = Socket.getifaddrs.find do |a|
|
142
|
+
a.name == 'lxcbr0' && a.broadaddr && a.broadaddr.afamily == 2 && !a.broadaddr.inspect_sockaddr.start_with?('169.254')
|
143
|
+
end.broadaddr.inspect_sockaddr
|
144
|
+
IPAddress("#{broad_ip.to_s}/24")
|
145
|
+
end
|
146
|
+
|
147
|
+
def setup_bridge
|
148
|
+
bridge_config = <<-eos
|
149
|
+
auto lo
|
150
|
+
iface lo inet loopback
|
151
|
+
|
152
|
+
auto lxcbr0
|
153
|
+
iface lxcbr0 inet dhcp
|
154
|
+
bridge_ports eth0
|
155
|
+
bridge_fd 0
|
156
|
+
bridge_maxwait 0
|
157
|
+
eos
|
158
|
+
interfaces = Tempfile.new('interfaces')
|
159
|
+
interfaces.write(bridge_config)
|
160
|
+
interfaces.close
|
161
|
+
execute("mv #{interfaces.path} #{File.join(@home, @name, 'rootfs', 'etc', 'network', 'interfaces')}")
|
162
|
+
|
163
|
+
execute("lxc-attach -n #{@name} -P #{@home} ifup lxcbr0")
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
def setup_image
|
168
|
+
@source_path = Linecook::ImageFetcher.fetch(@source_image)
|
169
|
+
if @remote
|
170
|
+
dest = "#{File.basename(@source_path)}"
|
171
|
+
@remote.upload(@source_path, dest) unless @remote.test("[ -f #{dest} ]")
|
172
|
+
@image_path = dest
|
173
|
+
else
|
174
|
+
@image_path = @source_path
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def tmpdir(label: 'tmp')
|
179
|
+
"/tmp/#{@name}-#{label}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def container_str
|
183
|
+
"-n #{@name} -P #{@home}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def capture(command, sudo: true)
|
187
|
+
execute(command, sudo: sudo, capture: true)
|
188
|
+
end
|
189
|
+
|
190
|
+
def execute(command, sudo: true, capture: false)
|
191
|
+
command = "sudo #{command}" if sudo
|
192
|
+
if @remote
|
193
|
+
if capture
|
194
|
+
return @remote.capture(command)
|
195
|
+
else
|
196
|
+
@remote.run(command)
|
197
|
+
end
|
198
|
+
else
|
199
|
+
if capture
|
200
|
+
return `#{command}`
|
201
|
+
else
|
202
|
+
system(command)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
module Config
|
209
|
+
extend self
|
210
|
+
DEFAULT_LXC_CONFIG = {
|
211
|
+
include: '/usr/share/lxc/config/ubuntu.common.conf',
|
212
|
+
aa_profile: 'lxc-container-default-with-nesting',
|
213
|
+
arch: 'x86.64',
|
214
|
+
utsname: 'linecook',
|
215
|
+
rootfs: '/u/lxc/linecook/rootfs',
|
216
|
+
network: {
|
217
|
+
type: 'veth',
|
218
|
+
flags: 'up',
|
219
|
+
link: 'br0'
|
220
|
+
},
|
221
|
+
mount: {
|
222
|
+
auto: 'cgroup'
|
223
|
+
},
|
224
|
+
cgroup: {
|
225
|
+
devices: {
|
226
|
+
allow: [
|
227
|
+
'b 7:* rwm',
|
228
|
+
'c 10:237 rwm'
|
229
|
+
]
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}.freeze
|
233
|
+
|
234
|
+
def generate(**kwargs)
|
235
|
+
cfg = []
|
236
|
+
flatten(DEFAULT_LXC_CONFIG.merge(kwargs || {})).each do |k, v|
|
237
|
+
[v].flatten.each do |val|
|
238
|
+
cfg << "lxc.#{k}=#{val}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
cfg.join("\n")
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def flatten(hash)
|
247
|
+
flattened = {}
|
248
|
+
hash.each do |k, v|
|
249
|
+
if v.is_a?(Hash)
|
250
|
+
flatten(v).each do |key, val|
|
251
|
+
flattened["#{k}.#{key}"] = val
|
252
|
+
end
|
253
|
+
else
|
254
|
+
flattened[k] = v
|
255
|
+
end
|
256
|
+
end
|
257
|
+
flattened
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
data/lib/linecook/ssh.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'sshkit'
|
2
|
+
require 'sshkit/dsl'
|
3
|
+
require 'net/ssh'
|
4
|
+
require 'net/ssh/proxy/command'
|
5
|
+
|
6
|
+
require 'linecook/config'
|
7
|
+
|
8
|
+
module Linecook
|
9
|
+
class SSHKit::Formatter::Linecook < SSHKit::Formatter::Pretty
|
10
|
+
def write_command(command)
|
11
|
+
log_command_start(command) unless command.started?
|
12
|
+
log_command_stdout(command) unless command.stdout.empty?
|
13
|
+
log_command_stderr(command) unless command.stderr.empty?
|
14
|
+
log_command_finished(command) if command.finished?
|
15
|
+
end
|
16
|
+
|
17
|
+
def log_command_start(command)
|
18
|
+
print(command, 'run'.colorize(:green), command.to_s.colorize(:yellow))
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_command_stdout(command)
|
22
|
+
command.stdout.lines.each do |line|
|
23
|
+
print(command, 'out'.colorize(:green), line)
|
24
|
+
end
|
25
|
+
command.stdout = ''
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_command_stderr(command)
|
29
|
+
command.stderr.lines.each do |line|
|
30
|
+
print(command, 'err'.colorize(:yellow), line)
|
31
|
+
end
|
32
|
+
command.stderr = ''
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_command_finished(command)
|
36
|
+
if command.failure?
|
37
|
+
print(command, 'failed'.colorize(:red), "with status #{command.exit_status} #{command.to_s.colorize(:yellow)} in #{sprintf('%5.3f seconds', command.runtime)}")
|
38
|
+
else
|
39
|
+
print(command, 'done'.colorize(:green), "#{command.to_s.colorize(:yellow)} in #{sprintf('%5.3f seconds', command.runtime)}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def print(command, state, message)
|
44
|
+
line = "[#{command.host.to_s.colorize(:blue)}][#{state}] #{message}"
|
45
|
+
line << "\n" unless line.end_with?("\n")
|
46
|
+
original_output << line
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class SSH
|
51
|
+
|
52
|
+
attr_reader :username, :hostname
|
53
|
+
def initialize(hostname, username: 'ubuntu', password: nil, proxy: nil)
|
54
|
+
@username = username
|
55
|
+
@password = password
|
56
|
+
@hostname = hostname
|
57
|
+
@proxy = proxy_command(proxy) if proxy
|
58
|
+
end
|
59
|
+
|
60
|
+
def forward(local, remote:nil)
|
61
|
+
remote ||= local
|
62
|
+
opts = { password: @password }
|
63
|
+
opts.merge!({ proxy: @proxy }) if @proxy
|
64
|
+
@session = Net::SSH.start(@hostname, @username, opts)
|
65
|
+
@session.forward.remote(local, '127.0.0.1', remote)
|
66
|
+
# Block to ensure it's open
|
67
|
+
@session.loop { !@session.forward.active_remotes.include?([remote, '127.0.0.1']) }
|
68
|
+
@keep_forwarding = true
|
69
|
+
@forward = Thread.new do
|
70
|
+
@session.loop(0.1) { @keep_forwarding }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def stop_forwarding
|
75
|
+
@keep_forwarding = false
|
76
|
+
@forward.join
|
77
|
+
@session.close unless @session.closed?
|
78
|
+
end
|
79
|
+
|
80
|
+
def test(check)
|
81
|
+
result = nil
|
82
|
+
on linecook_host do |_host|
|
83
|
+
result = test(check)
|
84
|
+
end
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
def run(command)
|
89
|
+
on linecook_host do |_host|
|
90
|
+
execute(command)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def capture(command)
|
95
|
+
output = nil
|
96
|
+
on linecook_host do |_host|
|
97
|
+
output = capture(command)
|
98
|
+
end
|
99
|
+
output
|
100
|
+
end
|
101
|
+
|
102
|
+
def upload(data, path)
|
103
|
+
on linecook_host do |_host|
|
104
|
+
contents = File.exist?(data) ? data : StringIO.new(data)
|
105
|
+
upload! contents, path
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def linecook_host
|
112
|
+
@host ||= begin
|
113
|
+
|
114
|
+
host = SSHKit::Host.new(user: @username, hostname: @hostname)
|
115
|
+
host.password = @password if @password
|
116
|
+
host.ssh_options = { proxy: @proxy } if @proxy
|
117
|
+
host
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def proxy_command(proxy)
|
122
|
+
ssh_command = "ssh #{proxy.username}@#{proxy.hostname} nc %h %p"
|
123
|
+
Net::SSH::Proxy::Command.new(ssh_command)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
SSHKit.config.output = SSHKit::Formatter::Linecook.new($stdout)
|
data/lib/linecook.rb
ADDED
data/man/LINECOOK.1
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
.TH LINECOOK 1 "December 2015" Unix "User Manuals"
|
2
|
+
.SH NAME
|
3
|
+
.PP
|
4
|
+
linecook \- system image builder
|
5
|
+
.SH SYNOPSIS
|
6
|
+
.PP
|
7
|
+
linecook help [\fB\fCCOMMAND\fR]\- for specific command help
|
8
|
+
.SH DESCRIPTION
|
9
|
+
.PP
|
10
|
+
Linecook
|
11
|
+
.SH CONFIGURATION
|
12
|
+
.PP
|
13
|
+
Describe config file here once it's been determined
|
14
|
+
.SH FILES
|
15
|
+
.TP
|
16
|
+
\fI\&./linecook.yml\fP
|
17
|
+
Local config file. Gets deep merged over the system config file. If not explicitly specified, found from current directory if exists.
|
18
|
+
.TP
|
19
|
+
\fI~/linecook/config.yml\fP
|
20
|
+
The system wide configuration file, base or 'common' configuration. Other configurations get deep merged on top of this.
|
21
|
+
.SH DEPENDENCIES
|
22
|
+
.PP
|
23
|
+
Ruby 2.0 or greater, gem, and bundler.
|
24
|
+
.PP
|
25
|
+
Does not work and will never work on Windows.
|
26
|
+
.SS Linux
|
27
|
+
.PP
|
28
|
+
Only tested on Gentoo and Ubuntu
|
29
|
+
.RS
|
30
|
+
.IP \(bu 2
|
31
|
+
lxc >= 1.0.7
|
32
|
+
.IP \(bu 2
|
33
|
+
brutils
|
34
|
+
.IP \(bu 2
|
35
|
+
dnsmasq
|
36
|
+
.IP \(bu 2
|
37
|
+
iptables with masquerade support
|
38
|
+
.IP \(bu 2
|
39
|
+
Linux 3.19 or greater with support for cgroups, and netfilter as described by lxc and iptables for NAT.
|
40
|
+
.RE
|
41
|
+
.SS OS X
|
42
|
+
.RS
|
43
|
+
.IP \(bu 2
|
44
|
+
OS X 10.10 or later (Hypervisor.framework required for Xhyve)
|
45
|
+
.RE
|
46
|
+
.SH QUIRKS
|
47
|
+
.RS
|
48
|
+
.IP \(bu 2
|
49
|
+
Overlayfs doesn't support unix domain sockets (yet), so anything using a unix domain socket outside of the /run tree should do manually symlink to /run.
|
50
|
+
.IP \(bu 2
|
51
|
+
Config file will allow you to explicitly mount tmpfs over things that don't do /run if you need to create unix domain sockets
|
52
|
+
.RE
|
53
|
+
.SH BUGS
|
54
|
+
.PP
|
55
|
+
Report bugs against github.com/dalehamel/linecook
|
56
|
+
.SH AUTHOR
|
57
|
+
.PP
|
58
|
+
Dale Hamel
|
59
|
+
\[la]dale.hamel@srvthe.net\[ra]
|
metadata
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: linecook-gem
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dale Hamel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: xhyve-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sshkit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.7.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sshkey
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.8.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.8.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: octokit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 4.2.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 4.2.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: chefdepartie
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.7
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.7
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: chef-provisioner
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.1.2
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.1.2
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activesupport
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 4.2.5
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 4.2.5
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: ruby-progressbar
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.7.5
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.7.5
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: ipaddress
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.8.0
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.8.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rake
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 10.4.2
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 10.4.2
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simplecov
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 0.10.0
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.10.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 3.2.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - '='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 3.2.0
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: md2man
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - '='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 4.0.0
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 4.0.0
|
195
|
+
description: Build and snapshot a system image for distribution, CI, or both using
|
196
|
+
real chef cookbooks and a fake server.
|
197
|
+
email: dale.hamel@srvthe.net
|
198
|
+
executables:
|
199
|
+
- linecook
|
200
|
+
extensions: []
|
201
|
+
extra_rdoc_files: []
|
202
|
+
files:
|
203
|
+
- bin/linecook
|
204
|
+
- lib/linecook.rb
|
205
|
+
- lib/linecook/bake.rb
|
206
|
+
- lib/linecook/build.rb
|
207
|
+
- lib/linecook/builder.rb
|
208
|
+
- lib/linecook/cli.rb
|
209
|
+
- lib/linecook/config.rb
|
210
|
+
- lib/linecook/darwin_backend.rb
|
211
|
+
- lib/linecook/image.rb
|
212
|
+
- lib/linecook/linux_backend.rb
|
213
|
+
- lib/linecook/lxc.rb
|
214
|
+
- lib/linecook/ssh.rb
|
215
|
+
- lib/linecook/version.rb
|
216
|
+
- man/LINECOOK.1
|
217
|
+
homepage: http://rubygems.org/gems/linecook
|
218
|
+
licenses:
|
219
|
+
- MIT
|
220
|
+
metadata: {}
|
221
|
+
post_install_message:
|
222
|
+
rdoc_options: []
|
223
|
+
require_paths:
|
224
|
+
- lib
|
225
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
231
|
+
requirements:
|
232
|
+
- - ">="
|
233
|
+
- !ruby/object:Gem::Version
|
234
|
+
version: '0'
|
235
|
+
requirements: []
|
236
|
+
rubyforge_project:
|
237
|
+
rubygems_version: 2.4.6
|
238
|
+
signing_key:
|
239
|
+
specification_version: 4
|
240
|
+
summary: Build system images using chef zero, LXC, and packer
|
241
|
+
test_files: []
|
242
|
+
has_rdoc:
|