Docker版OpenProjectでDemo project削除後の再起動不可への対処法
TL;DR
-
/app/app/seeders/demo_data_seeder.rb
のdata_seeder_classes
の要素をすべてコメントアウト - 永続化したい場合は、
compose.yaml
で以下のように記述compose.yamlservices: openproject: image: openproject/openproject:14.2.0 entrypoint: [] command: [ "bash", "-c", "sed -i -r 's/^(\\s+)(DemoData::)/\\1# \\2/' ./app/seeders/demo_data_seeder.rb && ./docker/prod/entrypoint.sh ./docker/prod/supervisord", ] ・・・
背景
Docker版OpenProjectで、初回起動時に自動で作成されるDemo project
やScrum project
を手動で削除してしまうと、アプリケーションデータやデータベースのデータを永続化している場合にコンテナ再起動あるいはコンテナ再作成時に以下のようなエラーとなり、コンテナが停止する。
I, [****-**-**T**:**:**.****** #**] INFO -- : Using schema cache file /app/db/schema_cache.yml
-----> Seeding database...
rake aborted!
ArgumentError: Nothing registered with reference :default_role_project_admin (ArgumentError)
raise ArgumentError, message
^^^^^^^^^^^^^^^^^^^^^^
/app/app/seeders/source/seed_data.rb:64:in `find_reference'
/app/app/seeders/demo_data/project_seeder.rb:85:in `set_members'
おそらく根本原因は、起動時にDemo project
関連の処理を実行したいのにもかかわらず、対象のプロジェクト情報がデータベース等から部分的(?)に削除されていることによって不整合が生じているためと思われる。
対処法
コンテナ起動時に実行されるDemo project
関連の処理をスキップさせる。
/app/app/seeders/demo_data_seeder.rb
において、処理に用いられるクラスのリストがdata_seeder_classes
によって定義されている。
class DemoDataSeeder < CompositeSeeder
def data_seeder_classes
[
DemoData::GroupSeeder,
DemoData::GlobalQuerySeeder,
DemoData::ProjectsSeeder,
DemoData::OverviewSeeder
]
end
・・・
このリストを空にすることで、Demo project
関連の処理をスキップさせることができる。
起動済みコンテナの場合
コンテナが停止する前に、ホストから以下コマンドでリストの要素をすべてコメントアウトする。
docker exec \
-it \
コンテナ名 \
sed -i -r \
's/^(\s+)(DemoData::)/\1# \2/' \
/app/app/seeders/demo_data_seeder.rb
コマンド実行後コンテナを再起動すれば、エラーで停止しなくなるはずである。
ただし、コンテナ再作成時には該当ソースへの変更がリセットされてしまうので、永続的な対処にはならない。
永続的に対処する場合
パターン1:パッチを当てたファイルをマウントする
まず、該当ソースをホストへコピーする。
docker cp \
コンテナ名:/app/app/seeders/demo_data_seeder.rb \
.
コピーしたファイルを以下のように変更する。(上記sed
コマンドでも可)
@@ -27,10 +27,10 @@
class DemoDataSeeder < CompositeSeeder
def data_seeder_classes
[
- DemoData::GroupSeeder,
- DemoData::GlobalQuerySeeder,
- DemoData::ProjectsSeeder,
- DemoData::OverviewSeeder
+ # DemoData::GroupSeeder,
+ # DemoData::GlobalQuerySeeder,
+ # DemoData::ProjectsSeeder,
+ # DemoData::OverviewSeeder
]
end
compose.yaml
で上記ファイルをマウントする。
services:
openproject:
image: openproject/openproject:14.2.0
volumes:
- "./demo_data_seeder.rb:/app/app/seeders/demo_data_seeder.rb:ro"
・・・
コンテナを再作成すればエラーで停止しなくなるはずである。
ただ、このアプローチだとホストにファイルを置くことが必須になるので、ソースコード管理対象になりうる/該当ソースの変更に弱い などソースコード管理ツールとの相性が悪い。
パッチを当てたファイルを含めたコンテナイメージを作成するのも一つの方法ではあるが、必要なことはsed
コマンドだけで対処できるので、そこまでするほどでもないように思える。
パターン2:起動コマンドを差し替える
compose.yaml
で起動時にsed
コマンドを実行させる。
services:
openproject:
image: openproject/openproject:14.2.0
entrypoint: []
command:
[
"bash",
"-c",
"sed -i -r 's/^(\\s+)(DemoData::)/\\1# \\2/' ./app/seeders/demo_data_seeder.rb && ./docker/prod/entrypoint.sh ./docker/prod/supervisord",
]
・・・
このアプローチであればパッチを当てたファイルをマウントすることもなく、起動時に該当ソースを正規表現で書き換えるだけなのでシンプルなうえ、変更にも強い。
詳細
詳細
エラーログのトレース情報は以下。
rake aborted!
ArgumentError: Nothing registered with reference :default_role_project_admin (ArgumentError)
raise ArgumentError, message
^^^^^^^^^^^^^^^^^^^^^^
/app/app/seeders/source/seed_data.rb:64:in `find_reference'
/app/app/seeders/demo_data/project_seeder.rb:85:in `set_members'
/app/app/seeders/demo_data/project_seeder.rb:43:in `seed_data!'
/app/app/seeders/seeder.rb:57:in `block in seed!'
/app/app/models/journal/notification_configuration.rb:43:in `with'
/app/app/seeders/seeder.rb:99:in `without_notifications'
/app/app/seeders/seeder.rb:56:in `seed!'
/app/app/seeders/demo_data/projects_seeder.rb:60:in `seed_project'
/app/app/seeders/demo_data/projects_seeder.rb:38:in `block in seed_data!'
/app/app/seeders/source/seed_data.rb:113:in `block in each_data'
/app/app/seeders/source/seed_data.rb:112:in `each_value'
/app/app/seeders/source/seed_data.rb:112:in `each_data'
/app/app/seeders/demo_data/projects_seeder.rb:37:in `seed_data!'
/app/app/seeders/seeder.rb:57:in `block in seed!'
/app/app/models/journal/notification_configuration.rb:43:in `with'
/app/app/seeders/seeder.rb:99:in `without_notifications'
/app/app/seeders/seeder.rb:56:in `seed!'
/app/app/seeders/composite_seeder.rb:42:in `block in seed_with'
/app/app/seeders/composite_seeder.rb:40:in `each'
/app/app/seeders/composite_seeder.rb:40:in `seed_with'
/app/app/seeders/composite_seeder.rb:30:in `block in seed_data!'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:342:in `transaction'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/transactions.rb:212:in `transaction'
/app/app/seeders/composite_seeder.rb:29:in `seed_data!'
/app/app/seeders/seeder.rb:57:in `block in seed!'
/app/app/models/journal/notification_configuration.rb:43:in `with'
/app/app/seeders/seeder.rb:99:in `without_notifications'
/app/app/seeders/seeder.rb:56:in `seed!'
/app/app/seeders/root_seeder.rb:137:in `seed_demo_data'
/app/app/seeders/root_seeder.rb:73:in `do_seed!'
/app/app/seeders/root_seeder.rb:63:in `block (3 levels) in seed_data!'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/transaction.rb:535:in `block in within_new_transaction'
/app/vendor/bundle/ruby/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/transaction.rb:532:in `within_new_transaction'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:344:in `transaction'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/transactions.rb:212:in `transaction'
/app/app/seeders/root_seeder.rb:62:in `block (2 levels) in seed_data!'
/app/app/seeders/root_seeder.rb:119:in `prepare_seed!'
/app/app/seeders/root_seeder.rb:61:in `block in seed_data!'
/app/app/seeders/root_seeder.rb:104:in `block in set_locale!'
/app/vendor/bundle/ruby/3.3.0/gems/i18n-1.14.5/lib/i18n.rb:351:in `with_locale'
/app/app/seeders/root_seeder.rb:102:in `set_locale!'
/app/app/seeders/root_seeder.rb:59:in `seed_data!'
/app/app/seeders/seeder.rb:57:in `block in seed!'
/app/app/models/journal/notification_configuration.rb:62:in `with_first'
/app/app/models/journal/notification_configuration.rb:45:in `with'
/app/app/seeders/seeder.rb:99:in `without_notifications'
/app/app/seeders/seeder.rb:56:in `seed!'
/app/db/seeds.rb:30:in `<top (required)>'
/app/vendor/bundle/ruby/3.3.0/gems/railties-7.1.3.4/lib/rails/engine.rb:563:in `load'
/app/vendor/bundle/ruby/3.3.0/gems/railties-7.1.3.4/lib/rails/engine.rb:563:in `block in load_seed'
/app/vendor/bundle/ruby/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:121:in `block in run_callbacks'
/app/vendor/bundle/ruby/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/execution_wrapper.rb:92:in `wrap'
/app/vendor/bundle/ruby/3.3.0/gems/railties-7.1.3.4/lib/rails/engine.rb:649:in `block (2 levels) in <class:Engine>'
/app/vendor/bundle/ruby/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:130:in `instance_exec'
/app/vendor/bundle/ruby/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:130:in `block in run_callbacks'
/app/vendor/bundle/ruby/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:141:in `run_callbacks'
/app/vendor/bundle/ruby/3.3.0/gems/railties-7.1.3.4/lib/rails/engine.rb:563:in `load_seed'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/tasks/database_tasks.rb:468:in `load_seed'
/app/vendor/bundle/ruby/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/railties/databases.rake:405:in `block (2 levels) in <top (required)>'
/app/vendor/bundle/ruby/3.3.0/gems/rake-13.2.1/exe/rake:27:in `<top (required)>'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/cli/exec.rb:58:in `load'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/cli/exec.rb:58:in `kernel_load'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/cli/exec.rb:23:in `run'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/cli.rb:455:in `exec'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/vendor/thor/lib/thor/command.rb:28:in `run'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/vendor/thor/lib/thor.rb:527:in `dispatch'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/cli.rb:35:in `dispatch'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/vendor/thor/lib/thor/base.rb:584:in `start'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/cli.rb:29:in `start'
/usr/local/bundle/gems/bundler-2.5.11/exe/bundle:28:in `block in <top (required)>'
/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/friendly_errors.rb:117:in `with_friendly_errors'
/usr/local/bundle/gems/bundler-2.5.11/exe/bundle:20:in `<top (required)>'
/usr/local/bundle/bin/bundle:25:in `load'
/usr/local/bundle/bin/bundle:25:in `<main>'
demo_data_seeder.rb
の参照元は/app/app/seeders/root_seeder.rb
で、関数seed_demo_data
によって処理が実行されている。
def seed_demo_data
print_status "*** Seeding demo data"
DemoDataSeeder.new(seed_data).seed!
end
seed_demo_data
の上位関数はdo_seed!
であるが、おそらくこの関数からの呼び出しには何も条件がないので、初期化処理が実行されると無条件でDemo project
関連の処理も実行されるようである。
def do_seed!
# Basic data needs be seeded before anything else.
seed_basic_data
seed_admin_user
seed_demo_data
seed_development_data if seed_development_data?
seed_plugins_data
seed_env_data
end
もちろん、このseed_demo_data
の行をコメントアウトしたり、seed_demo_data
の処理冒頭でreturn
させて何も処理させないようにしても同様に対処することもできるが、DemoDataSeeder
の再利用可能性を考慮してDemoDataSeeder
側の処理をスキップするようなアプローチをとっている。
また、entrypoint
とcommand
については、コンテナイメージの時点で以下のように定義されていたので、永続化のパターン2ではこれらをsed
コマンドの後に実行するように書き換えている。
{
・・・
"Config": {
・・・
"Cmd": [
"./docker/prod/supervisord"
],
・・・
"WorkingDir": "/app",
"Entrypoint": [
"./docker/prod/entrypoint.sh"
],
・・・
余談
Demo project
関連でおそらく指定できる環境変数としてOPENPROJECT_DEMO__PROJECTS__AVAILABLE
やOPENPROJECT_BOARDS__DEMO__DATA__AVAILABLE
というのもあったが詳細不明で、これらをfalse
指定しても別のエラーでコンテナが停止してしまうので不採用。
Discussion