Skip to content

Instantly share code, notes, and snippets.

@coderanger
Last active January 20, 2016 20:59
Show Gist options
  • Save coderanger/d5d762e99bba9c691099 to your computer and use it in GitHub Desktop.
Save coderanger/d5d762e99bba9c691099 to your computer and use it in GitHub Desktop.
WIP knife travis plugin.
require 'chef/knife'
module Poise
class Travis < Chef::Knife
include Chef::Mixin::ShellOut
deps do
require 'yaml'
require 'kitchen'
end
banner "knife travis"
option :ca_path,
long: '--ca VALUE',
description: 'Path to the TLS CA for Docker.',
default: File.expand_path('~/src/poise-docker/tls') # FIX THIS FOR PUBLIC?
option :docker,
long: '--docker HOSTNAME',
description: 'Hostname of the Docker server.',
default: 'docker.poise.io' # FIX THIS FOR PUBLIC?
option :halite,
long: '--halite',
description: 'Enable Halite mode',
boolean: true,
default: false
def run
if config[:halite]
raise 'Not in a gem folder' if Dir['*.gemspec'].empty?
else
raise 'Not in a cookbook folder' unless File.exists?('metadata.rb')
end
append_gitignore
FileUtils.mkdir_p('test/docker')
generate_key
encrypt_key
write_gemfiles if config[:halite]
existing_secure = load_existing_secure
write_travis_yml(existing_secure)
travis_encrypt(existing_secure)
git_misc
write_kitchen_travis
end
private
def read_kitchen_yml
Kitchen::Loader::YAML.new(
project_config: ENV['KITCHEN_YAML'],
local_config: ENV['KITCHEN_LOCAL_YAML'],
global_config: ENV['KITCHEN_GLOBAL_YAML'],
).read
end
def append_gitignore
# Make sure we never commit this stuff accidentally
IO.write('.gitignore', "test/docker/\n", mode: 'a') unless !File.exists?('.gitignore') || IO.read('.gitignore').include?('test/docker/')
IO.write('.gitignore', ".kitchen.local.yml\n", mode: 'a') unless !File.exists?('.gitignore') || IO.read('.gitignore').include?('.kitchen.local.yml')
end
def generate_key
return if File.exists?('test/docker/docker.crt')
ui.info('Generating new TLS key and certificate')
shell_out!("openssl req -new -newkey rsa:4096 -keyout test/docker/docker.key -out test/docker/docker.csr -nodes -subj '/CN=Docker client for #{File.basename(Dir.pwd)}/OU=kitchen-docker/O=Coderanger Consulting LLC'")
shell_out!("openssl x509 -req -days 3650 -CA #{File.join(config[:ca_path], 'ca.crt')} -CAkey #{File.join(config[:ca_path], 'ca.key')} -CAserial #{File.join(config[:ca_path], 'ca.srl')} -extfile #{File.join(config[:ca_path], 'extfile.cnf')} -in test/docker/docker.csr -out test/docker/docker.crt")
File.unlink('test/docker/docker.csr')
IO.write('test/docker/docker.ca', IO.read(File.join(config[:ca_path], 'ca.crt')))
end
def encrypt_key
return if File.exists?('test/docker/docker.pem')
ui.info('Creating encrypted TLS key')
password = SecureRandom.base64(30)
IO.write('test/docker/docker.pass', password)
shell_out!("openssl rsa -in test/docker/docker.key -out test/docker/docker-enc.key -aes256 -passout file:test/docker/docker.pass")
IO.write('test/docker/docker.pem', IO.read('test/docker/docker.crt')+IO.read('test/docker/docker-enc.key'))
File.delete('test/docker/docker-enc.key')
end
def load_existing_secure
return [] unless File.exists?('.travis.yml')
secure = []
travis = YAML.load(IO.read('.travis.yml'))
if travis['env'] && travis['env']['global']
travis['env']['global'].each do |env_var|
if env_var.is_a?(Hash) && env_var.first.first == 'secure'
secure << env_var
end
end
end
secure
end
def chef_versions
@versions ||= if File.exists?('.kitchen.yml')
kitchen = read_kitchen_yml
versions = kitchen[:chef_versions] || []
ui.warn("Travis only allows 5 concurrent builds, you have #{versions.length}. This may cause long builds.") if versions.length > 5
versions
else
[]
end
end
def write_gemfiles
FileUtils.mkdir_p('test/gemfiles')
chef_versions.each do |ver|
gemfile_path = "test/gemfiles/chef-#{ver}.gemfile"
IO.write(gemfile_path, "instance_eval(IO.read(File.expand_path('../../../Gemfile', __FILE__)))\ngem 'chef', '~> #{ver}.0'\n") unless File.exists?(gemfile_path)
end
end
def wrap_if(condition, &block)
block.call.map do |line|
"if [ #{condition} ]; then #{line}; fi"
end
end
def write_travis_yml(existing_secure)
travis = {
'sudo' => false,
'cache' => 'bundler',
'language' => 'ruby',
'rvm' => %w{2.2},
'addons' => {
'apt' => {
'packages' => %w{libgecode-dev},
},
},
'env' => {
'global' => [
'USE_SYSTEM_GECODE=true',
'KITCHEN_LOCAL_YAML=.kitchen.travis.yml',
],
},
'bundler_args' => '--binstubs=$PWD/bin --jobs 3 --retry 3',
'script' => [],
}
if config[:halite]
# Halite mode
travis['gemfile'] = chef_versions.map {|v| "test/gemfiles/chef-#{v}.gemfile" } + %w{test/gemfiles/master.gemfile} unless chef_versions.empty?
travis['script'] = ['./bin/rake travis']
else
travis['env']['matrix'] = chef_versions.map {|v| "CHEF_VERSION=#{v}" } unless chef_versions.empty?
travis['script'] = ['./bin/foodcritic -f any .']
end
# Do we have test-kitchen?
if File.exists?('.kitchen.yml') && !config[:halite]
travis['script'] += wrap_if('"$TRAVIS_SECURE_ENV_VARS" = true') do
[
'openssl rsa -in test/docker/docker.pem -passin env:KITCHEN_DOCKER_PASS -out test/docker/docker.key',
'wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O docker',
'chmod +x docker',
'./bin/kitchen test -d always',
]
end
end
travis['env']['global'] += existing_secure if existing_secure
ui.info('Updating .travis.yml')
IO.write('.travis.yml', travis.to_yaml)
end
def travis_encrypt(existing_secure)
# Add the password to travis.yml
return unless existing_secure.empty?
ui.info('Running travis encrypt for key passphrase')
cmd = shell_out("travis encrypt KITCHEN_DOCKER_PASS=#{IO.read('test/docker/docker.pass')} -a --skip-version-check")
ui.warn('Could not encrypt passphrase for Travis, skipping') if cmd.error?
end
def git_misc
# Git add some bits
shell_out!('git add -f test/docker/docker.ca test/docker/docker.pem')
end
def write_kitchen_travis
kitchen = {
'driver' => {
'name' => 'docker',
'binary' => './docker',
'socket' => "tcp://#{config[:docker]}:443",
'tls_verify' => true,
'tls_cacert' => 'test/docker/docker.ca',
'tls_cert' => 'test/docker/docker.pem',
'tls_key' => 'test/docker/docker.key',
}
}
ui.info('Updating .kitchen.travis.yml')
IO.write('.kitchen.travis.yml', kitchen.to_yaml)
# Locally we assume it is installed system wide
kitchen['driver'].delete('binary')
ui.info('Updating .kitchen.local.yml')
IO.write('.kitchen.local.yml', kitchen.to_yaml)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment