Skip to content

Commit d8ca552

Browse files
authored
Merge pull request rails#51481 from andrewn617/devcontainer-user-dependency-features
Use devcontainer features for optional dependencies
2 parents 8bedfd6 + 16afdbe commit d8ca552

File tree

7 files changed

+109
-64
lines changed

7 files changed

+109
-64
lines changed

railties/lib/rails/generators/devcontainer.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ def devcontainer_volumes
3939
@devcontainer_volumes
4040
end
4141

42+
def devcontainer_features
43+
return @devcontainer_features if @devcontainer_features
44+
45+
@devcontainer_features = {
46+
"ghcr.io/devcontainers/features/github-cli:1" => {}
47+
}
48+
49+
@devcontainer_features["ghcr.io/rails/devcontainer/features/activestorage"] = {} unless options[:skip_active_storage]
50+
@devcontainer_features.merge!(db_feature_for_devcontainer) if db_feature_for_devcontainer
51+
52+
@devcontainer_features
53+
end
54+
4255
def devcontainer_mounts
4356
return @devcontainer_mounts if @devcontainer_mounts
4457

@@ -90,6 +103,13 @@ def db_service_for_devcontainer(database = options[:database])
90103
end
91104
end
92105

106+
def db_feature_for_devcontainer(database = options[:database])
107+
case database
108+
when "mysql" then mysql_feature
109+
when "postgresql" then postgres_feature
110+
end
111+
end
112+
93113
def postgres_service
94114
{
95115
"postgres" => {
@@ -138,6 +158,21 @@ def db_service_names
138158
["mysql", "mariadb", "postgres"]
139159
end
140160

161+
def mysql_feature
162+
{ "ghcr.io/rails/devcontainer/features/mysql-client" => {} }
163+
end
164+
165+
def postgres_feature
166+
{ "ghcr.io/rails/devcontainer/features/postgres-client" => {} }
167+
end
168+
169+
def db_features
170+
[
171+
"ghcr.io/rails/devcontainer/features/mysql-client",
172+
"ghcr.io/rails/devcontainer/features/postgres-client"
173+
]
174+
end
175+
141176
def local_rails_mount
142177
{
143178
type: "bind",
Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,3 @@
11
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
22
ARG RUBY_VERSION=<%= gem_ruby_version %>
33
FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
4-
5-
<%- unless options.skip_active_storage -%>
6-
# Install packages needed to build gems
7-
RUN apt-get update -qq && \
8-
apt-get install --no-install-recommends -y \
9-
<%= db_package_for_dockerfile %> libvips \
10-
# For video thumbnails
11-
ffmpeg \
12-
# For pdf thumbnails. If you want to use mupdf instead of poppler,
13-
# you can install the following packages instead:
14-
# mupdf mupdf-tools
15-
poppler-utils
16-
<%- end -%>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

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

1414
<%- if !devcontainer_variables.empty? -%>

railties/lib/rails/generators/rails/db/system/change/change_generator.rb

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -101,27 +101,10 @@ def gem_entry_for(*gem_name_and_version)
101101
end
102102

103103
def edit_devcontainer_json
104-
devcontainer_json_path = File.expand_path(".devcontainer/devcontainer.json", destination_root)
105-
return unless File.exist?(devcontainer_json_path)
106-
107-
container_env = JSON.parse(File.read(devcontainer_json_path))["containerEnv"]
108-
db_name = db_name_for_devcontainer
109-
110-
if container_env["DB_HOST"]
111-
if db_name
112-
container_env["DB_HOST"] = db_name
113-
else
114-
container_env.delete("DB_HOST")
115-
end
116-
else
117-
if db_name
118-
container_env["DB_HOST"] = db_name
119-
end
120-
end
104+
return unless devcontainer_json
121105

122-
new_json = JSON.pretty_generate(container_env, indent: " ", object_nl: "\n ")
123-
124-
gsub_file(".devcontainer/devcontainer.json", /("containerEnv"\s*:\s*){[^}]*}/, "\\1#{new_json}")
106+
update_devcontainer_db_host
107+
update_devcontainer_db_feature
125108
end
126109

127110
def edit_compose_yaml
@@ -152,6 +135,52 @@ def edit_compose_yaml
152135

153136
File.write(compose_yaml_path, compose_config.to_yaml)
154137
end
138+
139+
def update_devcontainer_db_host
140+
container_env = devcontainer_json["containerEnv"]
141+
db_name = db_name_for_devcontainer
142+
143+
if container_env["DB_HOST"]
144+
if db_name
145+
container_env["DB_HOST"] = db_name
146+
else
147+
container_env.delete("DB_HOST")
148+
end
149+
else
150+
if db_name
151+
container_env["DB_HOST"] = db_name
152+
end
153+
end
154+
155+
new_json = JSON.pretty_generate(container_env, indent: " ", object_nl: "\n ")
156+
157+
gsub_file(".devcontainer/devcontainer.json", /("containerEnv"\s*:\s*)(.|\n)*?(^\s{2}})/, "\\1#{new_json}")
158+
end
159+
160+
def update_devcontainer_db_feature
161+
features = devcontainer_json["features"]
162+
db_feature = db_feature_for_devcontainer
163+
164+
db_features.each do |feature|
165+
features.delete(feature)
166+
end
167+
168+
features.merge!(db_feature) if db_feature
169+
170+
new_json = JSON.pretty_generate(features, indent: " ", object_nl: "\n ")
171+
172+
gsub_file(".devcontainer/devcontainer.json", /("features"\s*:\s*)(.|\n)*?(^\s{2}})/, "\\1#{new_json}")
173+
end
174+
175+
def devcontainer_json
176+
return unless File.exist?(devcontainer_json_path)
177+
178+
@devcontainer_json ||= JSON.parse(File.read(devcontainer_json_path))
179+
end
180+
181+
def devcontainer_json_path
182+
File.expand_path(".devcontainer/devcontainer.json", destination_root)
183+
end
155184
end
156185
end
157186
end

railties/test/fixtures/.devcontainer/devcontainer.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
"REDIS_URL": "redis://redis:6379/1"
1818
},
1919

20-
// Features to add to the dev container. More info: https://containers.dev/features.
21-
// "features": {},
22-
2320
// Use 'forwardPorts' to make a list of ports inside the container available locally.
2421
// "forwardPorts": [],
2522

railties/test/generators/app_generator_test.rb

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,16 +1241,16 @@ def test_name_option
12411241
def test_devcontainer
12421242
run_generator [destination_root, "--name=my-app"]
12431243

1244-
assert_file(".devcontainer/devcontainer.json") do |content|
1245-
assert_match(/"name": "my_app"/, content)
1246-
assert_match(/"REDIS_URL": "redis:\/\/redis:6379\/1"/, content)
1247-
assert_match(/"CAPYBARA_SERVER_PORT": "45678"/, content)
1248-
assert_match(/"SELENIUM_HOST": "selenium"/, content)
1244+
assert_devcontainer_json_file do |content|
1245+
assert_equal "my_app", content["name"]
1246+
assert_equal "redis://redis:6379/1", content["containerEnv"]["REDIS_URL"]
1247+
assert_equal "45678", content["containerEnv"]["CAPYBARA_SERVER_PORT"]
1248+
assert_equal "selenium", content["containerEnv"]["SELENIUM_HOST"]
1249+
assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/activestorage"])
1250+
assert_equal({}, content["features"]["ghcr.io/devcontainers/features/github-cli:1"])
12491251
end
12501252
assert_file(".devcontainer/Dockerfile") do |content|
1251-
assert_match(/libvips/, content)
1252-
assert_match(/ffmpeg/, content)
1253-
assert_match(/poppler-utils/, content)
1253+
assert_match(/ARG RUBY_VERSION=#{RUBY_VERSION}/, content)
12541254
end
12551255
assert_compose_file do |compose_config|
12561256
expected_rails_app_config = {
@@ -1317,15 +1317,13 @@ def test_devonctainer_postgresql
13171317
assert_equal expected_postgres_config, compose_config["services"]["postgres"]
13181318
assert_includes compose_config["volumes"].keys, "postgres-data"
13191319
end
1320-
assert_file(".devcontainer/devcontainer.json") do |content|
1321-
assert_match(/"DB_HOST": "postgres"/, content)
1320+
assert_devcontainer_json_file do |content|
1321+
assert_equal "postgres", content["containerEnv"]["DB_HOST"]
1322+
assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/postgres-client"])
13221323
end
13231324
assert_file("config/database.yml") do |content|
13241325
assert_match(/host: <%= ENV\["DB_HOST"\] %>/, content)
13251326
end
1326-
assert_file(".devcontainer/Dockerfile") do |content|
1327-
assert_match(/libpq-dev/, content)
1328-
end
13291327
end
13301328

13311329
def test_devonctainer_mysql
@@ -1348,15 +1346,13 @@ def test_devonctainer_mysql
13481346
assert_equal expected_mysql_config, compose_config["services"]["mysql"]
13491347
assert_includes compose_config["volumes"].keys, "mysql-data"
13501348
end
1351-
assert_file(".devcontainer/devcontainer.json") do |content|
1352-
assert_match(/"DB_HOST": "mysql"/, content)
1349+
assert_devcontainer_json_file do |content|
1350+
assert_equal "mysql", content["containerEnv"]["DB_HOST"]
1351+
assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/mysql-client"])
13531352
end
13541353
assert_file("config/database.yml") do |content|
13551354
assert_match(/host: <%= ENV.fetch\("DB_HOST"\) \{ "localhost" } %>/, content)
13561355
end
1357-
assert_file(".devcontainer/Dockerfile") do |content|
1358-
assert_match(/default-libmysqlclient-dev/, content)
1359-
end
13601356
end
13611357

13621358
def test_devonctainer_mariadb
@@ -1377,8 +1373,8 @@ def test_devonctainer_mariadb
13771373
assert_equal expected_mariadb_config, compose_config["services"]["mariadb"]
13781374
assert_includes compose_config["volumes"].keys, "mariadb-data"
13791375
end
1380-
assert_file(".devcontainer/devcontainer.json") do |content|
1381-
assert_match(/"DB_HOST": "mariadb"/, content)
1376+
assert_devcontainer_json_file do |content|
1377+
assert_equal "mariadb", content["containerEnv"]["DB_HOST"]
13821378
end
13831379
assert_file("config/database.yml") do |content|
13841380
assert_match(/host: <%= ENV.fetch\("DB_HOST"\) \{ "localhost" } %>/, content)
@@ -1392,18 +1388,16 @@ def test_devcontainer_no_selenium_when_skipping_system_test
13921388
assert_not_includes compose_config["services"]["rails-app"]["depends_on"], "selenium"
13931389
assert_not_includes compose_config["services"].keys, "selenium"
13941390
end
1395-
assert_file(".devcontainer/devcontainer.json") do |content|
1396-
assert_no_match(/CAPYBARA_SERVER_PORT/, content)
1391+
assert_devcontainer_json_file do |content|
1392+
assert_nil content["containerEnv"]["CAPYBARA_SERVER_PORT"]
13971393
end
13981394
end
13991395

1400-
def test_devcontainer_no_Dockerfile_packages_when_skipping_active_storage
1396+
def test_devcontainer_no_feature_when_skipping_active_storage
14011397
run_generator [ destination_root, "--skip-active-storage" ]
14021398

1403-
assert_file(".devcontainer/Dockerfile") do |content|
1404-
assert_no_match(/libvips/, content)
1405-
assert_no_match(/ffmpeg/, content)
1406-
assert_no_match(/poppler-utils/, content)
1399+
assert_devcontainer_json_file do |content|
1400+
assert_nil content["features"]["ghcr.io/rails/devcontainer/features/activestorage"]
14071401
end
14081402
end
14091403

railties/test/generators/db_system_change_generator_test.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class ChangeGeneratorTest < Rails::Generators::TestCase
5454

5555
assert_file(".devcontainer/devcontainer.json") do |content|
5656
assert_match(/"DB_HOST": "postgres"/, content)
57+
assert_match(/"ghcr.io\/rails\/devcontainer\/features\/postgres-client":/, content)
5758
end
5859

5960
assert_compose_file do |compose_config|
@@ -95,6 +96,7 @@ class ChangeGeneratorTest < Rails::Generators::TestCase
9596

9697
assert_file(".devcontainer/devcontainer.json") do |content|
9798
assert_match(/"DB_HOST": "mysql"/, content)
99+
assert_match(/"ghcr.io\/rails\/devcontainer\/features\/mysql-client":/, content)
98100
end
99101

100102
assert_compose_file do |compose_config|
@@ -203,6 +205,7 @@ class ChangeGeneratorTest < Rails::Generators::TestCase
203205

204206
assert_file(".devcontainer/devcontainer.json") do |content|
205207
assert_no_match(/"DB_HOST"/, content)
208+
assert_no_match(/"ghcr.io\/rails\/devcontainer\/features\/mysql-client":/, content)
206209
end
207210

208211
assert_compose_file do |compose_config|

0 commit comments

Comments
 (0)