🛌

Docker版OpenProjectでDemo project削除後の再起動不可への対処法

2024/07/18に公開

TL;DR

  • /app/app/seeders/demo_data_seeder.rbdata_seeder_classesの要素をすべてコメントアウト
  • 永続化したい場合は、compose.yamlで以下のように記述
    compose.yaml
    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",
          ]
    ・・・
    

背景

Docker版OpenProjectで、初回起動時に自動で作成されるDemo projectScrum 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によって定義されている。

/app/app/seeders/demo_data_seeder.rb
class DemoDataSeeder < CompositeSeeder
  def data_seeder_classes
    [
      DemoData::GroupSeeder,
      DemoData::GlobalQuerySeeder,
      DemoData::ProjectsSeeder,
      DemoData::OverviewSeeder
    ]
  end
・・・

このリストを空にすることで、Demo project関連の処理をスキップさせることができる。

起動済みコンテナの場合

コンテナが停止する前に、ホストから以下コマンドでリストの要素をすべてコメントアウトする。

command
docker exec \
  -it \
  コンテナ名 \
  sed -i -r \
  's/^(\s+)(DemoData::)/\1# \2/' \
  /app/app/seeders/demo_data_seeder.rb

コマンド実行後コンテナを再起動すれば、エラーで停止しなくなるはずである。

ただし、コンテナ再作成時には該当ソースへの変更がリセットされてしまうので、永続的な対処にはならない。

永続的に対処する場合

パターン1:パッチを当てたファイルをマウントする

まず、該当ソースをホストへコピーする。

command
docker cp \
  コンテナ名:/app/app/seeders/demo_data_seeder.rb \
  .

コピーしたファイルを以下のように変更する。(上記sedコマンドでも可)

demo_data_seeder.rb
@@ -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で上記ファイルをマウントする。

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コマンドを実行させる。

compose.yaml
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によって処理が実行されている。

/app/app/seeders/root_seeder.rb
  def seed_demo_data
    print_status "*** Seeding demo data"
    DemoDataSeeder.new(seed_data).seed!
  end

seed_demo_dataの上位関数はdo_seed!であるが、おそらくこの関数からの呼び出しには何も条件がないので、初期化処理が実行されると無条件でDemo project関連の処理も実行されるようである。

/app/app/seeders/root_seeder.rb
  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側の処理をスキップするようなアプローチをとっている。

また、entrypointcommandについては、コンテナイメージの時点で以下のように定義されていたので、永続化のパターン2ではこれらをsedコマンドの後に実行するように書き換えている。

openproject/openproject
{
・・・
    "Config": {
・・・
        "Cmd": [
            "./docker/prod/supervisord"
        ],
・・・
        "WorkingDir": "/app",
        "Entrypoint": [
            "./docker/prod/entrypoint.sh"
        ],
・・・

余談

Demo project関連でおそらく指定できる環境変数としてOPENPROJECT_DEMO__PROJECTS__AVAILABLEOPENPROJECT_BOARDS__DEMO__DATA__AVAILABLEというのもあったが詳細不明で、これらをfalse指定しても別のエラーでコンテナが停止してしまうので不採用。

参考

Discussion