linecook-gem 0.0.3 → 0.0.4
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 +4 -4
- data/lib/linecook/builder/build.rb +28 -0
- data/lib/linecook/{darwin_backend.rb → builder/darwin_backend.rb} +4 -1
- data/lib/linecook/{linux_backend.rb → builder/linux_backend.rb} +3 -3
- data/lib/linecook/{lxc.rb → builder/lxc.rb} +25 -43
- data/lib/linecook/builder/manager.rb +56 -0
- data/lib/linecook/cli.rb +77 -12
- data/lib/linecook/image/crypt.rb +49 -0
- data/lib/linecook/image/github.rb +27 -0
- data/lib/linecook/image/manager.rb +43 -0
- data/lib/linecook/image/s3.rb +47 -0
- data/lib/linecook/packager/ebs.rb +270 -0
- data/lib/linecook/packager/manager.rb +23 -0
- data/lib/linecook/{chef.rb → provisioner/chef-zero.rb} +3 -2
- data/lib/linecook/provisioner/manager.rb +34 -0
- data/lib/linecook/provisioner/packer.rb +82 -0
- data/lib/linecook/{config.rb → util/config.rb} +44 -4
- data/lib/linecook/util/downloader.rb +35 -0
- data/lib/linecook/util/executor.rb +33 -0
- data/lib/linecook/{ssh.rb → util/ssh.rb} +59 -2
- data/lib/linecook/version.rb +1 -1
- data/lib/linecook.rb +5 -4
- metadata +76 -11
- data/lib/linecook/bake.rb +0 -21
- data/lib/linecook/build.rb +0 -21
- data/lib/linecook/builder.rb +0 -93
- data/lib/linecook/image.rb +0 -51
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: e6a9328562fa6cf23fc9c9d97500f929b07299e3
         | 
| 4 | 
            +
              data.tar.gz: 70c988ff74e983fed3ae8e74c20c3cd59ea6baaa
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c7d3c9ce2562d88673b7bed970b8411c1a560d394e8c5401f982b5608464ca9a2c38026fbf825bf55b6097c5145c305bc3489cd21ac9e2a934d56770d513a9ea
         | 
| 7 | 
            +
              data.tar.gz: 5856d130e60480ca800ed090ef5e59b016e627225ca4f4ba6b00a9d9e6b6498218f0fd3737ea026802cb1b38efb2b73f60810199a521632d8189929043309542
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
            require 'linecook/builder/manager'
         | 
| 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: nil)
         | 
| 11 | 
            +
                  Linecook::Builder.start
         | 
| 12 | 
            +
                  @name = name
         | 
| 13 | 
            +
                  @image = image || Linecook::Config.load_config[:provisioner][:default_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, keyfile: Linecook::SSH.private_key)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def snapshot(save: false)
         | 
| 22 | 
            +
                  path = "/tmp/#{@name}-#{Time.now.to_i}.squashfs"
         | 
| 23 | 
            +
                  Linecook::Builder.ssh.run("sudo mksquashfs #{@container.root} #{path} -wildcards -e 'usr/src' 'var/lib/apt/lists/archive*' 'var/cache/apt/archives'") # FIXME make these excludes dynamic based on OS
         | 
| 24 | 
            +
                  Linecook::Builder.ssh.download(path, local: File.join(Linecook::ImageManager::IMAGE_PATH, File.basename(path))) if save
         | 
| 25 | 
            +
                  path
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            module Linecook
         | 
| 2 2 | 
             
              module LinuxBuilder
         | 
| 3 3 | 
             
                extend self
         | 
| 4 | 
            -
                LXC_MIN_VERSION = '1. | 
| 4 | 
            +
                LXC_MIN_VERSION = '1.1.4'
         | 
| 5 5 |  | 
| 6 6 | 
             
                def backend
         | 
| 7 7 | 
             
                  check_lxc_version
         | 
| 8 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:  | 
| 9 | 
            +
                  images = Linecook::Config.load_config[:image][:images]
         | 
| 10 | 
            +
                  Linecook::Lxc::Container.new(name: config[:name], home: config[:home], image: config[:image], bridge: true)
         | 
| 11 11 | 
             
                end
         | 
| 12 12 |  | 
| 13 13 | 
             
                private
         | 
| @@ -1,34 +1,37 @@ | |
| 1 | 
            -
            require 'linecook/ | 
| 2 | 
            -
            require 'linecook/ | 
| 3 | 
            -
            require 'linecook/config'
         | 
| 1 | 
            +
            require 'linecook/image/manager'
         | 
| 2 | 
            +
            require 'linecook/util/ssh'
         | 
| 3 | 
            +
            require 'linecook/util/config'
         | 
| 4 | 
            +
            require 'linecook/util/executor'
         | 
| 4 5 | 
             
            require 'tempfile'
         | 
| 5 6 | 
             
            require 'ipaddress'
         | 
| 6 7 |  | 
| 7 8 | 
             
            module Linecook
         | 
| 8 9 | 
             
              module Lxc
         | 
| 9 10 | 
             
                class Container
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  include Executor
         | 
| 10 13 | 
             
                  MAX_WAIT = 60
         | 
| 11 | 
            -
                  attr_reader :config
         | 
| 12 | 
            -
                  def initialize(name: 'linecook', home: '/u/lxc', image: nil, remote: :local)
         | 
| 13 | 
            -
                    @remote = remote == :local ? false : remote
         | 
| 14 | 
            -
                    config = { utsname: name, rootfs: File.join(home, name, 'rootfs') }
         | 
| 15 | 
            -
                    config.merge!(network: { type: 'veth', flags: 'up', link: 'lxcbr0' }) # FIXME
         | 
| 16 | 
            -
                    @config = Linecook::Lxc::Config.generate(config) # FIXME read link from config
         | 
| 17 | 
            -
                    @source_image = image || Linecook::Config.load_config[:images][:base_image]
         | 
| 14 | 
            +
                  attr_reader :config, :root
         | 
| 15 | 
            +
                  def initialize(name: 'linecook', home: '/u/lxc', image: nil, remote: :local, bridge: false)
         | 
| 18 16 | 
             
                    @name = name
         | 
| 19 17 | 
             
                    @home = home
         | 
| 18 | 
            +
                    @bridge = bridge
         | 
| 19 | 
            +
                    @remote = remote == :local ? false : remote
         | 
| 20 | 
            +
                    @root = File.join(@home, @name, 'rootfs')
         | 
| 21 | 
            +
                    config = { utsname: name, rootfs: @root }
         | 
| 22 | 
            +
                    @config = Linecook::Lxc::Config.generate(config) # FIXME read link from config
         | 
| 23 | 
            +
                    @source_image = image || :base_image
         | 
| 20 24 | 
             
                  end
         | 
| 21 25 |  | 
| 22 26 | 
             
                  def start
         | 
| 27 | 
            +
                    return if running?
         | 
| 23 28 | 
             
                    setup_image
         | 
| 24 29 | 
             
                    setup_dirs
         | 
| 25 30 | 
             
                    mount_all
         | 
| 26 31 | 
             
                    write_config
         | 
| 27 32 | 
             
                    execute("lxc-start #{container_str} -d")
         | 
| 28 33 | 
             
                    wait_running
         | 
| 29 | 
            -
                     | 
| 30 | 
            -
                    execute('[ -f /etc/init/cgmanager.conf ] && sudo status cgmanager | grep -q running && sudo stop cgmanager || true') if lxc?
         | 
| 31 | 
            -
                    setup_bridge unless @remote
         | 
| 34 | 
            +
                    setup_bridge if @bridge
         | 
| 32 35 | 
             
                    wait_ssh
         | 
| 33 36 | 
             
                    unmount unless running?
         | 
| 34 37 | 
             
                  end
         | 
| @@ -59,7 +62,7 @@ module Linecook | |
| 59 62 |  | 
| 60 63 | 
             
                  def info
         | 
| 61 64 | 
             
                    @info = {}
         | 
| 62 | 
            -
                    capture("lxc-info #{container_str}").lines.each do |line|
         | 
| 65 | 
            +
                    capture("lxc-info #{container_str} || true").lines.each do |line|
         | 
| 63 66 | 
             
                      k, v = line.strip.split(/:\s+/)
         | 
| 64 67 | 
             
                      key = k.downcase.to_sym
         | 
| 65 68 | 
             
                      @info[key] = @info[key] ? [@info[key]].flatten << v : v
         | 
| @@ -95,9 +98,8 @@ module Linecook | |
| 95 98 | 
             
                    @upper_base = tmpdir(label: 'upper')
         | 
| 96 99 | 
             
                    @upper_dir = File.join(@upper_base, '/upper')
         | 
| 97 100 | 
             
                    @work_dir = File.join(@upper_base, '/work')
         | 
| 98 | 
            -
                    @overlay = File.join(@home, @name, '/rootfs')
         | 
| 99 101 | 
             
                    @socket_dirs = []
         | 
| 100 | 
            -
                    (Linecook::Config.load_config[:socket_dirs] ||[]).each{ |sock| @socket_dirs << File.join(@ | 
| 102 | 
            +
                    (Linecook::Config.load_config[:socket_dirs] ||[]).each{ |sock| @socket_dirs << File.join(@root, sock) }
         | 
| 101 103 | 
             
                  end
         | 
| 102 104 |  | 
| 103 105 | 
             
                  def write_config
         | 
| @@ -115,14 +117,14 @@ module Linecook | |
| 115 117 |  | 
| 116 118 | 
             
                  def mount_all
         | 
| 117 119 | 
             
                    # Prepare an overlayfs
         | 
| 118 | 
            -
                    execute("mkdir -p #{@ | 
| 120 | 
            +
                    execute("mkdir -p #{@root}")
         | 
| 119 121 | 
             
                    execute("mkdir -p #{@lower_dir}")
         | 
| 120 122 | 
             
                    execute("mkdir -p #{@upper_base}")
         | 
| 121 123 | 
             
                    mount(@image_path, @lower_dir, options: '-o loop')
         | 
| 122 124 | 
             
                    mount('tmpfs', @upper_base, type: '-t tmpfs', options:'-o noatime') # FIXME: - don't always be tmpfs
         | 
| 123 125 | 
             
                    execute("mkdir -p #{@work_dir}")
         | 
| 124 126 | 
             
                    execute("mkdir -p #{@upper_dir}")
         | 
| 125 | 
            -
                    mount('overlay', @ | 
| 127 | 
            +
                    mount('overlay', @root, type: '-t overlay', options: "-o lowerdir=#{@lower_dir},upperdir=#{@upper_dir},workdir=#{@work_dir}")
         | 
| 126 128 | 
             
                    # Overlayfs doesn't support unix domain sockets
         | 
| 127 129 | 
             
                    @socket_dirs.each do |sock|
         | 
| 128 130 | 
             
                      execute("mkdir -p #{sock}")
         | 
| @@ -136,7 +138,7 @@ module Linecook | |
| 136 138 |  | 
| 137 139 | 
             
                  def unmount
         | 
| 138 140 | 
             
                    @socket_dirs.each { |sock| execute("umount #{sock}") }
         | 
| 139 | 
            -
                    execute("umount #{@ | 
| 141 | 
            +
                    execute("umount #{@root}")
         | 
| 140 142 | 
             
                    execute("umount #{@upper_base}")
         | 
| 141 143 | 
             
                    execute("umount #{@lower_dir}")
         | 
| 142 144 | 
             
                    execute("rmdir #{@lower_dir}")
         | 
| @@ -164,17 +166,17 @@ eos | |
| 164 166 | 
             
                    interfaces = Tempfile.new('interfaces')
         | 
| 165 167 | 
             
                    interfaces.write(bridge_config)
         | 
| 166 168 | 
             
                    interfaces.close
         | 
| 167 | 
            -
                    execute("mv #{interfaces.path} #{File.join(@ | 
| 169 | 
            +
                    execute("mv #{interfaces.path} #{File.join(@root, 'etc', 'network', 'interfaces')}")
         | 
| 168 170 |  | 
| 169 171 | 
             
                    execute("lxc-attach -n #{@name} -P #{@home} ifup lxcbr0")
         | 
| 170 172 | 
             
                  end
         | 
| 171 173 |  | 
| 172 174 |  | 
| 173 175 | 
             
                  def setup_image
         | 
| 174 | 
            -
                    @source_path = Linecook:: | 
| 176 | 
            +
                    @source_path = Linecook::ImageManager.fetch(@source_image)
         | 
| 175 177 | 
             
                    if @remote
         | 
| 176 178 | 
             
                      dest = "#{File.basename(@source_path)}"
         | 
| 177 | 
            -
                      @remote.upload(@source_path, dest) unless  | 
| 179 | 
            +
                      @remote.upload(@source_path, dest) unless test("[ -f #{dest} ]")
         | 
| 178 180 | 
             
                      @image_path = dest
         | 
| 179 181 | 
             
                    else
         | 
| 180 182 | 
             
                      @image_path = @source_path
         | 
| @@ -189,26 +191,6 @@ eos | |
| 189 191 | 
             
                    "-n #{@name} -P #{@home}"
         | 
| 190 192 | 
             
                  end
         | 
| 191 193 |  | 
| 192 | 
            -
                  def capture(command, sudo: true)
         | 
| 193 | 
            -
                    execute(command, sudo: sudo, capture: true)
         | 
| 194 | 
            -
                  end
         | 
| 195 | 
            -
             | 
| 196 | 
            -
                  def execute(command, sudo: true, capture: false)
         | 
| 197 | 
            -
                    command = "sudo #{command}" if sudo
         | 
| 198 | 
            -
                    if @remote
         | 
| 199 | 
            -
                      if capture
         | 
| 200 | 
            -
                        return @remote.capture(command)
         | 
| 201 | 
            -
                      else
         | 
| 202 | 
            -
                        @remote.run(command)
         | 
| 203 | 
            -
                      end
         | 
| 204 | 
            -
                    else
         | 
| 205 | 
            -
                      if capture
         | 
| 206 | 
            -
                        return `#{command}`
         | 
| 207 | 
            -
                      else
         | 
| 208 | 
            -
                        system(command)
         | 
| 209 | 
            -
                      end
         | 
| 210 | 
            -
                    end
         | 
| 211 | 
            -
                  end
         | 
| 212 194 | 
             
                end
         | 
| 213 195 |  | 
| 214 196 | 
             
                module Config
         | 
| @@ -222,7 +204,7 @@ eos | |
| 222 204 | 
             
                    network: {
         | 
| 223 205 | 
             
                      type: 'veth',
         | 
| 224 206 | 
             
                      flags: 'up',
         | 
| 225 | 
            -
                      link: ' | 
| 207 | 
            +
                      link: 'lxcbr0'
         | 
| 226 208 | 
             
                    },
         | 
| 227 209 | 
             
                    mount: {
         | 
| 228 210 | 
             
                      auto: 'cgroup'
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
            require 'sshkey'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'linecook/builder/lxc'
         | 
| 5 | 
            +
            require 'linecook/builder/darwin_backend'
         | 
| 6 | 
            +
            require 'linecook/builder/linux_backend'
         | 
| 7 | 
            +
            require 'linecook/builder/build'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module Linecook
         | 
| 10 | 
            +
              module Builder
         | 
| 11 | 
            +
                extend self
         | 
| 12 | 
            +
                extend Forwardable
         | 
| 13 | 
            +
                BUILD_HOME = '/u/lxc'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def_instance_delegators :backend, :stop, :ip, :info, :running?
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def backend
         | 
| 18 | 
            +
                  @backend ||= backend_for_platform
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def start
         | 
| 22 | 
            +
                  return if running?
         | 
| 23 | 
            +
                  backend.start
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def ssh
         | 
| 27 | 
            +
                  config = Linecook::Config.load_config[:builder]
         | 
| 28 | 
            +
                  @ssh ||= SSH.new(ip, username: config[:username], password: config[:password], keyfile: Linecook::SSH.private_key)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def builds
         | 
| 32 | 
            +
                  ssh.test("[ -d #{BUILD_HOME} ]") ? ssh.capture("find  #{BUILD_HOME} -maxdepth 1 -mindepth 1 -type d -printf \"%f\n\"").delete(';').lines : []
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def build_info
         | 
| 36 | 
            +
                  info = {}
         | 
| 37 | 
            +
                  builds.each do |build|
         | 
| 38 | 
            +
                    info[build] = Linecook::Build.new(build, nil).info
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                  info
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def backend_for_platform
         | 
| 46 | 
            +
                  case Config.platform
         | 
| 47 | 
            +
                  when 'linux'
         | 
| 48 | 
            +
                    LinuxBuilder.backend
         | 
| 49 | 
            +
                  when 'darwin'
         | 
| 50 | 
            +
                    OSXBuilder.backend
         | 
| 51 | 
            +
                  else
         | 
| 52 | 
            +
                    fail "Cannot find supported backend for #{Config.platform}"
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/lib/linecook/cli.rb
    CHANGED
    
    | @@ -1,6 +1,56 @@ | |
| 1 1 | 
             
            require 'thor'
         | 
| 2 2 | 
             
            require 'linecook'
         | 
| 3 3 |  | 
| 4 | 
            +
            class Crypto < Thor
         | 
| 5 | 
            +
              desc 'keygen', 'Generate AES key for securing images'
         | 
| 6 | 
            +
              def keygen
         | 
| 7 | 
            +
                puts Linecook::Crypto.keygen
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              desc 'decrypt IMAGE', ''
         | 
| 11 | 
            +
              def decrypt(image)
         | 
| 12 | 
            +
                puts Linecook::Crypto.new.decrypt_file(image)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              desc 'encrypt IMAGE', ''
         | 
| 16 | 
            +
              def encrypt(image)
         | 
| 17 | 
            +
                puts Linecook::Crypto.new.encrypt_file(image)
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            class Image < Thor
         | 
| 22 | 
            +
              desc 'crypto SUBCOMMAND', 'Manage image encryption'
         | 
| 23 | 
            +
              subcommand 'crypto', Crypto
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              desc 'list', 'List images' # add remote flag
         | 
| 26 | 
            +
              def list
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              desc 'fetch IMAGE_NAME', 'Fetch an image by name'
         | 
| 30 | 
            +
              method_options name: :string
         | 
| 31 | 
            +
              def fetch(name)
         | 
| 32 | 
            +
                Linecook::ImageManager.fetch(name)
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              desc 'upload IMAGE', 'Upload an image'
         | 
| 36 | 
            +
              method_options name: :string
         | 
| 37 | 
            +
              def upload(image)
         | 
| 38 | 
            +
                Linecook::ImageManager.upload(image)
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              desc 'url IMAGE', 'Get URL for image'
         | 
| 42 | 
            +
              method_options image: :string
         | 
| 43 | 
            +
              def url(image)
         | 
| 44 | 
            +
                puts Linecook::ImageManager.url(image)
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              desc 'package IMAGE', 'Package image'
         | 
| 48 | 
            +
              method_options image: :string
         | 
| 49 | 
            +
              def package(image)
         | 
| 50 | 
            +
                Linecook::Packager.package(image)
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
             | 
| 4 54 | 
             
            class Builder < Thor
         | 
| 5 55 | 
             
              desc 'info', 'Show builder info'
         | 
| 6 56 | 
             
              def info
         | 
| @@ -29,27 +79,42 @@ class Build < Thor | |
| 29 79 | 
             
                puts Linecook::Builder.builds
         | 
| 30 80 | 
             
              end
         | 
| 31 81 |  | 
| 32 | 
            -
              desc 'info', 'Show build info' | 
| 82 | 
            +
              desc 'info', 'Show build info'
         | 
| 33 83 | 
             
              def info
         | 
| 34 | 
            -
             | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              desc 'ip', 'Show IP address for build'
         | 
| 87 | 
            +
              def ip
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              desc 'snapshot', 'Take a snapshot of the build with NAME'
         | 
| 91 | 
            +
              method_option :name, type: :string, required: true, banner: 'ROLE_NAME', desc: 'Name of the role to build', aliases: '-n'
         | 
| 92 | 
            +
              def snapshot
         | 
| 93 | 
            +
                build = Linecook::Build.new(name, '')
         | 
| 94 | 
            +
                build.snapshot(save: true)
         | 
| 35 95 | 
             
              end
         | 
| 36 96 | 
             
            end
         | 
| 37 97 |  | 
| 38 98 | 
             
            class Linecook::CLI < Thor
         | 
| 39 | 
            -
             | 
| 99 | 
            +
             | 
| 100 | 
            +
              desc 'image SUBCOMMAND', 'Manage linecook images.'
         | 
| 101 | 
            +
              subcommand 'image', Image
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              desc 'builder SUBCOMMAND', 'Manage the builder.'
         | 
| 40 104 | 
             
              subcommand 'builder', Builder
         | 
| 41 105 |  | 
| 42 | 
            -
              desc 'build SUBCOMMAND', 'Manage builds'
         | 
| 106 | 
            +
              desc 'build SUBCOMMAND', 'Manage running and completed builds.'
         | 
| 43 107 | 
             
              subcommand 'build', Build
         | 
| 44 108 |  | 
| 45 | 
            -
              desc 'bake', 'Bake a new image'
         | 
| 109 | 
            +
              desc 'bake', 'Bake a new image.'
         | 
| 110 | 
            +
              method_option :name, type: :string, required: true, banner: 'ROLE_NAME', desc: 'Name of the role to build', aliases: '-n'
         | 
| 111 | 
            +
              method_option :image, type: :string,  banner: 'SOURCE_IMAGE', desc: 'Source image to seed the build.', aliases: '-i'
         | 
| 112 | 
            +
              method_option :build, type: :boolean, default: true, desc: 'Build the image', aliases: '-b'
         | 
| 113 | 
            +
              method_option :snapshot, type: :boolean, default: false, desc: 'Snapshot the resulting build to create an image', aliases: '-s'
         | 
| 114 | 
            +
              method_option :upload, type: :boolean, default: false, desc: 'Upload the resulting build. Implies --snapshot and --encrypt.', aliases: '-u'
         | 
| 115 | 
            +
              method_option :package, type: :boolean, default: false, desc: 'Package the resulting image. Implies --upload, --snapshot and --encrypt.', aliases: '-p'
         | 
| 46 116 | 
             
              def bake
         | 
| 47 | 
            -
                 | 
| 48 | 
            -
             | 
| 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)
         | 
| 117 | 
            +
                opts = options.symbolize_keys
         | 
| 118 | 
            +
                Linecook::Baker.bake(**opts)
         | 
| 54 119 | 
             
              end
         | 
| 55 120 | 
             
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require 'base64'
         | 
| 2 | 
            +
            require 'encryptor'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'linecook/image/manager'
         | 
| 5 | 
            +
            require 'linecook/util/executor'
         | 
| 6 | 
            +
            require 'linecook/util/config'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Linecook
         | 
| 9 | 
            +
              class Crypto
         | 
| 10 | 
            +
                include Executor
         | 
| 11 | 
            +
                CIPHER = 'aes-256-cbc'
         | 
| 12 | 
            +
                KEY_BYTES = 32 # 256 bits
         | 
| 13 | 
            +
                attr_reader :iv, :secret_key
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def initialize(remote: nil)
         | 
| 16 | 
            +
                  @remote = remote
         | 
| 17 | 
            +
                  load_key
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def encrypt_image(image)
         | 
| 21 | 
            +
                  image_path = File.join(Linecook::ImageManager::IMAGE_PATH,File.basename(image))
         | 
| 22 | 
            +
                  encrypt_file(image_path)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def encrypt_file(source, dest: nil, keypath: nil)
         | 
| 26 | 
            +
                  dest ||= "/tmp/#{File.basename(source)}"
         | 
| 27 | 
            +
                  capture("openssl enc -#{CIPHER} -out #{dest} -in #{source} -K #{@secret_key} -iv #{@iv}")
         | 
| 28 | 
            +
                  dest
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def decrypt_file(source, dest: nil, keypath: nil)
         | 
| 32 | 
            +
                  dest ||= "/tmp/#{File.basename(source)}-decrypted"
         | 
| 33 | 
            +
                  capture("openssl enc -#{CIPHER} -out #{dest} -in #{source} -K #{@secret_key} -iv #{@iv} -d")
         | 
| 34 | 
            +
                  dest
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def self.keygen
         | 
| 38 | 
            +
                  iv = OpenSSL::Cipher::Cipher.new(CIPHER).random_iv.unpack('H*').first
         | 
| 39 | 
            +
                  secret_key = Base64.encode64(OpenSSL::Random.random_bytes(KEY_BYTES)).unpack('H*').first
         | 
| 40 | 
            +
                  "[:IV:#{iv}:KY:#{secret_key}]"
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def load_key
         | 
| 46 | 
            +
                  @iv, @secret_key = Linecook::Config.secrets['aeskey'].match(/\[:IV:(.+):KY:(.+)\]/m).captures
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            require 'octokit'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'linecook/util/config'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Linecook
         | 
| 6 | 
            +
              module GithubManager
         | 
| 7 | 
            +
                extend self
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def url(name)
         | 
| 10 | 
            +
                  latest[:assets].find { |a| a[:name] =~ /#{name}/ }[:browser_download_url]
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def client
         | 
| 16 | 
            +
                  @client ||= Octokit::Client.new
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def source
         | 
| 20 | 
            +
                  @source ||= (Config.load_config['source_repo'] || 'dalehamel/lxb')
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def latest
         | 
| 24 | 
            +
                  client.releases(source).sort_by { |r| r[:published_at] }.last
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            require 'linecook/image/crypt'
         | 
| 2 | 
            +
            require 'linecook/image/github'
         | 
| 3 | 
            +
            require 'linecook/image/s3'
         | 
| 4 | 
            +
            require 'linecook/util/downloader'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Linecook
         | 
| 7 | 
            +
              module ImageManager
         | 
| 8 | 
            +
                IMAGE_PATH = File.join(Config::LINECOOK_HOME, 'images').freeze
         | 
| 9 | 
            +
                extend self
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def fetch(name, upgrade:false, profile: :private)
         | 
| 12 | 
            +
                  image_name = Linecook::Config.load_config[:image][:images][name][:name]
         | 
| 13 | 
            +
                  path = File.join(IMAGE_PATH, image_name)
         | 
| 14 | 
            +
                  url = provider(profile).url(name) unless File.exist?(path) || upgrade# FIXME
         | 
| 15 | 
            +
                  Linecook::Downloader.download(url, path) unless File.exist?(path) || upgrade
         | 
| 16 | 
            +
                  path
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def upload(image, profile: :private)
         | 
| 20 | 
            +
                  path = File.join(IMAGE_PATH, File.basename(image))
         | 
| 21 | 
            +
                  puts "Encrypting and uploading image #{path}"
         | 
| 22 | 
            +
                  provider(profile).upload(Linecook::Crypto.new.encrypt_file(path))
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def url(image, profile: :private)
         | 
| 26 | 
            +
                  provider(profile).url("builds/#{image}")
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def provider(image_profile)
         | 
| 32 | 
            +
                  profile = Linecook::Config.load_config[:image][:provider][image_profile]
         | 
| 33 | 
            +
                  case profile
         | 
| 34 | 
            +
                  when :s3
         | 
| 35 | 
            +
                    S3Manager
         | 
| 36 | 
            +
                  when :github
         | 
| 37 | 
            +
                    GithubManager
         | 
| 38 | 
            +
                  else
         | 
| 39 | 
            +
                    fail "No provider implemented for for #{profile}"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require 'ruby-progressbar'
         | 
| 2 | 
            +
            require 'aws-sdk'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Linecook
         | 
| 5 | 
            +
              module S3Manager
         | 
| 6 | 
            +
                extend self
         | 
| 7 | 
            +
                EXPIRY = 20
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def url(name)
         | 
| 10 | 
            +
                  client
         | 
| 11 | 
            +
                  s3 = Aws::S3::Resource.new
         | 
| 12 | 
            +
                  obj = s3.bucket(Linecook::Config.secrets['bucket']).object(name)
         | 
| 13 | 
            +
                  obj.presigned_url(:get, expires_in: EXPIRY * 60)
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def upload(path)
         | 
| 17 | 
            +
                  File.open(path, 'rb') do |file|
         | 
| 18 | 
            +
                    fname = File.basename(path)
         | 
| 19 | 
            +
                    pbar = ProgressBar.create(title: fname, total: file.size)
         | 
| 20 | 
            +
                    common_opts = { bucket: Linecook::Config.secrets['bucket'], key: File.join('builds', fname) }
         | 
| 21 | 
            +
                    resp = client.create_multipart_upload(storage_class: 'REDUCED_REDUNDANCY', server_side_encryption: 'AES256', **common_opts)
         | 
| 22 | 
            +
                    id = resp.upload_id
         | 
| 23 | 
            +
                    part = 0
         | 
| 24 | 
            +
                    total = 0
         | 
| 25 | 
            +
                    parts = []
         | 
| 26 | 
            +
                    while content = file.read(1048576 * 20)
         | 
| 27 | 
            +
                      part += 1
         | 
| 28 | 
            +
                      resp = client.upload_part(body: content, content_length: content.length, part_number: part, upload_id: id, **common_opts)
         | 
| 29 | 
            +
                      parts << { etag: resp.etag, part_number: part }
         | 
| 30 | 
            +
                      total += content.length
         | 
| 31 | 
            +
                      pbar.progress = total
         | 
| 32 | 
            +
                      pbar.title = "#{fname} - (#{((total.to_f/file.size.to_f)*100.0).round(2)}%)"
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                    client.complete_multipart_upload(upload_id: id, multipart_upload: { parts: parts }, **common_opts)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              private
         | 
| 39 | 
            +
                def client
         | 
| 40 | 
            +
                  @client ||= begin
         | 
| 41 | 
            +
                    Aws.config[:credentials] = Aws::Credentials.new(Linecook::Config.secrets['aws_access_key'], Linecook::Config.secrets['aws_secret_key'])
         | 
| 42 | 
            +
                    Aws.config[:region] = 'us-east-1'
         | 
| 43 | 
            +
                    Aws::S3::Client.new
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         |