kuby-core 0.13.0 → 0.16.1

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: a443b66d4dff19f6a2112b6365a9579b0490b049ef51ab612ad9e6cf4a807901
4
- data.tar.gz: 2b38151be094c758a64ecdc95c585f184f4e9eea1affbdc4d071cc7a20d499a0
3
+ metadata.gz: 5cbd792443dc33de6a20d02f6969f6d59d535f1b68fb75c9a8945615fd870a28
4
+ data.tar.gz: 3add1ef85d6758a2590d35d7aa0bfb4fffaeae55ef6567a34cf3b7557a56e55a
5
5
  SHA512:
6
- metadata.gz: 6cf93a88bc24ab142fa497daddc7227d417c138391f46db6f5b77a862c96e9c2733d880eded21d464170963902364ac0f2806f84cd083d0a5b408231987bcf2b
7
- data.tar.gz: dab2dcd180ff19421ef44aef1a3384e1faa0b49511925e6557d54500a652a5c854217a8c463cd738cd6a745b2eb1804262e35539635fdb4d98ed13d2abc90aa2
6
+ metadata.gz: 7d855f57aae01d93c2fa44e285ee417ebcc00f12de6f9c4b989a39626ec9ec13a4659eafe0b8924592de5d860666158376e5adbf69c70373703eb2a9074ca75d
7
+ data.tar.gz: dc553aa0b22e5ab262fa18fa1a60bebb421862eab7e747e06c63907fefe8f5371be56415d0bde2e959ffa775140b624aee3aa53006f1dbee272122c87bf53cc6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## 0.16.1
2
+ * Fix a few bugs caused by leaving `continue-on-error: true` in the GitHub actions config 🤦
3
+ - Docker builds would fail if no previous images existed in the registry.
4
+ - The assets image would fail to build because `RAILS_MASTER_KEY` was not propagated correctly.
5
+ * Use the kuby-prebundler gem in the integration tests instead of a custom layer.
6
+
7
+ ## 0.16.0
8
+ * Allow Bundler executable to be customized.
9
+ - Main use-case is to allow adding in the Prebundler plugin, https://github.com/getkuby/kuby-prebundler.
10
+ * Print error message and exit on missing Docker build args.
11
+ - Ignore with `--ignore-missing-args`.
12
+
13
+ ## 0.15.0
14
+ * Add an extra parameter to the Docker spec for specifying the URL of the Docker registry index.
15
+ - 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.
16
+ * Add config/master.key to the .dockerignore created by the Rails generator.
17
+ - This was an unfortunate oversight, but such are the perils of using pre-1.0 software.
18
+ * Print an error message if building images on non-x86 hardware.
19
+ - 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.
20
+ - We may need to revisit this in the future if ARM processors become more popular.
21
+
22
+ ## 0.14.0
23
+ * Make RAILS_MASTER_KEY available in the assets image (@palkan, #63)
24
+ * Allow specifying custom docker build options via CLI (@palkan, #65)
25
+
1
26
  ## 0.13.0
2
27
  * Fix handling rails/rake options in remote exec (@palkan, #60)
3
28
  * Add `bundler_phase.gemfiles(*paths)` to allow adding additional gemfiles (@palkan, #61)
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'
@@ -65,23 +65,32 @@ module Kuby
65
65
  desc 'Builds the Docker image.'
66
66
  command :build do |c|
67
67
  c.flag [:a, :arg], required: false, multiple: true
68
- c.action do |global_options, options, args|
68
+ c.switch [:'ignore-missing-args'], required: false, default: false
69
+ c.flag [:only], required: false
70
+ c.flag [:c, :context], required: false
71
+ c.action do |global_options, options, docker_args|
69
72
  build_args = {}.tap do |build_args|
70
73
  (options[:arg] || []).each do |a|
71
- key, value = a.split('=')
74
+ key, value = a.split('=', 2)
72
75
  value = value[1..-2] if value.start_with?('"') || value.start_with?("'")
73
76
  build_args[key] = value
74
77
  end
75
78
  end
76
79
 
77
- tasks.build(build_args)
80
+ tasks.build(
81
+ build_args, docker_args,
82
+ only: options[:only],
83
+ ignore_missing_args: options[:'ignore-missing-args'],
84
+ context: options[:context]
85
+ )
78
86
  end
79
87
  end
80
88
 
81
89
  desc 'Pushes the Docker image to the configured registry.'
82
90
  command :push do |c|
91
+ c.flag [:only], required: false
83
92
  c.action do |global_options, options, args|
84
- tasks.push
93
+ tasks.push(only: options[:only])
85
94
  end
86
95
  end
87
96
 
@@ -94,8 +103,9 @@ module Kuby
94
103
 
95
104
  desc 'Prints the effective Dockerfiles used to build Docker images.'
96
105
  command :dockerfiles do |c|
106
+ c.flag [:only], required: false
97
107
  c.action do |global_options, options, args|
98
- tasks.print_dockerfiles
108
+ tasks.print_dockerfiles(only: options[:only])
99
109
  end
100
110
  end
101
111
 
@@ -117,8 +127,10 @@ module Kuby
117
127
 
118
128
  desc 'Prints the effective Kubernetes resources that will be applied on deploy.'
119
129
  command :resources do |c|
130
+ c.flag [:K, :kind], required: false
131
+ c.flag [:N, :name], required: false
120
132
  c.action do |global_options, options, args|
121
- tasks.print_resources
133
+ tasks.print_resources(options[:kind], options[:name])
122
134
  end
123
135
  end
124
136
 
@@ -165,7 +177,7 @@ module Kuby
165
177
  rc.desc 'Runs an arbitrary command inside a running Rails pod.'
166
178
  rc.command :exec do |c|
167
179
  c.action do |global_options, options, args|
168
- tasks.remote_exec([*args, *@rails_options])
180
+ tasks.remote_exec([*args, *T.unsafe(@rails_options)])
169
181
  end
170
182
  end
171
183
 
@@ -6,13 +6,19 @@ module Kuby
6
6
  class AppImage < ::Kuby::Docker::TimestampedImage
7
7
  extend T::Sig
8
8
 
9
- sig { params(build_args: T::Hash[String, String]).returns(AppImage) }
10
- def build(build_args = {})
11
- unless ENV.fetch('RAILS_MASTER_KEY', '').empty?
12
- build_args['RAILS_MASTER_KEY'] = T.must(ENV['RAILS_MASTER_KEY'])
13
- end
14
-
15
- super(build_args)
9
+ sig {
10
+ params(
11
+ dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
12
+ image_url: String,
13
+ credentials: Credentials,
14
+ registry_index_url: T.nilable(String),
15
+ main_tag: T.nilable(String),
16
+ alias_tags: T::Array[String]
17
+ ).void
18
+ }
19
+ def initialize(dockerfile, image_url, credentials, registry_index_url = nil, main_tag = nil, alias_tags = [])
20
+ super
21
+ @identifier = "app"
16
22
  end
17
23
  end
18
24
  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,15 +53,24 @@ 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]).void }
57
- def build(image, build_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}"] },
61
68
  *build_args.flat_map do |arg, val|
62
69
  ['--build-arg', Shellwords.shellescape("#{arg}=#{val}")]
63
70
  end,
64
- '-f-', '.'
71
+ '-f-',
72
+ *docker_args,
73
+ context || '.'
65
74
  ]
66
75
 
67
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
@@ -5,9 +5,15 @@ module Kuby
5
5
  class Image
6
6
  extend T::Sig
7
7
 
8
+ sig { returns(T.nilable(String)) }
9
+ attr_reader :identifier
10
+
8
11
  sig { returns(String) }
9
12
  attr_reader :image_url
10
13
 
14
+ sig { returns(T.nilable(String)) }
15
+ attr_reader :registry_index_url
16
+
11
17
  sig { returns(Credentials) }
12
18
  attr_reader :credentials
13
19
 
@@ -22,19 +28,25 @@ module Kuby
22
28
  dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
23
29
  image_url: String,
24
30
  credentials: Credentials,
31
+ registry_index_url: T.nilable(String),
25
32
  main_tag: T.nilable(String),
26
33
  alias_tags: T::Array[String]
27
34
  ).void
28
35
  }
29
- 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 = [])
30
37
  @dockerfile = T.let(dockerfile, T.any(Dockerfile, T.proc.returns(Dockerfile)))
31
38
  @image_url = T.let(image_url, String)
39
+ @registry_index_url = T.let(registry_index_url, T.nilable(String))
32
40
  @credentials = T.let(credentials, Credentials)
33
41
  @main_tag = T.let(main_tag, T.nilable(String))
34
42
  @alias_tags = T.let(alias_tags, T::Array[String])
43
+ @identifier = T.let(@identifier, T.nilable(String))
35
44
 
36
45
  @image_host = T.let(@image_host, T.nilable(String))
37
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))
38
50
  @image_repo = T.let(@image_repo, T.nilable(String))
39
51
  @full_image_uri = T.let(@full_image_uri, T.nilable(DockerURI))
40
52
  @docker_cli = T.let(@docker_cli, T.nilable(Docker::CLI))
@@ -69,6 +81,16 @@ module Kuby
69
81
  @image_host ||= "#{image_uri.host}:#{image_uri.port}"
70
82
  end
71
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
+
72
94
  sig { returns(String) }
73
95
  def image_hostname
74
96
  @image_hostname ||= image_uri.host
@@ -81,7 +103,12 @@ module Kuby
81
103
 
82
104
  sig { returns(DockerURI) }
83
105
  def image_uri
84
- @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)
85
112
  end
86
113
 
87
114
  sig { returns(T::Array[String]) }
@@ -89,8 +116,8 @@ module Kuby
89
116
  [main_tag, *alias_tags].compact
90
117
  end
91
118
 
92
- sig { params(build_args: T::Hash[String, String]).void }
93
- def build(build_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)
94
121
  raise NotImplementedError, 'please use a Docker::Image subclass'
95
122
  end
96
123
 
@@ -108,7 +135,7 @@ module Kuby
108
135
 
109
136
  sig { params(main_tag: String, alias_tags: T::Array[String]).returns(Image) }
110
137
  def duplicate_with_tags(main_tag, alias_tags)
111
- 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)
112
139
  end
113
140
  end
114
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,9 +76,9 @@ module Kuby
73
76
  tag
74
77
  end
75
78
 
76
- sig { params(build_args: T::Hash[String, String]).void }
77
- def build(build_args = {})
78
- docker_cli.build(new_version, build_args: build_args)
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)
79
82
  @current_version = new_version
80
83
  @new_version = nil
81
84
  end
@@ -87,10 +90,17 @@ module Kuby
87
90
 
88
91
  private
89
92
 
93
+ sig { returns(Image) }
94
+ def create_new_version
95
+ duplicate_with_tags(
96
+ TimestampTag.new(Time.now).to_s, [Kuby::Docker::LATEST_TAG]
97
+ )
98
+ end
99
+
90
100
  sig { returns(::Docker::Remote::Client) }
91
101
  def remote_client
92
102
  @remote_client ||= ::Docker::Remote::Client.new(
93
- image_host, image_repo, credentials.username, credentials.password,
103
+ registry_index_host, image_repo, credentials.username, credentials.password,
94
104
  )
95
105
  end
96
106
 
@@ -106,7 +116,7 @@ module Kuby
106
116
 
107
117
  sig { returns(RemoteTags) }
108
118
  def remote
109
- @remote ||= RemoteTags.new(remote_client, image_url)
119
+ @remote ||= RemoteTags.new(remote_client, registry_index_host)
110
120
  end
111
121
  end
112
122
  end
@@ -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
@@ -317,14 +317,16 @@ module Kuby
317
317
  tags.each_cons(2) do |prev_tag, tag|
318
318
  prev_image_name = "#{app_name}-#{prev_tag}"
319
319
  df.from("#{base_image.image_url}:#{prev_tag}", as: prev_image_name)
320
+ df.arg('RAILS_MASTER_KEY')
320
321
  df.run("mkdir -p #{RAILS_MOUNT_PATH}")
321
- df.run("bundle exec rake kuby:rails_app:assets:copy")
322
+ df.run("env RAILS_MASTER_KEY=$RAILS_MASTER_KEY bundle exec rake kuby:rails_app:assets:copy")
322
323
 
323
324
  if tag
324
325
  image_name = "#{app_name}-#{tag}"
325
326
  df.from("#{base_image.image_url}:#{tag}", as: image_name)
327
+ df.arg('RAILS_MASTER_KEY')
326
328
  df.copy("--from=#{prev_image_name} #{RAILS_MOUNT_PATH}", RAILS_MOUNT_PATH)
327
- df.run("bundle exec rake kuby:rails_app:assets:copy")
329
+ df.run("env RAILS_MASTER_KEY=$RAILS_MASTER_KEY bundle exec rake kuby:rails_app:assets:copy")
328
330
  end
329
331
  end
330
332
 
@@ -4,9 +4,10 @@ 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
+ @identifier = "assets"
10
11
  end
11
12
 
12
13
  def new_version
@@ -27,8 +28,8 @@ module Kuby
27
28
  )
28
29
  end
29
30
 
30
- def build(build_args = {})
31
- docker_cli.build(current_version, build_args: build_args)
31
+ def build(build_args = {}, docker_args = [], context: nil)
32
+ docker_cli.build(current_version, build_args: build_args, docker_args: docker_args, context: context)
32
33
  end
33
34
 
34
35
  def push(tag)
@@ -41,6 +42,7 @@ module Kuby
41
42
  self.class.new(
42
43
  base_image,
43
44
  dockerfile,
45
+ registry_index_url,
44
46
  annotate_tag(image.main_tag),
45
47
  image.alias_tags.map { |at| annotate_tag(at) }
46
48
  )
@@ -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
data/lib/kuby/tasks.rb CHANGED
@@ -9,10 +9,13 @@ module Kuby
9
9
  @environment = environment
10
10
  end
11
11
 
12
- def print_dockerfiles
12
+ def print_dockerfiles(only: nil)
13
13
  kubernetes.docker_images.each do |image|
14
+ next if only && image.identifier != only
15
+
14
16
  image = image.current_version
15
- Kuby.logger.info("Dockerfile for image #{image.image_url} with tags #{image.tags.join(', ')}")
17
+ identifier = image.identifier ? " ##{image.identifier}" : ""
18
+ Kuby.logger.info("Dockerfile for#{identifier} image #{image.image_url} with tags #{image.tags.join(', ')}")
16
19
  theme = Rouge::Themes::Base16::Solarized.new
17
20
  formatter = Rouge::Formatters::Terminal256.new(theme)
18
21
  lexer = Rouge::Lexers::Docker.new
@@ -25,16 +28,31 @@ module Kuby
25
28
  environment.kubernetes.setup
26
29
  end
27
30
 
28
- def build(build_args = {})
31
+ def build(build_args = {}, docker_args = [], only: nil, ignore_missing_args: false, context: nil)
32
+ check_platform(docker_args)
33
+
34
+ build_args['RAILS_MASTER_KEY'] ||= ENV['RAILS_MASTER_KEY'] || begin
35
+ master_key_file = File.join('config', 'master.key')
36
+ File.exist?(master_key_file) ? File.read(master_key_file).strip : nil
37
+ end
38
+
39
+ check_build_args(build_args) unless ignore_missing_args
40
+
29
41
  kubernetes.docker_images.each do |image|
42
+ next if only && image.identifier != only
43
+
44
+ return unless perform_docker_login_if_necessary(image)
45
+
30
46
  image = image.new_version
31
47
  Kuby.logger.info("Building image #{image.image_url} with tags #{image.tags.join(', ')}")
32
- image.build(build_args)
48
+ image.build(build_args, docker_args, context: context)
33
49
  end
34
50
  end
35
51
 
36
- def push
52
+ def push(only: nil)
37
53
  kubernetes.docker_images.each do |image|
54
+ next if only && image.identifier != only
55
+
38
56
  image = image.current_version
39
57
  Kuby.logger.info("Pushing image #{image.image_url} with tags #{image.tags.join(', ')}")
40
58
  push_image(image)
@@ -42,21 +60,7 @@ module Kuby
42
60
  end
43
61
 
44
62
  def push_image(image)
45
- if image.credentials.username && !image.docker_cli.auths.include?(image.image_host)
46
- Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
47
-
48
- begin
49
- image.docker_cli.login(
50
- url: image.image_host,
51
- username: image.credentials.username,
52
- password: image.credentials.password
53
- )
54
- rescue Kuby::Docker::LoginError => e
55
- Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
56
- Kuby.logger.fatal(e.message)
57
- return
58
- end
59
- end
63
+ return unless perform_docker_login_if_necessary(image)
60
64
 
61
65
  begin
62
66
  image.tags.each { |tag| image.push(tag) }
@@ -77,10 +81,16 @@ module Kuby
77
81
  environment.kubernetes.rollback
78
82
  end
79
83
 
80
- def print_resources
84
+ def print_resources(kind = nil, name_pattern = nil)
81
85
  kubernetes.before_deploy
82
86
 
87
+ name_rxp = Regexp.new(name_pattern) if name_pattern
88
+
83
89
  kubernetes.resources.each do |res|
90
+ next if kind && res.kind_sym.to_s != kind
91
+
92
+ next if name_rxp && !name_rxp.match?(res.metadata.name)
93
+
84
94
  puts res.to_resource.serialize.to_yaml
85
95
  end
86
96
  end
@@ -132,6 +142,106 @@ module Kuby
132
142
 
133
143
  private
134
144
 
145
+ def check_platform(docker_args)
146
+ arch, * = RUBY_PLATFORM.split('-')
147
+
148
+ if arch != 'x86_64' && !docker_args.include?('--platform')
149
+ Kuby.logger.fatal(<<~END)
150
+ Hey there! It looks like your processor isn't x86-compatible.
151
+ By default, Docker will try to build images that match the
152
+ current architecture, in this case #{arch}. Most hosting
153
+ providers run x86 hardware, meaning Docker images built using
154
+ this computer's architecture might fail to run when deployed
155
+ to production. You can fix this by running the build command
156
+ with a special --platform flag, eg:
157
+
158
+ bundle exec kuby -e production build -- --platform linux/amd64
159
+
160
+ If you meant to build for the current architecture, you can
161
+ prevent this error by passing the --platform argument for the
162
+ current architecture, eg. --platform linux/arm64 for ARM, etc.
163
+ END
164
+
165
+ exit 1
166
+ end
167
+ end
168
+
169
+ def check_build_args(build_args)
170
+ required_args = kubernetes.docker_images.flat_map do |image|
171
+ image.dockerfile.commands.flat_map do |command|
172
+ case command
173
+ when Kuby::Docker::Dockerfile::Arg
174
+ command.args
175
+ else
176
+ []
177
+ end
178
+ end
179
+ end
180
+
181
+ required_args.uniq!
182
+
183
+ if File.exist?(File.join('config', 'master.key'))
184
+ required_args.delete('RAILS_MASTER_KEY')
185
+ end
186
+
187
+ missing_args = required_args - build_args.keys
188
+
189
+ if missing_args.any?
190
+ Kuby.logger.fatal(<<~END)
191
+ The following Docker build arguments are missing: #{missing_args.join(', ')}.
192
+ Please pass each argument to `kuby build` using the -a or --arg parameter (note
193
+ that the -a/--arg parameter can be specified multiple times). For example:
194
+
195
+ kuby build -a #{missing_args.first}=value ...
196
+
197
+ To ignore missing build args, pass the --ignore-missing-args parameter.
198
+ END
199
+
200
+ exit 1
201
+ end
202
+ end
203
+
204
+ def perform_docker_login_if_necessary(image)
205
+ auth_uris = image.docker_cli.auths.map do |url|
206
+ Kuby::Docker::DockerURI.parse_uri(url)
207
+ end
208
+
209
+ logged_in = image.credentials.username && (
210
+ auth_uris.any? do |uri|
211
+ image.image_hostname == uri.host ||
212
+ image.registry_index_hostname == uri.host
213
+ end
214
+ )
215
+
216
+ if !logged_in
217
+ Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
218
+
219
+ begin
220
+ # For some reason, Docker login with a port doesn't work for some
221
+ # registries (most notably Docker Hub). Since the default is 443 anyway,
222
+ # it should be fine to omit it.
223
+ url = if image.image_uri.has_default_port?
224
+ image.image_hostname # host without port
225
+ else
226
+ image.image_host # host with port
227
+ end
228
+
229
+ image.docker_cli.login(
230
+ url: url,
231
+ username: image.credentials.username,
232
+ password: image.credentials.password
233
+ )
234
+ rescue Kuby::Docker::LoginError => e
235
+ Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
236
+ Kuby.logger.fatal(e.message)
237
+
238
+ return false
239
+ end
240
+ end
241
+
242
+ true
243
+ end
244
+
135
245
  def get_first_pod
136
246
  pods = kubernetes_cli.get_objects(
137
247
  'pods', namespace, match_labels.serialize
data/lib/kuby/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # typed: true
2
2
 
3
3
  module Kuby
4
- VERSION = '0.13.0'.freeze
4
+ VERSION = '0.16.1'.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.13.0
4
+ version: 0.16.1
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-10-24 00:00:00.000000000 Z
11
+ date: 2022-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize