module Cfer::Provisioning

Constants

DEFAULT_HUP_INTERVAL_IN_MINUTES
VERSION

Public Instance Methods

build_write_json_cmd(chef_solo_json_path) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 66
  def build_write_json_cmd(chef_solo_json_path)
    python_json_dump = [
      'import sys; import json;',
      'print json.dumps(json.loads(sys.stdin.read())',
      '.get("CferExt::Provisioning::Chef", {}), sort_keys=True, indent=2)'
    ].join('')

    cmd = <<-BASH.strip_heredoc
      mkdir -p '#{File.dirname(chef_solo_json_path)}' &&
        cfn-get-metadata --region 'C{AWS.region}' \
                         -s 'C{AWS.stack_name}' \
                         -r #{@name} |
        python -c '#{python_json_dump}' > #{chef_solo_json_path}
    BASH

    Cfer.cfize(cmd)
  end
cfn_auth(name, options = {}) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 10
def cfn_auth(name, options = {})
  cfn_metadata['AWS::CloudFormation::Authentication'] ||= {}
  cfn_metadata['AWS::CloudFormation::Authentication'][name] = options
end
cfn_init_config(name, options = {}, &block) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 36
def cfn_init_config(name, options = {}, &block)
  cfg = ConfigSet.new(cloudformation_init[name])
  Docile.dsl_eval(cfg, &block)
  cloudformation_init[name] = cfg.to_h
end
cfn_init_config_set(name, sections) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 28
def cfn_init_config_set(name, sections)
  cfg_sets = cloudformation_init['configSets'] || { 'default' => [] }
  cfg_set = Set.new(cfg_sets[name] || [])
  cfg_set.merge sections
  cfg_sets[name] = cfg_set.to_a
  cloudformation_init['configSets'] = cfg_sets
end
cfn_init_setup(options = {}) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 15
def cfn_init_setup(options = {})
  cfn_metadata['AWS::CloudFormation::Init'] = {}
  cfn_init_set_cloud_init(options)

  if options[:cfn_hup_config_set]
    cfn_hup(options)
  end
end
cfn_metadata() click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 6
def cfn_metadata
  self[:Metadata] ||= {}
end
chef_client(options = {}) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 84
def chef_client(options = {})
  raise "Chef already configured on this resource" if @chef
  @chef = true

  raise "must specify chef_server_url" if options[:chef_server_url].nil?
  raise "must specify validation_client_name" if options[:validation_client_name].nil?

  options[:config_path] ||= '/etc/chef/client.rb'
  options[:json_path] ||= '/etc/chef/node.json'
  options[:cookbook_path] ||= '/var/chef/cookbooks'
  options[:data_bag_path] ||= '/var/chef/data_bags'
  options[:log_path] ||= '/var/log/chef-client.log'

  options[:service_type] ||= :upstart

  run_set = []

  install_chef_with_cloud_init(options) unless options[:no_install]

  add_write_chef_json(options)
  run_set << :write_chef_json

  cfn_init_config :run_chef_client do
    client_rb = Erubis::Eruby.new(IO.read("#{__dir__}/client.rb.erb")).result(options: options)

    file options[:config_path], content: Cfer.cfize(options[:client_rb] || client_rb),
      mode: '000400', owner: 'root', group: 'root'

    command :'00_run_chef_once', 'chef-client --once'
  end
  run_set << :run_chef_client

  cfn_init_config_set :run_chef_client, run_set
end
chef_solo(options = {}) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 119
  def chef_solo(options = {})
    raise "Chef already configured on this resource" if @chef
    @chef = true

    must_install_berkshelf = !options[:berksfile].nil? || options[:force_berkshelf_install]

    options[:config_path] ||= '/etc/chef/solo.rb'
    options[:json_path] ||= '/etc/chef/node.json'
    options[:cookbook_path] ||= '/var/chef/cookbooks'
    options[:data_bag_path] ||= '/var/chef/data_bags'
    options[:log_path] ||= '/var/log/chef-solo.log'

    install_chef_with_cloud_init(options) unless options[:no_install]

    if must_install_berkshelf
      install_berkshelf(options) if must_install_berkshelf # places cloud-init runners
    end

    run_set = []

    unless options[:berksfile].nil?
      emit_berksfile(options)
      run_set << :emit_berksfile
    end

    unless options[:berksfile].nil? || options[:no_run_berkshelf]
      run_berkshelf(options)
      run_set << :run_berkshelf
    end

    add_write_chef_json(options)
    run_set << :write_chef_json

    cfn_init_config :run_chef_solo do
      solo_rb = <<-RB.strip_heredoc
        cookbook_path '#{options[:cookbook_path]}'
        log_location '#{options[:log_path]}'

        json_attribs '#{options[:json_path]}'
      RB

      file options[:config_path], content: options[:solo_rb] || solo_rb,
        mode: '000400', owner: 'root', group: 'root'

      command :run_chef, 'chef-solo'
    end
    run_set << :run_chef_solo

    cfn_init_config_set :run_chef_solo, run_set
  end
cloud_init() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 12
def cloud_init
  unless self.key?(:CloudInit)
    self[:CloudInit] = {
      bootcmd: [],
      runcmd: [],
      packages: [],
      ssh_authorized_keys: [],
      write_files: [
        {
          path: '/etc/cfn-resource-name',
          permissions: '0444',
          content: @name.to_s
        },
        {
          path: '/etc/cfn-stack-name',
          permissions: '0444',
          content: 'C{AWS.stack_name}'
        },
        {
          path: '/etc/cfn-region',
          permissions: '0444',
          content: 'C{AWS.region}'
        }
      ],
      output: {}
    }
  end

  self[:CloudInit]
end
cloud_init_bootcmds() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 43
def cloud_init_bootcmds
  cloud_init[:bootcmd]
end
cloud_init_finalize!() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 67
def cloud_init_finalize!
  cloud_init_outputs[:all] ||= "| tee -a /var/log/cloud-init-output.log"

  user_data Fn.base64( cloud_init_to_user_data(self[:CloudInit]) )
  self.delete :CloudInit
end
cloud_init_outputs() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 51
def cloud_init_outputs
  cloud_init[:output]
end
cloud_init_packages() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 55
def cloud_init_packages
  cloud_init[:packages]
end
cloud_init_runcmds() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 47
def cloud_init_runcmds
  cloud_init[:runcmd]
end
cloud_init_ssh_authorized_keys() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 63
def cloud_init_ssh_authorized_keys
  cloud_init[:ssh_authorized_keys]
end
cloud_init_write_files() click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 59
def cloud_init_write_files
  cloud_init[:write_files]
end
config_set(name) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 24
def config_set(name)
  { "ConfigSet" => name }
end
emit_berksfile(options) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 53
def emit_berksfile(options)
  cfn_init_config :emit_berksfile do
    file '/var/chef/Berksfile', content: Cfer.cfize(options[:berksfile].strip_heredoc),
      mode: '000500', owner: 'root', group: 'root'
  end
end
install_berkshelf(options) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 15
  def install_berkshelf(options)
    cloud_init_packages << 'git'

    cloud_init_bootcmds << '/opt/chef/embedded/bin/gem install berkshelf --no-ri --no-rdoc'

    berks_content = <<-EOF.strip_heredoc
        # may be run before HOME is established (fixes RbReadLine bug)
        export HOME=/root
        export BERKSHELF_PATH=/var/chef/berkshelf

        # Some cookbooks have UTF-8, and cfn-init uses US-ASCII because of reasons
        export LANG=en_US.UTF-8
        export RUBYOPTS="-E utf-8"

        set -e
        [ -f /opt/chef/embedded/bin/berks ] || /opt/chef/embedded/bin/gem install berkshelf -v 4.3.5
        set +e

        # Berkshelf seems a bit unreliable, so retry these commands a couple times.
        if [ -e Berksfile.lock ]
        then
          for i in {1..3}; do
            /opt/chef/embedded/bin/berks update && break || sleep 15
          done
        fi
        for i in {1..3}; do
          /opt/chef/embedded/bin/berks vendor '#{options[:cookbook_path]}' \
            -b /var/chef/Berksfile && break || sleep 15
        done
      EOF

    cloud_init_write_files << {
      path: '/var/chef/berkshelf.sh',
      content: berks_content,
      permissions: '0500'
    }
  end
install_chef_with_cloud_init(options = {}) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 2
def install_chef_with_cloud_init(options = {})
  # we can't use the cloud-init `chef` module because it expects a server/validator.

  cloud_init_bootcmds <<
    "command -v chef-client || " \
      "curl https://www.opscode.com/chef/install.sh | " \
        "bash -s -- -v #{options[:version] || 'latest'}"
  cloud_init_bootcmds << "mkdir -p /etc/chef/ohai/hints"
  cloud_init_bootcmds << "touch /etc/chef/ohai/hints/ec2.json"
  cloud_init_bootcmds << "mkdir -p '#{options[:cookbook_path]}'"
  cloud_init_bootcmds << "mkdir -p '#{options[:data_bag_path]}'"
end
run_berkshelf(options) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 60
def run_berkshelf(options)
  cfn_init_config :run_berkshelf do
    command :run_berkshelf, '/var/chef/berkshelf.sh', cwd: '/var/chef'
  end
end

Private Instance Methods

add_write_chef_json(options) click to toggle source
# File lib/cfer/provisioning/chef.rb, line 171
def add_write_chef_json(options)
  options[:run_list] ||= []
  options[:json] ||= {}

  cfn_metadata['CferExt::Provisioning::Chef'] = options[:json].merge(run_list: options[:run_list])
  cfn_init_config :write_chef_json do
    command :write_chef_json, build_write_json_cmd(options[:json_path])
  end
end
cfn_hup(options) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 84
  def cfn_hup(options)
    resource_name = @name
    target_config_set = options[:cfn_hup_config_set] ||
      raise('Please specify a `cfn_hup_config_set`')

    cfn_init_config_set :cfn_hup, [ :cfn_hup ]

    cfn_init_config(:cfn_hup) do
      if options[:access_key] && options[:secret_key]
        key_content = <<-FILE.strip_heredoc
          AWSAccessKeyId=#{options[:access_key]}
          AWSSecretKey=#{options[:secret_key]}
        FILE

        file '/etc/cfn/cfn-credentials', content: key_content,
          mode: '000400', owner: 'root', group: 'root'
      end

      hup_conf_content = <<-FILE.strip_heredoc
        [main]
        stack=C{AWS.stack_name}
        region=C{AWS.region}
        interval=#{options[:hup_interval] || DEFAULT_HUP_INTERVAL_IN_MINUTES}
      FILE
      file '/etc/cfn/cfn-hup.conf', content: Cfer.cfize(hup_conf_content),
        mode: '000400', owner: 'root', group: 'root'

      cfn_init_reload_content = <<-FILE.strip_heredoc
        [cfn-auto-reloader-hook]
        triggers=post.update
        path=Resources.#{resource_name}.Metadata
        runas=root
        action=/usr/local/bin/cfn-init -r #{resource_name} --region C{AWS.region} -s C{AWS.stack_name} -c '#{target_config_set.join(",")}'
      FILE

      file '/etc/cfn/hooks.d/cfn-init-reload.conf',
        content: Cfer.cfize(cfn_init_reload_content),
        mode: '000400', owner: 'root', group: 'root'
    end
  end
cfn_init_set_cloud_init(options) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 125
def cfn_init_set_cloud_init(options)
  cloud_init_bootcmds << "mkdir -p /usr/local/bin"

  case options[:flavor]
  when :redhat, :centos, :amazon
    cloud_init_bootcmds <<
      'rpm -Uvh https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.amzn1.noarch.rpm'
  when :debian, :ubuntu, nil
    [
      'apt-get update --fix-missing',
      'apt-get install -y python-pip',
      'pip install setuptools',
      'easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz'
    ].each { |line| cloud_init_bootcmds << line }
  end

  cfn_script_eruby = Erubis::Eruby.new(IO.read("#{__dir__}/cfn-bootstrap.bash.erb"))

  cloud_init_write_files << {
    path: '/usr/local/bin/cfn-bootstrap.bash',
    content: cfn_script_eruby.evaluate(resource_name: @name, options: options)
  }

  cloud_init_runcmds << "bash /usr/local/bin/cfn-bootstrap.bash"
end
cloud_init_to_user_data(cloud_init) click to toggle source
# File lib/cfer/provisioning/cloud-init.rb, line 75
def cloud_init_to_user_data(cloud_init)

  Cfer.cfize([
    '#cloud-config',
    YAML.dump(cloud_init.to_hash_recursive.deep_stringify_keys).gsub(/^---$/, "")
  ].join("\n"))
end
cloudformation_init(options = {}) click to toggle source
# File lib/cfer/provisioning/cfn-bootstrap.rb, line 79
def cloudformation_init(options = {})
  cfn_metadata['AWS::CloudFormation::Init'] ||= {}
end