kuby-core 0.8.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/Gemfile +1 -0
  4. data/README.md +11 -1
  5. data/bin/kuby +4 -0
  6. data/kuby-core.gemspec +5 -2
  7. data/lib/kuby.rb +55 -18
  8. data/lib/kuby/basic_logger.rb +13 -0
  9. data/lib/kuby/cli_base.rb +125 -4
  10. data/lib/kuby/commands.rb +244 -0
  11. data/lib/kuby/definition.rb +1 -3
  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 +24 -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/errors.rb +1 -0
  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 +14 -34
  22. data/lib/kuby/docker/setup_phase.rb +3 -2
  23. data/lib/kuby/docker/spec.rb +31 -5
  24. data/lib/kuby/docker/timestamp_tag.rb +6 -0
  25. data/lib/kuby/environment.rb +10 -1
  26. data/lib/kuby/kubernetes.rb +9 -9
  27. data/lib/kuby/kubernetes/deploy_task.rb +4 -0
  28. data/lib/kuby/kubernetes/deployer.rb +66 -11
  29. data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +4 -4
  30. data/lib/kuby/kubernetes/provider.rb +8 -4
  31. data/lib/kuby/kubernetes/spec.rb +23 -22
  32. data/lib/kuby/plugin_registry.rb +27 -0
  33. data/lib/kuby/plugins/rails_app/database.rb +1 -2
  34. data/lib/kuby/plugins/rails_app/generators/kuby.rb +14 -16
  35. data/lib/kuby/plugins/rails_app/mysql.rb +0 -3
  36. data/lib/kuby/plugins/rails_app/plugin.rb +237 -42
  37. data/lib/kuby/plugins/rails_app/tasks.rake +2 -2
  38. data/lib/kuby/rails_commands.rb +83 -0
  39. data/lib/kuby/railtie.rb +0 -4
  40. data/lib/kuby/tasks.rb +77 -23
  41. data/lib/kuby/version.rb +1 -1
  42. data/spec/docker/metadata_spec.rb +84 -0
  43. data/spec/docker/spec_spec.rb +266 -0
  44. data/spec/docker/timestamp_tag_spec.rb +54 -4
  45. data/spec/dummy/Gemfile +54 -0
  46. data/spec/dummy/Gemfile.lock +223 -0
  47. data/spec/dummy/README.md +24 -0
  48. data/spec/dummy/Rakefile +6 -0
  49. data/spec/dummy/app/assets/config/manifest.js +2 -0
  50. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  51. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  52. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  53. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  55. data/spec/dummy/app/javascript/channels/consumer.js +6 -0
  56. data/spec/dummy/app/javascript/channels/index.js +5 -0
  57. data/spec/dummy/app/javascript/packs/application.js +17 -0
  58. data/spec/dummy/app/jobs/application_job.rb +7 -0
  59. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  60. data/spec/dummy/app/models/application_record.rb +3 -0
  61. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  62. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  63. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  64. data/spec/dummy/bin/bundle +114 -0
  65. data/spec/dummy/bin/rails +9 -0
  66. data/spec/dummy/bin/rake +9 -0
  67. data/spec/dummy/bin/setup +36 -0
  68. data/spec/dummy/bin/spring +17 -0
  69. data/spec/dummy/bin/yarn +11 -0
  70. data/spec/dummy/config.ru +5 -0
  71. data/spec/dummy/config/application.rb +19 -0
  72. data/spec/dummy/config/boot.rb +4 -0
  73. data/spec/dummy/config/cable.yml +10 -0
  74. data/spec/dummy/config/credentials.yml.enc +1 -0
  75. data/spec/dummy/config/database.yml +25 -0
  76. data/spec/dummy/config/environment.rb +5 -0
  77. data/spec/dummy/config/environments/development.rb +62 -0
  78. data/spec/dummy/config/environments/production.rb +112 -0
  79. data/spec/dummy/config/environments/test.rb +49 -0
  80. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  81. data/spec/dummy/config/initializers/assets.rb +14 -0
  82. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  83. data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
  84. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  85. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  86. data/spec/dummy/config/initializers/inflections.rb +16 -0
  87. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  88. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  89. data/spec/dummy/config/locales/en.yml +33 -0
  90. data/spec/dummy/config/master.key +1 -0
  91. data/spec/dummy/config/puma.rb +38 -0
  92. data/spec/dummy/config/routes.rb +3 -0
  93. data/spec/dummy/config/spring.rb +6 -0
  94. data/spec/dummy/config/storage.yml +34 -0
  95. data/spec/dummy/db/seeds.rb +7 -0
  96. data/spec/dummy/package.json +11 -0
  97. data/spec/dummy/public/404.html +67 -0
  98. data/spec/dummy/public/422.html +67 -0
  99. data/spec/dummy/public/500.html +66 -0
  100. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  101. data/spec/dummy/public/apple-touch-icon.png +0 -0
  102. data/spec/dummy/public/favicon.ico +0 -0
  103. data/spec/dummy/public/robots.txt +1 -0
  104. data/spec/dummy/test/application_system_test_case.rb +5 -0
  105. data/spec/dummy/test/channels/application_cable/connection_test.rb +11 -0
  106. data/spec/dummy/test/test_helper.rb +13 -0
  107. data/spec/dummy/tmp/cache/bootsnap-load-path-cache +0 -0
  108. data/spec/spec_helper.rb +77 -2
  109. data/spec/support/docker/fake_cli.rb +54 -0
  110. data/spec/support/docker/remote/fake_client.rb +16 -0
  111. data/spec/trailing_hash_spec.rb +23 -0
  112. metadata +111 -9
  113. data/lib/kuby/tasks/kuby.rake +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac73b245f1fb75244ed166e23a6441cc56dabaec66f532f67517b4dd8d412091
4
- data.tar.gz: 6418a3fbca89fdc195e277aeb8309caa2b519e1d3df0fccd53f3ae8b2e9f166f
3
+ metadata.gz: 2b7d982098e06b6e511fe3404385d6762c9f37fdf7df2ac91ce9775f40ce809e
4
+ data.tar.gz: b23e5c83e32a488ba8effb2ff3a8fd0f2834c305f6046072bcd6dd1e54c1f0e9
5
5
  SHA512:
6
- metadata.gz: 6f7bc8616cfdf23805bf9177198f62888b17372ae67b4fb4a77c4d325adbbcf6d734385edb0614094be95bb443d84b28f6d79fe70a8acaecdfb44b547bcf86b0
7
- data.tar.gz: 815fd1ae302c35fdd0fb31f43bb1d607073a9cfb1f24d3c849578b0b8a5271bca5cbe49f2747bdf7bb22d1a4b98d1fa455c3a917e7d27f059f5a4778da215f87
6
+ metadata.gz: d64e1c94a33045be652d9bbea27d68ec905779f8e67339a03f3e5a10729e2f9c0622f120cf66a08ab8978152dc2ae4bd863d74cca60588e9d4f0b96d484ca72a
7
+ data.tar.gz: acd984606d103c17d5150cca2fbba13757051c277733d360868cc79145472dd77e7f536fd8c71687d62da09c7eaad59fc5071aa590d95d08c4367a2ba61d1b35
@@ -1,3 +1,40 @@
1
+ ## 0.10.1
2
+ * Fix bug causing some `rails` and `rake` commands to not be executed.
3
+ * Fix issue restricting Docker CLI output.
4
+
5
+ ## 0.10.0
6
+ * Set default database user and password in dev environment.
7
+ * Add ability to run rake tasks in dev environment.
8
+ * Disallow running rails and rake tasks in non-dev environments.
9
+ * Don't run database config through ERB.
10
+ - Rails env often isn't loaded, so ERB rendering can blow up with `NoMethodError`s, etc.
11
+ - All we really need to know is what database engine to stand up.
12
+ * Require database user/password to be added manually to Kuby config.
13
+
14
+ ## 0.9.1
15
+ * Run dev setup when asked to.
16
+ - Bug caused dev setup to be skipped even when requested.
17
+ * Deployer should be tolerant of missing namespace.
18
+
19
+ ## 0.9.0
20
+ * Add support for developing your app using a local Kubernetes cluster.
21
+ - Includes a default `:development` Kuby environment.
22
+ * Remove rake tasks in favor of a `kuby` executable powered by [GLI](https://github.com/davetron5000/gli).
23
+ * Rename the `minikube` provider to `docker_desktop`, which is more accurate.
24
+ * Add more tests.
25
+ * Avoid running commands inside pods that aren't running or that are marked for deletion (#15).
26
+ * Pass `RAILS_MASTER_KEY` to Docker build (#14).
27
+ * Add `kuby remote restart` command for manually restarting Rails pods.
28
+ * Automatically restart Rails pods if deploy doesn't change the Docker image URL (#11).
29
+
30
+ ## 0.8.1
31
+ * Fix database config rewriter task.
32
+ - Broke with refactoring of database config code.
33
+ * More correctly parse Docker image URLs.
34
+ - 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.
35
+ - 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."
36
+ * Add a number of tests and a Rails dummy app in spec/.
37
+
1
38
  ## 0.8.0
2
39
  * Upgrade to Krane >= 1.1.4, < 2.0.
3
40
  * Remove Krane monkeypatch in ext/.
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,19 +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 'gli', '~> 2.0'
18
+ s.add_dependency 'helm-cli', '~> 0.3'
17
19
  # See: https://github.com/Shopify/krane/pull/720
18
20
  # See: https://github.com/Shopify/krane/blob/master/CHANGELOG.md#114
19
21
  s.add_dependency 'krane', '>= 1.1.4', '< 2.0'
20
22
  s.add_dependency 'kuby-cert-manager', '>= 0.3'
21
23
  s.add_dependency 'kube-dsl', '~> 0.3'
22
- s.add_dependency 'kuby-kube-db', '>= 0.5'
23
- s.add_dependency 'kubernetes-cli', '~> 0.2'
24
+ s.add_dependency 'kuby-kube-db', '>= 0.6'
25
+ s.add_dependency 'kubernetes-cli', '~> 0.3'
24
26
  s.add_dependency 'railties', '>= 5.1'
25
27
  s.add_dependency 'rouge', '~> 3.0'
26
28
 
27
29
  s.add_development_dependency 'rspec'
28
30
 
29
31
  s.require_path = 'lib'
32
+ s.executables << 'kuby'
30
33
 
31
34
  s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'kuby-core.gemspec']
32
35
  end
@@ -6,26 +6,41 @@ 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 :Plugin, 'kuby/plugin'
17
- autoload :Plugins, 'kuby/plugins'
18
- autoload :Tasks, 'kuby/tasks'
19
- 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
20
28
 
21
29
  class UndefinedEnvironmentError < StandardError; end
30
+ class MissingConfigError < StandardError; end
22
31
 
23
32
  class << self
24
33
  attr_reader :definition
25
34
  attr_writer :logger
26
35
 
27
- def load!
28
- 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
29
44
  end
30
45
 
31
46
  def define(name, &block)
@@ -34,9 +49,27 @@ module Kuby
34
49
  @definition = Definition.new(name.to_s)
35
50
  @definition.instance_eval(&block)
36
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
+
37
68
  @definition.environments.each do |_, env|
38
69
  env.kubernetes.after_configuration
39
70
  end
71
+
72
+ @definition
40
73
  end
41
74
 
42
75
  def environment(name = env)
@@ -54,8 +87,8 @@ module Kuby
54
87
  @providers ||= {}
55
88
  end
56
89
 
57
- def register_plugin(plugin_name, plugin_klass)
58
- plugins[plugin_name] = plugin_klass
90
+ def register_plugin(*args, **kwargs)
91
+ plugins.register(*args, **kwargs)
59
92
  end
60
93
 
61
94
  def register_distro(distro_name, distro_klass)
@@ -67,7 +100,7 @@ module Kuby
67
100
  end
68
101
 
69
102
  def plugins
70
- @plugins ||= {}
103
+ @plugins ||= PluginRegistry.new
71
104
  end
72
105
 
73
106
  def logger
@@ -97,16 +130,20 @@ module Kuby
97
130
  @packages ||= {}
98
131
  end
99
132
 
133
+ def env=(env_name)
134
+ @env = env_name.to_s
135
+ end
136
+
100
137
  def env
101
138
  ENV.fetch('KUBY_ENV') do
102
- (definition.environments.keys.first || Rails.env).to_s
139
+ (@env || Rails.env rescue nil || DEFAULT_ENV).to_s
103
140
  end
104
141
  end
105
142
  end
106
143
  end
107
144
 
108
145
  # providers
109
- Kuby.register_provider(:minikube, Kuby::Kubernetes::MinikubeProvider)
146
+ Kuby.register_provider(:docker_desktop, Kuby::Kubernetes::DockerDesktopProvider)
110
147
 
111
148
  # plugins
112
149
  Kuby.register_plugin(:rails_app, Kuby::Plugins::RailsApp::Plugin)
@@ -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
@@ -39,6 +81,14 @@ module Kuby
39
81
  end
40
82
 
41
83
  def systemm(cmd)
84
+ if stdout == STDOUT && stderr == STDERR
85
+ systemm_default(cmd)
86
+ else
87
+ systemm_open3(cmd)
88
+ end
89
+ end
90
+
91
+ def systemm_default(cmd)
42
92
  run_before_callbacks(cmd)
43
93
  cmd_s = cmd.join(' ')
44
94
  system(cmd_s).tap do
@@ -47,7 +97,41 @@ module Kuby
47
97
  end
48
98
  end
49
99
 
100
+ def systemm_open3(cmd)
101
+ run_before_callbacks(cmd)
102
+ cmd_s = cmd.join(' ')
103
+
104
+ Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
105
+ Thread.new(stdout) do |t_stdout|
106
+ begin
107
+ p_stdout.each { |line| t_stdout.puts(line) }
108
+ rescue IOError
109
+ end
110
+ end
111
+
112
+ Thread.new(stderr) do |t_stderr|
113
+ begin
114
+ p_stderr.each { |line| t_stderr.puts(line) }
115
+ rescue IOError
116
+ end
117
+ end
118
+
119
+ p_stdin.close
120
+ self.last_status = wait_thread.value
121
+ run_after_callbacks(cmd)
122
+ wait_thread.join
123
+ end
124
+ end
125
+
50
126
  def backticks(cmd)
127
+ if stdout == STDOUT && stderr == STDERR
128
+ backticks_default(cmd)
129
+ else
130
+ backticks_open3(cmd)
131
+ end
132
+ end
133
+
134
+ def backticks_default(cmd)
51
135
  run_before_callbacks(cmd)
52
136
  cmd_s = cmd.join(' ')
53
137
  `#{cmd_s}`.tap do
@@ -56,6 +140,35 @@ module Kuby
56
140
  end
57
141
  end
58
142
 
143
+ def backticks_open3(cmd)
144
+ run_before_callbacks(cmd)
145
+ cmd_s = cmd.join(' ')
146
+ result = StringIO.new
147
+
148
+ Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
149
+ Thread.new do
150
+ begin
151
+ p_stdout.each { |line| result.puts(line) }
152
+ rescue IOError
153
+ end
154
+ end
155
+
156
+ Thread.new(stderr) do |t_stderr|
157
+ begin
158
+ p_stderr.each { |line| t_stderr.puts(line) }
159
+ rescue IOError
160
+ end
161
+ end
162
+
163
+ p_stdin.close
164
+ self.last_status = wait_thread.value
165
+ run_after_callbacks(cmd)
166
+ wait_thread.join
167
+ end
168
+
169
+ result.string
170
+ end
171
+
59
172
  def run_before_callbacks(cmd)
60
173
  (@before_execute || []).each { |cb| cb.call(cmd) }
61
174
  end
@@ -71,5 +184,13 @@ module Kuby
71
184
  def status_key
72
185
  raise NotImplementedError, "#{__method__} must be defined in derived classes"
73
186
  end
187
+
188
+ def stdout_key
189
+ raise NotImplementedError, "#{__method__} must be defined in derived classes"
190
+ end
191
+
192
+ def stderr_key
193
+ raise NotImplementedError, "#{__method__} must be defined in derived classes"
194
+ end
74
195
  end
75
196
  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