.discourse-compatibility の紹介:古い Discourse バージョン向けに固定されたプラグイン・テーマのバージョン

みなさんこんにちは :wave:、古い Discourse インスタンスにインストールされた際に、プラグインやテーマが特定のバージョンをピン留めできるようにする新機能をマージしました。

プラグインまたはテーマのリポジトリのルートに .discourse-compatibility ファイルを含めることで、古い Discourse バージョンへのインストール時にチェックアウトすべきバージョンを指定できるようになりました。


背景

どのプラグインやテーマがどの Discourse バージョンと互換性があるかを覚えるのは大変です。管理者にとっては、プラグインのコミット履歴を読み通さなくても、これらの変更を簡単にスキャンし、自分の Discourse インストールに適切なバージョンを見つけられる必要があります。また、プラグインやテーマの作者にとっては、後方互換性のない変更を加えながらインストールバージョンを管理し、古いインストール環境を壊さないようにする必要があります。

Discourse のソフトウェアアップデートは非常に迅速に行われます。これは素晴らしいことですが、多くのプラグインを運用する Discourse インスタンスの維持を非常に困難にします。特に、現在の安定版など、他のリリーススケジュールやバージョンに従っている場合です。私の計画は、安定版や他のリリーススケジュールに従うユーザーにとって更新プロセスを容易にするエコシステムを構築し、サイト管理者がターゲットとする Discourse バージョンに互換性のあるプラグインバージョンを素早く自動的に取得できる方法を提供することです。

元の発表(上記のドキュメントに置き換えられました)

実装

まず注意点として、ここでは Discourse コアのタグに依存しています。Discourse のベータ版は頻繁にリリースされるため、プラグインのバージョンをそれらのベータ版に対してピン留めできるからです。Git ハッシュに対してピン留めするのは多くの理由から nightmare(地獄)なので、最も近いベータ版または安定版のタグを取得するために git describe を使用します。

プラグインまたはテーマのルートに、.discourse-compatibility という名前のバージョン互換性ファイルを用意できるようになりました。このファイルは、互換性マップを指定する降順(新しい Discourse バージョンから順)のリストです。

2.5.0.beta2: git-hash-1234e5f5d
2.4.4.beta6: 4444ffff33dd
2.4.2.beta1: named-git-tag-or-branch

各プラグイン/テーマにおいて、アップグレードまたは再構築は、現在の Discourse バージョンと等しい、またはそれ以降のバージョンが見つかるまで、後続の命名されたコミット/ブランチ/タグをチェックアウトし続けます。
例えば、上記のバージョンファイルで現在の Discourse バージョンが 2.4.6.beta12 の場合、ファイルを検索して 2.5.0.beta2 のエントリを選択します。

現在の Discourse バージョンが 2.4.4.beta6 の場合、2.4.4.beta6 の一致するエントリが選択されます。

それ以降のバージョンが存在しない場合は、現在チェックアウトされているバージョンのままになります。
例えば、2.5.0.beta3 の場合、ピン留めは行われません。

それ以前のバージョンが存在しない場合は、バージョンファイルに記載されている最も古いエントリがチェックアウトされます。
例えば、2.2.1.beta22 の場合、利用可能な最も古いバージョンとして 2.4.2.beta1 のエントリがチェックアウトされます。


ここでの目的は、将来的に tests-passed に厳密に従っていない代替デプロイメントの維持に伴う負担を軽減し、管理者にアップグレードのタイミングと場所に関する柔軟性を与えることです。これを実現するために、プラグインおよびテーマの作者が、古いバージョンの Discourse 上のインストールに影響を与えずに後方互換性のない変更を開発できる方法を提供しています。

「いいね!」 50

これは素晴らしい機能ですね、ありがとうございます :slight_smile:

この方向性が気に入っています。つまり、サイト管理者が特に何も行う必要なく、プラグイン自体からこの問題を管理できるようになります。

いくつかの初期の質問があります。

  • プラグインのアクティベーション時に存在する required_version のプラグインメタデータチェックは残りますか?また、それがこれとどのように関連する(もしあれば)とお考えですか?

  • 現時点ではこれは Rake タスクの形式で追加されているようです。discourse_docker(つまりランチャー)および docker_manager との関係はどのようなものでしょうか?意図された使い方は何ですか?両方のリポジトリに変更を加えられていますが、両方の環境でどのように機能する予定なのかご説明いただけますでしょうか。

「いいね!」 12

はい、その通りです。プラグイン作成者が後方互換性を追加できる機能を提供し、管理者が心配する必要をなくすことが目的です。

現在、required_version プラグインメタデータを変更または削除する予定はありません。これらは関連していますが、私の中では別物として考えています。required_version の min/max フルストップは、エラーをスローしてプラグインのインストールを禁止し、この互換性のプルより後に読み込まれます。もし「非常に古い」Discourse インスタンスがあなたのプラグインを使用するのを防ぎたいのであれば、最初の最小バージョンに対して required_version を含めることをお勧めします。互換性を追いかけても、それは解決できません :wink:

通常の使用において設定変更は不要で、変更は自動的に検出されます。Rake タスクは discourse_docker 内でプラグインがクローンされた後に実行されるため、ランチャーの順序は以下のようになります:

  • プラグインのクローン
  • 互換性の確認とチェックアウト(該当する場合)
  • マイグレーション

docker_manager では、古い Discourse バージョンでもプラグインを互換性のあるバージョンに更新できます。UI は同じままですが、「最新」かどうかの判定が、互換性ファイルに基づいて行われるようになります。

TL;DR:この機能を利用し始めるために、どちらのユースケースでも変更は不要です。

内部実装では、最新のファイルを読み取るために git show HEAD@{upstream}:.discourse-compatibility を、正しいバージョンをチェックアウトするために git reset --hard #{checkout_version} を使用します。これにより、最新の互換性情報を利用でき、古い Discourse バージョンが古い(無効な可能性のある)互換性ファイルに留まることを防ぎます。

「いいね!」 11

なるほど、説明ありがとうございます。

それで、自分自身で :wine_glass: を注いで、Custom Wizard Plugin で試してみました。

各タグを v2.6.0.beta1 から逆順に一つずつ確認しました。以下の git コマンドが役に立ちました:

git tag --list \\ 例えば git tag --list 'v2.5.0*'
git checkout tags/tag \\ 例えば git checkout tags/v2.5.0.beta7

現在のプラグインのバージョンと互換性がないタグを見つけるのにそれほど時間はかかりませんでした:v2.5.0.beta7 には、カスタムウィザードがインポートしようとする discourse/app/components/d-textarea が含まれていませんでした。

そこで、そのインポートを追加した コミット を特定し、前のコミット の sha1 を取得してチェックアウトし、テストしました(正常に動作しました)。その後、.discourse-compatibility に以下を追加しました:

v2.5.0.beta7: 802d74bab2ebe19a106f75275342dc2e9cc6066a

次に、その内容を 最新のプラグインコードを含むブランチ(通常は必須ではないテスト用ブランチ)にプッシュし、そのプラグインブランチと versionv2.5.0.beta7 に設定して Docker 化されたテストサーバーを再構築しました。

しかし、それは動作しませんでした。そこでふと気づきました。もちろん、rake タスク plugin:pull_compatible_allv2.5.0.beta7 には存在しないのですから、これは遡って機能するはずがありません( :wine_glass: のせいかもしれません)。実際、ランチャーのログには以下が表示されていました:

Don't know how to build task 'plugin:pull_compatible_all' (See the list of available tasks with `rake --tasks`)

ただし、あなたが想定している使い方の要点はこれで合っていますか?

required_version については、テストサーバーに discourse-legal-tools プラグインがインストールされていたため、ここで遭遇しました。このプラグインは required_versionv2.5.0 に設定しているため、当初 v2.5.0.beta7 で失敗しました。このプラグインを新しいシステムに移行しようと考えています。あなたが仰る通り、絶対的なベースラインを設定するために required_version は依然として有用だと考えています。

「いいね!」 11

はい、その通りです。これは現在、Discourseのコアに組み込まれていないため、2.6.0.beta1(現在)より古いバージョンでは動作しません。これを安定版と最新のベータ版に移植する必要がありますので、2.5.0に対しても使用可能になりますが、それ以前のバージョンへの移植は予定していません。

「いいね!」 11

追伸となりますが、Custom Wizard マスターに互換性ファイルを追加し、プラグインのバージョンを固定するようにしました。ポートされる際には、現在のベータ版である v2.6.0.beta1 と現在の安定版である v2.5.0 を対象とします。詳細は以下をご覧ください:

https://meta.discourse.org/t/custom-wizard-plugin/73345/562?u=angus

「いいね!」 5

一点の注記と質問があります。

まず、.discourse-compatibility ファイル内の Discourse バージョンタグの構文は、@featheredtoast の例のように 2.5.0 であり、v2.5.0 ではありません。v を含めると、以下のエラーが発生します。

Malformed version number string v2.6.0.beta1

次に、@featheredtoast指摘 した通り、ピン留めシステムは現在 stable ブランチにもバックポートされています。私は v2.5.0 タグを見ていたため、これを見逃していました。

これにより、少し疑問が湧きました。Discourse のブランチは、Discourse のリリースタグと直接対応しているわけではありません。古いバージョンの Discourse を実行しているサイトのほとんどは、リリース(例:v2.5.0)ではなく、ブランチ(例:stable)上で動作しているはずです。

もし(プラグインにとっての)破壊的変更が「古い」ブランチ(例:stable)にも追加された場合、これはピン留めに対してどのような意味を持ちますか?おそらく、バージョン管理システムの仕組みを完全に理解していないのだと思います。

「いいね!」 6

バージョンは、アプリ内で定義されたDiscourse のバージョンに対応し、git タグとは一致しません。

したがって、この文脈における「2.5.0」は、2.5.1(または 2.6.0.beta1)へのバージョンアップ前まで、2.5.0 と宣言された任意のバージョンを意味します。

プラグインにとっての破壊的変更が安定ブランチにも追加された場合、確かにそのプラグインも動作しなくなるでしょう。バージョン管理の目的は、そのバージョンに対して破壊的変更を導入していないことをある程度確信できるようにすることです。これは別途強く主張すべき点です。安定版の意図は、セキュリティ修正や重大なバグ修正のみを(適切であれば軽微な機能も含めて)バックポートすることにあります。

この仕組みの目的は、古い Discourse のバージョンに対して、古い動作可能なプラグインのバージョンを計算できるようにすることです。これは万能薬ではありませんが、バックポートする内容を慎重に選定すれば、そのための基盤を提供します。

「いいね!」 7

これは、–depth=1 でクローンする場合を除き、概ね機能します。

対象が初期段階で見つからない場合、すぐに git fetch --depth 1 {upstream} commit を呼び出すフックを実装する予定です。そうしなければ、部分的なクローンや完全なクローンに依存することになり、それは望ましくありません。必要なデータを取得できるはずです。

編集:こちらで更新しました:

これを Beta バージョンと Stable バージョンにもバックポートしました。これで、シャロークローンでもピン留めができるはずです。

「いいね!」 6

事前にお詫び申し上げます。私は深夜にこの話題に触れることが多く、100% 正しいかどうか確信が持てませんし、すでに既にご存知の内容かもしれません。私が何度かこの点でつまずいたため、自分の理解を整理する意味でもここに記録しておきます。

この仕組みは、オープンソースとして公開され、一般的な方法で更新されている(つまり、あなたが管理していない)インスタンスでプラグインが使用される場合、beta ブランチに対して実質的に機能しないと考えられます。一般的な更新方法は、サイト管理者が管理 UI のプロンプトに応じて行うものです。

以下の点をご考慮ください。

また、以下の点も重要です。

さらに、tests-passedbeta は同じ Discourse バージョンを持つものの、コードは異なります(例:現在どちらも 2.6.0.beta2)。

これにより、以下が導き出されます。

  1. beta ブランチをサポートするには、最新の beta リリースにコミットをピン留めする必要があります。なぜなら、beta ブランチ上のサイトが使用するものがそれだからです。

  2. しかし、最新の beta リリースが互換性ファイルに含まれている場合、tests-passed を実行しているインスタンスもそのピン留めされたコミットを使用することになります。

つまり、オープンソースのプラグインにおいて、tests-passedbeta の両方を標準的な使用方法で同時にサポートすることはできません。プラグインをインストールする人の大多数が tests-passed にいることを考えると、この方法では beta を事実上サポートできないことになります。

なお、stable ブランチについては、betatests-passed とは異なる Discourse バージョンを使用しているため、実際には問題なく機能します。

「いいね!」 4

ああ、これは私が認識している問題です。次のベータ版リリースまで完全に解決しません。これについては、ベータ版とテスト通過版を区別する方法を必ず見つけるべきです。

この機能を設定していた頃は、安定版と特殊なフォークを想定していましたが、後になって「最新」と「安定」が同じバージョンを共有していることに気づきました。

「いいね!」 6

確認ありがとうございます。

実質的な結論としては、インスタンスが tests-passed で実行されている場合はプロセスを実行しないようにすべきようです。上記の理由により、ファイルに tests-passed バージョンを含めることはできませんが、tests-passed に対してプロセスを除外しても、現在の動作は変わりません。

これを実装する一つの方法は以下の通りです。

def self.find_compatible_resource(version_list, version = ::Discourse::VERSION::STRING)
 
   return if Discourse.git_branch === 'tests-passed'

   ...
end

これにより、beta サイトでプラグインをピン留めする目的で、ファイルに最新の beta バージョンを使用することが可能になります。また、このファイルを使用せずに、プラグインの最新コミットで tests-passed のサポートを継続することもできます。もしこの方針に賛成であれば、PR を作成します。

あるいは、ここでの根本的な問題は以下の点だと感じています。

これは以前にも議論されたことかと思いますが、以下のような対応は可能でしょうか。

  • tests-passed: 2.6.0.tests-passed、つまり tests-passed ブランチで PREtests-passed に設定する。

  • beta: 2.6.0.beta2、つまり現在と同じようにする。

@jomaxro について、この点を理解するお手伝いをしていただけますか?

「いいね!」 7

はい、プレベータリリースと実際のベータリリースを区別するための追加のマーカーを導入することに賛成です。それが容易に実現できるのであれば、です。そうすれば、ここにある根本的な問題を解決できるでしょう。他のソフトウェアプロジェクトでは、リリース前にバージョンを「次の」バージョンに引き上げる例があります(例:beta1 をリリースした後、「version」を beta2 に引き上げるなど)。

ただし、Discourse は伝統的にその方法を採用してこなかったため、プロジェクトにとって最も導入しやすい方法に依存します。

「いいね!」 6

別の問題に遭遇しました。

この変更 は 2.6.0beta2 で $danger-low-mid を導入しました。

これにより discourse-styleguide プラグイン が壊れてしまったため、更新され、Discourse 2.5.0 用の以前のコミットにプラグインを留めるための .discourse-compatibility ファイルが導入されました。

しかし、これは Discourse 2.5.1 で破綻します。この変更は安定版には決してバックポートされないため、discourse-compatibility ファイルは新しい 2.5.x 安定版が出るたびに更新する必要があります。

すべての プラグインの すべての discourse-compatibility ファイルが、すべての 新しい 2.5.x 安定版のたびに更新されなければなりません。

あるいは、互換性ファイルでバージョン 2.5.999 を参照することもできます。これにより、プラグインは 2.5.x のライフサイクル全体を通じてコミット 1f86468b2c81b40e97f1bcd16ea4bb780634e2c7 に留められます。しかし、これは非常にハック的な手法だと私は考えます。

「いいね!」 6

2.6.0beta1 に固定するのが安全で、問題が beta2 で導入されたことを考えればより正確でもあります。その意味で合っていますか?そうすれば 2.5 の全バージョンもカバーできます。

あなたが提案した「ハック的な」2.5.999 という解決策は、確かに便利な回避策のように思えます(ただし、ご指摘の通り複雑ではありますね)。ここではピン留めをできるだけシンプルに保とうとしましたが、「2.5.x」を有効な固定バージョンとして明示する真の方法がまだ欠けているという点では、おっしゃる通りです。

最後の安定版を固定しつつ、次のバージョンのベータ版は固定しないという、その特定のケースに対する別の回避策として、少し巧妙かもしれませんが、2.5.x ブランチ全体をピン留めする点では「ハック的」ではないように感じられる方法があります。それは 2.6.0.beta0 または 2.6.0.alpha1 でピン留めすることです。これは構造的には、2.5.x から 2.6.0.beta1 の間のすべてのバージョンを固定することを意味します。つまり:

  • 2.5.x のすべてがピン留めでカバーされます。
  • 2.6.0.beta(1+) のすべては引き続き固定されません。
「いいね!」 6

はい、私も同様に、そちらの方がハック的ではないと感じます。

ただし、2.5.999 と 2.6.0beta0 の両方の解決策は、以下のような類似のケースには対応していません:2.6.0beta3 で問題が発生し、それが 2.5.2 にバックポートされた場合、どうすればよいでしょうか?

さらに多くのエッジケースと隠された機能:空白のエントリを使用して、ピンを「解除」できます。

互換性ファイルの内容は以下の通りです。

        2.6.0.beta1: twofiveall
        2.4.4.beta6: ~
        2.4.2.beta1: twofourtwobetaone

2.4.2.beta1 から 2.4.4.beta6 の間のバージョンはピンバックされません。その後、2.6.0.beta1 までのバージョンはピンされます。それ以降は再びピンが解除されます。

内部的には、nil 値として評価されるものは変更されず、最新バージョンのままになります。空白のエントリまたは ~ は(Ruby の YAML パーサーを介して)nil として評価されるため、ピン関数をバイパスします。

「いいね!」 8

古いバージョンのプラグインをセルフホストインスタンスで使用することは可能ですか?

「いいね!」 1

はい、アプリの app.yml 内の git clone コマンドを調整して、目的のバージョンをクローンできます。Discourse も目的のバージョンにピン留めされていることを確認する必要があります。また、実行中の Discourse のバージョンと互換性のあるバージョンに discourse_docker をピン留めする必要がある場合もあります。これらすべてを行う場合、アップグレードしない方が簡単です。

「いいね!」 2