Skip to content

Commit f31b6f0

Browse files
andrewn617rafaelfranca
authored andcommitted
Extract Dev Container implementation into its own generator
This refactoring prepares the way to implement a devcontainer command to add an devcontainer to an existing app.
1 parent a61bfe4 commit f31b6f0

File tree

8 files changed

+157
-120
lines changed

8 files changed

+157
-120
lines changed

railties/lib/rails/generators/app_base.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
module Rails
1313
module Generators
1414
class AppBase < Base # :nodoc:
15-
include Devcontainer
1615
include AppName
1716

1817
NODE_LTS_VERSION = "20.11.1"
@@ -454,10 +453,6 @@ def to_s
454453
end
455454
end
456455

457-
def gem_ruby_version
458-
Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.3.13") ? Gem.ruby_version : RUBY_VERSION
459-
end
460-
461456
def rails_prerelease?
462457
options.dev? || options.edge? || options.main?
463458
end

railties/lib/rails/generators/base.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,10 @@ def self.default_generator_root # :doc:
423423
path = File.expand_path(File.join(base_name, generator_name), base_root)
424424
path if File.exist?(path)
425425
end
426+
427+
def gem_ruby_version
428+
Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.3.13") ? Gem.ruby_version : RUBY_VERSION
429+
end
426430
end
427431
end
428432
end

railties/lib/rails/generators/devcontainer.rb

Lines changed: 0 additions & 96 deletions
This file was deleted.

railties/lib/rails/generators/rails/app/app_generator.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "rails/generators/app_base"
4+
require "rails/generators/rails/devcontainer/devcontainer_generator"
45

56
module Rails
67
module ActionMethods # :nodoc:
@@ -268,11 +269,17 @@ def config_target_version
268269
end
269270

270271
def devcontainer
271-
empty_directory ".devcontainer"
272-
273-
template ".devcontainer/devcontainer.json"
274-
template ".devcontainer/Dockerfile"
275-
template ".devcontainer/compose.yaml"
272+
devcontainer_options = {
273+
database: options[:database],
274+
redis: !(options[:skip_action_cable] && options[:skip_active_job]),
275+
system_test: depends_on_system_test?,
276+
active_storage: !options[:skip_active_storage],
277+
dev: options[:dev],
278+
node: using_node?,
279+
app_name: app_name
280+
}
281+
282+
Rails::Generators::DevcontainerGenerator.new([], devcontainer_options).invoke_all
276283
end
277284
end
278285

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# frozen_string_literal: true
2+
3+
require "rails/generators"
4+
5+
module Rails
6+
module Generators
7+
class DevcontainerGenerator < Base # :nodoc:
8+
class_option :app_name, type: :string, default: "rails_app",
9+
desc: "Name of the app"
10+
11+
class_option :database, enum: Database::DATABASES, type: :string, default: "sqlite3",
12+
desc: "Include configuration for selected database"
13+
14+
class_option :redis, type: :boolean, default: true,
15+
desc: "Include configuration for Redis"
16+
17+
class_option :system_test, type: :boolean, default: true,
18+
desc: "Include configuration for System Tests"
19+
20+
class_option :active_storage, type: :boolean, default: true,
21+
desc: "Include configuration for Active Storage"
22+
23+
class_option :node, type: :boolean, default: false,
24+
desc: "Include configuration for Node"
25+
26+
class_option :dev, type: :boolean, default: false,
27+
desc: "For applications pointing to a local Rails checkout"
28+
29+
def create_devcontainer
30+
empty_directory ".devcontainer"
31+
32+
template ".devcontainer/devcontainer.json"
33+
template ".devcontainer/Dockerfile"
34+
template ".devcontainer/compose.yaml"
35+
end
36+
37+
private
38+
def dependencies
39+
return @dependencies if @dependencies
40+
41+
@dependencies = []
42+
43+
@dependencies << "selenium" if options[:system_test]
44+
@dependencies << "redis" if options[:redis]
45+
@dependencies << database.name if database.service
46+
@dependencies
47+
end
48+
49+
def container_env
50+
return @container_env if @container_env
51+
52+
@container_env = {}
53+
54+
@container_env["CAPYBARA_SERVER_PORT"] = "45678" if options[:system_test]
55+
@container_env["SELENIUM_HOST"] = "selenium" if options[:system_test]
56+
@container_env["REDIS_URL"] = "redis://redis:6379/1" if options[:redis]
57+
@container_env["DB_HOST"] = database.name if database.service
58+
59+
@container_env
60+
end
61+
62+
def volumes
63+
return @volumes if @volumes
64+
65+
@volumes = []
66+
67+
@volumes << "redis-data" if options[:redis]
68+
@volumes << database.volume if database.volume
69+
70+
@volumes
71+
end
72+
73+
def features
74+
return @features if @features
75+
76+
@features = {
77+
"ghcr.io/devcontainers/features/github-cli:1" => {}
78+
}
79+
80+
@features["ghcr.io/rails/devcontainer/features/activestorage"] = {} if options[:active_storage]
81+
@features["ghcr.io/devcontainers/features/node:1"] = {} if options[:node]
82+
83+
@features.merge!(database.feature) if database.feature
84+
85+
@features
86+
end
87+
88+
def mounts
89+
return @mounts if @mounts
90+
91+
@mounts = []
92+
93+
@mounts << local_rails_mount if options[:dev]
94+
95+
@mounts
96+
end
97+
98+
def forward_ports
99+
return @forward_ports if @forward_ports
100+
101+
@forward_ports = [3000]
102+
@forward_ports << database.port if database.port
103+
@forward_ports << 6379 if options[:redis]
104+
105+
@forward_ports
106+
end
107+
108+
def database
109+
@database ||= Database.build(options[:database])
110+
end
111+
112+
def devcontainer_db_service_yaml(**options)
113+
return unless service = database.service
114+
115+
{ database.name => service }.to_yaml(**options)[4..-1]
116+
end
117+
118+
def local_rails_mount
119+
{
120+
type: "bind",
121+
source: Rails::Generators::RAILS_DEV_PATH,
122+
target: Rails::Generators::RAILS_DEV_PATH
123+
}
124+
end
125+
end
126+
end
127+
end

railties/lib/rails/generators/rails/app/templates/.devcontainer/compose.yaml.tt renamed to railties/lib/rails/generators/rails/devcontainer/templates/.devcontainer/compose.yaml.tt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: "<%= app_name %>"
1+
name: "<%= options[:app_name] %>"
22

33
services:
44
rails-app:
@@ -22,13 +22,13 @@ services:
2222
# (Adding the "ports" property to this file will not forward from a Codespace.)
2323
ports:
2424
- 45678:45678
25-
<%- if !devcontainer_dependencies.empty? -%>
25+
<%- if !dependencies.empty? -%>
2626
depends_on:
27-
<%- devcontainer_dependencies.each do |dependency| -%>
27+
<%- dependencies.each do |dependency| -%>
2828
- <%= dependency %>
2929
<%- end -%>
3030
<%- end -%>
31-
<%- if depends_on_system_test? -%>
31+
<%- if options[:system_test] -%>
3232

3333
selenium:
3434
image: seleniarm/standalone-chromium
@@ -37,7 +37,7 @@ services:
3737
- default
3838
<%- end -%>
3939

40-
<%- if devcontainer_needs_redis? -%>
40+
<%- if options[:redis] -%>
4141
redis:
4242
image: redis:7.2
4343
restart: unless-stopped
@@ -48,9 +48,9 @@ services:
4848

4949
<%- end -%>
5050
<%= devcontainer_db_service_yaml(indentation: 4) %>
51-
<%- if !devcontainer_volumes.empty? -%>
51+
<%- if !volumes.empty? -%>
5252
volumes:
53-
<%- devcontainer_volumes.each do |volume| -%>
53+
<%- volumes.each do |volume| -%>
5454
<%= volume %>:
5555
<%- end -%>
5656
<%- end -%>

railties/lib/rails/generators/rails/app/templates/.devcontainer/devcontainer.json.tt renamed to railties/lib/rails/generators/rails/devcontainer/templates/.devcontainer/devcontainer.json.tt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
22
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
33
{
4-
"name": "<%= app_name %>",
4+
"name": "<%= options[:app_name] %>",
55
"dockerComposeFile": "compose.yaml",
66
"service": "rails-app",
77
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
88

99
// Features to add to the dev container. More info: https://containers.dev/features.
1010
"features": {
11-
<%= devcontainer_features.map { |key, value| "\"#{key}\": #{value.as_json}" }.join(",\n ") %>
11+
<%= features.map { |key, value| "\"#{key}\": #{value.as_json}" }.join(",\n ") %>
1212
},
1313

14-
<%- if !devcontainer_variables.empty? -%>
14+
<%- if !container_env.empty? -%>
1515
"containerEnv": {
16-
<%= devcontainer_variables.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(",\n ") %>
16+
<%= container_env.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(",\n ") %>
1717
},
1818
<%- end -%>
1919

2020
// Use 'forwardPorts' to make a list of ports inside the container available locally.
21-
"forwardPorts": <%= devcontainer_forward_ports.as_json %>,
21+
"forwardPorts": <%= forward_ports.as_json %>,
2222

2323
// Configure tool-specific properties.
2424
// "customizations": {},
2525

2626
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
2727
// "remoteUser": "root",
2828

29-
<%- if !devcontainer_mounts.empty? -%>
29+
<%- if !mounts.empty? -%>
3030
"mounts": [
31-
<%= devcontainer_mounts.map { |mount| "{\n " + mount.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(",\n ") + "\n }" }.join(",\n ") %>
31+
<%= mounts.map { |mount| "{\n " + mount.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(",\n ") + "\n }" }.join(",\n ") %>
3232
],
3333
<%- end -%>
3434

0 commit comments

Comments
 (0)