kuby-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +21 -0
  5. data/README.md +186 -0
  6. data/Rakefile +14 -0
  7. data/kuby-core.gemspec +27 -0
  8. data/lib/kuby.rb +112 -0
  9. data/lib/kuby/basic_logger.rb +22 -0
  10. data/lib/kuby/cli_base.rb +75 -0
  11. data/lib/kuby/definition.rb +29 -0
  12. data/lib/kuby/docker.rb +27 -0
  13. data/lib/kuby/docker/alpine.rb +62 -0
  14. data/lib/kuby/docker/assets_phase.rb +11 -0
  15. data/lib/kuby/docker/bundler_phase.rb +56 -0
  16. data/lib/kuby/docker/cli.rb +72 -0
  17. data/lib/kuby/docker/copy_phase.rb +23 -0
  18. data/lib/kuby/docker/credentials.rb +11 -0
  19. data/lib/kuby/docker/debian.rb +66 -0
  20. data/lib/kuby/docker/dockerfile.rb +128 -0
  21. data/lib/kuby/docker/errors.rb +22 -0
  22. data/lib/kuby/docker/layer_stack.rb +42 -0
  23. data/lib/kuby/docker/local_tags.rb +38 -0
  24. data/lib/kuby/docker/metadata.rb +69 -0
  25. data/lib/kuby/docker/package_list.rb +34 -0
  26. data/lib/kuby/docker/package_phase.rb +59 -0
  27. data/lib/kuby/docker/packages.rb +10 -0
  28. data/lib/kuby/docker/packages/managed_package.rb +29 -0
  29. data/lib/kuby/docker/packages/nodejs.rb +29 -0
  30. data/lib/kuby/docker/packages/package.rb +22 -0
  31. data/lib/kuby/docker/packages/yarn.rb +47 -0
  32. data/lib/kuby/docker/phase.rb +21 -0
  33. data/lib/kuby/docker/remote_tags.rb +24 -0
  34. data/lib/kuby/docker/setup_phase.rb +29 -0
  35. data/lib/kuby/docker/spec.rb +147 -0
  36. data/lib/kuby/docker/tags.rb +39 -0
  37. data/lib/kuby/docker/timestamp_tag.rb +36 -0
  38. data/lib/kuby/docker/webserver_phase.rb +51 -0
  39. data/lib/kuby/docker/yarn_phase.rb +11 -0
  40. data/lib/kuby/kubernetes.rb +16 -0
  41. data/lib/kuby/kubernetes/deployer.rb +94 -0
  42. data/lib/kuby/kubernetes/docker_config.rb +27 -0
  43. data/lib/kuby/kubernetes/errors.rb +22 -0
  44. data/lib/kuby/kubernetes/manifest.rb +56 -0
  45. data/lib/kuby/kubernetes/minikube_provider.rb +51 -0
  46. data/lib/kuby/kubernetes/plugin.rb +55 -0
  47. data/lib/kuby/kubernetes/plugins.rb +8 -0
  48. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +61 -0
  49. data/lib/kuby/kubernetes/plugins/rails_app.rb +16 -0
  50. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +58 -0
  51. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +142 -0
  52. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +393 -0
  53. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +10 -0
  54. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +13 -0
  55. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +10 -0
  56. data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +23 -0
  57. data/lib/kuby/kubernetes/provider.rb +77 -0
  58. data/lib/kuby/kubernetes/registry_secret.rb +26 -0
  59. data/lib/kuby/kubernetes/spec.rb +152 -0
  60. data/lib/kuby/middleware.rb +5 -0
  61. data/lib/kuby/middleware/health_check.rb +16 -0
  62. data/lib/kuby/railtie.rb +18 -0
  63. data/lib/kuby/tasks.rb +135 -0
  64. data/lib/kuby/tasks/kuby.rake +63 -0
  65. data/lib/kuby/trailing_hash.rb +19 -0
  66. data/lib/kuby/version.rb +3 -0
  67. 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
@@ -0,0 +1,5 @@
1
+ module Kuby
2
+ module Middleware
3
+ autoload :HealthCheck, 'kuby/middleware/health_check'
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module Kuby
2
+ module Middleware
3
+ class HealthCheck
4
+ attr_reader :app
5
+
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ return [204, {}, ['']] if env.fetch('PATH_INFO') == '/healthz'
12
+ app.call(env)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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
@@ -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