kuby-core 0.7.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/Gemfile +1 -0
  4. data/README.md +11 -1
  5. data/bin/kuby +4 -0
  6. data/kuby-core.gemspec +9 -4
  7. data/lib/kuby.rb +58 -19
  8. data/lib/kuby/basic_logger.rb +13 -0
  9. data/lib/kuby/cli_base.rb +81 -8
  10. data/lib/kuby/commands.rb +244 -0
  11. data/lib/kuby/definition.rb +1 -11
  12. data/lib/kuby/dev_setup.rb +255 -0
  13. data/lib/kuby/docker.rb +1 -0
  14. data/lib/kuby/docker/bundler_phase.rb +3 -3
  15. data/lib/kuby/docker/cli.rb +13 -1
  16. data/lib/kuby/docker/dev_spec.rb +131 -0
  17. data/lib/kuby/docker/dockerfile.rb +16 -1
  18. data/lib/kuby/docker/layer.rb +4 -4
  19. data/lib/kuby/docker/layer_stack.rb +4 -0
  20. data/lib/kuby/docker/local_tags.rb +4 -0
  21. data/lib/kuby/docker/metadata.rb +18 -38
  22. data/lib/kuby/docker/package_phase.rb +2 -2
  23. data/lib/kuby/docker/setup_phase.rb +3 -2
  24. data/lib/kuby/docker/spec.rb +42 -16
  25. data/lib/kuby/docker/timestamp_tag.rb +6 -0
  26. data/lib/kuby/environment.rb +15 -2
  27. data/lib/kuby/kubernetes.rb +9 -11
  28. data/lib/kuby/kubernetes/deploy_task.rb +4 -1
  29. data/lib/kuby/kubernetes/deployer.rb +70 -15
  30. data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +8 -4
  31. data/lib/kuby/kubernetes/provider.rb +12 -8
  32. data/lib/kuby/kubernetes/spec.rb +30 -29
  33. data/lib/kuby/plugin.rb +59 -0
  34. data/lib/kuby/plugin_registry.rb +27 -0
  35. data/lib/kuby/plugins.rb +6 -0
  36. data/lib/kuby/plugins/nginx_ingress.rb +71 -0
  37. data/lib/kuby/plugins/rails_app.rb +18 -0
  38. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +117 -0
  39. data/lib/kuby/plugins/rails_app/assets.rb +347 -0
  40. data/lib/kuby/plugins/rails_app/database.rb +74 -0
  41. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/generators/kuby.rb +14 -16
  42. data/lib/kuby/plugins/rails_app/mysql.rb +152 -0
  43. data/lib/kuby/plugins/rails_app/plugin.rb +593 -0
  44. data/lib/kuby/plugins/rails_app/postgres.rb +143 -0
  45. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +11 -0
  46. data/lib/kuby/plugins/rails_app/sqlite.rb +32 -0
  47. data/lib/kuby/plugins/rails_app/tasks.rake +36 -0
  48. data/lib/kuby/rails_commands.rb +89 -0
  49. data/lib/kuby/railtie.rb +0 -4
  50. data/lib/kuby/tasks.rb +85 -31
  51. data/lib/kuby/version.rb +1 -1
  52. data/spec/docker/metadata_spec.rb +84 -0
  53. data/spec/docker/spec_spec.rb +266 -0
  54. data/spec/docker/timestamp_tag_spec.rb +54 -4
  55. data/spec/dummy/Gemfile +54 -0
  56. data/spec/dummy/Gemfile.lock +223 -0
  57. data/spec/dummy/README.md +24 -0
  58. data/spec/dummy/Rakefile +6 -0
  59. data/spec/dummy/app/assets/config/manifest.js +2 -0
  60. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  61. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  62. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  63. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  64. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  65. data/spec/dummy/app/javascript/channels/consumer.js +6 -0
  66. data/spec/dummy/app/javascript/channels/index.js +5 -0
  67. data/spec/dummy/app/javascript/packs/application.js +17 -0
  68. data/spec/dummy/app/jobs/application_job.rb +7 -0
  69. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  70. data/spec/dummy/app/models/application_record.rb +3 -0
  71. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  72. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  73. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  74. data/spec/dummy/bin/bundle +114 -0
  75. data/spec/dummy/bin/rails +9 -0
  76. data/spec/dummy/bin/rake +9 -0
  77. data/spec/dummy/bin/setup +36 -0
  78. data/spec/dummy/bin/spring +17 -0
  79. data/spec/dummy/bin/yarn +11 -0
  80. data/spec/dummy/config.ru +5 -0
  81. data/spec/dummy/config/application.rb +19 -0
  82. data/spec/dummy/config/boot.rb +4 -0
  83. data/spec/dummy/config/cable.yml +10 -0
  84. data/spec/dummy/config/credentials.yml.enc +1 -0
  85. data/spec/dummy/config/database.yml +25 -0
  86. data/spec/dummy/config/environment.rb +5 -0
  87. data/spec/dummy/config/environments/development.rb +62 -0
  88. data/spec/dummy/config/environments/production.rb +112 -0
  89. data/spec/dummy/config/environments/test.rb +49 -0
  90. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  91. data/spec/dummy/config/initializers/assets.rb +14 -0
  92. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  93. data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
  94. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  95. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  96. data/spec/dummy/config/initializers/inflections.rb +16 -0
  97. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  98. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  99. data/spec/dummy/config/locales/en.yml +33 -0
  100. data/spec/dummy/config/master.key +1 -0
  101. data/spec/dummy/config/puma.rb +38 -0
  102. data/spec/dummy/config/routes.rb +3 -0
  103. data/spec/dummy/config/spring.rb +6 -0
  104. data/spec/dummy/config/storage.yml +34 -0
  105. data/spec/dummy/db/seeds.rb +7 -0
  106. data/spec/dummy/package.json +11 -0
  107. data/spec/dummy/public/404.html +67 -0
  108. data/spec/dummy/public/422.html +67 -0
  109. data/spec/dummy/public/500.html +66 -0
  110. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  111. data/spec/dummy/public/apple-touch-icon.png +0 -0
  112. data/spec/dummy/public/favicon.ico +0 -0
  113. data/spec/dummy/public/robots.txt +1 -0
  114. data/spec/dummy/test/application_system_test_case.rb +5 -0
  115. data/spec/dummy/test/channels/application_cable/connection_test.rb +11 -0
  116. data/spec/dummy/test/test_helper.rb +13 -0
  117. data/spec/dummy/tmp/cache/bootsnap-load-path-cache +0 -0
  118. data/spec/spec_helper.rb +77 -2
  119. data/spec/support/docker/fake_cli.rb +54 -0
  120. data/spec/support/docker/remote/fake_client.rb +16 -0
  121. data/spec/trailing_hash_spec.rb +23 -0
  122. metadata +139 -30
  123. data/lib/ext/krane/kubernetes_resource.rb +0 -16
  124. data/lib/kuby/kubernetes/plugin.rb +0 -55
  125. data/lib/kuby/kubernetes/plugins.rb +0 -8
  126. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +0 -73
  127. data/lib/kuby/kubernetes/plugins/rails_app.rb +0 -16
  128. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +0 -79
  129. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +0 -154
  130. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +0 -379
  131. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +0 -142
  132. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +0 -13
  133. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +0 -30
  134. data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +0 -28
  135. data/lib/kuby/tasks/kuby.rake +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a163afc89c23d091afce04277e9308252ae8335f8137f2136b68355dccb74bc2
4
- data.tar.gz: 617007a29710ec9359dc4f3d5b31d850e22a0eacdc70175c444cf6d94a53462f
3
+ metadata.gz: f61bf7246e7fed1b51421252d1fee25da0bf1ad35da792e9f705d306ad5aeeba
4
+ data.tar.gz: fcaed876fd5da816cd1458c41c19fc735a7d3e5de2d23879427c526f2f0f94ea
5
5
  SHA512:
6
- metadata.gz: 8fd6302f94ffa2a740928bd324ca5e2bbe251f49356ce582e581c4d91740b66425b6c1a3b392e40fe7c52e649781fe939fa377d6894e435e00386ddaed0f4ee3
7
- data.tar.gz: 4ae55a8fc6c4aeceac417b54e41f679a988c34788e5740e268ddeb728223f949915c17e8bc738ffcd744a257a1d5ba74cf431d1df90c8b5b496eaa51861229d6
6
+ metadata.gz: 78f6cad56fd00b1265a43ac36169da5d46b15c50ceb5afce6f519ef175ff3949f84204ccf40ad9531b9dbb6b357d38ac0396e6969d848bfb17b42ba1c273ba6d
7
+ data.tar.gz: 056b9a151fd6f11079eb87fd5c0b7c4f92087cd8455a361ac7e668e9b94e4fc99a866b91c4f30d0a3eda582b0c2cafaac2e3ee882daebec0a52f1fd748786871
@@ -1,3 +1,45 @@
1
+ ## 0.10.0
2
+ * Set default database user and password in dev environment.
3
+ * Add ability to run rake tasks in dev environment.
4
+ * Disallow running rails and rake tasks in non-dev environments.
5
+ * Don't run database config through ERB.
6
+ - Rails env often isn't loaded, so ERB rendering can blow up with `NoMethodError`s, etc.
7
+ - All we really need to know is what database engine to stand up.
8
+ * Require database user/password to be added manually to Kuby config.
9
+
10
+ ## 0.9.1
11
+ * Run dev setup when asked to.
12
+ - Bug caused dev setup to be skipped even when requested.
13
+ * Deployer should be tolerant of missing namespace.
14
+
15
+ ## 0.9.0
16
+ * Add support for developing your app using a local Kubernetes cluster.
17
+ - Includes a default `:development` Kuby environment.
18
+ * Remove rake tasks in favor of a `kuby` executable powered by [GLI](https://github.com/davetron5000/gli).
19
+ * Rename the `minikube` provider to `docker_desktop`, which is more accurate.
20
+ * Add more tests.
21
+ * Avoid running commands inside pods that aren't running or that are marked for deletion (#15).
22
+ * Pass `RAILS_MASTER_KEY` to Docker build (#14).
23
+ * Add `kuby remote restart` command for manually restarting Rails pods.
24
+ * Automatically restart Rails pods if deploy doesn't change the Docker image URL (#11).
25
+
26
+ ## 0.8.1
27
+ * Fix database config rewriter task.
28
+ - Broke with refactoring of database config code.
29
+ * More correctly parse Docker image URLs.
30
+ - It can be challenging to identify the hostname in image URLs because 1) the host can be omitted, and 2) the scheme is often omitted.
31
+ - The new strategy is to look for a "." in the first segment of the URL if there is no scheme. It's not bulletproof but is better than what we had before, which was to assume the first segment was the host. Eg. for an image URL like camertron/foo, we would identify the host as "camertron."
32
+ * Add a number of tests and a Rails dummy app in spec/.
33
+
34
+ ## 0.8.0
35
+ * Upgrade to Krane >= 1.1.4, < 2.0.
36
+ * Remove Krane monkeypatch in ext/.
37
+ * Implement a Rails static asset server.
38
+ * Move plugins from `Kuby::Kubernetes` namespace to `Kuby` namespace.
39
+ - This is to eventually enable plugins to modify the Dockerfile and introduce additional Dockerfiles (i.e. to enable a development mode, etc).
40
+ * Pass `Environment` instead of `Definition` instances around.
41
+ - Providers, plugins, etc all take `Definition` instances. `Definition#kubernetes`, for example, returns the Kubernetes spec for the `Environment` specified by `KUBY_ENV` (or the first env defined if `KUBY_ENV` is not set). This is a problem for Kuby configs that specify multiple environments, and causes plugins to make changes to the default environment instead of the one they've been specifically added to. For example, if the `:production` env is defined first, the `:development` env still gets a cluster issuer from cert-manager even though `enable_tls` is set to `false`.
42
+
1
43
  ## 0.7.2
2
44
  * Fix issue causing `Kuby.environment(...)` to raise an `UndefinedEnvironmentError` for existing environments.
3
45
 
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ end
9
9
 
10
10
  group :test do
11
11
  gem 'rspec', '~> 3.0'
12
+ gem 'timecop', '~> 0.9'
12
13
  end
data/README.md CHANGED
@@ -8,6 +8,16 @@ At its core, Kuby is a set of tools and smart defaults that encapsulate and codi
8
8
 
9
9
  Under the hood, Kuby leverages the power of Docker and Kubernetes. It tries to make these technologies accessible to the average Rails dev without requiring a devops black belt.
10
10
 
11
+ ## Why Kuby?
12
+
13
+ Kuby embraces the same convention-over-configuration approach that Rails does. It aims to reduce the cognitive overhead associated with learning a bunch of ops tools to get your app onto the internet. In other words, Kuby does a whole lot for you. Specifically, it:
14
+
15
+ * leverages Docker and Kubernetes, industry-leading infrastructure tools.
16
+ * automatically configures your app with a TLS certificate from [LetsEncrypt](https://letsencrypt.org/).
17
+ * automatically spins up a database instance based on what's in your database.yml.
18
+ * runs a separate server for your static assets.
19
+ * features a powerful plugin system that allows, for example, easy [Sidekiq integration](https://github.com/getkuby/kuby-sidekiq).
20
+
11
21
  ## Getting Started
12
22
 
13
23
  See the [Quick Start Guide](https://github.com/getkuby/kuby-core/wiki/Quick-Start-Guide)
@@ -18,7 +28,7 @@ See the [wiki](https://github.com/getkuby/kuby-core/wiki).
18
28
 
19
29
  ## Running Tests
20
30
 
21
- `bundle exec rspec` should do the trick... or at least it would if there were any tests. Don't worry, it's on my radar.
31
+ `bundle exec rspec` should do the trick. Test coverage is very minimal at the moment, however.
22
32
 
23
33
  ## License
24
34
 
@@ -0,0 +1,4 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'kuby'
4
+ exit Kuby::Commands.run(ARGV)
@@ -14,17 +14,22 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.add_dependency 'colorize', '~> 0.8'
16
16
  s.add_dependency 'docker-remote', '~> 0.1'
17
- s.add_dependency 'krane', '~> 1.0'
18
- s.add_dependency 'kuby-cert-manager', '~> 0.2'
17
+ s.add_dependency 'gli', '~> 2.0'
18
+ s.add_dependency 'helm-cli', '~> 0.3'
19
+ # See: https://github.com/Shopify/krane/pull/720
20
+ # See: https://github.com/Shopify/krane/blob/master/CHANGELOG.md#114
21
+ s.add_dependency 'krane', '>= 1.1.4', '< 2.0'
22
+ s.add_dependency 'kuby-cert-manager', '>= 0.3'
19
23
  s.add_dependency 'kube-dsl', '~> 0.3'
20
- s.add_dependency 'kuby-kube-db', '~> 0.4'
21
- s.add_dependency 'kubernetes-cli', '~> 0.2'
24
+ s.add_dependency 'kuby-kube-db', '>= 0.6'
25
+ s.add_dependency 'kubernetes-cli', '~> 0.3'
22
26
  s.add_dependency 'railties', '>= 5.1'
23
27
  s.add_dependency 'rouge', '~> 3.0'
24
28
 
25
29
  s.add_development_dependency 'rspec'
26
30
 
27
31
  s.require_path = 'lib'
32
+ s.executables << 'kuby'
28
33
 
29
34
  s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'kuby-core.gemspec']
30
35
  end
@@ -1,29 +1,46 @@
1
1
  require 'kuby/railtie'
2
2
 
3
3
  begin
4
- require 'kuby/kubernetes/plugins/rails_app/generators/kuby'
4
+ require 'kuby/plugins/rails_app/generators/kuby'
5
5
  rescue NameError
6
6
  end
7
7
 
8
8
  module Kuby
9
- autoload :BasicLogger, 'kuby/basic_logger'
10
- autoload :CLIBase, 'kuby/cli_base'
11
- autoload :Definition, 'kuby/definition'
12
- autoload :Docker, 'kuby/docker'
13
- autoload :Environment, 'kuby/environment'
14
- autoload :Kubernetes, 'kuby/kubernetes'
15
- autoload :Middleware, 'kuby/middleware'
16
- autoload :Tasks, 'kuby/tasks'
17
- autoload :TrailingHash, 'kuby/trailing_hash'
9
+ autoload :BasicLogger, 'kuby/basic_logger'
10
+ autoload :CLIBase, 'kuby/cli_base'
11
+ autoload :Commands, 'kuby/commands'
12
+ autoload :Definition, 'kuby/definition'
13
+ autoload :DevSetup, 'kuby/dev_setup'
14
+ autoload :Docker, 'kuby/docker'
15
+ autoload :Environment, 'kuby/environment'
16
+ autoload :Kubernetes, 'kuby/kubernetes'
17
+ autoload :Middleware, 'kuby/middleware'
18
+ autoload :Plugin, 'kuby/plugin'
19
+ autoload :PluginRegistry, 'kuby/plugin_registry'
20
+ autoload :Plugins, 'kuby/plugins'
21
+ autoload :RailsCommands, 'kuby/rails_commands'
22
+ autoload :Tasks, 'kuby/tasks'
23
+ autoload :TrailingHash, 'kuby/trailing_hash'
24
+
25
+ DEFAULT_ENV = 'development'.freeze
26
+ DEFAULT_DB_USER = 'root'.freeze
27
+ DEFAULT_DB_PASSWORD = 'password'.freeze
18
28
 
19
29
  class UndefinedEnvironmentError < StandardError; end
30
+ class MissingConfigError < StandardError; end
20
31
 
21
32
  class << self
22
33
  attr_reader :definition
23
34
  attr_writer :logger
24
35
 
25
- def load!
26
- require ENV['KUBY_CONFIG'] || File.join('.', 'kuby.rb')
36
+ def load!(config_file = nil)
37
+ config_file ||= ENV['KUBY_CONFIG'] || File.join('.', 'kuby.rb')
38
+
39
+ unless File.exist?(config_file)
40
+ raise MissingConfigError, "couldn't find Kuby config file at #{config_file}"
41
+ end
42
+
43
+ require config_file
27
44
  end
28
45
 
29
46
  def define(name, &block)
@@ -32,9 +49,27 @@ module Kuby
32
49
  @definition = Definition.new(name.to_s)
33
50
  @definition.instance_eval(&block)
34
51
 
52
+ # default development environment
53
+ @definition.environment(:development) do
54
+ kubernetes do
55
+ add_plugin(:rails_app) do
56
+ tls_enabled false
57
+
58
+ database do
59
+ user(DEFAULT_DB_USER) if respond_to?(:user)
60
+ password(DEFAULT_DB_PASSWORD) if respond_to?(:password)
61
+ end
62
+ end
63
+
64
+ provider :docker_desktop
65
+ end
66
+ end
67
+
35
68
  @definition.environments.each do |_, env|
36
69
  env.kubernetes.after_configuration
37
70
  end
71
+
72
+ @definition
38
73
  end
39
74
 
40
75
  def environment(name = env)
@@ -52,8 +87,8 @@ module Kuby
52
87
  @providers ||= {}
53
88
  end
54
89
 
55
- def register_plugin(plugin_name, plugin_klass)
56
- plugins[plugin_name] = plugin_klass
90
+ def register_plugin(*args, **kwargs)
91
+ plugins.register(*args, **kwargs)
57
92
  end
58
93
 
59
94
  def register_distro(distro_name, distro_klass)
@@ -65,7 +100,7 @@ module Kuby
65
100
  end
66
101
 
67
102
  def plugins
68
- @plugins ||= {}
103
+ @plugins ||= PluginRegistry.new
69
104
  end
70
105
 
71
106
  def logger
@@ -95,20 +130,24 @@ module Kuby
95
130
  @packages ||= {}
96
131
  end
97
132
 
133
+ def env=(env_name)
134
+ @env = env_name.to_s
135
+ end
136
+
98
137
  def env
99
138
  ENV.fetch('KUBY_ENV') do
100
- (definition.environments.keys.first || Rails.env).to_s
139
+ (@env || Rails.env rescue nil || DEFAULT_ENV).to_s
101
140
  end
102
141
  end
103
142
  end
104
143
  end
105
144
 
106
145
  # providers
107
- Kuby.register_provider(:minikube, Kuby::Kubernetes::MinikubeProvider)
146
+ Kuby.register_provider(:docker_desktop, Kuby::Kubernetes::DockerDesktopProvider)
108
147
 
109
148
  # plugins
110
- Kuby.register_plugin(:rails_app, Kuby::Kubernetes::Plugins::RailsApp::Plugin)
111
- Kuby.register_plugin(:nginx_ingress, Kuby::Kubernetes::Plugins::NginxIngress)
149
+ Kuby.register_plugin(:rails_app, Kuby::Plugins::RailsApp::Plugin)
150
+ Kuby.register_plugin(:nginx_ingress, Kuby::Plugins::NginxIngress)
112
151
 
113
152
  # distros
114
153
  Kuby.register_distro(:debian, Kuby::Docker::Debian)
@@ -18,5 +18,18 @@ module Kuby
18
18
  def fatal(msg, *args)
19
19
  super(ColorizedString[msg].red, *args)
20
20
  end
21
+
22
+ # adhere to the "CLI" interface
23
+ def with_pipes(out = STDOUT, err = STDERR)
24
+ previous_logdev = @logdev || STDERR
25
+ reopen(err)
26
+ yield
27
+ ensure
28
+ reopen(previous_logdev)
29
+ end
30
+
31
+ def last_status
32
+ nil
33
+ end
21
34
  end
22
35
  end
@@ -17,17 +17,59 @@ module Kuby
17
17
  @after_execute << block
18
18
  end
19
19
 
20
+ def with_pipes(out = STDOUT, err = STDERR)
21
+ previous_stdout = self.stdout
22
+ previous_stderr = self.stderr
23
+ self.stdout = out
24
+ self.stderr = err
25
+ yield
26
+ ensure
27
+ self.stdout = previous_stdout
28
+ self.stderr = previous_stderr
29
+ end
30
+
31
+ def stdout
32
+ Thread.current[stdout_key] || STDOUT
33
+ end
34
+
35
+ def stdout=(new_stdout)
36
+ Thread.current[stdout_key] = new_stdout
37
+ end
38
+
39
+ def stderr
40
+ Thread.current[stderr_key] || STDERR
41
+ end
42
+
43
+ def stderr=(new_stderr)
44
+ Thread.current[stderr_key] = new_stderr
45
+ end
46
+
20
47
  private
21
48
 
22
49
  def open3_w(env, cmd, opts = {}, &block)
23
50
  run_before_callbacks(cmd)
24
51
  cmd_s = cmd.join(' ')
25
52
 
26
- Open3.pipeline_w([env, cmd_s], opts) do |stdin, wait_threads|
27
- yield(stdin, wait_threads).tap do
28
- stdin.close
29
- self.last_status = wait_threads.last.value
53
+ Open3.popen3(env, cmd_s, opts) do |p_stdin, p_stdout, p_stderr, wait_thread|
54
+ Thread.new(stdout) do |t_stdout|
55
+ begin
56
+ p_stdout.each { |line| t_stdout.puts(line) }
57
+ rescue IOError
58
+ end
59
+ end
60
+
61
+ Thread.new(stderr) do |t_stderr|
62
+ begin
63
+ p_stderr.each { |line| t_stderr.puts(line) }
64
+ rescue IOError
65
+ end
66
+ end
67
+
68
+ yield(p_stdin).tap do
69
+ p_stdin.close
70
+ self.last_status = wait_thread.value
30
71
  run_after_callbacks(cmd)
72
+ wait_thread.join
31
73
  end
32
74
  end
33
75
  end
@@ -41,19 +83,42 @@ module Kuby
41
83
  def systemm(cmd)
42
84
  run_before_callbacks(cmd)
43
85
  cmd_s = cmd.join(' ')
44
- system(cmd_s).tap do
45
- self.last_status = $?
86
+
87
+ Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
88
+ Thread.new(stdout) do |t_stdout|
89
+ p_stdout.each { |line| t_stdout.puts(line) }
90
+ end
91
+
92
+ Thread.new(stderr) do |t_stderr|
93
+ p_stderr.each { |line| t_stderr.puts(line) }
94
+ end
95
+
96
+ self.last_status = wait_thread.value
46
97
  run_after_callbacks(cmd)
98
+ wait_thread.join
47
99
  end
48
100
  end
49
101
 
50
102
  def backticks(cmd)
51
103
  run_before_callbacks(cmd)
52
104
  cmd_s = cmd.join(' ')
53
- `#{cmd_s}`.tap do
54
- self.last_status = $?
105
+ result = StringIO.new
106
+
107
+ Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
108
+ Thread.new do
109
+ p_stdout.each { |line| result.puts(line) }
110
+ end
111
+
112
+ Thread.new(stderr) do |t_stderr|
113
+ p_stderr.each { |line| t_stderr.puts(line) }
114
+ end
115
+
116
+ self.last_status = wait_thread.value
55
117
  run_after_callbacks(cmd)
118
+ wait_thread.join
56
119
  end
120
+
121
+ result.string
57
122
  end
58
123
 
59
124
  def run_before_callbacks(cmd)
@@ -71,5 +136,13 @@ module Kuby
71
136
  def status_key
72
137
  raise NotImplementedError, "#{__method__} must be defined in derived classes"
73
138
  end
139
+
140
+ def stdout_key
141
+ raise NotImplementedError, "#{__method__} must be defined in derived classes"
142
+ end
143
+
144
+ def stderr_key
145
+ raise NotImplementedError, "#{__method__} must be defined in derived classes"
146
+ end
74
147
  end
75
148
  end
@@ -0,0 +1,244 @@
1
+ require 'kuby/version'
2
+ require 'gli'
3
+
4
+ module Kuby
5
+ class Commands
6
+ extend GLI::App
7
+
8
+ # GLI doesn't have a wildcard option, so it's impossible to tell it to
9
+ # slurp up all args after a certain point. In our case, we want to be
10
+ # able to invoke `rails` commands and pass through all the original
11
+ # flags, switches, etc. To get around GLI's limitations, we identify
12
+ # `rails` commands in this hijacked `run` method and only use GLI to
13
+ # parse global options (like -e). The rest of the Rails options are
14
+ # captured in an instance variable and thereby made available to the
15
+ # Rails command handlers defined below. We use Module#prepend here to
16
+ # avoid the usual series of cryptic alias_method calls (note that there
17
+ # is no singleton class version of #prepend in the Ruby language).
18
+ singleton_class.send(:prepend, Module.new do
19
+ def run(args)
20
+ if idx = args.index('rails') || idx = args.index('rake')
21
+ @rails_options = args[idx..-1]
22
+ super(args[0..idx])
23
+ else
24
+ super
25
+ end
26
+ end
27
+ end)
28
+
29
+ def self.tasks
30
+ Kuby::Tasks.new(Kuby.environment)
31
+ end
32
+
33
+ def self.must_be_dev_env!
34
+ unless Kuby.environment.development?
35
+ fail "Command not supported in the '#{Kuby.environment.name}' environment"
36
+ end
37
+ end
38
+
39
+ program_desc 'Kuby command-line interface. Kuby is a convention '\
40
+ 'over configuration approach for running Rails apps in Kubernetes.'
41
+
42
+ version Kuby::VERSION
43
+
44
+ subcommand_option_handling :normal
45
+ arguments :loose
46
+
47
+ desc 'The Kuby environment to use. Overrides KUBY_ENV.'
48
+ flag [:e, :environment], type: String, required: false
49
+
50
+ desc 'Path to your Kuby config file. Overrides KUBY_CONFIG.'
51
+ default_value './kuby.rb'
52
+ flag [:c, :config]
53
+
54
+ pre do |global_options, options, args|
55
+ Kuby.env = global_options[:environment] if global_options[:environment]
56
+ Kuby.load!(global_options[:config])
57
+
58
+ # GLI will abort unless this block returns a truthy value
59
+ true
60
+ end
61
+
62
+ # These are only stubs included to fill out the help screens. Rails
63
+ # commands are handled by the RailsCommands class.
64
+ desc 'Runs a Rails command.'
65
+ command :rails do |rc|
66
+ rc.action do |global_options, options, args|
67
+ must_be_dev_env!
68
+ exit 1 unless tasks.dev_deployment_ok
69
+ Kuby::RailsCommands.run(@rails_options)
70
+ end
71
+
72
+ rc.desc 'Runs the rails server (run `rails server --help` for options)'
73
+ rc.command [:server, :s] do |c|
74
+ c.action do |global_options, options, args|
75
+ must_be_dev_env!
76
+ exit 1 unless tasks.dev_deployment_ok
77
+ Kuby::RailsCommands.run(@rails_options)
78
+ end
79
+ end
80
+
81
+ rc.desc 'Runs a script in the Rails environment (run `rails runner --help` for options)'
82
+ rc.command [:runner, :r] do |c|
83
+ c.action do |global_options, options, args|
84
+ must_be_dev_env!
85
+ exit 1 unless tasks.dev_deployment_ok
86
+ Kuby::RailsCommands.run(@rails_options)
87
+ end
88
+ end
89
+
90
+ rc.desc 'Starts an interactive Ruby console with the Rails environment loaded '\
91
+ '(run `rails console --help` for options)'
92
+ rc.command [:console, :c] do |c|
93
+ c.action do |global_options, options, args|
94
+ must_be_dev_env!
95
+ exit 1 unless tasks.dev_deployment_ok
96
+ Kuby::RailsCommands.run(@rails_options)
97
+ end
98
+ end
99
+ end
100
+
101
+ desc 'Runs a rake task.'
102
+ command :rake do |rc|
103
+ rc.action do |global_options, options, args|
104
+ must_be_dev_env!
105
+ exit 1 unless tasks.dev_deployment_ok
106
+ Kuby::RailsCommands.run(@rails_options)
107
+ end
108
+ end
109
+
110
+ desc 'Builds the Docker image.'
111
+ command :build do |c|
112
+ c.action do |global_options, options, args|
113
+ tasks.build
114
+ end
115
+ end
116
+
117
+ desc 'Pushes the Docker image to the configured registry.'
118
+ command :push do |c|
119
+ c.action do |global_options, options, args|
120
+ tasks.push
121
+ end
122
+ end
123
+
124
+ desc 'Gets your Kubernetes cluster ready to run your Rails app.'
125
+ command :setup do |c|
126
+ c.action do |global_options, options, args|
127
+ tasks.setup
128
+ end
129
+ end
130
+
131
+ desc 'Prints the effective Dockerfile used to build the Docker image.'
132
+ command :dockerfile do |c|
133
+ c.action do |global_options, options, args|
134
+ tasks.print_dockerfile
135
+ end
136
+ end
137
+
138
+ desc 'Deploys the application.'
139
+ command :deploy do |c|
140
+ c.desc 'The Docker tag to deploy. Defaults to the most recent tag.'
141
+ c.flag [:t, :tag], required: false
142
+ c.action do |global_options, options, args|
143
+ tasks.deploy(options[:tag])
144
+ end
145
+ end
146
+
147
+ desc 'Rolls back to the previous Docker tag.'
148
+ command :rollback do |c|
149
+ c.action do |global_options, options, args|
150
+ tasks.rollback
151
+ end
152
+ end
153
+
154
+ desc 'Prints the effective Kubernetes resources that will be applied on deploy.'
155
+ command :resources do |c|
156
+ c.action do |global_options, options, args|
157
+ tasks.print_resources
158
+ end
159
+ end
160
+
161
+ desc 'Prints out the contents of the kubeconfig Kuby is using to communicate with your cluster.'
162
+ command :kubeconfig do |c|
163
+ c.action do |global_options, options, args|
164
+ tasks.print_kubeconfig
165
+ end
166
+ end
167
+
168
+ desc 'Runs an arbitrary kubectl command.'
169
+ command :kubectl do |c|
170
+ c.desc 'Prefixes the kubectl command with the namespace associated with '\
171
+ 'the current environment. For example, if the Kuby env is "production", '\
172
+ 'this option will prefix the kubectl command with "-n myapp-production".'
173
+ c.switch [:N, :namespaced], default: false
174
+ c.action do |global_options, options, args|
175
+ if options[:namespaced]
176
+ # sorry Demeter
177
+ namespace = Kuby.definition.environment.kubernetes.namespace.metadata.name
178
+ args = ['-n', namespace, *args]
179
+ end
180
+
181
+ tasks.kubectl(*args)
182
+ end
183
+ end
184
+
185
+ desc 'Runs commands, etc against the Kubernetes cluster.'
186
+ command :remote do |rc|
187
+ rc.desc 'Tails (i.e. continuously streams) the Rails log from your running application.'
188
+ rc.command :logs do |c|
189
+ c.action do |global_options, options, args|
190
+ exit 1 unless tasks.dev_deployment_ok
191
+ tasks.remote_logs
192
+ end
193
+ end
194
+
195
+ rc.desc 'Lists running Kubernetes pods.'
196
+ rc.command :status do |c|
197
+ c.action do |global_options, options, args|
198
+ exit 1 unless tasks.dev_deployment_ok
199
+ tasks.remote_status
200
+ end
201
+ end
202
+
203
+ rc.desc 'Runs an arbitrary command inside a running Rails pod.'
204
+ rc.command :exec do |c|
205
+ c.action do |global_options, options, args|
206
+ exit 1 unless tasks.dev_deployment_ok
207
+ tasks.remote_exec(args)
208
+ end
209
+ end
210
+
211
+ rc.desc 'Establishes a shell inside a running Rails pod.'
212
+ rc.command :shell do |c|
213
+ c.action do |global_options, options, args|
214
+ exit 1 unless tasks.dev_deployment_ok
215
+ tasks.remote_shell
216
+ end
217
+ end
218
+
219
+ rc.desc 'Establishes a Rails console inside a running Rails pod.'
220
+ rc.command :console do |c|
221
+ c.action do |global_options, options, args|
222
+ exit 1 unless tasks.dev_deployment_ok
223
+ tasks.remote_console
224
+ end
225
+ end
226
+
227
+ rc.desc 'Establishes a database console inside a running Rails pod.'
228
+ rc.command :dbconsole do |c|
229
+ c.action do |global_options, options, args|
230
+ exit 1 unless tasks.dev_deployment_ok
231
+ tasks.remote_dbconsole
232
+ end
233
+ end
234
+
235
+ rc.desc "Restarts the Rails app's web pods."
236
+ rc.command :restart do |c|
237
+ c.action do |global_options, options, args|
238
+ exit 1 unless tasks.dev_deployment_ok
239
+ tasks.remote_restart
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end