kuby-core 0.8.1 → 0.11.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 (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
@@ -1,3 +1,5 @@
1
+ # typed: strict
2
+
1
3
  require 'kuby/docker/errors'
2
4
 
3
5
  module Kuby
@@ -9,6 +11,8 @@ module Kuby
9
11
  autoload :CopyPhase, 'kuby/docker/copy_phase'
10
12
  autoload :Credentials, 'kuby/docker/credentials'
11
13
  autoload :Debian, 'kuby/docker/debian'
14
+ autoload :DevSpec, 'kuby/docker/dev_spec'
15
+ autoload :Distro, 'kuby/docker/distro'
12
16
  autoload :Dockerfile, 'kuby/docker/dockerfile'
13
17
  autoload :InlineLayer, 'kuby/docker/inline_layer'
14
18
  autoload :Layer, 'kuby/docker/layer'
@@ -1,40 +1,43 @@
1
+ # typed: strict
2
+
1
3
  module Kuby
2
4
  module Docker
3
- class Alpine
4
- SHELL_EXE = '/bin/sh'.freeze
5
+ class Alpine < Distro
6
+ SHELL_EXE = T.let('/bin/sh'.freeze, String)
5
7
 
6
- DEFAULT_PACKAGES = [
7
- [:ca_certificates],
8
+ DEFAULT_PACKAGES = T.let([
9
+ [:ca_certificates, nil],
8
10
  [:nodejs, '12.14.1'],
9
11
  [:yarn, '1.21.1'],
10
- [:c_toolchain],
11
- [:tzdata]
12
- ].freeze
12
+ [:c_toolchain, nil],
13
+ [:tzdata, nil]
14
+ ].freeze, T::Array[[Symbol, T.nilable(String)]])
13
15
 
16
+ sig { returns(Layer) }
14
17
  attr_reader :phase
15
18
 
16
- def initialize(phase)
17
- @phase = phase
18
- end
19
-
19
+ sig { override.params(packages: T::Array[Distro::PackageImpl], into: Dockerfile).void }
20
20
  def install(packages, into:)
21
21
  dockerfile = into
22
22
  install_managed(packages, dockerfile)
23
23
  install_unmanaged(packages, dockerfile)
24
24
  end
25
25
 
26
+ sig { override.returns(T::Array[[Symbol, T.nilable(String)]]) }
26
27
  def default_packages
27
28
  DEFAULT_PACKAGES
28
29
  end
29
30
 
31
+ sig { override.returns(String) }
30
32
  def shell_exe
31
33
  SHELL_EXE
32
34
  end
33
35
 
34
36
  private
35
37
 
38
+ sig { params(packages: T::Array[Distro::PackageImpl], dockerfile: Dockerfile).void }
36
39
  def install_managed(packages, dockerfile)
37
- pkgs = packages.select(&:managed?)
40
+ pkgs = T.cast(packages.select(&:managed?), T::Array[Distro::ManagedPackageImpl])
38
41
 
39
42
  unless pkgs.empty?
40
43
  package_names = pkgs.map { |pkg| pkg.package_name_for(:alpine) }
@@ -44,6 +47,7 @@ module Kuby
44
47
  end
45
48
  end
46
49
 
50
+ sig { params(packages: T::Array[Distro::PackageImpl], dockerfile: Dockerfile).void }
47
51
  def install_unmanaged(packages, dockerfile)
48
52
  packages
49
53
  .reject(&:managed?)
@@ -1,6 +1,11 @@
1
+ # typed: strict
2
+
1
3
  module Kuby
2
4
  module Docker
3
5
  class AssetsPhase < Layer
6
+ extend T::Sig
7
+
8
+ sig { override.params(dockerfile: Dockerfile).void }
4
9
  def apply_to(dockerfile)
5
10
  dockerfile.run(
6
11
  'bundle', 'exec', 'rake', 'assets:precompile'
@@ -1,26 +1,58 @@
1
+ # typed: strict
2
+
1
3
  require 'pathname'
2
4
 
3
5
  module Kuby
4
6
  module Docker
5
7
  class BundlerPhase < Layer
6
- DEFAULT_WITHOUT = ['development', 'test', 'deploy'].freeze
8
+ extend T::Sig
9
+
10
+ DEFAULT_WITHOUT = T.let(
11
+ ['development', 'test', 'deploy'].freeze, T::Array[String]
12
+ )
13
+
14
+ sig { returns(T.nilable(String)) }
15
+ attr_reader :version
16
+
17
+ sig { params(version: String).void }
18
+ attr_writer :version
19
+
20
+ sig { returns(T.nilable(String)) }
21
+ attr_reader :gemfile
7
22
 
8
- attr_accessor :version, :gemfile, :without
23
+ sig { params(gemfile: String).void }
24
+ attr_writer :gemfile
25
+
26
+ sig { returns(T.nilable(T::Array[String])) }
27
+ attr_reader :without
28
+
29
+ sig { params(without: T::Array[String]).void }
30
+ attr_writer :without
31
+
32
+ sig { params(environment: Environment).void }
33
+ def initialize(environment)
34
+ super
35
+
36
+ @version = T.let(@version, T.nilable(String))
37
+ @gemfile = T.let(@gemfile, T.nilable(String))
38
+ @without = T.let(@without, T.nilable(T::Array[String]))
39
+ end
9
40
 
41
+ sig { override.params(dockerfile: Dockerfile).void }
10
42
  def apply_to(dockerfile)
11
43
  gf = gemfile || default_gemfile
12
44
  lf = "#{gf}.lock"
13
45
  v = version || default_version
14
46
  wo = without || DEFAULT_WITHOUT
15
47
 
16
- dockerfile.run("gem install bundler -v #{v}")
48
+ dockerfile.run('gem', 'install', 'bundler', '-v', v)
17
49
 
18
50
  # bundle install
19
51
  dockerfile.copy(gf, '.')
20
52
  dockerfile.copy(lf, '.')
21
53
 
22
54
  # set bundle path so docker will cache the bundle
23
- dockerfile.run('mkdir ./bundle')
55
+ dockerfile.run('mkdir', './bundle')
24
56
  dockerfile.env('BUNDLE_PATH=./bundle')
25
57
 
26
58
  unless wo.empty?
@@ -35,16 +67,18 @@ module Kuby
35
67
  )
36
68
 
37
69
  # generate binstubs and add the bin directory to our path
38
- dockerfile.run('bundle binstubs --all')
70
+ dockerfile.run('bundle', 'binstubs', '--all')
39
71
  dockerfile.env("PATH=./bin:$PATH")
40
72
  end
41
73
 
42
74
  private
43
75
 
76
+ sig { returns(String) }
44
77
  def default_version
45
78
  Bundler::VERSION
46
79
  end
47
80
 
81
+ sig { returns(String) }
48
82
  def default_gemfile
49
83
  Bundler
50
84
  .definition
@@ -1,25 +1,35 @@
1
+ # typed: strict
2
+
1
3
  require 'json'
2
4
  require 'open3'
5
+ require 'shellwords'
3
6
 
4
7
  module Kuby
5
8
  module Docker
6
9
  class CLI < CLIBase
10
+ extend T::Sig
11
+
12
+ sig { returns(String) }
7
13
  attr_reader :executable
8
14
 
15
+ sig { params(executable: T.nilable(String)).void }
9
16
  def initialize(executable = nil)
10
- @executable = executable || `which docker`.strip
17
+ @executable = T.let(executable || `which docker`.strip, String)
11
18
  end
12
19
 
20
+ sig { returns(T.nilable(String)) }
13
21
  def config_file
14
22
  if File.exist?(default_config_file)
15
23
  default_config_file
16
24
  end
17
25
  end
18
26
 
27
+ sig { returns(String) }
19
28
  def default_config_file
20
29
  File.join(Dir.home, '.docker', 'config.json')
21
30
  end
22
31
 
32
+ sig { params(url: String, username: String, password: String).void }
23
33
  def login(url:, username:, password:)
24
34
  cmd = [
25
35
  executable, 'login', url, '--username', username, '--password-stdin'
@@ -35,17 +45,30 @@ module Kuby
35
45
  end
36
46
  end
37
47
 
48
+ sig { returns(T::Array[String]) }
38
49
  def auths
39
50
  return [] unless config_file
40
51
 
41
- config = JSON.parse(File.read(config_file))
52
+ config = JSON.parse(File.read(T.must(config_file)))
42
53
  config.fetch('auths', {}).keys
43
54
  end
44
55
 
45
- def build(dockerfile:, image_url:, tags:)
56
+ sig {
57
+ params(
58
+ dockerfile: Dockerfile,
59
+ image_url: String,
60
+ tags: T::Array[String],
61
+ build_args: T::Hash[T.any(Symbol, String), String]
62
+ )
63
+ .void
64
+ }
65
+ def build(dockerfile:, image_url:, tags:, build_args: {})
46
66
  cmd = [
47
67
  executable, 'build',
48
68
  *tags.flat_map { |tag| ['-t', "#{image_url}:#{tag}"] },
69
+ *build_args.flat_map do |arg, val|
70
+ ['--build-arg', Shellwords.shellescape("#{arg}=#{val}")]
71
+ end,
49
72
  '-f-', '.'
50
73
  ]
51
74
 
@@ -59,6 +82,15 @@ module Kuby
59
82
  end
60
83
  end
61
84
 
85
+ sig {
86
+ params(
87
+ image_url: String,
88
+ tag: String,
89
+ env: T::Hash[T.any(Symbol, String), String],
90
+ ports: T::Array[T.any(String, Integer)]
91
+ )
92
+ .void
93
+ }
62
94
  def run(image_url:, tag: 'latest', env: {}, ports: [])
63
95
  cmd = [
64
96
  executable, 'run',
@@ -72,6 +104,7 @@ module Kuby
72
104
  execc(cmd)
73
105
  end
74
106
 
107
+ sig { params(image_url: String).returns(T::Array[T::Hash[Symbol, String]]) }
75
108
  def images(image_url)
76
109
  cmd = [
77
110
  executable, 'images', image_url,
@@ -85,6 +118,7 @@ module Kuby
85
118
  end
86
119
  end
87
120
 
121
+ sig { params(image_url: String, tag: String).void }
88
122
  def push(image_url, tag)
89
123
  systemm([
90
124
  executable, 'push', "#{image_url}:#{tag}"
@@ -96,9 +130,32 @@ module Kuby
96
130
  end
97
131
  end
98
132
 
133
+ sig { params(image_url: String, tag: String).void }
134
+ def pull(image_url, tag)
135
+ systemm([
136
+ executable, 'pull', "#{image_url}:#{tag}"
137
+ ])
138
+
139
+ unless last_status.success?
140
+ raise PullError, 'pull failed: docker command exited with '\
141
+ "status code #{last_status.exitstatus}"
142
+ end
143
+ end
144
+
145
+ sig { returns(Symbol) }
99
146
  def status_key
100
147
  :kuby_docker_cli_last_status
101
148
  end
149
+
150
+ sig { returns(Symbol) }
151
+ def stdout_key
152
+ :kuby_docker_stdout
153
+ end
154
+
155
+ sig { returns(Symbol) }
156
+ def stderr_key
157
+ :kuby_docker_stderr
158
+ end
102
159
  end
103
160
  end
104
161
  end
@@ -1,19 +1,27 @@
1
+ # typed: strict
2
+
1
3
  module Kuby
2
4
  module Docker
3
5
  class CopyPhase < Layer
4
- DEFAULT_PATHS = ['./'].freeze
6
+ extend T::Sig
7
+
8
+ DEFAULT_PATHS = T.let(['./'].freeze, T::Array[String])
5
9
 
10
+ sig { returns(T::Array[String]) }
6
11
  attr_reader :paths
7
12
 
8
- def initialize(*args)
13
+ sig { params(environment: Environment).void }
14
+ def initialize(environment)
9
15
  super
10
- @paths = []
16
+ @paths = T.let([], T::Array[String])
11
17
  end
12
18
 
19
+ sig { params(path: String).void }
13
20
  def <<(path)
14
21
  paths << path
15
22
  end
16
23
 
24
+ sig { params(dockerfile: Dockerfile).void }
17
25
  def apply_to(dockerfile)
18
26
  to_copy = paths.empty? ? DEFAULT_PATHS : paths
19
27
  to_copy.each { |path| dockerfile.copy(path, '.') }
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  require 'kube-dsl'
2
3
 
3
4
  module Kuby
@@ -1,38 +1,41 @@
1
+ # typed: strict
2
+
1
3
  module Kuby
2
4
  module Docker
3
- class Debian
4
- SHELL_EXE = '/bin/bash'
5
+ class Debian < Distro
6
+ SHELL_EXE = T.let('/bin/bash'.freeze, String)
5
7
 
6
- DEFAULT_PACKAGES = [
7
- [:ca_certificates],
8
+ DEFAULT_PACKAGES = T.let([
9
+ [:ca_certificates, nil],
8
10
  [:nodejs, '12.14.1'],
9
11
  [:yarn, '1.21.1']
10
- ].freeze
12
+ ].freeze, T::Array[[Symbol, T.nilable(String)]])
11
13
 
14
+ sig { returns(Layer) }
12
15
  attr_reader :phase
13
16
 
14
- def initialize(phase)
15
- @phase = phase
16
- end
17
-
17
+ sig { override.params(packages: T::Array[Distro::PackageImpl], into: Dockerfile).void }
18
18
  def install(packages, into:)
19
19
  dockerfile = into
20
20
  install_managed(packages, dockerfile)
21
21
  install_unmanaged(packages, dockerfile)
22
22
  end
23
23
 
24
+ sig { override.returns(T::Array[[Symbol, T.nilable(String)]]) }
24
25
  def default_packages
25
26
  DEFAULT_PACKAGES
26
27
  end
27
28
 
29
+ sig { override.returns(String) }
28
30
  def shell_exe
29
31
  SHELL_EXE
30
32
  end
31
33
 
32
34
  private
33
35
 
36
+ sig { params(packages: T::Array[Distro::PackageImpl], dockerfile: Dockerfile).void }
34
37
  def install_managed(packages, dockerfile)
35
- pkgs = packages.select(&:managed?)
38
+ pkgs = T.cast(packages.select(&:managed?), T::Array[Distro::ManagedPackageImpl])
36
39
 
37
40
  pkg_cmd = <<~CMD.strip
38
41
  apt-get update -qq && \\
@@ -49,6 +52,7 @@ module Kuby
49
52
  dockerfile.run(pkg_cmd)
50
53
  end
51
54
 
55
+ sig { params(packages: T::Array[Distro::PackageImpl], dockerfile: Dockerfile).void }
52
56
  def install_unmanaged(packages, dockerfile)
53
57
  packages
54
58
  .reject(&:managed?)
@@ -0,0 +1,195 @@
1
+ # typed: strict
2
+
3
+ module Kuby
4
+ module Docker
5
+ class WebserverDevPhase < Layer
6
+ extend T::Sig
7
+
8
+ DEFAULT_PORT = T.let('3000'.freeze, String)
9
+
10
+ sig { params(port: String).void }
11
+ attr_writer :port
12
+
13
+ sig { params(environment: Environment).void }
14
+ def initialize(environment)
15
+ super
16
+
17
+ @port = T.let(@port, T.nilable(String))
18
+ end
19
+
20
+ sig { override.params(dockerfile: Dockerfile).void }
21
+ def apply_to(dockerfile)
22
+ dockerfile.expose(port)
23
+ end
24
+
25
+ sig { returns(String) }
26
+ def port
27
+ @port || DEFAULT_PORT
28
+ end
29
+ end
30
+
31
+ class DevSpec
32
+ extend T::Sig
33
+
34
+ sig { returns(Environment) }
35
+ attr_reader :environment
36
+
37
+ sig { params(environment: Environment).void }
38
+ def initialize(environment)
39
+ @environment = environment
40
+
41
+ @setup_phase = T.let(@setup_phase, T.nilable(SetupPhase))
42
+ @package_phase = T.let(@package_phase, T.nilable(PackagePhase))
43
+ @webserver_phase = T.let(@webserver_phase, T.nilable(WebserverDevPhase))
44
+ @metadata = T.let(@metadata, T.nilable(Metadata))
45
+ @distro_spec = T.let(@distro_spec, T.nilable(Distro))
46
+ @cli = T.let(@cli, T.nilable(CLI))
47
+ @tags = T.let(@tags, T.nilable(LocalTags))
48
+ @layer_stack = T.let(@layer_stack, T.nilable(Docker::LayerStack))
49
+ end
50
+
51
+ sig { params(dir: String).void }
52
+ def working_dir(dir)
53
+ setup_phase.working_dir = dir
54
+ end
55
+
56
+ sig { params(env: String).void }
57
+ def rails_env(env)
58
+ setup_phase.rails_env = env
59
+ end
60
+
61
+ sig {
62
+ params(
63
+ package_name: Symbol,
64
+ version: T.nilable(String)
65
+ )
66
+ .void
67
+ }
68
+ def package(package_name, version = nil)
69
+ package_phase.add(package_name, version)
70
+ end
71
+
72
+ sig { params(distro_name: Symbol).void }
73
+ def distro(distro_name)
74
+ metadata.distro = distro_name
75
+ @distro_spec = nil
76
+ end
77
+
78
+ sig { params(port: String).void }
79
+ def port(port)
80
+ webserver_phase.port = port
81
+ end
82
+
83
+ sig {
84
+ params(
85
+ name: Symbol,
86
+ layer: T.nilable(Layer),
87
+ block: T.nilable(T.proc.params(df: Dockerfile).void)
88
+ )
89
+ .void
90
+ }
91
+ def use(name, layer = nil, &block)
92
+ layer_stack.use(name, layer, &block)
93
+ end
94
+
95
+ sig {
96
+ params(
97
+ name: Symbol,
98
+ layer: T.nilable(T.any(Layer, T::Hash[Symbol, T.untyped])),
99
+ options: T::Hash[Symbol, T.untyped],
100
+ block: T.nilable(T.proc.params(df: Dockerfile).void)
101
+ )
102
+ .void
103
+ }
104
+ def insert(name, layer = nil, options = {}, &block)
105
+ layer_stack.insert(name, layer, options, &block)
106
+ end
107
+
108
+ sig { params(name: Symbol).void }
109
+ def delete(name)
110
+ layer_stack.delete(name)
111
+ end
112
+
113
+ sig { params(name: Symbol).returns(T::Boolean) }
114
+ def exists?(name)
115
+ layer_stack.includes?(name)
116
+ end
117
+
118
+ sig { returns(Dockerfile) }
119
+ def to_dockerfile
120
+ Dockerfile.new.tap do |df|
121
+ layer_stack.each { |layer| layer.apply_to(df) }
122
+ df.cmd("#{distro_spec.shell_exe} -c 'while test 1; do sleep 5; done'")
123
+ end
124
+ end
125
+
126
+ sig { returns(SetupPhase) }
127
+ def setup_phase
128
+ @setup_phase ||= SetupPhase.new(environment)
129
+ end
130
+
131
+ sig { returns(PackagePhase) }
132
+ def package_phase
133
+ @package_phase ||= PackagePhase.new(environment)
134
+ end
135
+
136
+ sig { returns(WebserverDevPhase) }
137
+ def webserver_phase
138
+ @webserver_phase ||= WebserverDevPhase.new(environment)
139
+ end
140
+
141
+ sig { returns(Metadata) }
142
+ def metadata
143
+ @metadata ||= Metadata.new(environment)
144
+ end
145
+
146
+ sig { returns(Distro) }
147
+ def distro_spec
148
+ @distro_spec ||= if distro_klass = Kuby.distros[metadata.distro]
149
+ distro_klass.new(self)
150
+ else
151
+ raise MissingDistroError, "distro '#{metadata.distro}' hasn't been registered"
152
+ end
153
+ end
154
+
155
+ sig { returns(String) }
156
+ def tag
157
+ t = ENV.fetch('KUBY_DOCKER_TAG') do
158
+ tags.latest_timestamp_tag
159
+ end
160
+
161
+ unless t
162
+ raise MissingTagError, 'could not find latest timestamped tag'
163
+ end
164
+
165
+ t.to_s
166
+ end
167
+
168
+ sig { params(current_tag: String).returns(String) }
169
+ def previous_tag(current_tag)
170
+ raise MissingTagError, 'cannot roll back in the development environment'
171
+ end
172
+
173
+ sig { returns(CLI) }
174
+ def cli
175
+ @cli ||= Docker::CLI.new
176
+ end
177
+
178
+ private
179
+
180
+ sig { returns(LocalTags) }
181
+ def tags
182
+ @tags ||= LocalTags.new(cli, metadata)
183
+ end
184
+
185
+ sig { returns(Docker::LayerStack) }
186
+ def layer_stack
187
+ @layer_stack ||= Docker::LayerStack.new.tap do |stack|
188
+ stack.use(:setup_phase, setup_phase)
189
+ stack.use(:package_phase, package_phase)
190
+ stack.use(:webserver_phase, webserver_phase)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end