kuby-core 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +16 -0
- data/LICENSE +21 -0
- data/README.md +186 -0
- data/Rakefile +14 -0
- data/kuby-core.gemspec +27 -0
- data/lib/kuby.rb +112 -0
- data/lib/kuby/basic_logger.rb +22 -0
- data/lib/kuby/cli_base.rb +75 -0
- data/lib/kuby/definition.rb +29 -0
- data/lib/kuby/docker.rb +27 -0
- data/lib/kuby/docker/alpine.rb +62 -0
- data/lib/kuby/docker/assets_phase.rb +11 -0
- data/lib/kuby/docker/bundler_phase.rb +56 -0
- data/lib/kuby/docker/cli.rb +72 -0
- data/lib/kuby/docker/copy_phase.rb +23 -0
- data/lib/kuby/docker/credentials.rb +11 -0
- data/lib/kuby/docker/debian.rb +66 -0
- data/lib/kuby/docker/dockerfile.rb +128 -0
- data/lib/kuby/docker/errors.rb +22 -0
- data/lib/kuby/docker/layer_stack.rb +42 -0
- data/lib/kuby/docker/local_tags.rb +38 -0
- data/lib/kuby/docker/metadata.rb +69 -0
- data/lib/kuby/docker/package_list.rb +34 -0
- data/lib/kuby/docker/package_phase.rb +59 -0
- data/lib/kuby/docker/packages.rb +10 -0
- data/lib/kuby/docker/packages/managed_package.rb +29 -0
- data/lib/kuby/docker/packages/nodejs.rb +29 -0
- data/lib/kuby/docker/packages/package.rb +22 -0
- data/lib/kuby/docker/packages/yarn.rb +47 -0
- data/lib/kuby/docker/phase.rb +21 -0
- data/lib/kuby/docker/remote_tags.rb +24 -0
- data/lib/kuby/docker/setup_phase.rb +29 -0
- data/lib/kuby/docker/spec.rb +147 -0
- data/lib/kuby/docker/tags.rb +39 -0
- data/lib/kuby/docker/timestamp_tag.rb +36 -0
- data/lib/kuby/docker/webserver_phase.rb +51 -0
- data/lib/kuby/docker/yarn_phase.rb +11 -0
- data/lib/kuby/kubernetes.rb +16 -0
- data/lib/kuby/kubernetes/deployer.rb +94 -0
- data/lib/kuby/kubernetes/docker_config.rb +27 -0
- data/lib/kuby/kubernetes/errors.rb +22 -0
- data/lib/kuby/kubernetes/manifest.rb +56 -0
- data/lib/kuby/kubernetes/minikube_provider.rb +51 -0
- data/lib/kuby/kubernetes/plugin.rb +55 -0
- data/lib/kuby/kubernetes/plugins.rb +8 -0
- data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +61 -0
- data/lib/kuby/kubernetes/plugins/rails_app.rb +16 -0
- data/lib/kuby/kubernetes/plugins/rails_app/database.rb +58 -0
- data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +142 -0
- data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +393 -0
- data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +10 -0
- data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +13 -0
- data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +10 -0
- data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +23 -0
- data/lib/kuby/kubernetes/provider.rb +77 -0
- data/lib/kuby/kubernetes/registry_secret.rb +26 -0
- data/lib/kuby/kubernetes/spec.rb +152 -0
- data/lib/kuby/middleware.rb +5 -0
- data/lib/kuby/middleware/health_check.rb +16 -0
- data/lib/kuby/railtie.rb +18 -0
- data/lib/kuby/tasks.rb +135 -0
- data/lib/kuby/tasks/kuby.rake +63 -0
- data/lib/kuby/trailing_hash.rb +19 -0
- data/lib/kuby/version.rb +3 -0
- metadata +233 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kuby
|
2
|
+
module Docker
|
3
|
+
class SetupPhase < Phase
|
4
|
+
DEFAULT_WORKING_DIR = '/usr/src/app'.freeze
|
5
|
+
|
6
|
+
attr_accessor :base_image, :working_dir
|
7
|
+
|
8
|
+
def apply_to(dockerfile)
|
9
|
+
dockerfile.from(base_image || default_base_image)
|
10
|
+
dockerfile.workdir(working_dir || DEFAULT_WORKING_DIR)
|
11
|
+
dockerfile.env("RAILS_ENV=#{Kuby.env}")
|
12
|
+
dockerfile.env("KUBY_ENV=#{Kuby.env}")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def default_base_image
|
18
|
+
@default_base_image ||= case metadata.distro
|
19
|
+
when :debian
|
20
|
+
"ruby:#{RUBY_VERSION}"
|
21
|
+
when :alpine
|
22
|
+
"ruby:#{RUBY_VERSION}-alpine"
|
23
|
+
else
|
24
|
+
# ERROR
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'docker/remote'
|
2
|
+
|
3
|
+
module Kuby
|
4
|
+
module Docker
|
5
|
+
class Spec
|
6
|
+
attr_reader :definition
|
7
|
+
|
8
|
+
def initialize(definition)
|
9
|
+
@definition = definition
|
10
|
+
end
|
11
|
+
|
12
|
+
def base_image(image_url)
|
13
|
+
setup_phase.base_image = image_url
|
14
|
+
end
|
15
|
+
|
16
|
+
def working_dir(dir)
|
17
|
+
setup_phase.working_dir = dir
|
18
|
+
end
|
19
|
+
|
20
|
+
def rails_env(env)
|
21
|
+
setup_phase.rails_env = env
|
22
|
+
end
|
23
|
+
|
24
|
+
def bundler_version(version)
|
25
|
+
bundler_phase.bundler_version = version
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemfile(path)
|
29
|
+
bundler_phase.gemfile = path
|
30
|
+
end
|
31
|
+
|
32
|
+
def package(pkg)
|
33
|
+
package_phase << pkg
|
34
|
+
end
|
35
|
+
|
36
|
+
def distro(distro_name)
|
37
|
+
metadata.distro = distro_name
|
38
|
+
@distro_spec = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def files(path)
|
42
|
+
copy_phase << path
|
43
|
+
end
|
44
|
+
|
45
|
+
def port(port)
|
46
|
+
webserver_phase.port = port
|
47
|
+
end
|
48
|
+
|
49
|
+
def image_url(url)
|
50
|
+
metadata.image_url = url
|
51
|
+
end
|
52
|
+
|
53
|
+
def use(*args)
|
54
|
+
layer_stack.use(*args)
|
55
|
+
end
|
56
|
+
|
57
|
+
def insert(*args)
|
58
|
+
layer_stack.insert(*args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete(*args)
|
62
|
+
layer_stack.delete(*args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def credentials(&block)
|
66
|
+
@credentials ||= Credentials.new
|
67
|
+
@credentials.instance_eval(&block) if block
|
68
|
+
@credentials
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_dockerfile
|
72
|
+
Dockerfile.new.tap do |df|
|
73
|
+
layer_stack.each { |layer| layer.apply_to(df) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def setup_phase
|
78
|
+
@setup_phase ||= SetupPhase.new(definition)
|
79
|
+
end
|
80
|
+
|
81
|
+
def package_phase
|
82
|
+
@package_phase ||= PackagePhase.new(definition)
|
83
|
+
end
|
84
|
+
|
85
|
+
def bundler_phase
|
86
|
+
@bundler_phase ||= BundlerPhase.new(definition)
|
87
|
+
end
|
88
|
+
|
89
|
+
def yarn_phase
|
90
|
+
@yarn_phase ||= YarnPhase.new(definition)
|
91
|
+
end
|
92
|
+
|
93
|
+
def copy_phase
|
94
|
+
@copy_phase ||= CopyPhase.new(definition)
|
95
|
+
end
|
96
|
+
|
97
|
+
def assets_phase
|
98
|
+
@assets_phase ||= AssetsPhase.new(definition)
|
99
|
+
end
|
100
|
+
|
101
|
+
def webserver_phase
|
102
|
+
@webserver_phase ||= WebserverPhase.new(definition)
|
103
|
+
end
|
104
|
+
|
105
|
+
def metadata
|
106
|
+
@metadata ||= Metadata.new(definition)
|
107
|
+
end
|
108
|
+
|
109
|
+
def tags
|
110
|
+
@tags ||= Tags.new(cli, remote_client, metadata)
|
111
|
+
end
|
112
|
+
|
113
|
+
def cli
|
114
|
+
@cli ||= Docker::CLI.new
|
115
|
+
end
|
116
|
+
|
117
|
+
def remote_client
|
118
|
+
@remote_client ||= ::Docker::Remote::Client.new(
|
119
|
+
metadata.image_host, metadata.image_repo,
|
120
|
+
credentials.username, credentials.password,
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
def distro_spec
|
125
|
+
@distro_spec ||= if distro_klass = Kuby.distros[metadata.distro]
|
126
|
+
distro_klass.new(self)
|
127
|
+
else
|
128
|
+
raise MissingDistroError, "distro '#{metadata.distro}' hasn't been registered"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def layer_stack
|
135
|
+
@layer_stack ||= LayerStack.new.tap do |stack|
|
136
|
+
stack.use(:setup_phase, setup_phase)
|
137
|
+
stack.use(:package_phase, package_phase)
|
138
|
+
stack.use(:bundler_phase, bundler_phase)
|
139
|
+
stack.use(:yarn_phase, yarn_phase)
|
140
|
+
stack.use(:copy_phase, copy_phase)
|
141
|
+
stack.use(:assets_phase, assets_phase)
|
142
|
+
stack.use(:webserver_phase, webserver_phase)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Kuby
|
2
|
+
module Docker
|
3
|
+
class Tags
|
4
|
+
LATEST = Metadata::LATEST_TAG
|
5
|
+
|
6
|
+
attr_reader :cli, :remote_client, :metadata
|
7
|
+
|
8
|
+
def initialize(cli, remote_client, metadata)
|
9
|
+
@cli = cli
|
10
|
+
@remote_client = remote_client
|
11
|
+
@metadata = metadata
|
12
|
+
end
|
13
|
+
|
14
|
+
def tags
|
15
|
+
(local.tags + remote.tags).uniq
|
16
|
+
end
|
17
|
+
|
18
|
+
def latest_tags
|
19
|
+
(local.latest_tags + remote.latest_tags).uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
def timestamp_tags
|
23
|
+
(local.timestamp_tags + remote.timestamp_tags).uniq
|
24
|
+
end
|
25
|
+
|
26
|
+
def all
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def local
|
31
|
+
@local ||= LocalTags.new(cli, metadata)
|
32
|
+
end
|
33
|
+
|
34
|
+
def remote
|
35
|
+
@remote ||= RemoteTags.new(remote_client, metadata)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Kuby
|
2
|
+
module Docker
|
3
|
+
class TimestampTag
|
4
|
+
RE = /20[\d]{2}(?:0[1-9]|11|12)(?:0[1-9]|1[1-9]|2[1-9]|3[01])/.freeze
|
5
|
+
FORMAT = '%Y%m%d%H%M%S'.freeze
|
6
|
+
|
7
|
+
def self.try_parse(str)
|
8
|
+
if str =~ RE
|
9
|
+
new(Time.strptime(str, FORMAT))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :time
|
14
|
+
|
15
|
+
def initialize(time)
|
16
|
+
@time = time
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
time.strftime(FORMAT)
|
21
|
+
end
|
22
|
+
|
23
|
+
def <=>(other)
|
24
|
+
time <=> other.time
|
25
|
+
end
|
26
|
+
|
27
|
+
def hash
|
28
|
+
time.hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(other)
|
32
|
+
time == other.time
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Kuby
|
2
|
+
module Docker
|
3
|
+
class WebserverPhase < Phase
|
4
|
+
class Puma
|
5
|
+
attr_reader :phase
|
6
|
+
|
7
|
+
def initialize(phase)
|
8
|
+
@phase = phase
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply_to(dockerfile)
|
12
|
+
dockerfile.cmd(
|
13
|
+
'puma',
|
14
|
+
'--workers', '4',
|
15
|
+
'--bind', 'tcp://0.0.0.0',
|
16
|
+
'--port', phase.port,
|
17
|
+
'--pidfile', './server.pid',
|
18
|
+
'./config.ru'
|
19
|
+
)
|
20
|
+
|
21
|
+
dockerfile.expose(phase.port)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
DEFAULT_PORT = 8080
|
26
|
+
WEBSERVER_MAP = { puma: Puma }.freeze
|
27
|
+
|
28
|
+
attr_accessor :port, :webserver
|
29
|
+
|
30
|
+
def apply_to(dockerfile)
|
31
|
+
ws = webserver || default_webserver
|
32
|
+
ws_class = WEBSERVER_MAP[ws]
|
33
|
+
raise "No webserver named #{ws}" unless ws_class
|
34
|
+
|
35
|
+
ws_class.new(self).apply_to(dockerfile)
|
36
|
+
end
|
37
|
+
|
38
|
+
def port
|
39
|
+
@port || DEFAULT_PORT
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def default_webserver
|
45
|
+
if Gem.loaded_specs.include?('puma')
|
46
|
+
:puma
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'kuby/kubernetes/errors'
|
2
|
+
|
3
|
+
module Kuby
|
4
|
+
module Kubernetes
|
5
|
+
autoload :MinikubeProvider, 'kuby/kubernetes/minikube_provider'
|
6
|
+
autoload :Deployer, 'kuby/kubernetes/deployer'
|
7
|
+
autoload :DockerConfig, 'kuby/kubernetes/docker_config'
|
8
|
+
autoload :Manifest, 'kuby/kubernetes/manifest'
|
9
|
+
autoload :Monitors, 'kuby/kubernetes/monitors'
|
10
|
+
autoload :Plugin, 'kuby/kubernetes/plugin'
|
11
|
+
autoload :Plugins, 'kuby/kubernetes/plugins'
|
12
|
+
autoload :Provider, 'kuby/kubernetes/provider'
|
13
|
+
autoload :RegistrySecret, 'kuby/kubernetes/registry_secret'
|
14
|
+
autoload :Spec, 'kuby/kubernetes/spec'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'krane'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module Kuby
|
7
|
+
module Kubernetes
|
8
|
+
class Deployer
|
9
|
+
attr_reader :definition
|
10
|
+
|
11
|
+
def initialize(definition)
|
12
|
+
@definition = definition
|
13
|
+
end
|
14
|
+
|
15
|
+
def deploy
|
16
|
+
namespaced, global = all_resources.partition do |resource|
|
17
|
+
# Unfortunately we can't use respond_to here because all KubeDSL
|
18
|
+
# objects use ObjectMeta, which has a namespace field. Not sure
|
19
|
+
# why, since it makes no sense for a namespace to have a namespace.
|
20
|
+
# Instead we just check for nil here.
|
21
|
+
resource.metadata.namespace
|
22
|
+
end
|
23
|
+
|
24
|
+
deploy_global_resources(global)
|
25
|
+
deploy_namespaced_resources(namespaced)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def deploy_global_resources(resources)
|
31
|
+
resources.each do |res|
|
32
|
+
Kuby.logger.info(
|
33
|
+
"Validating global resource, #{res.kind_sym.to_s.humanize.downcase} '#{res.metadata.name}'"
|
34
|
+
)
|
35
|
+
|
36
|
+
cli.apply(res, dry_run: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
resources.each do |res|
|
40
|
+
Kuby.logger.info(
|
41
|
+
"Deploying #{res.kind_sym.to_s.humanize.downcase} '#{res.metadata.name}'"
|
42
|
+
)
|
43
|
+
|
44
|
+
cli.apply(res)
|
45
|
+
end
|
46
|
+
rescue InvalidResourceError => e
|
47
|
+
Kuby.logger.fatal(e.message)
|
48
|
+
Kuby.logger.fatal(e.resource.to_resource.to_yaml)
|
49
|
+
end
|
50
|
+
|
51
|
+
def deploy_namespaced_resources(resources)
|
52
|
+
old_kubeconfig = ENV['KUBECONFIG']
|
53
|
+
ENV['KUBECONFIG'] = provider.kubeconfig_path
|
54
|
+
|
55
|
+
tmpdir = Dir.mktmpdir('kuby-deploy-resources')
|
56
|
+
|
57
|
+
resources.each do |resource|
|
58
|
+
resource_path = File.join(
|
59
|
+
tmpdir, "#{SecureRandom.hex(6)}-#{resource.kind_sym.downcase}.yaml"
|
60
|
+
)
|
61
|
+
|
62
|
+
File.write(resource_path, resource.to_resource.to_yaml)
|
63
|
+
end
|
64
|
+
|
65
|
+
task = ::Krane::DeployTask.new(
|
66
|
+
namespace: namespace.metadata.name,
|
67
|
+
context: cli.current_context,
|
68
|
+
filenames: [tmpdir]
|
69
|
+
)
|
70
|
+
|
71
|
+
task.run!(verify_result: true, prune: false)
|
72
|
+
ensure
|
73
|
+
ENV['KUBECONFIG'] = old_kubeconfig
|
74
|
+
FileUtils.rm_rf(tmpdir)
|
75
|
+
end
|
76
|
+
|
77
|
+
def provider
|
78
|
+
definition.kubernetes.provider
|
79
|
+
end
|
80
|
+
|
81
|
+
def namespace
|
82
|
+
definition.kubernetes.namespace
|
83
|
+
end
|
84
|
+
|
85
|
+
def all_resources
|
86
|
+
definition.kubernetes.resources
|
87
|
+
end
|
88
|
+
|
89
|
+
def cli
|
90
|
+
provider.kubernetes_cli
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'kube-dsl'
|
3
|
+
|
4
|
+
module Kuby
|
5
|
+
module Kubernetes
|
6
|
+
class DockerConfig
|
7
|
+
extend ::KubeDSL::ValueFields
|
8
|
+
|
9
|
+
value_fields :registry_host, :username, :password, :email
|
10
|
+
|
11
|
+
def initialize(&block)
|
12
|
+
instance_eval(&block) if block
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize
|
16
|
+
{
|
17
|
+
registry_host.to_sym => {
|
18
|
+
username: username,
|
19
|
+
password: password,
|
20
|
+
email: email,
|
21
|
+
auth: Base64.strict_encode64("#{username}:#{password}")
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|