Skip to content

Commit

Permalink
Refactoring Devcontainer implementation
Browse files Browse the repository at this point in the history
Extract all the DB information (service, volume, feature etc.) into an object. This let's us remove the growing number of case statements in this code. Additionally, it allows the change generator to create a Devcontainer::Database object itself, instead of needing to mix in the Devcontainer concern. That was always a little suspiscious since the change generator does not know everything about the app (if it uses redis, for example) so it could theoretically generate incorrect devcontainer config if we called those methods.
  • Loading branch information
andrewn617 committed May 8, 2024
1 parent dcfabdb commit 297c0a6
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 134 deletions.
266 changes: 156 additions & 110 deletions railties/lib/rails/generators/devcontainer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
module Rails
module Generators
module Devcontainer
DB_FEATURES = {
"mysql" => "ghcr.io/rails/devcontainer/features/mysql-client",
"postgresql" => "ghcr.io/rails/devcontainer/features/postgres-client",
"sqlite3" => "ghcr.io/rails/devcontainer/features/sqlite3"
}

private
def devcontainer_dependencies
return @devcontainer_dependencies if @devcontainer_dependencies
Expand All @@ -17,7 +11,7 @@ def devcontainer_dependencies

@devcontainer_dependencies << "selenium" if depends_on_system_test?
@devcontainer_dependencies << "redis" if devcontainer_needs_redis?
@devcontainer_dependencies << db_name_for_devcontainer if db_name_for_devcontainer
@devcontainer_dependencies << devcontainer_database.name if devcontainer_database.service
@devcontainer_dependencies
end

Expand All @@ -29,7 +23,7 @@ def devcontainer_variables
@devcontainer_variables["CAPYBARA_SERVER_PORT"] = "45678" if depends_on_system_test?
@devcontainer_variables["SELENIUM_HOST"] = "selenium" if depends_on_system_test?
@devcontainer_variables["REDIS_URL"] = "redis://redis:6379/1" if devcontainer_needs_redis?
@devcontainer_variables["DB_HOST"] = db_name_for_devcontainer if db_name_for_devcontainer
@devcontainer_variables["DB_HOST"] = devcontainer_database.name if devcontainer_database.service

@devcontainer_variables
end
Expand All @@ -40,7 +34,7 @@ def devcontainer_volumes
@devcontainer_volumes = []

@devcontainer_volumes << "redis-data" if devcontainer_needs_redis?
@devcontainer_volumes << db_volume_name_for_devcontainer if db_volume_name_for_devcontainer
@devcontainer_volumes << devcontainer_database.volume if devcontainer_database.volume

@devcontainer_volumes
end
Expand All @@ -55,7 +49,7 @@ def devcontainer_features
@devcontainer_features["ghcr.io/rails/devcontainer/features/activestorage"] = {} unless options[:skip_active_storage]
@devcontainer_features["ghcr.io/devcontainers/features/node:1"] = {} if using_node?

@devcontainer_features.merge!(db_feature_for_devcontainer) if db_feature_for_devcontainer
@devcontainer_features.merge!(devcontainer_database.feature) if devcontainer_database.feature

@devcontainer_features
end
Expand All @@ -74,7 +68,7 @@ def devcontainer_forward_ports
return @devcontainer_forward_ports if @devcontainer_forward_ports

@devcontainer_forward_ports = [3000]
@devcontainer_forward_ports << db_port_for_devcontainer if db_port_for_devcontainer
@devcontainer_forward_ports << devcontainer_database.port if devcontainer_database.port
@devcontainer_forward_ports << 6379 if devcontainer_needs_redis?

@devcontainer_forward_ports
Expand All @@ -84,128 +78,180 @@ def devcontainer_needs_redis?
!(options.skip_action_cable? && options.skip_active_job?)
end

def db_port_for_devcontainer(database = options[:database])
case database
when "mysql", "trilogy" then 3306
when "postgresql" then 5432
end
def devcontainer_database
@devcontainer_database ||= Database.build(options[:database])
end

def db_name_for_devcontainer(database = options[:database])
case database
when "mysql" then "mysql"
when "trilogy" then "mariadb"
when "postgresql" then "postgres"
end
def devcontainer_db_service_yaml(**options)
return unless service = devcontainer_database.service

{ devcontainer_database.name => service }.to_yaml(**options)[4..-1]
end

def db_volume_name_for_devcontainer(database = options[:database])
case database
when "mysql" then "mysql-data"
when "trilogy" then "mariadb-data"
when "postgresql" then "postgres-data"
end
def local_rails_mount
{
type: "bind",
source: Rails::Generators::RAILS_DEV_PATH,
target: Rails::Generators::RAILS_DEV_PATH
}
end

def db_package_for_dockerfile(database = options[:database])
case database
when "mysql" then "default-libmysqlclient-dev"
when "postgresql" then "libpq-dev"
class Database
class << self
def build(database_name)
case database_name
when "mysql" then MySQL.new
when "postgresql" then PostgreSQL.new
when "trilogy" then MariaDB.new
when "sqlite3" then SQLite3.new
else Null.new
end
end

def all
@all ||= [
MySQL.new,
PostgreSQL.new,
MariaDB.new,
SQLite3.new,
]
end
end
end

def devcontainer_db_service_yaml(**options)
return unless service = db_service_for_devcontainer
def name
raise NotImplementedError
end

service.to_yaml(**options)[4..-1]
end
def service
raise NotImplementedError
end

def db_service_for_devcontainer(database = options[:database])
case database
when "mysql" then mysql_service
when "trilogy" then mariadb_service
when "postgresql" then postgres_service
def port
raise NotImplementedError
end
end

def db_feature_for_devcontainer(database = options[:database])
case database
when "sqlite3" then sqlite3_feature
when "mysql" then mysql_feature
when "postgresql" then postgres_feature
def feature_name
raise NotImplementedError
end
end

def postgres_service
{
"postgres" => {
"image" => "postgres:16.1",
"restart" => "unless-stopped",
"networks" => ["default"],
"volumes" => ["postgres-data:/var/lib/postgresql/data"],
"environment" => {
"POSTGRES_USER" => "postgres",
"POSTGRES_PASSWORD" => "postgres"
def feature
return unless feature_name

{ feature_name => {} }
end

def volume
return unless service

"#{name}-data"
end

class MySQL < Database
def name
"mysql"
end

def service
{
"image" => "mysql/mysql-server:8.0",
"restart" => "unless-stopped",
"environment" => {
"MYSQL_ALLOW_EMPTY_PASSWORD" => "true",
"MYSQL_ROOT_HOST" => "%"
},
"volumes" => ["mysql-data:/var/lib/mysql"],
"networks" => ["default"],
}
}
}
end
end

def mysql_service
{
"mysql" => {
"image" => "mysql/mysql-server:8.0",
"restart" => "unless-stopped",
"environment" => {
"MYSQL_ALLOW_EMPTY_PASSWORD" => "true",
"MYSQL_ROOT_HOST" => "%"
},
"volumes" => ["mysql-data:/var/lib/mysql"],
"networks" => ["default"],
}
}
end
def port
3306
end

def mariadb_service
{
"mariadb" => {
"image" => "mariadb:10.5",
"restart" => "unless-stopped",
"networks" => ["default"],
"volumes" => ["mariadb-data:/var/lib/mysql"],
"environment" => {
"MARIADB_ALLOW_EMPTY_ROOT_PASSWORD" => "true",
},
}
}
end
def feature_name
"ghcr.io/rails/devcontainer/features/mysql-client"
end
end

def db_service_names
["mysql", "mariadb", "postgres"]
end
class PostgreSQL < Database
def name
"postgres"
end

def service
{
"image" => "postgres:16.1",
"restart" => "unless-stopped",
"networks" => ["default"],
"volumes" => ["postgres-data:/var/lib/postgresql/data"],
"environment" => {
"POSTGRES_USER" => "postgres",
"POSTGRES_PASSWORD" => "postgres"
}
}
end

def mysql_feature
{ DB_FEATURES["mysql"] => {} }
end
def port
5432
end

def postgres_feature
{ DB_FEATURES["postgresql"] => {} }
end
def feature_name
"ghcr.io/rails/devcontainer/features/postgres-client"
end
end

def sqlite3_feature
{ DB_FEATURES["sqlite3"] => {} }
end
class MariaDB < Database
def name
"mariadb"
end

def service
{
"image" => "mariadb:10.5",
"restart" => "unless-stopped",
"networks" => ["default"],
"volumes" => ["mariadb-data:/var/lib/mysql"],
"environment" => {
"MARIADB_ALLOW_EMPTY_ROOT_PASSWORD" => "true",
},
}
end

def db_features
@db_features ||= DB_FEATURES.values
end
def port
3306
end

def local_rails_mount
{
type: "bind",
source: Rails::Generators::RAILS_DEV_PATH,
target: Rails::Generators::RAILS_DEV_PATH
}
def feature_name
nil
end
end

class SQLite3 < Database
def name
"sqlite3"
end

def service
nil
end

def port
nil
end

def feature_name
"ghcr.io/rails/devcontainer/features/sqlite3"
end
end

class Null < Database
def name; end
def service; end
def port; end
def volume; end
def feature; end
def feature_name; end
end
end
end
end
Expand Down

0 comments on commit 297c0a6

Please sign in to comment.