Introduzione di .discourse-compatibility: versioni bloccate di plugin e temi per versioni più vecchie di Discourse

Ciao a tutti :wave:, ho appena unito una nuova funzionalità che aiuterà plugin e temi a fissare determinate versioni quando installati su istanze Discourse più vecchie.

Ora è possibile includere un file .discourse-compatibility nella radice di un repository di plugin o tema, che specifica quale versione controllare quando si esegue l’installazione su versioni precedenti di Discourse.


Motivazione

È frustrante dover ricordare quali plugin e temi sono compatibili con quali versioni di Discourse. Come amministratore, dovrebbe essere possibile scansionare facilmente queste modifiche e trovare una versione adatta alla propria installazione di Discourse senza dover leggere la cronologia dei commit del plugin. Come autore di plugin o temi, dovrebbe essere possibile gestire le versioni di installazione mentre si apportano modifiche incompatibili con le versioni precedenti, in modo da non rompere le installazioni esistenti.

Gli aggiornamenti del software Discourse vengono distribuiti piuttosto rapidamente, il che, sebbene sia fantastico, rende a volte molto difficile mantenere istanze di Discourse con molti plugin, specialmente se si seguono altri cicli di rilascio/versioni, come la versione stabile corrente. Il mio obiettivo qui è permettere un ecosistema che faciliti il processo di aggiornamento per chi segue la versione stabile o altri cicli di rilascio, offrendo agli amministratori dei siti un metodo per recuperare rapidamente e automaticamente qualsiasi versione del plugin fosse compatibile con la versione di Discourse che stanno utilizzando.

Annuncio originale (ora sostituito dalla documentazione collegata sopra)

Implementazione

La prima cosa da notare: ci basiamo sui tag del core di Discourse, dato che le versioni beta di Discourse vengono distribuite frequentemente enough da permetterci di fissare le versioni dei plugin contro di esse. Fissare le versioni contro hash git è un incubo per molte ragioni, quindi possiamo usare git describe per ottenere il tag beta/stabile più vicino.

In un plugin o in un tema, ora supportiamo un file di compatibilità delle versioni chiamato .discourse-compatibility nella radice. Questo file è un elenco ordinato in ordine decrescente (versioni più recenti di Discourse per prime) che specifica una mappa di compatibilità.

Esempio

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

Per ogni plugin/tema, un aggiornamento o una ricompilazione continuerà a controllare un commit/branch/tag nominato successivo fino a trovare uno uguale o successivo alla versione corrente di Discourse.
Ad esempio, per il file di versione sopra, se la versione corrente di Discourse fosse 2.4.6.beta12, scansionerebbe il file e sceglierebbe la voce per 2.5.0.beta2.

Se la versione corrente di Discourse fosse 2.4.4.beta6, sceglierebbe la voce corrispondente per 2.4.4.beta6.

Se non esiste una versione successiva, rimane sulla versione attualmente controllata.
Ad esempio, per 2.5.0.beta3 non avverrebbe alcun fissaggio.

Se non esiste una versione precedente, viene controllata la prima elencata nel file delle versioni.
Ad esempio, per 2.2.1.beta22 verrebbe controllata la versione più antica disponibile, ovvero la voce per 2.4.2.beta1.


L’obiettivo qui è ridurre il disagio nel mantenere distribuzioni alternative che non siano strettamente basate su “tests-passed” in futuro, offrendo flessibilità agli amministratori su quando e dove eseguire l’aggiornamento. Lo stiamo facendo consentendo agli autori di plugin e temi di sviluppare modifiche incompatibili con le versioni precedenti senza influenzare le installazioni su versioni più vecchie di Discourse.

50 Mi Piace

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 Mi Piace

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 Mi Piace

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 Mi Piace

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 Mi Piace

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 Mi Piace

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 Mi Piace

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 Mi Piace

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 Mi Piace

Apologies in advance as I often turn to this somewhat late in the night, so I’m not sure if I’m 100% correct here, and this may well be something you’re aware of already. I’m memorialising it here partly for my own sanity as it’s tripped me up a couple of times now.

I think this mechanism is effectively unworkable for the beta branch if the plugin is used on an instance you don’t control (i.e. it’s open source) that’s being updated in the common fashion. The common fashion of updating is for the site admin to do so when prompted in the admin UI.

Given this

And this

And that tests-passed and beta have the same Discourse version but not the same code, e.g. both are currently 2.6.0.beta2:

This follows:

  1. To support the beta branch you need to pin a commit to the latest beta release, as that is the one sites on the beta branch will be using.

  2. However, if the latest beta release is in the compatability file, instances running tests-passed will also use the pinned commit.

This means you can’t support the standard usage of both tests-passed and beta at the same time in an open souce plugin. Given that the majority of people who install plugins are on tests-passed, you effectively can’t support beta via this method.

Note that it does work in practice for the stable branch as stable has a different Discourse version from beta and tests-passed.

4 Mi Piace

Oh yeah, this is something I am aware of, it doesn’t truly resolve until the next beta cut. We should definitely also find a way to distinguish between beta and tests passed versions for this.

When I was setting the feature up, I had stable and weird forks in mind, and didn’t see until later that latest and stable were sharing versions.

6 Mi Piace

Thanks for confirming.

It seems the effective upshot is that the process should not run if the instance is running tests-passed. You can’t include the tests-passed version in the file for the reasons described above, so precluding the process on tests-passed would not change its current behaviour.

One way to implement this would be

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

   ...
end

This would make it possible to use the latest beta version in the file for the purposes of pinning plugins for sites running beta. And you could continue supporting tests-passed in the latest commit of the plugin, i.e. without using this file. If you’re on board with that I can make a PR.

Alternatively, I feel like the underlying issue here is this

I’m sure this is something you guys have discussed before, but would it be possible to do something like

  • tests-passed: 2.6.0.tests-passed, i.e. set the PRE as tests-passed on the tests-passed branch.

  • beta: 2.6.0.beta2, i.e. as it is currently

@jomaxro Could you help me understand this one?

7 Mi Piace

yeah, I am in favor of adding an additional marking for pre-beta release vs an actual beta release, provided it’s easy enough to do. That would help resolve the underlying issue here. I’ve seen other software projects bump the version to the next version before its release (eg after releasing beta1, bump the “version” to beta2).

Traditionally though Discourse has not done that so it depends on what method is easiest for the project to adopt.

6 Mi Piace

I ran into another issue.

This change introduces $danger-low-mid in 2.6.0beta2.

This broke the discourse-styleguide plugin so that was updated and a .discourse-compatibility file was introduced to keep the plugin at the previous commit for Discourse 2.5.0.

This breaks on Discourse 2.5.1. Since this change will never be backported to stable, the discourse-compatibility file would need to be updated on every new 2.5.x stable version.

Every discourse-compatibility file of every plugin would need to be updated on every new 2.5.x stable version.

Alternatively, the compatibility file could refer to version 2.5.999, effectively keeping the plugin at commit 1f86468b2c81b40e97f1bcd16ea4bb780634e2c7 for the entire 2.5.x lifetime. But this seems very hacky to me.

6 Mi Piace

It sounds like it would be safe to pin to 2.6.0beta1, which would also be more correct as the issue was introduced in beta2, does that make sense? That would also then cover all 2.5 versions.

Your “hacky” solution of 2.5.999 sounds like it would be a neat workaround (although messy as you mentioned). I tried to keep pinning here as simple as I could, but you’re right in that we are still lacking a true way of stating 2.5.x as a valid pinned version.

Another workaround for that particular case for pinning last stable, but keeping next version’s betas unpinned that might be a bit more subtle but feels much less hacky to me for pinning the entire 2.5.x branch is make the pin at 2.6.0.beta0, or 2.6.0.alpha1 which semantically is pin everything before and up-to the version between 2.5.x and 2.6.0.beta1. This means:

Everything 2.5.x is covered by the pin.
Everything 2.6.0.beta(1+) still remains unpinned.

6 Mi Piace

Yes, that feels less hacky for me too.

But both the 2.5.999 and 2.6.0beta0 solutions do not cover a similar case: what if an issue would be introduced in 2.6.0beta3, and it is backported to 2.5.2 ?

More edge cases, more hidden features: You can actually “unset” the pin with a blank entry:

With a compatibility file of:

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

Anything between 2.4.2.beta1 to 2.4.4.beta6 will not be pinned back. Anything after up to 2.6.0.beta1 will e pinned. After that will be unpinned again.

Underneath, anything that evaluates to a nil value will be untouched/be on latest. A blank entry or ~ evaluates to nil (via ruby’s yaml parser), which bypasses the pinning function.

8 Mi Piace

È possibile utilizzare un plugin di vecchia versione su un’istanza self-hosted?

1 Mi Piace

Sì, puoi modificare i comandi git clone nel tuo file app.yml per clonare la versione desiderata. Tieni presente che devi assicurarti che anche Discourse sia bloccato alla versione desiderata. Potrebbe anche essere necessario bloccare discourse_docker a una versione compatibile con la versione di Discourse in esecuzione. Se stai facendo tutto questo, è più facile non aggiornare.

2 Mi Piace