kuby-core 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.
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