This repository has been archived by the owner on Apr 6, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from shantytown/docker-plugin
Create docker plugin
- Loading branch information
Showing
36 changed files
with
1,636 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.gem | ||
coverage | ||
Gemfile.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
require: rubocop-rspec | ||
|
||
LineLength: | ||
Max: 120 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
sudo: false | ||
language: ruby | ||
rvm: | ||
- 2.2.3 | ||
|
||
# We have to install Rust manually, as Travis does not yet allow multiligual | ||
# builds. We install the latest stable Rust build. | ||
before_install: | ||
- mkdir ~/rust-installer | ||
- curl -sL https://static.rust-lang.org/rustup.sh -o ~/rust-installer/rustup.sh | ||
- sh ~/rust-installer/rustup.sh --prefix=~/rust --spec=stable -y --disable-sudo 2> /dev/null | ||
- export PATH=~/rust/bin:$PATH | ||
- export LD_LIBRARY_PATH=~/rust/lib:$LD_LIBRARY_PATH | ||
- rustc --version | ||
- cargo --version | ||
|
||
script: bundle exec shanty test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source 'https://rubygems.org' | ||
|
||
gemspec | ||
gem 'shanty', git: 'https://github.com/shantytown/shanty' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
require 'shanty/plugins/bundler_plugin' | ||
require 'shanty/plugins/rspec_plugin' | ||
require 'shanty/plugins/rubocop_plugin' | ||
require 'shanty/plugins/rubygem_plugin' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
require 'shanty_docker_plugin/docker_credentials' | ||
require 'shanty_docker_plugin/docker_plugin' | ||
require 'shanty_docker_plugin/error' | ||
require 'shanty_docker_plugin/image_wrapper' | ||
# Public: shanty docker plugin | ||
module DockerPlugin | ||
DOCKER_FILE = 'Dockerfile' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
require 'base64' | ||
require 'docker' | ||
require 'json' | ||
require 'uri' | ||
|
||
module DockerPlugin | ||
# Public: Set up credentials from ~/.dockercfg | ||
class DockerCredentials | ||
def initialize(dockercfg_file = "#{Dir.home}/.dockercfg") | ||
@dockercfg_file = dockercfg_file | ||
end | ||
|
||
def auth!(registry) | ||
Docker.creds = find_credentials(registry) | ||
end | ||
|
||
private | ||
|
||
def find_credentials(registry) | ||
(credentials[registry] || credentials['index.docker.io'] || {}).to_h | ||
end | ||
|
||
def credentials | ||
@credentials ||= read_dockercfg.each_with_object({}) do |(k, v), acc| | ||
username, password = decode_credentials(v['auth']) | ||
acc[registry(k)] = { username: username, password: password, email: v['email'] } | ||
end | ||
end | ||
|
||
def registry(url) | ||
host = url | ||
host = URI(url).host if url.start_with?('http') | ||
host | ||
end | ||
|
||
def decode_credentials(base64) | ||
Base64.decode64(base64).split(':') | ||
end | ||
|
||
def read_dockercfg | ||
return {} unless File.exist?(@dockercfg_file) | ||
JSON.parse(File.open(@dockercfg_file, 'rb').read) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
require 'erubis' | ||
require 'fileutils' | ||
require 'shanty/env' | ||
require 'shanty_docker_plugin' | ||
|
||
module DockerPlugin | ||
# Public: Process a dockerfile as an ERB template | ||
class DockerFile | ||
include Shanty::Logger | ||
|
||
attr_reader :relative_output_file, :output_file | ||
|
||
# Public: initialize the docker file | ||
# | ||
# base_dir - the directory where the docker file is located | ||
# registry - the docker registry to inject into the template | ||
# tag - the build tag to inject into the template | ||
# artifacts - an array of relative paths to artifacts built by dependencies | ||
# build_dir - the directory to output the calculated dockerfile | ||
def initialize(base_dir, registry, tag, build_dir, artifacts = nil) | ||
@base_dir = base_dir | ||
@registry = registry | ||
@tag = tag | ||
@artifacts = artifacts | ||
@output_dir = File.join(base_dir, build_dir) | ||
@relative_output_file = File.join(build_dir, DOCKER_FILE) | ||
@output_file = File.join(base_dir, @relative_output_file) | ||
@docker_file = File.join(base_dir, DOCKER_FILE) | ||
end | ||
|
||
# Public: write the docker file from the template | ||
def write! | ||
FileUtils.mkdir_p(@output_dir) | ||
File.write(@output_file, contents) | ||
end | ||
|
||
# Public: process the contents of a docker file | ||
def contents | ||
fail("Docker file does not exist at #{@docker_file}") unless File.exist?(@docker_file) | ||
@contents ||= Erubis::Eruby.new(File.read(@docker_file)).result(registry: @registry, | ||
tag: @tag, | ||
artifacts: @artifacts) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
require 'shanty_docker_plugin/error' | ||
require 'shanty/logger' | ||
|
||
module DockerPlugin | ||
# Methods for parsing the output from docker | ||
module DockerOutputParser | ||
extend Shanty::Logger | ||
|
||
def self.parse_chunk(chunk) | ||
parse_output(chunk) do |output| | ||
if output.respond_to?(:key?) | ||
fail output['error'] if output.key?('error') | ||
logger.info(output['stream']) if output.key?('stream') | ||
end | ||
end | ||
end | ||
|
||
private_class_method | ||
|
||
# Private: safely parse the output from docker | ||
# logs a warning if the output could not be parsed | ||
# | ||
# body - the body of the output to process | ||
# &block - the block to call with the parsed output | ||
def self.parse_output(body, &block) | ||
parse_output!(body, &block) | ||
rescue Docker::Error::UnexpectedResponseError => e | ||
logger.warn("Could not parse output from docker: #{e}") | ||
logger.warn(body) | ||
end | ||
|
||
# Private: parse the output from docker | ||
# | ||
# body - the body of the output to process | ||
# block - the block to call with the parsed output | ||
def self.parse_output!(body, &block) | ||
if body.include?('}{') | ||
body.split('}{').each do |line| | ||
line = "{#{line}" unless line =~ /\A{/ | ||
line = "#{line}}" unless line =~ /}\z/ | ||
yield(Docker::Util.parse_json(line)) | ||
end | ||
else | ||
body.each_line { |line| block.call(Docker::Util.parse_json(line)) } | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
require 'shanty/plugin' | ||
require 'shanty/project' | ||
require 'shanty_docker_plugin/docker_credentials' | ||
require 'shanty_docker_plugin/image_resolution_fallback' | ||
require 'rugged' | ||
|
||
module DockerPlugin | ||
# Shanty docker plugin | ||
class DockerPlugin < Shanty::Plugin | ||
NAME_OPTION = :docker_project_name | ||
DEPENDENCY_OPTION = :docker_dependency | ||
|
||
def initialize(*args) | ||
super(*args) | ||
@docker_crendentials = DockerCredentials.new | ||
end | ||
|
||
provides_config NAME_OPTION | ||
provides_config :registry | ||
provides_config :tag | ||
provides_config :output_dir, 'build' | ||
provides_config :fallback_tag, 'latest' | ||
subscribe :build, :on_build | ||
subscribe :deploy, :on_deploy | ||
provides_projects :resolve_projects | ||
description 'Discovers and builds Docker projects' | ||
|
||
def self.resolve_projects(env) | ||
project_dependencies(find_projects(env)) | ||
end | ||
|
||
private_class_method | ||
|
||
def self.find_projects(env) | ||
env.file_tree.glob("**/#{DOCKER_FILE}").each_with_object({}) do |f, acc| | ||
project_path = File.dirname(f) | ||
project = find_or_create_project(project_path, env) | ||
|
||
project_name(project) | ||
project.config[DEPENDENCY_OPTION] = project_dependency(f) | ||
|
||
acc[project.config[NAME_OPTION]] = project | ||
end | ||
end | ||
|
||
def self.project_dependencies(projects) | ||
projects.values.each do |project| | ||
project.add_parent(projects[project.config[DEPENDENCY_OPTION]]) unless project.config[DEPENDENCY_OPTION].nil? | ||
end | ||
end | ||
|
||
def self.project_name(project) | ||
project.config[NAME_OPTION] = File.basename(project.path) if project.config[NAME_OPTION].empty? | ||
end | ||
|
||
def self.project_dependency(file) | ||
return unless (match = File.open(file, 'rb').read.match(%r{(FROM|from)\s+<%=\s*registry\s*%>/(?<name>\S+):\S+})) | ||
match[:name] | ||
end | ||
|
||
def on_build | ||
if project.changed? | ||
build_image | ||
else | ||
@docker_crendentials.auth!(image_wrapper.registry) | ||
ImageResolutionFallback.pull!(image_wrapper, config[:fallbacktag]) | ||
end | ||
end | ||
|
||
def on_deploy | ||
@docker_crendentials.auth!(image_wrapper.registry) | ||
image_wrapper.image.push | ||
end | ||
|
||
private | ||
|
||
def tag | ||
config[:tag] || git_tag || 'latest' | ||
end | ||
|
||
def git_tag | ||
@git_tag ||= GitTag.current_branch(env.root) | ||
end | ||
|
||
def image_wrapper | ||
ImageWrapper.new(project.config[NAME_OPTION], tag, config[:registry]) | ||
end | ||
|
||
def build_image | ||
artifacts = ParentArtifacts.copy(project, config[:output_dir]) | ||
|
||
image_wrapper.build(project.path, docker_file(project.path, artifacts)) | ||
end | ||
|
||
def docker_file(project_path, artifacts = nil) | ||
DockerFile.new(project_path, | ||
config[:registry], | ||
tag, | ||
config[:output_dir], | ||
artifacts) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module DockerPlugin | ||
module Error | ||
class PullError < StandardError; end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
require 'shanty/logger' | ||
require 'rugged' | ||
|
||
module DockerPlugin | ||
# Public: methods for getting the docker tag from | ||
# git branch | ||
module GitTag | ||
extend Shanty::Logger | ||
|
||
module_function | ||
|
||
# Public: get the current branch from a git repo | ||
# | ||
# repo_root - the git root directory | ||
# | ||
# Returns the current branch | ||
def current_branch(repo_root) | ||
repo = Rugged::Repository.new(repo_root) | ||
repo.head.name.sub(%r{^refs/heads/}, '') | ||
rescue | ||
logger.debug('Could not resolve current branch') | ||
nil | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
require 'shanty_docker_plugin/error' | ||
require 'shanty/logger' | ||
|
||
module DockerPlugin | ||
# Methods for falling back to a different tag when resolving images | ||
module ImageResolutionFallback | ||
extend Shanty::Logger | ||
|
||
module_function | ||
|
||
def pull!(image, fallback_tag) | ||
image.pull! | ||
rescue Error::PullError | ||
logger.warn( | ||
"Could not find image #{image.name} with tag #{image.tag}, trying with #{fallback_tag}" | ||
) | ||
new_image = ImageWrapper.new(image.name, fallback_tag, image.registry) | ||
new_image.pull! | ||
new_image.add_tag!(image.tag) | ||
end | ||
end | ||
end |
Oops, something went wrong.