class Apcera::Stager

Constants

PKG_NAME
UPDATED_PKG_NAME

Attributes

app_path[RW]
pkg_path[RW]
root_path[RW]
stager_url[RW]
system_options[RW]
updated_pkg_path[RW]

Public Class Methods

new(options = {}) click to toggle source
# File lib/apcera/stager/stager.rb, line 8
def initialize(options = {})
  # Require stager url. Needed to talk to the Staging Coordinator.
  @stager_url = options[:stager_url] || ENV["STAGER_URL"]
  raise Apcera::Error::StagerURLRequired.new("stager_url required") unless @stager_url

  # Setup the environment, some test items here.
  setup_environment
end

Public Instance Methods

app_path=(value) click to toggle source

Set @app_path, the location that will uploaded. Also updates location where execute_app will run commands from.

# File lib/apcera/stager/stager.rb, line 322
def app_path=(value)
  @app_path=value
  @run_path=value
end
complete() click to toggle source

Finish staging, compress your app dir and send to the staging coordinator. Then tell the staging coordinator we are done.

# File lib/apcera/stager/stager.rb, line 270
def complete
  upload
  done
end
dependencies_add(type, name) click to toggle source

Add dependencies to package.

# File lib/apcera/stager/stager.rb, line 184
def dependencies_add(type, name)
  exists = self.meta["dependencies"].detect { |dep| dep["type"] == type && dep["name"] == name }
  return false if exists

  response = RestClient.put(stager_meta_url, {
    :resource => "dependencies",
    :action => "add",
    :type => type,
    :name => name
  })

  true
rescue => e
  fail e
end
dependencies_remove(type, name) click to toggle source

Delete dependencies from package.

# File lib/apcera/stager/stager.rb, line 201
def dependencies_remove(type, name)
  exists = self.meta["dependencies"].detect { |dep| dep["type"] == type && dep["name"] == name}
  return false if !exists

  response = RestClient.put(stager_meta_url, {
    :resource => "dependencies",
    :action => "remove",
    :type => type,
    :name => name
  })

  true
rescue => e
  fail e
end
done() click to toggle source

Tell the staging coordinator you are done.

# File lib/apcera/stager/stager.rb, line 253
def done
  response = RestClient.post(@stager_url+"/done", {})
  exit0r 0
rescue => e
  fail e
end
download() click to toggle source

Download a package from the staging coordinator. We use Net::HTTP here because it supports streaming downloads.

# File lib/apcera/stager/stager.rb, line 35
def download
  uri = URI(@stager_url + "/data")

  Net::HTTP.start(uri.host.to_s, uri.port.to_s) do |http|
    request = Net::HTTP::Get.new uri.request_uri

    http.request request do |response|
      if response.code.to_i == 200
        open @pkg_path, 'wb' do |io|
          response.read_body do |chunk|
            io.write chunk
          end
        end
      else
        raise Apcera::Error::DownloadError.new("package download failed.\n")
      end
    end
  end
rescue => e
  fail e
end
environment_add(key, value) click to toggle source

Add environment variable to package.

# File lib/apcera/stager/stager.rb, line 137
def environment_add(key, value)
  response = RestClient.put(stager_meta_url, {
    :resource => "environment",
    :action => "add",
    :key => key,
    :value => value
  })
rescue => e
  fail e
end
environment_remove(key) click to toggle source

Delete environment variable from package.

# File lib/apcera/stager/stager.rb, line 149
def environment_remove(key)
  response = RestClient.put(stager_meta_url, {
    :resource => "environment",
    :action => "remove",
    :key => key
  })
rescue => e
  fail e
end
execute(*cmd) click to toggle source

Execute a command in the shell. We don’t want real commands in tests.

# File lib/apcera/stager/stager.rb, line 59
def execute(*cmd)
  Bundler.with_clean_env do
    result = system(*cmd, @system_options)
    if !result
      raise Apcera::Error::ExecuteError.new("failed to execute: #{cmd.join(' ')}.\n")
    end

    result
  end
rescue => e
  fail e
end
execute_app(*cmd) click to toggle source

Execute a command in the directory your package was extracted to (or where you manually set @app_dir). Useful helper.

# File lib/apcera/stager/stager.rb, line 74
def execute_app(*cmd)
  raise_app_path_error if @run_path == nil
  Bundler.with_clean_env do
    Dir.chdir(@run_path) do |run_path|
      result = system(*cmd, @system_options)
      if !result
        raise Apcera::Error::ExecuteError.new("failed to execute: #{cmd.join(' ')}.\n")
      end

      result
    end
  end
rescue => e
  fail e
end
exit0r(code) click to toggle source

Exit, needed for tests to not quit.

# File lib/apcera/stager/stager.rb, line 306
def exit0r(code)
  exit code
end
extract(location="") click to toggle source

Extract the package to a location within staging path, optionally creating the named folder first and extract into it. If a location parameter is given, when upload is run the folder will be uploaded up along with the files. In either case, execute_app will run commands in the location where files were extracted to.

# File lib/apcera/stager/stager.rb, line 95
def extract(location="")
  @app_path=File.join(@root_path, "staging")
  Dir.mkdir(@app_path) unless Dir.exists?(@app_path)

  @run_path = location.empty? ? @app_path : File.join(@app_path, location)
  Dir.mkdir(@run_path) unless Dir.exists?(@run_path)

  execute_app("tar -zxf #{@pkg_path}")
rescue => e
  fail e
end
fail(error = nil) click to toggle source

Fail the stager, something went wrong.

# File lib/apcera/stager/stager.rb, line 296
def fail(error = nil)
  output_error "Error: #{error.message}.\n" if error
  RestClient.post(@stager_url+"/failed", {})
rescue => e
  output_error "Error: #{e.message}.\n"
ensure
  exit0r 1
end
meta() click to toggle source

Get metadata for the package being staged.

# File lib/apcera/stager/stager.rb, line 244
def meta
  response = RestClient.get(stager_meta_url)
  return JSON.parse(response.to_s)
rescue => e
  output_error "Error: #{e.message}.\n"
  raise e
end
output(text) click to toggle source

Output to stdout

# File lib/apcera/stager/stager.rb, line 316
def output(text)
  $stdout.puts text
end
output_error(text) click to toggle source

Output to stderr.

# File lib/apcera/stager/stager.rb, line 311
def output_error(text)
  $stderr.puts text
end
provides_add(type, name) click to toggle source

Add provides to package.

# File lib/apcera/stager/stager.rb, line 160
def provides_add(type, name)
  response = RestClient.put(stager_meta_url, {
    :resource => "provides",
    :action => "add",
    :type => type,
    :name => name
  })
rescue => e
  fail e
end
provides_remove(type, name) click to toggle source

Delete provides from package.

# File lib/apcera/stager/stager.rb, line 172
def provides_remove(type, name)
  response = RestClient.put(stager_meta_url, {
    :resource => "provides",
    :action => "remove",
    :type => type,
    :name => name
  })
rescue => e
  fail e
end
relaunch() click to toggle source

Tell the staging coordinator you need to relaunch.

# File lib/apcera/stager/stager.rb, line 261
def relaunch
  response = RestClient.post(@stager_url+"/relaunch", {})
  exit0r 0
rescue => e
  fail e
end
setup_chroot() click to toggle source

Setup /stagerfs chroot environment so it is ready to run commands from pulled in dependencies. This does the following:

  • Setup working resolv.conf

  • Bind mounts /proc to /stagerfs/proc

  • Recursively bind mounts /dev to /stagerfs/dev

# File lib/apcera/stager/stager.rb, line 22
def setup_chroot
  execute("sudo mkdir -p /stagerfs/etc")
  execute("sudo cp /etc/resolv.conf /stagerfs/etc/resolv.conf")

  execute("sudo mkdir -p /stagerfs/proc")
  execute("sudo mount --bind /proc /stagerfs/proc")

  execute("sudo mkdir -p /stagerfs/dev")
  execute("sudo mount --rbind /dev /stagerfs/dev")
end
snapshot() click to toggle source

Snapshot the stager filesystem for app.

# File lib/apcera/stager/stager.rb, line 130
def snapshot
  response = RestClient.post(@stager_url+"/snapshot", {})
rescue => e
  fail e
end
start_command() click to toggle source

Returns the start command for the package.

# File lib/apcera/stager/stager.rb, line 276
def start_command
  self.meta["environment"]["START_COMMAND"]
end
start_command=(val) click to toggle source

Easily set the start command.

# File lib/apcera/stager/stager.rb, line 281
def start_command=(val)
  self.environment_add("START_COMMAND", val)
end
start_path() click to toggle source

Returns the start path for the package.

# File lib/apcera/stager/stager.rb, line 286
def start_path
  self.meta["environment"]["START_PATH"]
end
start_path=(val) click to toggle source

Easily set the start path.

# File lib/apcera/stager/stager.rb, line 291
def start_path=(val)
  self.environment_add("START_PATH", val)
end
templates_add(path, left_delimiter = "{{", right_delimiter = "}}") click to toggle source

Add template to package.

# File lib/apcera/stager/stager.rb, line 218
def templates_add(path, left_delimiter = "{{", right_delimiter = "}}")
  response = RestClient.put(stager_meta_url, {
    :resource => "templates",
    :action => "add",
    :path => path,
    :left_delimiter => left_delimiter,
    :right_delimiter => right_delimiter
  })
rescue => e
  fail e
end
templates_remove(path, left_delimiter = "{{", right_delimiter = "}}") click to toggle source

Delete template from package.

# File lib/apcera/stager/stager.rb, line 231
def templates_remove(path, left_delimiter = "{{", right_delimiter = "}}")
  response = RestClient.put(stager_meta_url, {
    :resource => "templates",
    :action => "remove",
    :path => path,
    :left_delimiter => left_delimiter,
    :right_delimiter => right_delimiter
  })
rescue => e
  fail e
end
upload() click to toggle source

Upload the new package to the staging coordinator. If we have an app extracted we send that to the staging coordinator. If no app was ever extracted it is a noop.

# File lib/apcera/stager/stager.rb, line 110
def upload
  if @app_path == nil
    unless File.exist?(@pkg_path)
      download
    end

    upload_file(@pkg_path)
  else
    # Use execute instead of execute_app so that if the user provided a dir
    # to extract into it results in the uploaded package being wrapped in
    # that directory.
    execute("cd #{@app_path} && tar czf #{@updated_pkg_path} ./*")

    upload_file(@updated_pkg_path)
  end
rescue => e
  fail e
end

Private Instance Methods

raise_app_path_error() click to toggle source
# File lib/apcera/stager/stager.rb, line 329
def raise_app_path_error
  raise Apcera::Error::AppPathError.new("app path not set, please run extract!\n")
end
setup_environment() click to toggle source
# File lib/apcera/stager/stager.rb, line 333
def setup_environment
  # When staging we use the root path. These are overridden in tests.
  @root_path = "/tmp"
  @pkg_path = File.join(@root_path, PKG_NAME)
  @updated_pkg_path = File.join(@root_path, UPDATED_PKG_NAME)
  @system_options = {}
end
stager_meta_url() click to toggle source
# File lib/apcera/stager/stager.rb, line 341
def stager_meta_url
  @stager_url + "/meta"
end
upload_file(file) click to toggle source
# File lib/apcera/stager/stager.rb, line 345
def upload_file(file)
  sha256 = Digest::SHA256.file(file)
  File.open(file, "rb") do |f|
    response = RestClient.post(@stager_url+"/data?sha256=#{sha256.to_s}", f, { :content_type => "application/octet-stream" } )
  end
end