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,77 @@
|
|
1
|
+
require 'kubernetes-cli'
|
2
|
+
|
3
|
+
module Kuby
|
4
|
+
module Kubernetes
|
5
|
+
class Provider
|
6
|
+
attr_reader :definition
|
7
|
+
|
8
|
+
def initialize(definition)
|
9
|
+
@definition = definition
|
10
|
+
after_initialize
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure(&block)
|
14
|
+
# do nothing by default
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup
|
18
|
+
# do nothing by default
|
19
|
+
end
|
20
|
+
|
21
|
+
# called after all providers and plugins have been configured
|
22
|
+
def after_configuration
|
23
|
+
# do nothing by default
|
24
|
+
end
|
25
|
+
|
26
|
+
# called before any providers or plugins have been setup
|
27
|
+
def before_setup
|
28
|
+
# do nothing by default
|
29
|
+
end
|
30
|
+
|
31
|
+
# called after all providers and plugins have been setup
|
32
|
+
def after_setup
|
33
|
+
# do nothing by default
|
34
|
+
end
|
35
|
+
|
36
|
+
# called before the deploy is initiated
|
37
|
+
def before_deploy(manifest)
|
38
|
+
# do nothing by default
|
39
|
+
end
|
40
|
+
|
41
|
+
# called after the deploy has completed
|
42
|
+
def after_deploy(manifest)
|
43
|
+
# do nothing by default
|
44
|
+
end
|
45
|
+
|
46
|
+
def deploy
|
47
|
+
deployer.deploy
|
48
|
+
end
|
49
|
+
|
50
|
+
def rollback
|
51
|
+
deployer.rollback
|
52
|
+
end
|
53
|
+
|
54
|
+
def kubernetes_cli
|
55
|
+
@kubernetes_cli ||= ::KubernetesCLI.new(kubeconfig_path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def kubeconfig_path
|
59
|
+
raise NotImplementedError, "please define #{__method__} in #{self.class.name}"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def after_initialize
|
65
|
+
# override this in derived classes
|
66
|
+
end
|
67
|
+
|
68
|
+
def deployer
|
69
|
+
@deployer ||= Kuby::Kubernetes::Deployer.new(definition)
|
70
|
+
end
|
71
|
+
|
72
|
+
def spec
|
73
|
+
definition.kubernetes
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Kuby
|
4
|
+
module Kubernetes
|
5
|
+
class RegistrySecret < ::KubeDSL::DSL::V1::Secret
|
6
|
+
array_field(:docker_config) { DockerConfig.new }
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
instance_eval(&block) if block
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize
|
13
|
+
super.tap do |result|
|
14
|
+
result[:type] = 'kubernetes.io/dockerconfigjson'
|
15
|
+
result[:data] = {
|
16
|
+
:".dockerconfigjson" => Base64.strict_encode64({
|
17
|
+
auths: docker_configs.each_with_object({}) do |dc, ret|
|
18
|
+
ret.merge!(dc.serialize)
|
19
|
+
end
|
20
|
+
}.to_json)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'kube-dsl'
|
2
|
+
|
3
|
+
module Kuby
|
4
|
+
module Kubernetes
|
5
|
+
class Spec
|
6
|
+
extend ::KubeDSL::ValueFields
|
7
|
+
|
8
|
+
attr_reader :definition, :plugins
|
9
|
+
|
10
|
+
def initialize(definition)
|
11
|
+
@definition = definition
|
12
|
+
@plugins = TrailingHash.new
|
13
|
+
|
14
|
+
# default plugins
|
15
|
+
add_plugin(:rails_app)
|
16
|
+
end
|
17
|
+
|
18
|
+
def provider(provider_name = nil, &block)
|
19
|
+
if provider_name
|
20
|
+
if @provider || provider_klass = Kuby.providers[provider_name]
|
21
|
+
@provider ||= provider_klass.new(definition)
|
22
|
+
@provider.configure(&block)
|
23
|
+
else
|
24
|
+
msg = if provider_name
|
25
|
+
"no provider registered with name #{provider_name}, "\
|
26
|
+
'do you need to add a gem to your Gemfile?'
|
27
|
+
else
|
28
|
+
'no provider configured'
|
29
|
+
end
|
30
|
+
|
31
|
+
raise MissingProviderError, msg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@provider
|
36
|
+
end
|
37
|
+
|
38
|
+
def configure_plugin(plugin_name, &block)
|
39
|
+
if @plugins[plugin_name] || plugin_klass = Kuby.plugins[plugin_name]
|
40
|
+
@plugins[plugin_name] ||= plugin_klass.new(definition)
|
41
|
+
@plugins[plugin_name].configure(&block) if block
|
42
|
+
else
|
43
|
+
raise MissingPluginError, "no plugin registered with name #{plugin_name}, "\
|
44
|
+
'do you need to add a gem to your Gemfile?'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :add_plugin, :configure_plugin
|
49
|
+
|
50
|
+
def plugin(plugin_name)
|
51
|
+
@plugins[plugin_name]
|
52
|
+
end
|
53
|
+
|
54
|
+
def after_configuration
|
55
|
+
@plugins.each { |_, plg| plg.after_configuration }
|
56
|
+
provider.after_configuration
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup
|
60
|
+
provider.before_setup
|
61
|
+
provider.setup
|
62
|
+
|
63
|
+
@plugins.each { |_, plg| plg.before_setup }
|
64
|
+
@plugins.each { |_, plg| plg.setup }
|
65
|
+
@plugins.each { |_, plg| plg.after_setup }
|
66
|
+
|
67
|
+
provider.after_setup
|
68
|
+
end
|
69
|
+
|
70
|
+
def deploy(tag = nil)
|
71
|
+
tag ||= latest_tag
|
72
|
+
|
73
|
+
unless tag
|
74
|
+
raise Kuby::Docker::MissingTagError, 'could not find latest timestamped tag'
|
75
|
+
end
|
76
|
+
|
77
|
+
set_tag(tag)
|
78
|
+
|
79
|
+
provider.before_deploy(resources)
|
80
|
+
@plugins.each { |_, plg| plg.before_deploy(resources) }
|
81
|
+
|
82
|
+
provider.deploy
|
83
|
+
|
84
|
+
@plugins.each { |_, plg| plg.after_deploy(resources) }
|
85
|
+
provider.after_deploy(resources)
|
86
|
+
end
|
87
|
+
|
88
|
+
def rollback
|
89
|
+
depl = provider.kubernetes_cli.get_object(
|
90
|
+
'deployment', namespace.metadata.name, deployment.metadata.name
|
91
|
+
)
|
92
|
+
|
93
|
+
image_url = depl.dig('spec', 'template', 'spec', 'containers', 0, 'image')
|
94
|
+
|
95
|
+
unless image_url
|
96
|
+
raise MissingDeploymentError, "couldn't find an existing deployment"
|
97
|
+
end
|
98
|
+
|
99
|
+
deployed_tag = ::Kuby::Docker::TimestampTag.try_parse(image_url.split(':').last)
|
100
|
+
all_tags = docker.tags.all.timestamp_tags.sort
|
101
|
+
tag_idx = all_tags.index { |tag| tag.time == deployed_tag.time } || 0
|
102
|
+
|
103
|
+
if tag_idx == 0
|
104
|
+
raise Kuby::Docker::MissingTagError, 'could not find previous tag'
|
105
|
+
end
|
106
|
+
|
107
|
+
previous_tag = all_tags[tag_idx - 1]
|
108
|
+
deploy(previous_tag.to_s)
|
109
|
+
end
|
110
|
+
|
111
|
+
def namespace(&block)
|
112
|
+
spec = self
|
113
|
+
|
114
|
+
@namespace ||= KubeDSL.namespace do
|
115
|
+
metadata do
|
116
|
+
name "#{spec.selector_app}-#{spec.definition.environment}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
@namespace.instance_eval(&block) if block
|
121
|
+
@namespace
|
122
|
+
end
|
123
|
+
|
124
|
+
def resources
|
125
|
+
@resources ||= Manifest.new([
|
126
|
+
namespace,
|
127
|
+
*@plugins.flat_map { |_, plugin| plugin.resources }
|
128
|
+
])
|
129
|
+
end
|
130
|
+
|
131
|
+
def set_tag(tag)
|
132
|
+
plugin(:rails_app).set_image("#{docker.metadata.image_url}:#{tag}")
|
133
|
+
end
|
134
|
+
|
135
|
+
def selector_app
|
136
|
+
@selector_app ||= definition.app_name.downcase
|
137
|
+
end
|
138
|
+
|
139
|
+
def docker
|
140
|
+
definition.docker
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def latest_tag
|
146
|
+
@latest_tag ||= docker.tags.local.latest_tags.find do |tag|
|
147
|
+
tag != ::Kuby::Docker::Tags::LATEST
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/kuby/railtie.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'rails/railtie'
|
3
|
+
|
4
|
+
module Kuby
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
rake_tasks do
|
7
|
+
load File.expand_path(File.join('tasks', 'kuby.rake'), __dir__)
|
8
|
+
end
|
9
|
+
|
10
|
+
initializer 'kuby.startup' do |_app|
|
11
|
+
Kuby.logger = Kuby::BasicLogger.new(STDERR)
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer 'kuby.health_check_middleware' do |app|
|
15
|
+
app.middleware.use Kuby::Middleware::HealthCheck
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/kuby/tasks.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'rouge'
|
2
|
+
|
3
|
+
module Kuby
|
4
|
+
class Tasks
|
5
|
+
attr_reader :definition
|
6
|
+
|
7
|
+
def initialize(definition)
|
8
|
+
@definition = definition
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_dockerfile
|
12
|
+
theme = Rouge::Themes::Base16::Solarized.new
|
13
|
+
formatter = Rouge::Formatters::Terminal256.new(theme)
|
14
|
+
lexer = Rouge::Lexers::Docker.new
|
15
|
+
tokens = lexer.lex(Kuby.definition.docker.to_dockerfile.to_s)
|
16
|
+
puts formatter.format(tokens)
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
docker.cli.build(
|
21
|
+
dockerfile: docker.to_dockerfile,
|
22
|
+
image_url: docker.metadata.image_url,
|
23
|
+
tags: docker.metadata.tags
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
dockerfile = docker.to_dockerfile
|
29
|
+
|
30
|
+
docker.cli.run(
|
31
|
+
image_url: docker.metadata.image_url,
|
32
|
+
tag: 'latest',
|
33
|
+
ports: dockerfile.exposed_ports
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def push
|
38
|
+
image_url = docker.metadata.image_url
|
39
|
+
|
40
|
+
begin
|
41
|
+
docker.tags.local.latest_tags.each do |tag|
|
42
|
+
docker.cli.push(image_url, tag)
|
43
|
+
end
|
44
|
+
rescue Kuby::Docker::MissingTagError => e
|
45
|
+
msg = "#{e.message} Run rake kuby:build to build the"\
|
46
|
+
'Docker image before running this task.'
|
47
|
+
|
48
|
+
Kuby.logger.fatal(msg)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def print_resources
|
53
|
+
kubernetes.resources.each do |res|
|
54
|
+
puts res.to_resource.serialize.to_yaml
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def print_kubeconfig
|
59
|
+
path = kubernetes.provider.kubeconfig_path
|
60
|
+
Kuby.logger.info("Printing contents of #{path}")
|
61
|
+
puts File.read(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def remote_logs
|
65
|
+
kubernetes_cli.logtail(namespace, match_labels.serialize)
|
66
|
+
end
|
67
|
+
|
68
|
+
def remote_status
|
69
|
+
kubernetes_cli.run_cmd(['-n', namespace, 'get', 'pods'])
|
70
|
+
end
|
71
|
+
|
72
|
+
def remote_shell
|
73
|
+
first_pod = get_first_pod
|
74
|
+
shell = docker.distro_spec.shell_exe
|
75
|
+
kubernetes_cli.exec_cmd(shell, namespace, first_pod.dig('metadata', 'name'))
|
76
|
+
end
|
77
|
+
|
78
|
+
def remote_console
|
79
|
+
first_pod = get_first_pod
|
80
|
+
|
81
|
+
kubernetes_cli.exec_cmd(
|
82
|
+
'bundle exec rails console', namespace, first_pod.dig('metadata', 'name')
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def remote_dbconsole
|
87
|
+
first_pod = get_first_pod
|
88
|
+
|
89
|
+
kubernetes_cli.exec_cmd(
|
90
|
+
'bundle exec rails dbconsole', namespace, first_pod.dig('metadata', 'name')
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def get_first_pod
|
97
|
+
pods = kubernetes_cli.get_objects(
|
98
|
+
'pods', namespace, match_labels.serialize
|
99
|
+
)
|
100
|
+
|
101
|
+
if pods.empty?
|
102
|
+
raise Kuby::Kubernetes::MissingResourceError,
|
103
|
+
"Couldn't find any running pods in namespace '#{namespace}' :("
|
104
|
+
|
105
|
+
exit 1
|
106
|
+
end
|
107
|
+
|
108
|
+
pods.first
|
109
|
+
end
|
110
|
+
|
111
|
+
def namespace
|
112
|
+
kubernetes.namespace.metadata.name
|
113
|
+
end
|
114
|
+
|
115
|
+
def match_labels
|
116
|
+
rails_app.deployment.spec.selector.match_labels
|
117
|
+
end
|
118
|
+
|
119
|
+
def rails_app
|
120
|
+
kubernetes.plugin(:rails_app)
|
121
|
+
end
|
122
|
+
|
123
|
+
def kubernetes_cli
|
124
|
+
kubernetes.provider.kubernetes_cli
|
125
|
+
end
|
126
|
+
|
127
|
+
def kubernetes
|
128
|
+
Kuby.definition.kubernetes
|
129
|
+
end
|
130
|
+
|
131
|
+
def docker
|
132
|
+
Kuby.definition.docker
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
namespace :kuby do
|
2
|
+
def tasks
|
3
|
+
@tasks ||= Kuby::Tasks.new(Kuby.definition)
|
4
|
+
end
|
5
|
+
|
6
|
+
task dockerfile: :environment do
|
7
|
+
tasks.print_dockerfile
|
8
|
+
end
|
9
|
+
|
10
|
+
task build: :environment do
|
11
|
+
tasks.build
|
12
|
+
end
|
13
|
+
|
14
|
+
task run: :environment do
|
15
|
+
tasks.run
|
16
|
+
end
|
17
|
+
|
18
|
+
task push: :environment do
|
19
|
+
tasks.push
|
20
|
+
end
|
21
|
+
|
22
|
+
task resources: :environment do
|
23
|
+
tasks.print_resources
|
24
|
+
end
|
25
|
+
|
26
|
+
task deploy: :environment do
|
27
|
+
Kuby.definition.kubernetes.deploy
|
28
|
+
end
|
29
|
+
|
30
|
+
task rollback: :environment do
|
31
|
+
Kuby.definition.kubernetes.rollback
|
32
|
+
end
|
33
|
+
|
34
|
+
task kubeconfig: :environment do
|
35
|
+
tasks.print_kubeconfig
|
36
|
+
end
|
37
|
+
|
38
|
+
task setup: :environment do
|
39
|
+
Kuby.definition.kubernetes.setup
|
40
|
+
end
|
41
|
+
|
42
|
+
namespace :remote do
|
43
|
+
task logs: :environment do
|
44
|
+
tasks.remote_logs
|
45
|
+
end
|
46
|
+
|
47
|
+
task status: :environment do
|
48
|
+
tasks.remote_status
|
49
|
+
end
|
50
|
+
|
51
|
+
task shell: :environment do
|
52
|
+
tasks.remote_shell
|
53
|
+
end
|
54
|
+
|
55
|
+
task console: :environment do
|
56
|
+
tasks.remote_console
|
57
|
+
end
|
58
|
+
|
59
|
+
task dbconsole: :environment do
|
60
|
+
tasks.remote_dbconsole
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|