Skip to content

Commit 65e1644

Browse files
committed
fix(runtime): polish relaunch smoke behavior and health checks
Intent: end the relaunch history with the small but important fixes that make operations and regression diagnosis trustworthy. Goals: keep container health checks configurable, make smoke cleanup failure-safe, and lock the final feed and health behavior into request specs. Changes: tighten the Docker healthcheck setup, harden the smoke task cleanup path, and add regression coverage for the late feed and health fixes.
1 parent 82582f5 commit 65e1644

4 files changed

Lines changed: 731 additions & 43 deletions

File tree

Dockerfile

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
# Stage 1: Build
2-
FROM ruby:4.0.1-alpine3.23 AS builder
1+
ARG RUBY_BASE_IMAGE=ruby:4.0.1-alpine3.23
2+
3+
# Stage 1: Frontend Build
4+
FROM node:22-alpine AS frontend-builder
5+
6+
WORKDIR /app/frontend
7+
COPY frontend/package*.json ./
8+
RUN npm ci
9+
COPY frontend/ ./
10+
RUN npm run build
11+
12+
# Stage 2: Ruby Build
13+
FROM ${RUBY_BASE_IMAGE} AS builder
314

415
LABEL maintainer="Gil Desmarais <html2rss-web-docker@desmarais.de>"
516

@@ -22,21 +33,22 @@ RUN apk add --no-cache \
2233
&& bundle install --retry=5 --jobs=$(nproc) \
2334
&& bundle binstubs bundler html2rss
2435

25-
# Stage 2: Runtime
26-
FROM ruby:4.0.1-alpine3.23
36+
# Stage 3: Runtime
37+
FROM ${RUBY_BASE_IMAGE}
2738

2839
LABEL maintainer="Gil Desmarais <html2rss-web-docker@desmarais.de>"
2940

3041
SHELL ["/bin/ash", "-o", "pipefail", "-c"]
3142

32-
ENV PORT=3000 \
33-
RACK_ENV=production \
34-
RUBY_YJIT_ENABLE=1
43+
ENV PORT=4000 \
44+
RACK_ENV=production \
45+
RUBY_YJIT_ENABLE=1
3546

3647
EXPOSE $PORT
3748

3849
HEALTHCHECK --interval=30m --timeout=60s --start-period=5s \
39-
CMD curl -f http://${HEALTH_CHECK_USERNAME}:${HEALTH_CHECK_PASSWORD}@localhost:${PORT}/health_check.txt || exit 1
50+
CMD TOKEN="${HEALTH_CHECK_TOKEN:-CHANGE_ME_HEALTH_CHECK_TOKEN}" && \
51+
curl -f -H "Authorization: Bearer ${TOKEN}" http://localhost:${PORT}/api/v1/health || exit 1
4052

4153
ARG USER=html2rss
4254
ARG UID=991
@@ -67,5 +79,6 @@ USER html2rss
6779

6880
COPY --from=builder /usr/local/bundle /usr/local/bundle
6981
COPY --chown=$USER:$USER . /app
82+
COPY --from=frontend-builder --chown=$USER:$USER /app/public/frontend ./public/frontend
7083

7184
CMD ["bundle", "exec", "puma", "-C", "./config/puma.rb"]

Rakefile

Lines changed: 101 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
require 'json'
4+
require 'fileutils'
5+
require 'open3'
46

57
##
68
# Helper methods used during :test run
@@ -27,21 +29,32 @@ module Output
2729
end
2830
end
2931

32+
def test_container_exists?(container_name)
33+
inspection, status = Open3.capture2e('docker', 'inspect', container_name)
34+
return false unless status.success?
35+
return false if inspection.strip.empty?
36+
37+
JSON.parse(inspection).any?
38+
rescue JSON::ParserError
39+
false
40+
end
41+
3042
task default: %w[test]
3143

3244
desc 'Build and run docker image/container, and send requests to it'
3345

3446
task :test do
3547
current_dir = ENV.fetch('GITHUB_WORKSPACE', __dir__)
48+
smoke_auto_source_enabled = ENV.fetch('SMOKE_AUTO_SOURCE_ENABLED', 'false')
3649

3750
Output.describe 'Building and running'
3851
sh 'docker build -t gilcreator/html2rss-web -f Dockerfile .'
3952
sh ['docker run',
4053
'-d',
41-
'-p 3000:3000',
54+
'-p 4000:4000',
4255
'--env PUMA_LOG_CONFIG=1',
43-
'--env HEALTH_CHECK_USERNAME=username',
44-
'--env HEALTH_CHECK_PASSWORD=password',
56+
'--env HEALTH_CHECK_TOKEN=CHANGE_ME_HEALTH_CHECK_TOKEN',
57+
"--env AUTO_SOURCE_ENABLED=#{smoke_auto_source_enabled}",
4558
"--mount type=bind,source=#{current_dir}/config,target=/app/config",
4659
'--name html2rss-web-test',
4760
'gilcreator/html2rss-web'].join(' ')
@@ -51,22 +64,17 @@ task :test do
5164
Output.describe 'Listing docker containers matching html2rss-web-test filter'
5265
sh 'docker ps -a --filter name=html2rss-web-test'
5366

54-
Output.describe 'Generating feed from a html2rss-configs config'
55-
sh 'curl -f "http://127.0.0.1:3000/github.com/releases.rss?username=html2rss&repository=html2rss-web" || exit 1'
56-
57-
Output.describe 'Generating example feed from feeds.yml'
58-
sh 'curl -f http://127.0.0.1:3000/example.rss || exit 1'
59-
60-
Output.describe 'Authenticated request to GET /health_check.txt'
61-
sh 'docker exec html2rss-web-test curl -f http://username:password@127.0.0.1:3000/health_check.txt || exit 1'
62-
63-
# skipped as html2rss is used in development version
64-
# Output.describe 'Print output of `html2rss help`'
65-
# sh 'docker exec html2rss-web-test html2rss help'
67+
Output.describe 'Running RSpec smoke suite against container'
68+
smoke_env = {
69+
'SMOKE_BASE_URL' => 'http://127.0.0.1:4000',
70+
'SMOKE_HEALTH_TOKEN' => 'CHANGE_ME_HEALTH_CHECK_TOKEN',
71+
'SMOKE_API_TOKEN' => 'CHANGE_ME_ADMIN_TOKEN',
72+
'SMOKE_AUTO_SOURCE_ENABLED' => smoke_auto_source_enabled,
73+
'RUN_DOCKER_SPECS' => 'true'
74+
}
75+
sh smoke_env, 'bundle exec rspec --tag docker'
6676
ensure
67-
test_container_exists = JSON.parse(`docker inspect html2rss-web-test`).any?
68-
69-
if test_container_exists
77+
if test_container_exists?('html2rss-web-test')
7078
Output.describe 'Cleaning up test container'
7179

7280
sh 'docker logs --tail all html2rss-web-test'
@@ -76,3 +84,78 @@ ensure
7684

7785
exit 1 if $ERROR_INFO
7886
end
87+
88+
namespace :openapi do
89+
desc 'Generate OpenAPI YAML from request specs'
90+
task :generate do
91+
FileUtils.mkdir_p('docs/api/v1')
92+
FileUtils.rm_f('docs/api/v1/openapi.yaml')
93+
sh({ 'OPENAPI' => '1' }, 'bundle exec rspec spec/html2rss/web/api/v1_spec.rb --order defined')
94+
end
95+
96+
desc 'Verify generated OpenAPI YAML is up to date'
97+
task verify: :generate do
98+
sh 'git diff --exit-code -- docs/api/v1/openapi.yaml'
99+
end
100+
end
101+
102+
namespace :yard do
103+
desc 'Fail when public methods in app/ are missing essential YARD docs'
104+
task :verify_public_docs do
105+
require 'yard'
106+
107+
files = Dir.glob(File.join(__dir__, 'app/**/*.rb'))
108+
YARD::Registry.clear
109+
YARD::Registry.load(files, true)
110+
111+
violations = []
112+
113+
YARD::Registry.all(:method).each do |method_object|
114+
next unless method_object.visibility == :public
115+
next unless method_object.file&.include?('/app/')
116+
117+
location = "#{method_object.path} (#{method_object.file}:#{method_object.line})"
118+
normalize_param_name = lambda do |name|
119+
name.to_s.sub(/\A[*&]/, '').sub(/:$/, '')
120+
end
121+
122+
param_tags = method_object.tags(:param)
123+
params = method_object.parameters.map(&:first).map { |name| normalize_param_name.call(name) }
124+
params.reject! { |name| name == 'block' }
125+
126+
param_tag_names = param_tags.map { |tag| normalize_param_name.call(tag.name) }
127+
missing_params = params - param_tag_names
128+
violations << "#{location} missing @param for: #{missing_params.join(', ')}" unless missing_params.empty?
129+
130+
param_tags.each do |tag|
131+
violations << "#{location} @param #{tag.name} missing type" if tag.types.nil? || tag.types.empty?
132+
end
133+
134+
return_tag = method_object.tag(:return)
135+
if return_tag.nil?
136+
violations << "#{location} missing @return"
137+
elsif return_tag.types.nil? || return_tag.types.empty?
138+
violations << "#{location} @return missing type"
139+
end
140+
end
141+
142+
if violations.any?
143+
puts 'YARD public method documentation check failed:'
144+
violations.sort.each { |violation| puts " - #{violation}" }
145+
abort "\nFound #{violations.count} YARD documentation violation(s)."
146+
end
147+
148+
puts 'YARD public method documentation check passed.'
149+
end
150+
end
151+
152+
namespace :zeitwerk do
153+
desc 'Fail when Zeitwerk cannot eager load the app tree cleanly'
154+
task :verify do
155+
ENV['RACK_ENV'] ||= 'test'
156+
require_relative 'app'
157+
158+
Html2rss::Web::Boot.eager_load!
159+
puts 'Zeitwerk eager load check passed.'
160+
end
161+
end

0 commit comments

Comments
 (0)