kuby-core 0.14.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b6d5377f043be7d0aa02cddf817a9dd7fdbaa500c2ed94a2fdb92fa7ef0c1cc
4
- data.tar.gz: 57c6cb1a15c4385a7675a05637dbfd085e2b2f438f5429104ec66203e854dd68
3
+ metadata.gz: 06ed279a1b0ecaac0daed1b868610bac67e6564611360de4cd841a24827438e2
4
+ data.tar.gz: f8616a5b0ab87c4fb71afa3afba0e001760b3c0855b7670b976013de3c1db5d6
5
5
  SHA512:
6
- metadata.gz: 9c903b0768473d63df1a3ddb9ee3146193abad9c809614f7ccd2e7b7c711a2a3effd3810a3b67780ee68ea3198b8770099fcb3fd4d2a334b39b664f59b8e9e8f
7
- data.tar.gz: 69fc8934c14181930c03d3d3ffe050e2da0a85edbbd2f5bba38bf0509131149c643ea42577afcec3a4625057eab52f57bab1e00c3271bedcf491d2e11f1ae696
6
+ metadata.gz: d75bf991f55a97798152449606740b2d94c9a012ad7c17ea0b9b940c9ca9b3aad0e31baa4635ffeeec584144492d967054cdbfffa386c05c7427f1e90ac58e8f
7
+ data.tar.gz: 73b7718181d0e43ab1382c70a4a22da1835da30c887c2f42bc67bcce15d811aa0efab93df3a5fd6d28e2b2ce6d6dc3a73154a4cacdadcfeb9641ca0b2ba5604c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ## 0.17.1
2
+ * Allow storage class to be customized when using the built-in bare metal provider.
3
+ * Fix a bug where the assets image would be built using the previous app image instead of the current one.
4
+ * Centralize the lookup of the Rails master key (not user-facing).
5
+ * Add descriptions to numerous CLI commands and their arguments.
6
+ * CLI now accepts multiple `--only` flags passed to subcommands like `build`, `push`, etc.
7
+ * Allow running setup steps for individual plugins.
8
+
9
+ ## 0.16.1
10
+ * Fix a few bugs caused by leaving `continue-on-error: true` in the GitHub actions config 🤦
11
+ - Docker builds would fail if no previous images existed in the registry.
12
+ - The assets image would fail to build because `RAILS_MASTER_KEY` was not propagated correctly.
13
+ * Use the kuby-prebundler gem in the integration tests instead of a custom layer.
14
+
15
+ ## 0.16.0
16
+ * Allow Bundler executable to be customized.
17
+ - Main use-case is to allow adding in the Prebundler plugin, https://github.com/getkuby/kuby-prebundler.
18
+ * Print error message and exit on missing Docker build args.
19
+ - Ignore with `--ignore-missing-args`.
20
+
21
+ ## 0.15.0
22
+ * Add an extra parameter to the Docker spec for specifying the URL of the Docker registry index.
23
+ - In most cases, the registry and registry index URLs will be the same. However Docker Hub, the default registry, uses index.docker.io for API requests (catalog, tags, etc) but only allows pushes to docker.io.
24
+ * Add config/master.key to the .dockerignore created by the Rails generator.
25
+ - This was an unfortunate oversight, but such are the perils of using pre-1.0 software.
26
+ * Print an error message if building images on non-x86 hardware.
27
+ - People with M1 Macs are running into mismatches between their laptop's arch and the arch of their hosting provider's hardware. The error message asks you to explicitly pass the `--platform` flag to Docker.
28
+ - We may need to revisit this in the future if ARM processors become more popular.
29
+
1
30
  ## 0.14.0
2
31
  * Make RAILS_MASTER_KEY available in the assets image (@palkan, #63)
3
32
  * Allow specifying custom docker build options via CLI (@palkan, #65)
data/lib/kuby/commands.rb CHANGED
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: false
2
2
 
3
3
  require 'kuby/version'
4
4
  require 'gli'
@@ -62,10 +62,22 @@ module Kuby
62
62
  true
63
63
  end
64
64
 
65
- desc 'Builds the Docker image.'
65
+ desc 'Builds Docker images.'
66
66
  command :build do |c|
67
+ c.desc 'Docker build argument.'
67
68
  c.flag [:a, :arg], required: false, multiple: true
68
- c.flag [:only], required: false
69
+
70
+ c.desc 'When enabled, ignores missing build arguments.'
71
+ c.switch [:'ignore-missing-args'], required: false, default: false
72
+
73
+ c.desc 'Build only the images associated with the specified identifier(s). '\
74
+ 'Run `kuby images` for a list of all valid identifiers (note that '\
75
+ 'identifiers can be associated with more than one image).'
76
+ c.flag [:only], required: false, multiple: true
77
+
78
+ c.desc 'The directory to use as the Docker build context.'
79
+ c.flag [:c, :context], required: false
80
+
69
81
  c.action do |global_options, options, docker_args|
70
82
  build_args = {}.tap do |build_args|
71
83
  (options[:arg] || []).each do |a|
@@ -75,30 +87,42 @@ module Kuby
75
87
  end
76
88
  end
77
89
 
78
- tasks.build(build_args, docker_args, options[:only])
90
+ tasks.build(
91
+ build_args, docker_args,
92
+ only: options[:only],
93
+ ignore_missing_args: options[:'ignore-missing-args'],
94
+ context: options[:context]
95
+ )
79
96
  end
80
97
  end
81
98
 
82
- desc 'Pushes the Docker image to the configured registry.'
99
+ desc 'Pushes Docker images to their associated registries.'
83
100
  command :push do |c|
84
- c.flag [:only], required: false
101
+ c.desc 'Push only the images associated with the specified identifier(s). '\
102
+ 'Run `kuby images` for a list of all valid identifiers (note that '\
103
+ 'identifiers can be associated with more than one image).'
104
+ c.flag [:only], required: false, multiple: true
85
105
  c.action do |global_options, options, args|
86
- tasks.push(options[:only])
106
+ tasks.push(only: options[:only])
87
107
  end
88
108
  end
89
109
 
90
110
  desc 'Gets your Kubernetes cluster ready to run your Rails app.'
91
111
  command :setup do |c|
112
+ c.desc 'Run the setup routines for only the specified plugin identifier(s).'
113
+ c.flag [:only], required: false, multiple: true
92
114
  c.action do |global_options, options, args|
93
- tasks.setup
115
+ tasks.setup(only: options[:only])
94
116
  end
95
117
  end
96
118
 
97
119
  desc 'Prints the effective Dockerfiles used to build Docker images.'
98
120
  command :dockerfiles do |c|
99
- c.flag [:only], required: false
121
+ c.desc 'Print Dockerfiles for only the images associated with the specified '\
122
+ 'identifier(s).'
123
+ c.flag [:only], required: false, multiple: true
100
124
  c.action do |global_options, options, args|
101
- tasks.print_dockerfiles(options[:only])
125
+ tasks.print_dockerfiles(only: options[:only])
102
126
  end
103
127
  end
104
128
 
@@ -111,7 +135,7 @@ module Kuby
111
135
  end
112
136
  end
113
137
 
114
- desc 'Rolls back to the previous Docker tag.'
138
+ desc 'Rolls back to the previous release.'
115
139
  command :rollback do |c|
116
140
  c.action do |global_options, options, args|
117
141
  tasks.rollback
@@ -120,25 +144,37 @@ module Kuby
120
144
 
121
145
  desc 'Prints the effective Kubernetes resources that will be applied on deploy.'
122
146
  command :resources do |c|
147
+ c.desc 'Only print resources of the given kind.'
123
148
  c.flag [:K, :kind], required: false
149
+
150
+ c.desc 'Only print resources that match the given name.'
124
151
  c.flag [:N, :name], required: false
152
+
125
153
  c.action do |global_options, options, args|
126
154
  tasks.print_resources(options[:kind], options[:name])
127
155
  end
128
156
  end
129
157
 
130
- desc 'Prints out the contents of the kubeconfig Kuby is using to communicate with your cluster.'
158
+ desc 'Prints out the contents of the kubeconfig file Kuby is using to communicate '\
159
+ 'with your cluster.'
131
160
  command :kubeconfig do |c|
132
161
  c.action do |global_options, options, args|
133
162
  tasks.print_kubeconfig
134
163
  end
135
164
  end
136
165
 
166
+ desc 'Prints out the URLs to the latest Docker images in the Docker registry.'
167
+ command :images do |c|
168
+ c.action do |global_options, options, args|
169
+ tasks.print_images
170
+ end
171
+ end
172
+
137
173
  desc 'Runs an arbitrary kubectl command.'
138
174
  command :kubectl do |c|
139
175
  c.desc 'Prefixes the kubectl command with the namespace associated with '\
140
- 'the current environment. For example, if the Kuby env is "production", '\
141
- 'this option will prefix the kubectl command with "-n myapp-production".'
176
+ 'the current environment. For example, if the Kuby env is "production", '\
177
+ 'this option will prefix the kubectl command with "-n myapp-production".'
142
178
  c.switch [:N, :namespaced], default: false
143
179
  c.action do |global_options, options, args|
144
180
  if options[:namespaced]
@@ -170,7 +206,7 @@ module Kuby
170
206
  rc.desc 'Runs an arbitrary command inside a running Rails pod.'
171
207
  rc.command :exec do |c|
172
208
  c.action do |global_options, options, args|
173
- tasks.remote_exec([*args, *@rails_options])
209
+ tasks.remote_exec([*args, *T.unsafe(@rails_options)])
174
210
  end
175
211
  end
176
212
 
@@ -11,23 +11,15 @@ module Kuby
11
11
  dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
12
12
  image_url: String,
13
13
  credentials: Credentials,
14
+ registry_index_url: T.nilable(String),
14
15
  main_tag: T.nilable(String),
15
16
  alias_tags: T::Array[String]
16
17
  ).void
17
18
  }
18
- def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
19
+ def initialize(dockerfile, image_url, credentials, registry_index_url = nil, main_tag = nil, alias_tags = [])
19
20
  super
20
21
  @identifier = "app"
21
22
  end
22
-
23
- sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).returns(AppImage) }
24
- def build(build_args = {}, docker_args = [])
25
- unless ENV.fetch('RAILS_MASTER_KEY', '').empty?
26
- build_args['RAILS_MASTER_KEY'] = T.must(ENV['RAILS_MASTER_KEY'])
27
- end
28
-
29
- super(build_args, docker_args)
30
- end
31
23
  end
32
24
  end
33
25
  end
@@ -29,6 +29,12 @@ module Kuby
29
29
  sig { params(without: T::Array[String]).void }
30
30
  attr_writer :without
31
31
 
32
+ sig { returns(T.nilable(String)) }
33
+ attr_reader :executable
34
+
35
+ sig { params(executable: String).void }
36
+ attr_writer :executable
37
+
32
38
  sig { params(environment: Environment).void }
33
39
  def initialize(environment)
34
40
  super
@@ -37,6 +43,7 @@ module Kuby
37
43
  @gemfile = T.let(@gemfile, T.nilable(String))
38
44
  @gemfiles = T.let([], T::Array[String])
39
45
  @without = T.let(@without, T.nilable(T::Array[String]))
46
+ @executable = T.let(@executable, T.nilable(String))
40
47
  end
41
48
 
42
49
  sig { override.params(dockerfile: Dockerfile).void }
@@ -61,14 +68,14 @@ module Kuby
61
68
  end
62
69
 
63
70
  dockerfile.run(
64
- 'bundle', 'install',
71
+ executable || 'bundle', 'install',
65
72
  '--jobs', '$(nproc)',
66
73
  '--retry', '3',
67
74
  '--gemfile', gf
68
75
  )
69
76
 
70
77
  # generate binstubs and add the bin directory to our path
71
- dockerfile.run('bundle', 'binstubs', '--all')
78
+ dockerfile.run(executable || 'bundle', 'binstubs', '--all')
72
79
  dockerfile.env("PATH=./bin:$PATH")
73
80
  end
74
81
 
@@ -53,8 +53,15 @@ module Kuby
53
53
  config.fetch('auths', {}).keys
54
54
  end
55
55
 
56
- sig { params(image: Image, build_args: T::Hash[T.any(Symbol, String), String], docker_args: T::Array[String]).void }
57
- def build(image, build_args: {}, docker_args: [])
56
+ sig {
57
+ params(
58
+ image: Image,
59
+ build_args: T::Hash[T.any(Symbol, String), String],
60
+ docker_args: T::Array[String],
61
+ context: T.nilable(String)
62
+ ).void
63
+ }
64
+ def build(image, build_args: {}, docker_args: [], context: nil)
58
65
  cmd = [
59
66
  executable, 'build',
60
67
  *image.tags.flat_map { |tag| ['-t', "#{image.image_url}:#{tag}"] },
@@ -63,7 +70,7 @@ module Kuby
63
70
  end,
64
71
  '-f-',
65
72
  *docker_args,
66
- '.'
73
+ context || '.'
67
74
  ]
68
75
 
69
76
  open3_w(cmd) do |stdin, _wait_threads|
@@ -5,11 +5,36 @@ module Kuby
5
5
  class DockerURI
6
6
  extend T::Sig
7
7
 
8
- DEFAULT_REGISTRY_HOST = T.let('index.docker.io'.freeze, String)
9
- DEFAULT_REGISTRY_PORT = T.let(443, Integer)
8
+ DEFAULT_REGISTRY_HOST = T.let('docker.io'.freeze, String)
9
+ DEFAULT_REGISTRY_INDEX_HOST = T.let('index.docker.io'.freeze, String)
10
+ DEFAULT_PORT = T.let(443, Integer)
10
11
 
11
12
  sig { params(url: String).returns(DockerURI) }
12
- def self.parse(url)
13
+ def self.parse_uri(url)
14
+ parse(
15
+ url,
16
+ default_host: DEFAULT_REGISTRY_HOST,
17
+ default_port: DEFAULT_PORT
18
+ )
19
+ end
20
+
21
+ sig { params(url: String).returns(DockerURI) }
22
+ def self.parse_index_uri(url)
23
+ parse(
24
+ url,
25
+ default_host: DEFAULT_REGISTRY_INDEX_HOST,
26
+ default_port: DEFAULT_PORT
27
+ )
28
+ end
29
+
30
+ sig {
31
+ params(
32
+ url: String,
33
+ default_host: T.nilable(String),
34
+ default_port: T.nilable(Integer)
35
+ ).returns(DockerURI)
36
+ }
37
+ def self.parse(url, default_host:, default_port:)
13
38
  if idx = url.index('://')
14
39
  url = url[(idx + 3)..-1] || ''
15
40
  end
@@ -17,9 +42,9 @@ module Kuby
17
42
  host_port, *path = url.split('/')
18
43
  host, port, *path = if host_port =~ /[.:]/
19
44
  hst, prt = T.must(host_port).split(':')
20
- [T.must(hst), prt || DEFAULT_REGISTRY_PORT, *path]
45
+ [T.must(hst), prt || default_port, *path]
21
46
  else
22
- [DEFAULT_REGISTRY_HOST, DEFAULT_REGISTRY_PORT, host_port, *path]
47
+ [default_host, default_port, host_port, *path]
23
48
  end
24
49
 
25
50
  new(host.to_s, port.to_i, (path || []).join('/'))
@@ -40,6 +65,11 @@ module Kuby
40
65
  @port = port
41
66
  @path = path
42
67
  end
68
+
69
+ sig { returns(T::Boolean) }
70
+ def has_default_port?
71
+ port == DEFAULT_PORT
72
+ end
43
73
  end
44
74
  end
45
75
  end
@@ -11,6 +11,9 @@ module Kuby
11
11
  sig { returns(String) }
12
12
  attr_reader :image_url
13
13
 
14
+ sig { returns(T.nilable(String)) }
15
+ attr_reader :registry_index_url
16
+
14
17
  sig { returns(Credentials) }
15
18
  attr_reader :credentials
16
19
 
@@ -25,13 +28,15 @@ module Kuby
25
28
  dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
26
29
  image_url: String,
27
30
  credentials: Credentials,
31
+ registry_index_url: T.nilable(String),
28
32
  main_tag: T.nilable(String),
29
33
  alias_tags: T::Array[String]
30
34
  ).void
31
35
  }
32
- def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
36
+ def initialize(dockerfile, image_url, credentials, registry_index_url = nil, main_tag = nil, alias_tags = [])
33
37
  @dockerfile = T.let(dockerfile, T.any(Dockerfile, T.proc.returns(Dockerfile)))
34
38
  @image_url = T.let(image_url, String)
39
+ @registry_index_url = T.let(registry_index_url, T.nilable(String))
35
40
  @credentials = T.let(credentials, Credentials)
36
41
  @main_tag = T.let(main_tag, T.nilable(String))
37
42
  @alias_tags = T.let(alias_tags, T::Array[String])
@@ -39,6 +44,9 @@ module Kuby
39
44
 
40
45
  @image_host = T.let(@image_host, T.nilable(String))
41
46
  @image_hostname = T.let(@image_hostname, T.nilable(String))
47
+ @registry_index_host = T.let(@registry_index_host, T.nilable(String))
48
+ @registry_index_hostname = T.let(@registry_index_hostname, T.nilable(String))
49
+ @registry_index_uri = T.let(@registry_index_uri, T.nilable(DockerURI))
42
50
  @image_repo = T.let(@image_repo, T.nilable(String))
43
51
  @full_image_uri = T.let(@full_image_uri, T.nilable(DockerURI))
44
52
  @docker_cli = T.let(@docker_cli, T.nilable(Docker::CLI))
@@ -73,6 +81,16 @@ module Kuby
73
81
  @image_host ||= "#{image_uri.host}:#{image_uri.port}"
74
82
  end
75
83
 
84
+ sig { returns(String) }
85
+ def registry_index_host
86
+ @registry_index_host ||= "#{registry_index_uri.host}:#{registry_index_uri.port}"
87
+ end
88
+
89
+ sig { returns(String) }
90
+ def registry_index_hostname
91
+ @registry_index_hostname ||= registry_index_uri.host
92
+ end
93
+
76
94
  sig { returns(String) }
77
95
  def image_hostname
78
96
  @image_hostname ||= image_uri.host
@@ -85,7 +103,12 @@ module Kuby
85
103
 
86
104
  sig { returns(DockerURI) }
87
105
  def image_uri
88
- @full_image_uri ||= DockerURI.parse(image_url)
106
+ @full_image_uri ||= DockerURI.parse_uri(image_url)
107
+ end
108
+
109
+ sig { returns(DockerURI) }
110
+ def registry_index_uri
111
+ @registry_index_uri ||= DockerURI.parse_index_uri(registry_index_url || image_url)
89
112
  end
90
113
 
91
114
  sig { returns(T::Array[String]) }
@@ -93,8 +116,8 @@ module Kuby
93
116
  [main_tag, *alias_tags].compact
94
117
  end
95
118
 
96
- sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).void }
97
- def build(build_args = {}, docker_args = [])
119
+ sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String], context: T.nilable(String)).void }
120
+ def build(build_args = {}, docker_args = [], context: nil)
98
121
  raise NotImplementedError, 'please use a Docker::Image subclass'
99
122
  end
100
123
 
@@ -112,7 +135,7 @@ module Kuby
112
135
 
113
136
  sig { params(main_tag: String, alias_tags: T::Array[String]).returns(Image) }
114
137
  def duplicate_with_tags(main_tag, alias_tags)
115
- self.class.new(dockerfile, image_url, credentials, main_tag, alias_tags)
138
+ self.class.new(dockerfile, image_url, credentials, registry_index_url, main_tag, alias_tags)
116
139
  end
117
140
  end
118
141
  end
@@ -13,6 +13,9 @@ module Kuby
13
13
  sig { returns(T.nilable(String)) }
14
14
  attr_reader :image_url_str
15
15
 
16
+ sig { returns(T.nilable(String)) }
17
+ attr_reader :registry_index_url_str
18
+
16
19
  sig { params(environment: Environment).void }
17
20
  def initialize(environment)
18
21
  @environment = environment
@@ -31,6 +34,7 @@ module Kuby
31
34
  @layer_stack = T.let(@layer_stack, T.nilable(Kuby::Docker::LayerStack))
32
35
 
33
36
  @image_url_str = T.let(@image_url_str, T.nilable(String))
37
+ @registry_index_url_str = T.let(@registry_index_url_str, T.nilable(String))
34
38
  @image = T.let(@image, T.nilable(Docker::AppImage))
35
39
  end
36
40
 
@@ -96,6 +100,11 @@ module Kuby
96
100
  @image_url_str = url
97
101
  end
98
102
 
103
+ sig { params(url: String).void }
104
+ def registry_index_url(url)
105
+ @registry_index_url_str = url
106
+ end
107
+
99
108
  sig {
100
109
  params(
101
110
  name: Symbol,
@@ -148,7 +157,7 @@ module Kuby
148
157
  end
149
158
 
150
159
  Docker::AppImage.new(
151
- dockerfile, T.must(image_url_str), credentials
160
+ dockerfile, T.must(image_url_str), credentials, registry_index_url_str
152
161
  )
153
162
  end
154
163
  end
@@ -12,11 +12,12 @@ module Kuby
12
12
  dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
13
13
  image_url: String,
14
14
  credentials: Credentials,
15
+ registry_index_url_str: T.nilable(String),
15
16
  main_tag: T.nilable(String),
16
17
  alias_tags: T::Array[String]
17
18
  ).void
18
19
  }
19
- def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
20
+ def initialize(dockerfile, image_url, credentials, registry_index_url_str = nil, main_tag = nil, alias_tags = [])
20
21
  @new_version = T.let(@new_version, T.nilable(Image))
21
22
  @current_version = T.let(@current_version, T.nilable(Image))
22
23
  @previous_version = T.let(@previous_version, T.nilable(Image))
@@ -30,16 +31,18 @@ module Kuby
30
31
 
31
32
  sig { returns(Image) }
32
33
  def new_version
33
- @new_version ||= duplicate_with_tags(
34
- TimestampTag.new(Time.now).to_s, [Kuby::Docker::LATEST_TAG]
35
- )
34
+ @new_version ||= create_new_version
36
35
  end
37
36
 
38
37
  sig { returns(Image) }
39
38
  def current_version
40
- @current_version ||= duplicate_with_tags(
41
- latest_timestamp_tag.to_s, [Kuby::Docker::LATEST_TAG]
42
- )
39
+ @current_version ||= begin
40
+ duplicate_with_tags(
41
+ latest_timestamp_tag.to_s, [Kuby::Docker::LATEST_TAG]
42
+ )
43
+ rescue MissingTagError
44
+ create_new_version
45
+ end
43
46
  end
44
47
 
45
48
  sig { params(current_tag: T.nilable(String)).returns(Image) }
@@ -73,11 +76,9 @@ module Kuby
73
76
  tag
74
77
  end
75
78
 
76
- sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).void }
77
- def build(build_args = {}, docker_args = [])
78
- docker_cli.build(new_version, build_args: build_args, docker_args: docker_args)
79
- @current_version = new_version
80
- @new_version = nil
79
+ sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String], context: T.nilable(String)).void }
80
+ def build(build_args = {}, docker_args = [], context: nil)
81
+ docker_cli.build(new_version, build_args: build_args, docker_args: docker_args, context: context)
81
82
  end
82
83
 
83
84
  sig { params(tag: String).void }
@@ -87,10 +88,17 @@ module Kuby
87
88
 
88
89
  private
89
90
 
91
+ sig { returns(Image) }
92
+ def create_new_version
93
+ duplicate_with_tags(
94
+ TimestampTag.new(Time.now).to_s, [Kuby::Docker::LATEST_TAG]
95
+ )
96
+ end
97
+
90
98
  sig { returns(::Docker::Remote::Client) }
91
99
  def remote_client
92
100
  @remote_client ||= ::Docker::Remote::Client.new(
93
- image_host, image_repo, credentials.username, credentials.password,
101
+ registry_index_host, image_repo, credentials.username, credentials.password,
94
102
  )
95
103
  end
96
104
 
@@ -106,7 +114,7 @@ module Kuby
106
114
 
107
115
  sig { returns(RemoteTags) }
108
116
  def remote
109
- @remote ||= RemoteTags.new(remote_client, image_url)
117
+ @remote ||= RemoteTags.new(remote_client, registry_index_host)
110
118
  end
111
119
  end
112
120
  end
@@ -7,12 +7,13 @@ module Kuby
7
7
  class BareMetalProvider < Provider
8
8
  extend T::Sig
9
9
 
10
- STORAGE_CLASS_NAME = T.let('hostpath'.freeze, String)
10
+ DEFAULT_STORAGE_CLASS = T.let('hostpath'.freeze, String)
11
11
 
12
12
  class Config
13
13
  extend ::KubeDSL::ValueFields
14
14
 
15
15
  value_fields :kubeconfig
16
+ value_fields :storage_class
16
17
  end
17
18
 
18
19
  sig { returns(Config) }
@@ -36,7 +37,7 @@ module Kuby
36
37
 
37
38
  sig { returns(String) }
38
39
  def storage_class_name
39
- STORAGE_CLASS_NAME
40
+ config.storage_class
40
41
  end
41
42
 
42
43
  private
@@ -46,6 +47,7 @@ module Kuby
46
47
  configure do
47
48
  # default kubeconfig path
48
49
  kubeconfig File.join(ENV['HOME'], '.kube', 'config')
50
+ storage_class DEFAULT_STORAGE_CLASS
49
51
  end
50
52
  end
51
53
  end
@@ -89,13 +89,23 @@ module Kuby
89
89
  @tag = nil
90
90
  end
91
91
 
92
- def setup
93
- provider.before_setup
94
- provider.setup
92
+ def setup(only: [])
93
+ plugins = if only.empty?
94
+ @plugins
95
+ else
96
+ @plugins.each_with_object({}) do |(name, plg), memo|
97
+ memo[name] = plg if only.include?(name)
98
+ end
99
+ end
100
+
101
+ if only.empty?
102
+ provider.before_setup
103
+ provider.setup
104
+ end
95
105
 
96
- @plugins.each { |_, plg| plg.before_setup }
97
- @plugins.each { |_, plg| plg.setup }
98
- @plugins.each { |_, plg| plg.after_setup }
106
+ plugins.each { |_, plg| plg.before_setup }
107
+ plugins.each { |_, plg| plg.setup }
108
+ plugins.each { |_, plg| plg.after_setup }
99
109
 
100
110
  provider.after_setup
101
111
  end
data/lib/kuby/plugin.rb CHANGED
@@ -12,6 +12,7 @@ module Kuby
12
12
  # do nothing by default
13
13
  end
14
14
 
15
+ # install any global resources like operators, etc
15
16
  def setup
16
17
  # do nothing by default
17
18
  end
@@ -1,6 +1,8 @@
1
1
  # typed: false
2
2
  module Kuby
3
3
  class PluginRegistry
4
+ include Enumerable
5
+
4
6
  ANY = 'any'.freeze
5
7
 
6
8
  def register(plugin_name, plugin_klass, environment: ANY)
@@ -19,6 +21,19 @@ module Kuby
19
21
  plugins_by_env[environment] || plugins_by_env[ANY]
20
22
  end
21
23
 
24
+ def each(&block)
25
+ return to_enum(__method__) unless block
26
+
27
+ @plugins.each_pair do |plugin_name, plugins_by_env|
28
+ plugins_by_env.each_pair do |env, plugin_klass|
29
+ case env
30
+ when ANY, Kuby.env
31
+ yield plugin_name, plugin_klass
32
+ end
33
+ end
34
+ end
35
+ end
36
+
22
37
  private
23
38
 
24
39
  def plugins
@@ -296,7 +296,7 @@ module Kuby
296
296
  end
297
297
 
298
298
  def image
299
- @image ||= RailsApp::AssetsImage.new(docker.image, -> { dockerfile })
299
+ @image ||= RailsApp::AssetsImage.new(docker.image, -> { dockerfile }, docker.image.registry_index_url)
300
300
  end
301
301
 
302
302
  private
@@ -324,6 +324,7 @@ module Kuby
324
324
  if tag
325
325
  image_name = "#{app_name}-#{tag}"
326
326
  df.from("#{base_image.image_url}:#{tag}", as: image_name)
327
+ df.arg('RAILS_MASTER_KEY')
327
328
  df.copy("--from=#{prev_image_name} #{RAILS_MOUNT_PATH}", RAILS_MOUNT_PATH)
328
329
  df.run("env RAILS_MASTER_KEY=$RAILS_MASTER_KEY bundle exec rake kuby:rails_app:assets:copy")
329
330
  end
@@ -4,16 +4,18 @@ module Kuby
4
4
  class AssetsImage < ::Kuby::Docker::Image
5
5
  attr_reader :base_image
6
6
 
7
- def initialize(base_image, dockerfile, main_tag = nil, alias_tags = [])
8
- super(dockerfile, base_image.image_url, base_image.credentials, main_tag, alias_tags)
7
+ def initialize(base_image, dockerfile, registry_index_url = nil, main_tag = nil, alias_tags = [])
8
+ super(dockerfile, base_image.image_url, base_image.credentials, registry_index_url, main_tag, alias_tags)
9
9
  @base_image = base_image
10
10
  @identifier = "assets"
11
11
  end
12
12
 
13
13
  def new_version
14
- # Asset images track the base image, so return the current version
14
+ # Asset images track the base image, so return the new version
15
15
  # here. There can be no asset image without a base image.
16
- current_version
16
+ @new_version ||= duplicate_with_annotated_tags(
17
+ base_image.new_version
18
+ )
17
19
  end
18
20
 
19
21
  def current_version
@@ -28,8 +30,8 @@ module Kuby
28
30
  )
29
31
  end
30
32
 
31
- def build(build_args = {}, docker_args = [])
32
- docker_cli.build(current_version, build_args: build_args, docker_args: docker_args)
33
+ def build(build_args = {}, docker_args = [], context: nil)
34
+ docker_cli.build(new_version, build_args: build_args, docker_args: docker_args, context: context)
33
35
  end
34
36
 
35
37
  def push(tag)
@@ -42,6 +44,7 @@ module Kuby
42
44
  self.class.new(
43
45
  base_image,
44
46
  dockerfile,
47
+ registry_index_url,
45
48
  annotate_tag(image.main_tag),
46
49
  image.alias_tags.map { |at| annotate_tag(at) }
47
50
  )
@@ -86,16 +86,38 @@ class KubyGenerator < Rails::Generators::Base
86
86
  create_file(
87
87
  '.dockerignore',
88
88
  <<~END
89
- .bundle/
90
- vendor/bundle
91
- node_modules/
92
- .node_modules/
93
- **/.git*
94
- tmp/
95
- log/
96
- engines/**/log/
97
- engines/**/tmp/
89
+ .git/
90
+
91
+ # Ignore bundler config.
92
+ .bundle
93
+
94
+ # Ignore all logfiles and tempfiles.
95
+ log/*
96
+ tmp/*
97
+ !log/.keep
98
+ !tmp/.keep
99
+
100
+ # Ignore pidfiles, but keep the directory.
101
+ tmp/pids/*
102
+ !tmp/pids/
103
+ !tmp/pids/.keep
104
+
105
+ # Ignore uploaded files in development.
106
+ storage/*
107
+ !storage/.keep
108
+
98
109
  public/assets
110
+ **/.byebug_history
111
+
112
+ # Ignore master key for decrypting credentials and more.
113
+ config/master.key
114
+
115
+ public/packs
116
+ public/packs-test
117
+ node_modules
118
+ yarn-error.log
119
+ **/yarn-debug.log*
120
+ **/.yarn-integrity
99
121
  END
100
122
  )
101
123
  end
@@ -209,15 +209,7 @@ module Kuby
209
209
  type 'Opaque'
210
210
 
211
211
  data do
212
- if master_key = ENV[MASTER_KEY_VAR]
213
- add MASTER_KEY_VAR.to_sym, master_key
214
- else
215
- master_key_path = File.join(spec.root, 'config', 'master.key')
216
-
217
- if File.exist?(master_key_path)
218
- add MASTER_KEY_VAR.to_sym, File.read(master_key_path).strip
219
- end
220
- end
212
+ add MASTER_KEY_VAR.to_sym, spec.master_key
221
213
  end
222
214
  end
223
215
 
@@ -434,6 +426,13 @@ module Kuby
434
426
  def namespace
435
427
  environment.kubernetes.namespace
436
428
  end
429
+
430
+ def master_key
431
+ @master_key ||= ENV[MASTER_KEY_VAR] || begin
432
+ master_key_path = File.join(root, 'config', 'master.key')
433
+ File.read(master_key_path).strip if File.exist?(master_key_path)
434
+ end
435
+ end
437
436
  end
438
437
  end
439
438
  end
data/lib/kuby/tasks.rb CHANGED
@@ -9,7 +9,7 @@ module Kuby
9
9
  @environment = environment
10
10
  end
11
11
 
12
- def print_dockerfiles(only = nil)
12
+ def print_dockerfiles(only: nil)
13
13
  kubernetes.docker_images.each do |image|
14
14
  next if only && image.identifier != only
15
15
 
@@ -24,23 +24,30 @@ module Kuby
24
24
  end
25
25
  end
26
26
 
27
- def setup
28
- environment.kubernetes.setup
27
+ def setup(only: [])
28
+ environment.kubernetes.setup(only: only.map(&:to_sym))
29
29
  end
30
30
 
31
- def build(build_args = {}, docker_args = [], only = nil)
31
+ def build(build_args = {}, docker_args = [], only: [], ignore_missing_args: false, context: nil)
32
+ check_platform(docker_args)
33
+
34
+ build_args['RAILS_MASTER_KEY'] ||= rails_app.master_key
35
+
36
+ check_build_args(build_args) unless ignore_missing_args
37
+
32
38
  kubernetes.docker_images.each do |image|
33
- next if only && image.identifier != only
39
+ next unless only.empty? || only.include?(image.identifier)
40
+ return unless perform_docker_login_if_necessary(image)
34
41
 
35
42
  image = image.new_version
36
43
  Kuby.logger.info("Building image #{image.image_url} with tags #{image.tags.join(', ')}")
37
- image.build(build_args, docker_args)
44
+ image.build(build_args, docker_args, context: context)
38
45
  end
39
46
  end
40
47
 
41
- def push(only = nil)
48
+ def push(only: [])
42
49
  kubernetes.docker_images.each do |image|
43
- next if only && image.identifier != only
50
+ next unless only.empty? || only.include?(image.identifier)
44
51
 
45
52
  image = image.current_version
46
53
  Kuby.logger.info("Pushing image #{image.image_url} with tags #{image.tags.join(', ')}")
@@ -49,21 +56,7 @@ module Kuby
49
56
  end
50
57
 
51
58
  def push_image(image)
52
- if image.credentials.username && !image.docker_cli.auths.include?(image.image_host)
53
- Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
54
-
55
- begin
56
- image.docker_cli.login(
57
- url: image.image_host,
58
- username: image.credentials.username,
59
- password: image.credentials.password
60
- )
61
- rescue Kuby::Docker::LoginError => e
62
- Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
63
- Kuby.logger.fatal(e.message)
64
- return
65
- end
66
- end
59
+ return unless perform_docker_login_if_necessary(image)
67
60
 
68
61
  begin
69
62
  image.tags.each { |tag| image.push(tag) }
@@ -104,6 +97,18 @@ module Kuby
104
97
  puts File.read(path)
105
98
  end
106
99
 
100
+ def print_images
101
+ rows = kubernetes.docker_images.flat_map do |image|
102
+ image = image.current_version
103
+
104
+ image.tags.map do |tag|
105
+ [image.identifier, "#{image.image_url}:#{tag}"]
106
+ end
107
+ end
108
+
109
+ puts Kuby::Utils::Table.new(%w(IDENTIFIER URL), rows).to_s
110
+ end
111
+
107
112
  def kubectl(*cmd)
108
113
  kubernetes_cli.run_cmd(cmd)
109
114
  end
@@ -145,6 +150,106 @@ module Kuby
145
150
 
146
151
  private
147
152
 
153
+ def check_platform(docker_args)
154
+ arch, * = RUBY_PLATFORM.split('-')
155
+
156
+ if arch != 'x86_64' && !docker_args.include?('--platform')
157
+ Kuby.logger.fatal(<<~END)
158
+ Hey there! It looks like your processor isn't x86-compatible.
159
+ By default, Docker will try to build images that match the
160
+ current architecture, in this case #{arch}. Most hosting
161
+ providers run x86 hardware, meaning Docker images built using
162
+ this computer's architecture might fail to run when deployed
163
+ to production. You can fix this by running the build command
164
+ with a special --platform flag, eg:
165
+
166
+ bundle exec kuby -e production build -- --platform linux/amd64
167
+
168
+ If you meant to build for the current architecture, you can
169
+ prevent this error by passing the --platform argument for the
170
+ current architecture, eg. --platform linux/arm64 for ARM, etc.
171
+ END
172
+
173
+ exit 1
174
+ end
175
+ end
176
+
177
+ def check_build_args(build_args)
178
+ required_args = kubernetes.docker_images.flat_map do |image|
179
+ image.dockerfile.commands.flat_map do |command|
180
+ case command
181
+ when Kuby::Docker::Dockerfile::Arg
182
+ command.args
183
+ else
184
+ []
185
+ end
186
+ end
187
+ end
188
+
189
+ required_args.uniq!
190
+
191
+ if File.exist?(File.join('config', 'master.key'))
192
+ required_args.delete('RAILS_MASTER_KEY')
193
+ end
194
+
195
+ missing_args = required_args - build_args.keys
196
+
197
+ if missing_args.any?
198
+ Kuby.logger.fatal(<<~END)
199
+ The following Docker build arguments are missing: #{missing_args.join(', ')}.
200
+ Please pass each argument to `kuby build` using the -a or --arg parameter (note
201
+ that the -a/--arg parameter can be specified multiple times). For example:
202
+
203
+ kuby build -a #{missing_args.first}=value ...
204
+
205
+ To ignore missing build args, pass the --ignore-missing-args parameter.
206
+ END
207
+
208
+ exit 1
209
+ end
210
+ end
211
+
212
+ def perform_docker_login_if_necessary(image)
213
+ auth_uris = image.docker_cli.auths.map do |url|
214
+ Kuby::Docker::DockerURI.parse_uri(url)
215
+ end
216
+
217
+ logged_in = image.credentials.username && (
218
+ auth_uris.any? do |uri|
219
+ image.image_hostname == uri.host ||
220
+ image.registry_index_hostname == uri.host
221
+ end
222
+ )
223
+
224
+ if !logged_in
225
+ Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
226
+
227
+ begin
228
+ # For some reason, Docker login with a port doesn't work for some
229
+ # registries (most notably Docker Hub). Since the default is 443 anyway,
230
+ # it should be fine to omit it.
231
+ url = if image.image_uri.has_default_port?
232
+ image.image_hostname # host without port
233
+ else
234
+ image.image_host # host with port
235
+ end
236
+
237
+ image.docker_cli.login(
238
+ url: url,
239
+ username: image.credentials.username,
240
+ password: image.credentials.password
241
+ )
242
+ rescue Kuby::Docker::LoginError => e
243
+ Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
244
+ Kuby.logger.fatal(e.message)
245
+
246
+ return false
247
+ end
248
+ end
249
+
250
+ true
251
+ end
252
+
148
253
  def get_first_pod
149
254
  pods = kubernetes_cli.get_objects(
150
255
  'pods', namespace, match_labels.serialize
@@ -0,0 +1,35 @@
1
+ module Kuby
2
+ module Utils
3
+ class Table
4
+ attr_reader :headers, :rows
5
+
6
+ def initialize(headers, rows)
7
+ @headers = headers
8
+ @rows = rows
9
+ end
10
+
11
+ def to_s
12
+ [headers, *rows].map { |vals| make_row(vals) }.join("\n")
13
+ end
14
+
15
+ private
16
+
17
+ def make_row(values)
18
+ columns = values.each_with_index.map do |value, idx|
19
+ col_width = col_width_at(idx) + 2
20
+ value.ljust(col_width, ' ')
21
+ end
22
+
23
+ columns.join
24
+ end
25
+
26
+ def col_width_at(idx)
27
+ col_widths[idx] ||= [headers[idx].size, *rows.map { |r| r[idx].size }].max
28
+ end
29
+
30
+ def col_widths
31
+ @col_widths ||= {}
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/kuby/utils.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Kuby
2
+ module Utils
3
+ autoload :Table, 'kuby/utils/table'
4
+ end
5
+ end
data/lib/kuby/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # typed: true
2
2
 
3
3
  module Kuby
4
- VERSION = '0.14.0'.freeze
4
+ VERSION = '0.17.0'.freeze
5
5
  end
@@ -19,8 +19,8 @@ describe Kuby::Docker::TimestampedImage do
19
19
  subject { image.current_version&.main_tag }
20
20
 
21
21
  context 'with no local or remote tags' do
22
- it 'raises an error' do
23
- expect { subject }.to raise_error(Kuby::Docker::MissingTagError)
22
+ it 'creates a new tag' do
23
+ expect(subject).to match(/\d{14}/)
24
24
  end
25
25
  end
26
26
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kuby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-28 00:00:00.000000000 Z
11
+ date: 2022-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -285,6 +285,8 @@ files:
285
285
  - lib/kuby/railtie.rb
286
286
  - lib/kuby/tasks.rb
287
287
  - lib/kuby/trailing_hash.rb
288
+ - lib/kuby/utils.rb
289
+ - lib/kuby/utils/table.rb
288
290
  - lib/kuby/version.rb
289
291
  - spec/docker/spec_spec.rb
290
292
  - spec/docker/timestamp_tag_spec.rb