linecook-gem 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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
@@ -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)
@@ -0,0 +1,3 @@
1
+ module Linecook
2
+ VERSION = '0.0.1'
3
+ end
data/lib/linecook.rb ADDED
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+
3
+ require 'active_support/all'
4
+ require 'linecook/version'
5
+ require 'linecook/config'
6
+ require 'linecook/build'
7
+ require 'linecook/bake'
8
+ require 'linecook/image'
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: