kuby-core 0.8.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -1
  3. data/Gemfile +1 -0
  4. data/README.md +15 -3
  5. data/bin/kuby +4 -0
  6. data/kuby-core.gemspec +7 -3
  7. data/lib/kuby-core.rb +1 -0
  8. data/lib/kuby.rb +60 -18
  9. data/lib/kuby/basic_logger.rb +14 -0
  10. data/lib/kuby/cli_base.rb +126 -4
  11. data/lib/kuby/commands.rb +245 -0
  12. data/lib/kuby/definition.rb +2 -3
  13. data/lib/kuby/dev_setup.rb +256 -0
  14. data/lib/kuby/docker.rb +4 -0
  15. data/lib/kuby/docker/alpine.rb +16 -12
  16. data/lib/kuby/docker/assets_phase.rb +5 -0
  17. data/lib/kuby/docker/bundler_phase.rb +39 -5
  18. data/lib/kuby/docker/cli.rb +60 -3
  19. data/lib/kuby/docker/copy_phase.rb +11 -3
  20. data/lib/kuby/docker/credentials.rb +1 -0
  21. data/lib/kuby/docker/debian.rb +14 -10
  22. data/lib/kuby/docker/dev_spec.rb +195 -0
  23. data/lib/kuby/docker/distro.rb +43 -0
  24. data/lib/kuby/docker/dockerfile.rb +81 -18
  25. data/lib/kuby/docker/errors.rb +9 -0
  26. data/lib/kuby/docker/inline_layer.rb +7 -0
  27. data/lib/kuby/docker/layer.rb +11 -0
  28. data/lib/kuby/docker/layer_stack.rb +47 -6
  29. data/lib/kuby/docker/local_tags.rb +27 -2
  30. data/lib/kuby/docker/metadata.rb +35 -29
  31. data/lib/kuby/docker/package_list.rb +22 -3
  32. data/lib/kuby/docker/package_phase.rb +25 -5
  33. data/lib/kuby/docker/packages.rb +1 -0
  34. data/lib/kuby/docker/packages/managed_package.rb +20 -2
  35. data/lib/kuby/docker/packages/nodejs.rb +8 -0
  36. data/lib/kuby/docker/packages/package.rb +15 -1
  37. data/lib/kuby/docker/packages/simple_managed_package.rb +10 -1
  38. data/lib/kuby/docker/packages/yarn.rb +14 -0
  39. data/lib/kuby/docker/remote_tags.rb +21 -2
  40. data/lib/kuby/docker/setup_phase.rb +37 -5
  41. data/lib/kuby/docker/spec.rb +114 -12
  42. data/lib/kuby/docker/tags.rb +37 -2
  43. data/lib/kuby/docker/timestamp_tag.rb +14 -2
  44. data/lib/kuby/docker/webserver_phase.rb +40 -5
  45. data/lib/kuby/docker/yarn_phase.rb +5 -0
  46. data/lib/kuby/environment.rb +14 -1
  47. data/lib/kuby/kubernetes.rb +10 -9
  48. data/lib/kuby/kubernetes/deploy_task.rb +5 -0
  49. data/lib/kuby/kubernetes/deployer.rb +67 -11
  50. data/lib/kuby/kubernetes/docker_config.rb +1 -0
  51. data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +5 -4
  52. data/lib/kuby/kubernetes/errors.rb +1 -0
  53. data/lib/kuby/kubernetes/manifest.rb +1 -0
  54. data/lib/kuby/kubernetes/provider.rb +9 -4
  55. data/lib/kuby/kubernetes/registry_secret.rb +1 -0
  56. data/lib/kuby/kubernetes/spec.rb +24 -22
  57. data/lib/kuby/middleware.rb +1 -0
  58. data/lib/kuby/middleware/health_check.rb +1 -0
  59. data/lib/kuby/plugin.rb +1 -0
  60. data/lib/kuby/plugin_registry.rb +28 -0
  61. data/lib/kuby/plugins.rb +1 -0
  62. data/lib/kuby/plugins/nginx_ingress.rb +1 -0
  63. data/lib/kuby/plugins/rails_app.rb +1 -0
  64. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +1 -0
  65. data/lib/kuby/plugins/rails_app/assets.rb +1 -0
  66. data/lib/kuby/plugins/rails_app/database.rb +10 -9
  67. data/lib/kuby/plugins/rails_app/generators/kuby.rb +45 -44
  68. data/lib/kuby/plugins/rails_app/mysql.rb +1 -3
  69. data/lib/kuby/plugins/rails_app/plugin.rb +244 -44
  70. data/lib/kuby/plugins/rails_app/postgres.rb +1 -0
  71. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +1 -0
  72. data/lib/kuby/plugins/rails_app/sqlite.rb +1 -0
  73. data/lib/kuby/rails_commands.rb +84 -0
  74. data/lib/kuby/railtie.rb +1 -4
  75. data/lib/kuby/tasks.rb +78 -23
  76. data/lib/kuby/trailing_hash.rb +5 -3
  77. data/lib/kuby/version.rb +3 -1
  78. data/spec/docker/metadata_spec.rb +1 -108
  79. data/spec/docker/spec_spec.rb +303 -0
  80. data/spec/docker/timestamp_tag_spec.rb +1 -0
  81. data/spec/dummy/app/channels/application_cable/channel.rb +1 -0
  82. data/spec/dummy/app/channels/application_cable/connection.rb +1 -0
  83. data/spec/dummy/app/controllers/application_controller.rb +1 -0
  84. data/spec/dummy/app/helpers/application_helper.rb +1 -0
  85. data/spec/dummy/app/jobs/application_job.rb +1 -0
  86. data/spec/dummy/app/mailers/application_mailer.rb +1 -0
  87. data/spec/dummy/app/models/application_record.rb +1 -0
  88. data/spec/dummy/config/application.rb +1 -0
  89. data/spec/dummy/config/boot.rb +1 -0
  90. data/spec/dummy/config/environment.rb +1 -0
  91. data/spec/dummy/config/environments/development.rb +1 -0
  92. data/spec/dummy/config/environments/production.rb +1 -0
  93. data/spec/dummy/config/environments/test.rb +1 -0
  94. data/spec/dummy/config/initializers/application_controller_renderer.rb +1 -0
  95. data/spec/dummy/config/initializers/assets.rb +1 -0
  96. data/spec/dummy/config/initializers/backtrace_silencers.rb +1 -0
  97. data/spec/dummy/config/initializers/content_security_policy.rb +1 -0
  98. data/spec/dummy/config/initializers/cookies_serializer.rb +1 -0
  99. data/spec/dummy/config/initializers/filter_parameter_logging.rb +1 -0
  100. data/spec/dummy/config/initializers/inflections.rb +1 -0
  101. data/spec/dummy/config/initializers/mime_types.rb +1 -0
  102. data/spec/dummy/config/initializers/wrap_parameters.rb +1 -0
  103. data/spec/dummy/config/puma.rb +1 -0
  104. data/spec/dummy/config/routes.rb +1 -0
  105. data/spec/dummy/config/spring.rb +1 -0
  106. data/spec/dummy/db/seeds.rb +1 -0
  107. data/spec/dummy/test/application_system_test_case.rb +1 -0
  108. data/spec/dummy/test/channels/application_cable/connection_test.rb +1 -0
  109. data/spec/dummy/test/test_helper.rb +1 -0
  110. data/spec/spec_helper.rb +18 -1
  111. data/spec/support/docker/fake_cli.rb +1 -0
  112. data/spec/support/docker/remote/fake_client.rb +1 -0
  113. data/spec/trailing_hash_spec.rb +2 -0
  114. metadata +62 -11
  115. data/lib/kuby/tasks/kuby.rake +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0f3610dd2dc149fb09e589c56dc477534ece66a752106b251a94ac265cb1e34
4
- data.tar.gz: 673285ecde402c63a461ef4c4374a1353be45ff33610db840590658cc6cab5ed
3
+ metadata.gz: 63e975e73abc299262e996d4bc336f951633d993c8fe56ce997543fb442abe57
4
+ data.tar.gz: 1a662d6ff200ecf2b9b8fdd7446d0936329599d940afd06800166537bf82005e
5
5
  SHA512:
6
- metadata.gz: 6c8e407acf3a35f21b4446b0c0752a754720a7fbd03b887b64c4fe0991e61e6f107f61a2adb2394ac88d24470be5c814c6168cd96106295c777f50a35f77820a
7
- data.tar.gz: 1871572ccb876d772eec874ed224235905ba7265ced27319f4e7abd678a65e1862584bd88f0223e2f87ba1b99158342e6c6a2c4d033d758b28338296153f5e4b
6
+ metadata.gz: 30d01ba9e76b0bc79c0d22c63ab65a4edbec0f35add876935233402ae6c96077adeb1b91cef3003858f0534441b1939beae31872860ba453ef3b9ef13aa84d76
7
+ data.tar.gz: bb75588789e02dc6bf4135ab59fb401d1ce8e07d8169460ff86282128d1ea610e01de01c44e564c4a9196959a9f6975640f0c14407bd3d02e8257499b0723099
@@ -1,10 +1,50 @@
1
+ ## 0.11.0
2
+ * Defer evaluation of database config until after Kuby has been configured.
3
+ - The issue that prompted this was that the `database` block was being evaluated before the Rails root had been set via `root`. Kuby couldn't find database.yml in a non-standard location and blew up with an error.
4
+ * Fix tests broken in 0.10.1.
5
+ * Set up Travis CI builds.
6
+ * Add a few tests for custom build phases.
7
+ * Add the `Environment#configured?` method that will return `true` if Kuby has been configured and `false` if configuration hasn't happened or is in progress.
8
+ * Add sorbet typedefs for some classes.
9
+ * Fix issue in Rails generator (hadn't been updated with new `environment` block).
10
+ * Add kuby-core.rb so Bundler setup works for Kuby without having to add a Rails initializer.
11
+
12
+ ## 0.10.1
13
+ * Fix bug causing some `rails` and `rake` commands to not be executed.
14
+ * Fix issue restricting Docker CLI output.
15
+
16
+ ## 0.10.0
17
+ * Set default database user and password in dev environment.
18
+ * Add ability to run rake tasks in dev environment.
19
+ * Disallow running rails and rake tasks in non-dev environments.
20
+ * Don't run database config through ERB.
21
+ - Rails env often isn't loaded, so ERB rendering can blow up with `NoMethodError`s, etc.
22
+ - All we really need to know is what database engine to stand up.
23
+ * Require database user/password to be added manually to Kuby config.
24
+
25
+ ## 0.9.1
26
+ * Run dev setup when asked to.
27
+ - Bug caused dev setup to be skipped even when requested.
28
+ * Deployer should be tolerant of missing namespace.
29
+
30
+ ## 0.9.0
31
+ * Add support for developing your app using a local Kubernetes cluster.
32
+ - Includes a default `:development` Kuby environment.
33
+ * Remove rake tasks in favor of a `kuby` executable powered by [GLI](https://github.com/davetron5000/gli).
34
+ * Rename the `minikube` provider to `docker_desktop`, which is more accurate.
35
+ * Add more tests.
36
+ * Avoid running commands inside pods that aren't running or that are marked for deletion (#15).
37
+ * Pass `RAILS_MASTER_KEY` to Docker build (#14).
38
+ * Add `kuby remote restart` command for manually restarting Rails pods.
39
+ * Automatically restart Rails pods if deploy doesn't change the Docker image URL (#11).
40
+
1
41
  ## 0.8.1
2
42
  * Fix database config rewriter task.
3
43
  - Broke with refactoring of database config code.
4
44
  * More correctly parse Docker image URLs.
5
45
  - 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.
6
46
  - 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."
7
- * Added a number of tests and a Rails dummy app in spec/.
47
+ * Add a number of tests and a Rails dummy app in spec/.
8
48
 
9
49
  ## 0.8.0
10
50
  * Upgrade to Krane >= 1.1.4, < 2.0.
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ gemspec
5
5
  group :development, :test do
6
6
  gem 'pry-byebug'
7
7
  gem 'rake'
8
+ gem 'sorbet', '~> 0.5'
8
9
  end
9
10
 
10
11
  group :test do
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ## Kuby
2
2
 
3
+ [![Build Status](https://travis-ci.com/getkuby/kuby-core.svg?branch=master)](https://travis-ci.com/getkuby/kuby-core)
4
+
3
5
  Deploy your Rails app the easy way.
4
6
 
5
7
  ## What is Kuby?
@@ -8,17 +10,27 @@ At its core, Kuby is a set of tools and smart defaults that encapsulate and codi
8
10
 
9
11
  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
12
 
13
+ ## Why Kuby?
14
+
15
+ 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:
16
+
17
+ * leverages Docker and Kubernetes, industry-leading infrastructure tools.
18
+ * automatically configures your app with a TLS certificate from [LetsEncrypt](https://letsencrypt.org/).
19
+ * automatically spins up a database instance based on what's in your database.yml.
20
+ * runs a separate server for your static assets.
21
+ * features a powerful plugin system that allows, for example, easy [Sidekiq integration](https://github.com/getkuby/kuby-sidekiq).
22
+
11
23
  ## Getting Started
12
24
 
13
- See the [Quick Start Guide](https://github.com/getkuby/kuby-core/wiki/Quick-Start-Guide)
25
+ See the [Quick Start Guide](https://getkuby.io/docs)
14
26
 
15
27
  ## More Info
16
28
 
17
- See the [wiki](https://github.com/getkuby/kuby-core/wiki).
29
+ Check out [getkuby.io](https://getkuby.io).
18
30
 
19
31
  ## Running Tests
20
32
 
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.
33
+ `bundle exec rspec` should do the trick. Test coverage is very minimal at the moment, however.
22
34
 
23
35
  ## License
24
36
 
@@ -0,0 +1,4 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'kuby'
4
+ exit Kuby::Commands.run(ARGV)
@@ -14,19 +14,23 @@ 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
- s.add_dependency 'kube-dsl', '~> 0.3'
22
- s.add_dependency 'kuby-kube-db', '>= 0.5'
23
- s.add_dependency 'kubernetes-cli', '~> 0.2'
23
+ s.add_dependency 'kube-dsl', '~> 0.4'
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'
28
+ s.add_dependency 'sorbet-runtime-stub', '~> 0.2'
26
29
 
27
30
  s.add_development_dependency 'rspec'
28
31
 
29
32
  s.require_path = 'lib'
33
+ s.executables << 'kuby'
30
34
 
31
35
  s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'kuby-core.gemspec']
32
36
  end
@@ -0,0 +1 @@
1
+ require 'kuby'
@@ -1,3 +1,6 @@
1
+ # typed: false
2
+
3
+ require 'sorbet-runtime-stub'
1
4
  require 'kuby/railtie'
2
5
 
3
6
  begin
@@ -6,26 +9,41 @@ rescue NameError
6
9
  end
7
10
 
8
11
  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'
12
+ autoload :BasicLogger, 'kuby/basic_logger'
13
+ autoload :CLIBase, 'kuby/cli_base'
14
+ autoload :Commands, 'kuby/commands'
15
+ autoload :Definition, 'kuby/definition'
16
+ autoload :DevSetup, 'kuby/dev_setup'
17
+ autoload :Docker, 'kuby/docker'
18
+ autoload :Environment, 'kuby/environment'
19
+ autoload :Kubernetes, 'kuby/kubernetes'
20
+ autoload :Middleware, 'kuby/middleware'
21
+ autoload :Plugin, 'kuby/plugin'
22
+ autoload :PluginRegistry, 'kuby/plugin_registry'
23
+ autoload :Plugins, 'kuby/plugins'
24
+ autoload :RailsCommands, 'kuby/rails_commands'
25
+ autoload :Tasks, 'kuby/tasks'
26
+ autoload :TrailingHash, 'kuby/trailing_hash'
27
+
28
+ DEFAULT_ENV = 'development'.freeze
29
+ DEFAULT_DB_USER = 'root'.freeze
30
+ DEFAULT_DB_PASSWORD = 'password'.freeze
20
31
 
21
32
  class UndefinedEnvironmentError < StandardError; end
33
+ class MissingConfigError < StandardError; end
22
34
 
23
35
  class << self
24
36
  attr_reader :definition
25
37
  attr_writer :logger
26
38
 
27
- def load!
28
- require ENV['KUBY_CONFIG'] || File.join('.', 'kuby.rb')
39
+ def load!(config_file = nil)
40
+ config_file ||= ENV['KUBY_CONFIG'] || File.join('.', 'kuby.rb')
41
+
42
+ unless File.exist?(config_file)
43
+ raise MissingConfigError, "couldn't find Kuby config file at #{config_file}"
44
+ end
45
+
46
+ require config_file
29
47
  end
30
48
 
31
49
  def define(name, &block)
@@ -34,10 +52,30 @@ module Kuby
34
52
  @definition = Definition.new(name.to_s)
35
53
  @definition.instance_eval(&block)
36
54
 
55
+ # default development environment
56
+ @definition.environment(:development) do
57
+ kubernetes do
58
+ add_plugin(:rails_app) do
59
+ tls_enabled false
60
+
61
+ database do
62
+ user(DEFAULT_DB_USER) if respond_to?(:user)
63
+ password(DEFAULT_DB_PASSWORD) if respond_to?(:password)
64
+ end
65
+ end
66
+
67
+ provider :docker_desktop
68
+ end
69
+ end
70
+
37
71
  @definition.environments.each do |_, env|
38
72
  env.kubernetes.after_configuration
39
73
  end
40
74
 
75
+ @definition.environments.each do |_, env|
76
+ env.configured = true
77
+ end
78
+
41
79
  @definition
42
80
  end
43
81
 
@@ -56,8 +94,8 @@ module Kuby
56
94
  @providers ||= {}
57
95
  end
58
96
 
59
- def register_plugin(plugin_name, plugin_klass)
60
- plugins[plugin_name] = plugin_klass
97
+ def register_plugin(*args, **kwargs)
98
+ plugins.register(*args, **kwargs)
61
99
  end
62
100
 
63
101
  def register_distro(distro_name, distro_klass)
@@ -69,7 +107,7 @@ module Kuby
69
107
  end
70
108
 
71
109
  def plugins
72
- @plugins ||= {}
110
+ @plugins ||= PluginRegistry.new
73
111
  end
74
112
 
75
113
  def logger
@@ -99,16 +137,20 @@ module Kuby
99
137
  @packages ||= {}
100
138
  end
101
139
 
140
+ def env=(env_name)
141
+ @env = env_name.to_s
142
+ end
143
+
102
144
  def env
103
145
  ENV.fetch('KUBY_ENV') do
104
- (definition.environments.keys.first || Rails.env).to_s
146
+ (@env || Rails.env rescue nil || DEFAULT_ENV).to_s
105
147
  end
106
148
  end
107
149
  end
108
150
  end
109
151
 
110
152
  # providers
111
- Kuby.register_provider(:minikube, Kuby::Kubernetes::MinikubeProvider)
153
+ Kuby.register_provider(:docker_desktop, Kuby::Kubernetes::DockerDesktopProvider)
112
154
 
113
155
  # plugins
114
156
  Kuby.register_plugin(:rails_app, Kuby::Plugins::RailsApp::Plugin)
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  require 'logger'
2
3
  require 'colorized_string'
3
4
 
@@ -18,5 +19,18 @@ module Kuby
18
19
  def fatal(msg, *args)
19
20
  super(ColorizedString[msg].red, *args)
20
21
  end
22
+
23
+ # adhere to the "CLI" interface
24
+ def with_pipes(out = STDOUT, err = STDERR)
25
+ previous_logdev = @logdev || STDERR
26
+ reopen(err)
27
+ yield
28
+ ensure
29
+ reopen(previous_logdev)
30
+ end
31
+
32
+ def last_status
33
+ nil
34
+ end
21
35
  end
22
36
  end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  require 'open3'
2
3
  require 'thread'
3
4
 
@@ -17,17 +18,59 @@ module Kuby
17
18
  @after_execute << block
18
19
  end
19
20
 
21
+ def with_pipes(out = STDOUT, err = STDERR)
22
+ previous_stdout = self.stdout
23
+ previous_stderr = self.stderr
24
+ self.stdout = out
25
+ self.stderr = err
26
+ yield
27
+ ensure
28
+ self.stdout = previous_stdout
29
+ self.stderr = previous_stderr
30
+ end
31
+
32
+ def stdout
33
+ Thread.current[stdout_key] || STDOUT
34
+ end
35
+
36
+ def stdout=(new_stdout)
37
+ Thread.current[stdout_key] = new_stdout
38
+ end
39
+
40
+ def stderr
41
+ Thread.current[stderr_key] || STDERR
42
+ end
43
+
44
+ def stderr=(new_stderr)
45
+ Thread.current[stderr_key] = new_stderr
46
+ end
47
+
20
48
  private
21
49
 
22
50
  def open3_w(env, cmd, opts = {}, &block)
23
51
  run_before_callbacks(cmd)
24
52
  cmd_s = cmd.join(' ')
25
53
 
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
54
+ Open3.popen3(env, cmd_s, opts) do |p_stdin, p_stdout, p_stderr, wait_thread|
55
+ Thread.new(stdout) do |t_stdout|
56
+ begin
57
+ p_stdout.each { |line| t_stdout.puts(line) }
58
+ rescue IOError
59
+ end
60
+ end
61
+
62
+ Thread.new(stderr) do |t_stderr|
63
+ begin
64
+ p_stderr.each { |line| t_stderr.puts(line) }
65
+ rescue IOError
66
+ end
67
+ end
68
+
69
+ yield(p_stdin).tap do
70
+ p_stdin.close
71
+ self.last_status = wait_thread.value
30
72
  run_after_callbacks(cmd)
73
+ wait_thread.join
31
74
  end
32
75
  end
33
76
  end
@@ -39,6 +82,14 @@ module Kuby
39
82
  end
40
83
 
41
84
  def systemm(cmd)
85
+ if stdout == STDOUT && stderr == STDERR
86
+ systemm_default(cmd)
87
+ else
88
+ systemm_open3(cmd)
89
+ end
90
+ end
91
+
92
+ def systemm_default(cmd)
42
93
  run_before_callbacks(cmd)
43
94
  cmd_s = cmd.join(' ')
44
95
  system(cmd_s).tap do
@@ -47,7 +98,41 @@ module Kuby
47
98
  end
48
99
  end
49
100
 
101
+ def systemm_open3(cmd)
102
+ run_before_callbacks(cmd)
103
+ cmd_s = cmd.join(' ')
104
+
105
+ Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
106
+ Thread.new(stdout) do |t_stdout|
107
+ begin
108
+ p_stdout.each { |line| t_stdout.puts(line) }
109
+ rescue IOError
110
+ end
111
+ end
112
+
113
+ Thread.new(stderr) do |t_stderr|
114
+ begin
115
+ p_stderr.each { |line| t_stderr.puts(line) }
116
+ rescue IOError
117
+ end
118
+ end
119
+
120
+ p_stdin.close
121
+ self.last_status = wait_thread.value
122
+ run_after_callbacks(cmd)
123
+ wait_thread.join
124
+ end
125
+ end
126
+
50
127
  def backticks(cmd)
128
+ if stdout == STDOUT && stderr == STDERR
129
+ backticks_default(cmd)
130
+ else
131
+ backticks_open3(cmd)
132
+ end
133
+ end
134
+
135
+ def backticks_default(cmd)
51
136
  run_before_callbacks(cmd)
52
137
  cmd_s = cmd.join(' ')
53
138
  `#{cmd_s}`.tap do
@@ -56,6 +141,35 @@ module Kuby
56
141
  end
57
142
  end
58
143
 
144
+ def backticks_open3(cmd)
145
+ run_before_callbacks(cmd)
146
+ cmd_s = cmd.join(' ')
147
+ result = StringIO.new
148
+
149
+ Open3.popen3(cmd_s) do |p_stdin, p_stdout, p_stderr, wait_thread|
150
+ Thread.new do
151
+ begin
152
+ p_stdout.each { |line| result.puts(line) }
153
+ rescue IOError
154
+ end
155
+ end
156
+
157
+ Thread.new(stderr) do |t_stderr|
158
+ begin
159
+ p_stderr.each { |line| t_stderr.puts(line) }
160
+ rescue IOError
161
+ end
162
+ end
163
+
164
+ p_stdin.close
165
+ self.last_status = wait_thread.value
166
+ run_after_callbacks(cmd)
167
+ wait_thread.join
168
+ end
169
+
170
+ result.string
171
+ end
172
+
59
173
  def run_before_callbacks(cmd)
60
174
  (@before_execute || []).each { |cb| cb.call(cmd) }
61
175
  end
@@ -71,5 +185,13 @@ module Kuby
71
185
  def status_key
72
186
  raise NotImplementedError, "#{__method__} must be defined in derived classes"
73
187
  end
188
+
189
+ def stdout_key
190
+ raise NotImplementedError, "#{__method__} must be defined in derived classes"
191
+ end
192
+
193
+ def stderr_key
194
+ raise NotImplementedError, "#{__method__} must be defined in derived classes"
195
+ end
74
196
  end
75
197
  end