diff --git a/Dockerfile b/Dockerfile index 0306b79..8deba39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,12 +33,11 @@ ARG TARGETPLATFORM # Upgrade inherited packages even when their existing versions satisfy dependencies. RUN set -xe; \ apk upgrade --no-cache; \ - # Replace bundled gems, including the old default JSON implementation. + # Refresh bundled gems to the latest releases compatible with this Ruby. apk add --no-cache --virtual .wodby-gem-build-deps build-base; \ system_gems="$(ruby -e 'print Gem.default_dir')"; \ - gem install --install-dir "$system_gems" --no-document json -v '~> 2.19.9'; \ - gem install --install-dir "$system_gems" --no-document net-imap -v '~> 0.5.15'; \ - ruby -rjson -rnet/imap -e 'abort unless Gem.loaded_specs.fetch("json").version >= Gem::Version.new("2.19.9")'; \ + gem install --install-dir "$system_gems" --no-document json net-imap; \ + # Remove the replaced default JSON implementation as well as its gemspec. \ ruby -rrbconfig -rfileutils -e ' \ root = RbConfig::CONFIG.fetch("rubylibdir"); \ arch = RbConfig::CONFIG.fetch("archdir"); \ diff --git a/Makefile b/Makefile index 9b24b4e..adf8853 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,7 @@ buildx-imagetools-create: .PHONY: buildx-imagetools-create test: + docker run --rm --network none --entrypoint ruby -v "$(CURDIR)/tests/system-gems.rb:/tmp/system-gems.rb:ro" $(REPO):$(TAG) /tmp/system-gems.rb cd ./tests && IMAGE=$(REPO):$(TAG) bash ./workspace-contract.sh ifneq ($(RUBY_DEV),) cd ./tests && RUBY_IMAGE=$(REPO):$(TAG) ./run.sh diff --git a/tests/system-gems.rb b/tests/system-gems.rb new file mode 100644 index 0000000..8cdc17d --- /dev/null +++ b/tests/system-gems.rb @@ -0,0 +1,25 @@ +# Verify refreshed system gems load after removing the inherited implementations. +require 'json' +require 'net/imap' +require 'rbconfig' + +%w[json net-imap].each do |name| + spec = Gem.loaded_specs.fetch(name) + abort "#{name} was not installed in the system gem directory" unless spec.base_dir == Gem.default_dir + abort "Old #{name} versions remain installed" unless Gem::Specification.find_all_by_name(name).size == 1 + puts "#{name}=#{spec.version}" +end + +abort 'JSON round trip failed' unless JSON.parse(JSON.generate('ok' => true)).fetch('ok') +abort 'Net::IMAP did not load' unless defined?(Net::IMAP::ResponseParser) + +# Default gems cannot be removed by gem cleanup; their files need explicit removal. +root = RbConfig::CONFIG.fetch('rubylibdir') +arch = RbConfig::CONFIG.fetch('archdir') +[File.join(root, 'json'), File.join(root, 'json.rb'), File.join(arch, 'json')].each do |path| + abort "Inherited JSON implementation remains at #{path}" if File.exist?(path) +end + +# System gem updates must not create root-owned files in the application bundle. +abort 'System gem updates created an application Gemfile' if File.exist?(File.join(Gem.dir, 'Gemfile')) +abort 'System gem updates created an application lockfile' if File.exist?(File.join(Gem.dir, 'Gemfile.lock'))