picsolve_docker_builder 0.1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/README.md +25 -0
- data/Rakefile +80 -0
- data/bin/docker_build +5 -0
- data/integration/integration_play_spec.rb +51 -0
- data/integration/play_hello_world/.docker-builder.yml +3 -0
- data/integration/play_hello_world/.gitignore +12 -0
- data/integration/play_hello_world/Gemfile +2 -0
- data/integration/play_hello_world/LICENSE +8 -0
- data/integration/play_hello_world/README +4 -0
- data/integration/play_hello_world/Rakefile +3 -0
- data/integration/play_hello_world/app/controllers/Application.scala +12 -0
- data/integration/play_hello_world/app/views/index.scala.html +7 -0
- data/integration/play_hello_world/app/views/main.scala.html +15 -0
- data/integration/play_hello_world/build.sbt +20 -0
- data/integration/play_hello_world/conf/application.conf +44 -0
- data/integration/play_hello_world/conf/logback.xml +22 -0
- data/integration/play_hello_world/conf/routes +9 -0
- data/integration/play_hello_world/project/build.properties +4 -0
- data/integration/play_hello_world/project/plugins.sbt +16 -0
- data/integration/play_hello_world/public/images/favicon.png +0 -0
- data/integration/play_hello_world/public/javascripts/hello.js +3 -0
- data/integration/play_hello_world/public/stylesheets/main.css +0 -0
- data/integration/play_hello_world/test/ApplicationSpec.scala +30 -0
- data/integration/play_hello_world/test/IntegrationSpec.scala +24 -0
- data/integration/spec_helper.rb +3 -0
- data/lib/picsolve_docker_builder.rb +5 -0
- data/lib/picsolve_docker_builder/base.rb +66 -0
- data/lib/picsolve_docker_builder/builder/builder.rb +113 -0
- data/lib/picsolve_docker_builder/builder/file.rb +46 -0
- data/lib/picsolve_docker_builder/composer/composer.rb +134 -0
- data/lib/picsolve_docker_builder/composer/image.rb +68 -0
- data/lib/picsolve_docker_builder/composer/registry.rb +80 -0
- data/lib/picsolve_docker_builder/frame.rb +298 -0
- data/lib/picsolve_docker_builder/helpers/kubeclient.rb +34 -0
- data/lib/picsolve_docker_builder/helpers/kubernetes/pod.rb +38 -0
- data/lib/picsolve_docker_builder/helpers/kubernetes/rc.rb +98 -0
- data/lib/picsolve_docker_builder/helpers/kubernetes/resource.rb +28 -0
- data/lib/picsolve_docker_builder/helpers/kubernetes/service.rb +50 -0
- data/lib/picsolve_docker_builder/helpers/kubernetes_manager.rb +102 -0
- data/lib/picsolve_docker_builder/helpers/repository.rb +24 -0
- data/lib/picsolve_docker_builder/helpers/ssh_forward.rb +75 -0
- data/lib/picsolve_docker_builder/scala.rb +196 -0
- data/lib/picsolve_docker_builder/version.rb +4 -0
- data/lib/tasks/compose.rake +25 -0
- data/lib/tasks/docker.rake +24 -0
- data/lib/tasks/scala.rake +11 -0
- data/picsolve_docker_builder.gemspec +35 -0
- metadata +250 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
import org.specs2.mutable._
|
2
|
+
import org.specs2.runner._
|
3
|
+
import org.junit.runner._
|
4
|
+
|
5
|
+
import play.api.test._
|
6
|
+
import play.api.test.Helpers._
|
7
|
+
|
8
|
+
/**
|
9
|
+
* add your integration spec here.
|
10
|
+
* An integration test will fire up a whole play application in a real (or headless) browser
|
11
|
+
*/
|
12
|
+
@RunWith(classOf[JUnitRunner])
|
13
|
+
class IntegrationSpec extends Specification {
|
14
|
+
|
15
|
+
"Application" should {
|
16
|
+
|
17
|
+
"work from within a browser" in new WithBrowser {
|
18
|
+
|
19
|
+
browser.goTo("http://localhost:" + port)
|
20
|
+
|
21
|
+
browser.pageSource must contain("Your new application is ready.")
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'psych'
|
3
|
+
|
4
|
+
# add a deep_merge function to hashes
|
5
|
+
# rubocop:disable Style/CaseEquality, Style/ClassAndModuleChildren
|
6
|
+
class ::Hash
|
7
|
+
def deep_merge(second)
|
8
|
+
merger = proc do |_key, v1, v2|
|
9
|
+
if Hash === v1 && Hash === v2
|
10
|
+
v1.merge(v2, &merger)
|
11
|
+
else
|
12
|
+
v2
|
13
|
+
end
|
14
|
+
end
|
15
|
+
merge(second, &merger)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module PicsolveDockerBuilder
|
20
|
+
# Base class for everything
|
21
|
+
# * logging
|
22
|
+
# * accessing config
|
23
|
+
module Base
|
24
|
+
def create_logger
|
25
|
+
log = Logger.new(STDOUT)
|
26
|
+
log.level = Logger::DEBUG
|
27
|
+
log
|
28
|
+
end
|
29
|
+
|
30
|
+
def log
|
31
|
+
@logger ||= create_logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def config
|
35
|
+
@config ||= read_config
|
36
|
+
end
|
37
|
+
|
38
|
+
def base_dir
|
39
|
+
Dir.pwd
|
40
|
+
end
|
41
|
+
|
42
|
+
def config_path
|
43
|
+
File.join(base_dir, '.docker-builder.yml')
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_config
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_config(c)
|
51
|
+
c
|
52
|
+
end
|
53
|
+
|
54
|
+
def read_config
|
55
|
+
c = default_config
|
56
|
+
path = config_path
|
57
|
+
begin
|
58
|
+
yaml = Psych.load_file path
|
59
|
+
c = c.deep_merge(yaml)
|
60
|
+
rescue Errno::ENOENT
|
61
|
+
log.debug "can not find config at '#{path}'"
|
62
|
+
end
|
63
|
+
validate_config(c)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'rubygems/package'
|
2
|
+
require 'zlib'
|
3
|
+
require 'picsolve_docker_builder/base'
|
4
|
+
require 'picsolve_docker_builder/builder/file'
|
5
|
+
|
6
|
+
module PicsolveDockerBuilder
|
7
|
+
module Builder
|
8
|
+
# This Builder supports the building process of an docker image
|
9
|
+
class Builder
|
10
|
+
include PicsolveDockerBuilder::Base
|
11
|
+
def initialize(opts = {})
|
12
|
+
@opts = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def maintainer
|
16
|
+
@opts[:maintainer]
|
17
|
+
end
|
18
|
+
|
19
|
+
def base_image
|
20
|
+
@opts[:base_image]
|
21
|
+
end
|
22
|
+
|
23
|
+
def ports
|
24
|
+
ports = @opts[:ports]
|
25
|
+
ports = [ports] unless ports.is_a?(Array)
|
26
|
+
ports.map do |port|
|
27
|
+
"EXPOSE #{port}"
|
28
|
+
end.join('\n')
|
29
|
+
end
|
30
|
+
|
31
|
+
def adds
|
32
|
+
files = @opts[:files].select(&:add_to_image?)
|
33
|
+
files.map! do |f|
|
34
|
+
"ADD #{f.path} #{f.destination}"
|
35
|
+
end
|
36
|
+
files.join("\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
def command
|
40
|
+
"[\"#{@opts[:command].join('", "')}\"]"
|
41
|
+
end
|
42
|
+
|
43
|
+
def hook(name)
|
44
|
+
v = @opts[:hooks][name]
|
45
|
+
v = [v] unless v.is_a?(Array)
|
46
|
+
v.join("\n") + "\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def required_attrs
|
50
|
+
[
|
51
|
+
:base_image,
|
52
|
+
:maintainer,
|
53
|
+
:ports,
|
54
|
+
:command,
|
55
|
+
:files
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_tar
|
60
|
+
tarfile = StringIO.new('')
|
61
|
+
Gem::Package::TarWriter.new(tarfile) do |tar|
|
62
|
+
# add Dockerfile to tar
|
63
|
+
dockerfile.each_line do |line|
|
64
|
+
log.debug "docker_build Dockerfile: #{line.strip}"
|
65
|
+
end
|
66
|
+
tar.add_file 'Dockerfile', 0644 do |tf|
|
67
|
+
tf.write dockerfile
|
68
|
+
end
|
69
|
+
|
70
|
+
# add other files to tar
|
71
|
+
@opts[:files].each do |f|
|
72
|
+
f.add_to_tar tar
|
73
|
+
end
|
74
|
+
end
|
75
|
+
StringIO.new(tarfile.string)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Overwrite docker_build and filter build assets
|
79
|
+
def build
|
80
|
+
log.info 'docker build starting...'
|
81
|
+
Docker::Image.build_from_tar(build_tar) do |stream|
|
82
|
+
s = JSON.parse(stream)['stream']
|
83
|
+
log.debug s.strip unless s.nil?
|
84
|
+
end
|
85
|
+
rescue NoMethodError => e
|
86
|
+
log.fatal "docker build failed: #{e}"
|
87
|
+
exit 1
|
88
|
+
end
|
89
|
+
|
90
|
+
def dockerfile
|
91
|
+
<<EOS
|
92
|
+
FROM #{base_image}
|
93
|
+
MAINTAINER #{maintainer}
|
94
|
+
|
95
|
+
#{hook(:early)}
|
96
|
+
|
97
|
+
#{hook(:before_adds)}
|
98
|
+
|
99
|
+
#{adds}
|
100
|
+
|
101
|
+
#{hook(:after_adds)}
|
102
|
+
|
103
|
+
#{ports}
|
104
|
+
|
105
|
+
# call run script
|
106
|
+
CMD #{command}
|
107
|
+
|
108
|
+
#{hook(:late)}
|
109
|
+
EOS
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'picsolve_docker_builder/base'
|
2
|
+
|
3
|
+
module PicsolveDockerBuilder
|
4
|
+
module Builder
|
5
|
+
# File helper to add files to tars
|
6
|
+
class File
|
7
|
+
include PicsolveDockerBuilder::Base
|
8
|
+
attr_reader :path
|
9
|
+
def initialize(path, opts = {})
|
10
|
+
@opts = opts
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def content
|
15
|
+
return @opts[:content] if @opts.key? :content
|
16
|
+
|
17
|
+
return read_source if @opts.key? :source
|
18
|
+
|
19
|
+
fail 'No content found'
|
20
|
+
end
|
21
|
+
|
22
|
+
def destination
|
23
|
+
@opts[:destination]
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_to_image?
|
27
|
+
@opts.key? :destination
|
28
|
+
end
|
29
|
+
|
30
|
+
def source
|
31
|
+
@opts[:source]
|
32
|
+
end
|
33
|
+
|
34
|
+
def mode
|
35
|
+
::File.stat(source).mode
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_to_tar(tar)
|
39
|
+
log.debug "add file from #{source} to #{path} in tar"
|
40
|
+
tar.add_file path, mode do |tf|
|
41
|
+
::File.open(source, 'rb') { |f| tf.write f.read }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'picsolve_docker_builder/base'
|
2
|
+
require 'picsolve_docker_builder/helpers/kubernetes_manager'
|
3
|
+
require 'picsolve_docker_builder/composer/image'
|
4
|
+
require 'excon'
|
5
|
+
require 'psych'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
module PicsolveDockerBuilder
|
9
|
+
module Composer
|
10
|
+
# The composer parses a docker-compose.yml and tries to spin up the
|
11
|
+
# containers in a kubernetes cluster
|
12
|
+
class Composer
|
13
|
+
include PicsolveDockerBuilder::Base
|
14
|
+
attr_reader :stage
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
end
|
18
|
+
|
19
|
+
def prepare
|
20
|
+
images
|
21
|
+
end
|
22
|
+
|
23
|
+
def stage=(my_stage)
|
24
|
+
@stage = my_stage.downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def deploy
|
28
|
+
log.info "start deploying app_name=#{app_name} to stage #{stage}"
|
29
|
+
|
30
|
+
# reset hash
|
31
|
+
@hash = nil
|
32
|
+
|
33
|
+
# deploy services
|
34
|
+
images.each do |i|
|
35
|
+
i.service.deploy
|
36
|
+
end
|
37
|
+
|
38
|
+
# deploy rcs
|
39
|
+
images.each do |i|
|
40
|
+
i.rc.deploy
|
41
|
+
end
|
42
|
+
|
43
|
+
# wait for deployed rc
|
44
|
+
wait_timeout = 300
|
45
|
+
wait_sleep = 2
|
46
|
+
wait_beginning = 15
|
47
|
+
wait_count = wait_beginning
|
48
|
+
unready_images = images.dup
|
49
|
+
log.info \
|
50
|
+
'Wait for the new pods to be up and running for at least 20 seconds'
|
51
|
+
sleep wait_beginning
|
52
|
+
loop do
|
53
|
+
unready_images.each do |i|
|
54
|
+
unready_images.delete(i) if i.rc.ready?
|
55
|
+
end
|
56
|
+
log.debug "Still waiting for: #{unready_images.map(&:name)}"
|
57
|
+
wait_count += wait_sleep
|
58
|
+
break if wait_count > wait_timeout
|
59
|
+
break if unready_images.length == 0
|
60
|
+
sleep wait_sleep
|
61
|
+
end
|
62
|
+
|
63
|
+
# build list of successful deplyoed images
|
64
|
+
successful_images = images.dup
|
65
|
+
unready_images.each do |u|
|
66
|
+
successful_images.delete(u)
|
67
|
+
end
|
68
|
+
|
69
|
+
# remove old rcs of successful deployed images
|
70
|
+
successful_images.each do |i|
|
71
|
+
log.debug "Remove pods for #{i.name}}"
|
72
|
+
i.rc.remove_old_rcs
|
73
|
+
end
|
74
|
+
|
75
|
+
fail "Failed to deploy this service: #{unready_images.map(&:name)}" \
|
76
|
+
if unready_images.length > 0
|
77
|
+
end
|
78
|
+
|
79
|
+
def app_name
|
80
|
+
config['compose']['app_name'].downcase.gsub(/[^a-z0-9\-]/, '-')
|
81
|
+
end
|
82
|
+
|
83
|
+
def namespace
|
84
|
+
"#{app_name}-#{stage}".downcase
|
85
|
+
end
|
86
|
+
|
87
|
+
def kubernetes
|
88
|
+
@kubernetes ||= PicsolveDockerBuilder::Helpers::KubernetesManager.new
|
89
|
+
end
|
90
|
+
|
91
|
+
def hash
|
92
|
+
@hash ||= SecureRandom.hex[0..7]
|
93
|
+
end
|
94
|
+
|
95
|
+
def default_config
|
96
|
+
{ 'compose' => {} }
|
97
|
+
end
|
98
|
+
|
99
|
+
def images
|
100
|
+
@images ||= read_images
|
101
|
+
end
|
102
|
+
|
103
|
+
def read_images
|
104
|
+
i = []
|
105
|
+
docker_compose.each do |name, config|
|
106
|
+
i << Image.new(name, config, self)
|
107
|
+
end
|
108
|
+
i
|
109
|
+
end
|
110
|
+
|
111
|
+
def docker_compose
|
112
|
+
@docker_compose ||= read_docker_compose
|
113
|
+
end
|
114
|
+
|
115
|
+
def base_dir
|
116
|
+
Dir.pwd
|
117
|
+
end
|
118
|
+
|
119
|
+
def docker_compose_path
|
120
|
+
File.join(base_dir, 'docker-compose.yml')
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_docker_compose
|
124
|
+
path = docker_compose_path
|
125
|
+
Psych.load_file path
|
126
|
+
rescue Errno::ENOENT
|
127
|
+
log.debug "can not find docker-compose.yml at '#{path}'"
|
128
|
+
end
|
129
|
+
|
130
|
+
def query_registry
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'picsolve_docker_builder/base'
|
2
|
+
require 'picsolve_docker_builder/composer/registry'
|
3
|
+
|
4
|
+
module PicsolveDockerBuilder
|
5
|
+
module Composer
|
6
|
+
# Represents an image in the registry
|
7
|
+
class Image
|
8
|
+
include PicsolveDockerBuilder::Base
|
9
|
+
attr_reader :config, :name, :composer
|
10
|
+
def initialize(name, config, composer)
|
11
|
+
log.info "Image name=#{name} config=#{config}"
|
12
|
+
@config = config
|
13
|
+
@name = name
|
14
|
+
@composer = composer
|
15
|
+
log.info "repo_tag_uniqe is #{repo_tag_unique}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def repo_tag
|
19
|
+
config['image']
|
20
|
+
end
|
21
|
+
|
22
|
+
def kubernetes
|
23
|
+
composer.kubernetes
|
24
|
+
end
|
25
|
+
|
26
|
+
def rc
|
27
|
+
@rc ||= kubernetes.rc self
|
28
|
+
end
|
29
|
+
|
30
|
+
def service
|
31
|
+
@service ||= kubernetes.service self
|
32
|
+
end
|
33
|
+
|
34
|
+
def ports
|
35
|
+
config['ports'].map do |port|
|
36
|
+
port_split = port.split(/:/)
|
37
|
+
{
|
38
|
+
'port' => port_split[1].to_i
|
39
|
+
}
|
40
|
+
end
|
41
|
+
rescue StandardError
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
|
45
|
+
def ports_rc
|
46
|
+
p = []
|
47
|
+
ports.each do |port|
|
48
|
+
p << {
|
49
|
+
'containerPort' => port['port']
|
50
|
+
}
|
51
|
+
end
|
52
|
+
p
|
53
|
+
end
|
54
|
+
|
55
|
+
def repo_tag_hash_unique
|
56
|
+
@repo_tag_hash_unique ||= Registry.repo_tag_unique(config['image'])
|
57
|
+
end
|
58
|
+
|
59
|
+
def repo_tag_unique
|
60
|
+
repo_tag_hash_unique[:tag_unique]
|
61
|
+
end
|
62
|
+
|
63
|
+
def hash
|
64
|
+
repo_tag_hash_unique[:hash]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|