إطلاق .discourse-compatibility: إصدارات مثبتة من الإضافات/السمات لعمليات Discourse الأقدم

مرحبًا بالجميع :wave:، لقد دمجتُ ميزة جديدة ستساعد الإضافات (plugins) والمظاهر (themes) في تثبيت إصدارات معينة عند تثبيتها على نسخ أقدم من Discourse.

يمكنك الآن تضمين ملف .discourse-compatibility في الجذر (root) لمستودع إضافة أو مظهر، يحدد الإصدار الذي يجب استرداده (checkout) عند التثبيت على نسخ أقدم من Discourse.


المبررات

من المحبط تذكر أي الإضافات والمظاهر متوافقة مع أي إصدار من Discourse. بصفتك مسؤولًا (admin)، يجب أن يكون من الممكن مسح هذه التغييرات بسهولة وإيجاد الإصدار المناسب لنسختك من Discourse دون الاضطرار لقراءة سجل الالتزامات (commit history) للإضافة. وبصفتك كاتبًا لإضافة أو مظهر، يجب أن تتمكن من إدارة إصدارات التثبيت مع إجراء تغييرات غير متوافقة مع الإصدارات الأقدم، حتى لا تكسر التثبيتات القديمة.

تتسارع تحديثات برنامج Discourse بشكل كبير، وهو أمر رائع بلا شك، لكنه يجعل صيانة نسخ Discourse التي تحتوي على العديد من الإضافات أمرًا صعبًا جدًا في بعض الأحيان، خاصة إذا كنت تتبع إيقاعات إصدارات/نسخ أخرى مثل النسخة المستقرة الحالية. خطتي هنا هي تمكين نظام بيئي يخفف عملية التحديث لأولئك الذين يتبعون النسخة المستقرة أو أي إيقاع إصدار آخر، وتزويد مسؤولي المواقع بطريقة لاستعادة أي إصدار من الإضافة متوافق مع إصدار Discourse المستهدف بسرعة وبشكل تلقائي.

الإعلان الأصلي (تم استبداله الآن بالوثائق المرتبطة أعلاه)

التنفيذ

أول ما يجب ملاحظته: نحن نعتمد هنا على الوسوم (tags) في نواة Discourse، حيث أن نسخ بيتا (betas) من Discourse تُصدر بشكل متكرر بما يكفي لتمكيننا من تثبيت إصدارات الإضافات مقابلها. تثبيت الإصدارات مقابل تجزئات Git (git hashes) هو كابوس لعدة أسباب، لذا يمكننا استخدام 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 إعجابًا

This is a great feature, thank you :slight_smile:

I like the directionality of this, i.e. it makes it possible to manage this issue from within the plugin itself, not requiring the site admin to do anything per se.

I have a few initial questions:

  • Will the existing required_version plugin metadata check in plugin activation remain? And how do you see that interelating with this (if at all)?

  • I see this is added in the form of a rake task atm. How does it relate to, what is the intended use, with discourse_docker (i.e. the launcher) and docker_manager? I see you’ve made additions to both repos, but could you explain how it’s intended to work in both environments?

12 إعجابًا

Yeah, that’s the idea - make it so plugin authors have the capability to add backwards compatibility so that admins don’t need to worry!

There are currently no plans to change or remove the required_version plugin metadata. They’re related, but are still separate in my mind - the required_version min/max fullstop disallows the plugin from being installed by throwing an error, and is loaded after this compatibility pull. If you want to prevent ancient Discourse instances from using your plugin, I’d say it’s still a good idea to include required_version for the first minimum version - no amount of compatibility hunting is going to fix that :wink:

There shouldn’t be any config changes needed in normal use, it’ll pick up changes automatically. The rake task is called in discourse_docker after the plugins are cloned, so in the launcher order:

  • Plugins cloned
  • Compatibility checked, and checked out (if applicable)
  • migrate

In docker_manager, older Discourse versions will be able to update plugins to a compatible version. The UI here stays the same, but an “up to date” plugin is now according to the compatibility file.

TLDR, no changes are needed for either use-case to start taking advantage of this, if that’s what you’re wondering.

Under the hood, it uses git show HEAD@{upstream}:.discourse-compatibility to read the latest file, and git reset --hard #{checkout_version} to checkout the correct version. That way we are able to use the latest compatibility, and older Discourse versions will not be stuck on an old (possible invalid) compat file.

11 إعجابًا

Cool, thanks for explaining that.

So I poured myself a :wine_glass: and gave this a shot with the Custom Wizard Plugin.

I checked each tag out one by one in reverse order starting with v2.6.0.beta1. I found these git commands helpful:

git tag --list \\ e.g. git tag --list 'v2.5.0*'
git checkout tags/tag \\ e.g. git checkout tags/v2.5.0.beta7

It didn’t take too long to find a tag that wasn’t working with the current version of the plugin: v2.5.0.beta7 doesn’t include discourse/app/components/d-textarea which the custom wizard tries to import.

So, then I found the commit in the plugin that added that import, took the sha1 of the previous commit, checked that out and tested (worked fine), and added this to .discourse-compatibility:

v2.5.0.beta7: 802d74bab2ebe19a106f75275342dc2e9cc6066a

I then pushed that to a branch with the latest plugin code (a branch for testing, not necessary normally), and rebuilt a dockerized test server with that plugin branch and the version set at v2.5.0.beta7.

That didn’t work, then it hit me that, of course, the rake task plugin:pull_compatible_all doesn’t exist in v2.5.0.beta7, so this isn’t going to work retrospectively (I blame the :wine_glass:). Sure enough in the launcher logs I see

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

Is that the gist of how you imagine this being used though?

On the required_version front, I encountered that here as the test server had the discourse-legal-tools plugin installed, which has a required_version of v2.5.0, so it initially failed on v2.5.0.beta7. I think I’ll transfer that plugin over to this new system. I can still see required_version being useful to set an absolute baseline as you say.

11 إعجابًا

Yeah that’s correct - because this isn’t baked into discourse core until now, it won’t work on anything older than 2.6.0.beta1 (currently). I still have to port this to stable + latest beta, so we’ll be able to use it against 2.5.0, but we’re not planning on porting it any earlier.

11 إعجابًا

Just a follow up here that I’ve now added a compatbility file to Custom Wizard master and will be using it to pin the plugin, including v2.6.0.beta1 (current beta) and v2.5.0 (current stable) when this is ported. See further:

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

5 إعجابات

One note and a question here.

Firstly, just a note that the syntax for discourse version tags in the .discourse-compatibility file is 2.5.0 (like in @featheredtoast’s example), not v2.5.0. If you have a v you’ll get this error:

Malformed version number string v2.6.0.beta1

Secondly, @featheredtoast pointed out that the pinning system is now backported to stable. I missed this as I was looking at the v2.5.0 tag.

That raises a slight question for me. Discourse’s branches do not directly equate to discourse’s release tags. Most sites running older versions of discourse are probably on a branch, i.e. stable as opposed to a release, i.e. v2.5.0.

If a breaking change (for a plugin) is also added to an “older” branch, i.e. stable, what does this mean for the pins? I probably just don’t fully understand how the versioning system works.

6 إعجابات

Versions correspond to the discourse version defined in the app, not the git tag.

So “2.5.0” in this context will mean any version declared as 2.5.0, up to the new version bump of 2.5.1 (or 2.6.0.beta1).

If a breaking change (for a plugin) is also added to the stable branch, that would indeed also break the plugin. The entire point of versioning is so we can be relatively sure we aren’t introducing breaking changes to that version, so that’s something to scream about separately. Our intent for stable is to backport only security and critical fixes (and minor features, when appropriate).

The intent of this is so we have the ability to calculate older working versions of plugins against older working versions of Discourse. This is by no means a silver bullet, but it does give us a platform in which to do it, if we are actually careful about choosing what to backport.

7 إعجابات

This mostly works, unless you’re cloning with a --depth=1.

I’ll implement a hook to call a git fetch --depth 1 {upstream} commit soon if the target cannot be found initially, otherwise we’d be stuck doing a partial/full clone which is not ideal. We should be able to fetch what we need.

Edit: Updated here:

I’ve backported this one to Beta and Stable as well - so we should be able to pin even with shallow clones now.

6 إعجابات

أعتذر مقدماً، فأنا غالباً ما أعود إلى هذا في وقت متأخر من الليل، لذا لا أستطيع الجزم بنسبة 100% من دقة ما أقوله، وقد يكون هذا أمراً تدركونه بالفعل. أوثق ذلك هنا جزئياً من أجل هدوئي النفسي، إذ أنه أوقعني في حيرة عدة مرات حتى الآن.

أعتقد أن هذه الآلية غير قابلة للتطبيق فعلياً على فرع beta إذا كان الإضافة (plugin) يُستخدم على مثابة (instance) لا تملكها (أي أنها مفتوحة المصدر) ويتم تحديثها بالطريقة المعتادة. والطريقة المعتادة للتحديث هي أن يقوم مسؤول الموقع بذلك عند ظهور التنبيه في واجهة المسؤول.

وبالنظر إلى ما يلي:

وهذا أيضاً:

وأن tests-passed و beta لديهما نفس إصدار Discourse ولكن ليس نفس الكود، فمثلاً كلاهما حالياً 2.6.0.beta2:

ينتج عن ذلك:

  1. لدعم فرع beta، يجب تثبيت (pin) تعهد (commit) مرتبط بأحدث إصدار تجريبي، لأن هذا هو الإصدار الذي ستستخدمه المواقع على فرع beta.

  2. ومع ذلك، إذا كان أحدث إصدار تجريبي موجوداً في ملف التوافق، فإن المثيلات التي تعمل على tests-passed ستستخدم أيضاً التعهد المثبت.

وهذا يعني أنه لا يمكن دعم الاستخدام القياسي لكل من tests-passed و beta في وقت واحد في إضافة مفتوحة المصدر. وبما أن الغالبية العظمى من الأشخاص الذين يثبتون الإضافات يستخدمون tests-passed، فإنك عملياً لا تستطيع دعم beta عبر هذه الطريقة.

لاحظ أن هذا يعمل عملياً مع فرع stable، لأن stable لديه إصدار Discourse مختلف عن beta و tests-passed.

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، أي تعيين PRE إلى tests-passed في فرع tests-passed.

  • beta: 2.6.0.beta2، أي كما هو حاليًا.

@jomaxro هل يمكنك مساعدتي في فهم هذا الأمر؟

7 إعجابات

نعم، أنا مؤيد لإضافة علامة إضافية للإصدار ما قبل الاختبار (pre-beta) مقارنة بالإصدار الاختباري الفعلي (beta)، شريطة أن يكون ذلك سهلاً بما يكفي. سيساعد ذلك في حل المشكلة الأساسية هنا. لقد رأيت مشاريع برمجية أخرى ترقّي الإصدار إلى الإصدار التالي قبل إصداره (على سبيل المثال، بعد إصدار beta1، يتم ترقية “الإصدار” إلى beta2).

ومع ذلك، تقليدياً، لم يفعل Discourse ذلك، لذا يعتمد الأمر على أي طريقة أسهل للمشروع لاعتمادها.

6 إعجابات

لقد واجهت مشكلة أخرى.

هذا التغيير يُدخل $danger-low-mid في الإصدار 2.6.0beta2.

هذا كسر إضافة discourse-styleguide، لذا تم تحديثها وتم إدخال ملف .discourse-compatibility لـ إبقاء الإضافة عند الإصدار السابق لـ Discourse 2.5.0.

هذا يسبب مشكلة في Discourse 2.5.1. وبما أن هذا التغيير لن يتم نقله إلى الإصدارات المستقرة أبدًا، فسيكون من الضروري تحديث ملف discourse-compatibility مع كل إصدار مستقر جديد من 2.5.x.

سيكون من الضروري تحديث ملف discourse-compatibility الخاص بكل إضافة مع كل إصدار مستقر جديد من 2.5.x.

بدلاً من ذلك، يمكن أن يشير ملف التوافق إلى الإصدار 2.5.999، مما يبقي الإضافة فعليًا عند الإصدار 1f86468b2c81b40e97f1bcd16ea4bb780634e2c7 طوال عمر الإصدار 2.5.x. لكن هذا يبدو لي حلًا غير أنيق جدًا.

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 لن تُلمس وتبقى على أحدث إصدار. الإدخال الفارغ أو ~ يُقيّم إلى nil (عبر محلل YAML في Ruby)، مما يتجاوز دالة التثبيت.

8 إعجابات

هل يمكن استخدام إضافة قديمة على نسخة ذاتية الاستضافة؟

إعجاب واحد (1)

نعم، يمكنك تعديل أوامر git clone في ملف app.yml لاستنساخ الإصدار الذي تريده. لاحظ أنك بحاجة إلى التأكد من أن Discourse مثبت أيضًا على الإصدار الذي تريده. وقد تحتاج أيضًا إلى تثبيت discourse_docker على إصدار متوافق مع إصدار Discourse الذي تقوم بتشغيله. إذا كنت تقوم بكل ذلك، فمن الأسهل عدم الترقية.

إعجابَين (2)