Apresentando .discourse-compatibility: versões fixas de plugins/temas para versões mais antigas do Discourse

Olá, pessoal :wave:, acabei de mesclar um novo recurso que ajudará plugins e temas a fixar versões específicas quando instalados em instâncias mais antigas do Discourse.

Agora é possível incluir um arquivo .discourse-compatibility na raiz de um repositório de plugin ou tema, que define qual versão deve ser verificada ao instalar em versões mais antigas do Discourse.


Fundamentação

É chato ter que lembrar quais plugins e temas são compatíveis com quais versões do Discourse. Como administrador, deve ser possível examinar facilmente essas alterações e encontrar uma versão adequada para sua instalação do Discourse, sem precisar ler o histórico de commits do plugin. Como autor de plugin ou tema, deve ser possível gerenciar as versões de instalação enquanto se fazem alterações incompatíveis com versões anteriores, para não quebrar instalações mais antigas.

As atualizações do software Discourse são lançadas com bastante rapidez, o que, embora seja incrível, torna a manutenção de instâncias do Discourse com muitos plugins às vezes muito difícil, especialmente se você estiver seguindo outros ciclos de lançamento/versões, como a versão estável atual. Meu plano aqui é permitir um ecossistema que facilite o processo de atualização para quem segue a versão estável ou outro ciclo de lançamento, e oferecer aos administradores do site um método para buscar rápida e automaticamente qualquer versão de plugin compatível com a versão do Discourse que estão mirando.

Anúncio Original (agora substituído pela documentação vinculada acima)

Implementação

Primeira coisa a notar: estamos dependendo das tags do núcleo do Discourse aqui, já que as versões beta do Discourse são lançadas com frequência suficiente para que possamos fixar versões de plugins contra elas. Fixar versões contra hashes do git é um pesadelo por muitos motivos, então podemos usar o git describe para obter a tag beta/estável mais próxima.

Em um plugin ou tema, agora suportamos um arquivo de compatibilidade de versão chamado .discourse-compatibility na raiz. Este arquivo é uma lista ordenada decrescente (versões mais recentes do Discourse primeiro) que especifica um mapa de compatibilidade.

Exemplo

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

Para cada plugin/tema, uma atualização ou reconstrução continuará a verificar um commit/branch/tag nomeado posterior até encontrar um que seja igual ou posterior à versão atual do Discourse.
Por exemplo, para o arquivo de versão acima, se a versão atual do Discourse for 2.4.6.beta12, ele irá escanear o arquivo e escolher a entrada para 2.5.0.beta2.

Se a versão atual do Discourse for 2.4.4.beta6, ele escolherá a entrada correspondente para 2.4.4.beta6.

Se não houver versão posterior, ele permanece na versão atual verificada.
Por exemplo, para 2.5.0.beta3, nenhuma fixação ocorreria.

Se não houver versão anterior, ele verifica a mais antiga listada no arquivo de versão.
Por exemplo, para 2.2.1.beta22, ele verificaria a mais antiga possível dada a “versão”, a entrada para 2.4.2.beta1.


O objetivo aqui é aliviar a dor de manter implantações alternativas que não estejam estritamente em “tests-passed” no futuro e oferecer flexibilidade aos administradores sobre quando e onde atualizar. Estamos fazendo isso permitindo que autores de plugins e temas tenham uma maneira de desenvolver alterações incompatíveis com versões anteriores sem afetar instalações em versões mais antigas do Discourse.

50 curtidas

Essa é uma ótima funcionalidade, obrigado :slight_smile:

Gosto da direcionalidade disso, ou seja, torna possível gerenciar essa questão dentro do próprio plugin, sem exigir que o administrador do site faça nada propriamente dito.

Tenho algumas perguntas iniciais:

  • A verificação de metadados do plugin required_version existente na ativação do plugin permanecerá? E como você vê a relação disso com isso (se houver)?

  • Vejo que isso foi adicionado na forma de uma tarefa rake no momento. Como isso se relaciona, qual é o uso pretendido, com o discourse_docker (ou seja, o launcher) e o docker_manager? Vejo que você fez adições em ambos os repositórios, mas poderia explicar como deve funcionar em ambos os ambientes?

12 curtidas

Sim, essa é a ideia: permitir que os autores de plugins adicionem compatibilidade retroativa, para que os administradores não precisem se preocupar!

Atualmente, não há planos de alterar ou remover os metadados do plugin required_version. Eles estão relacionados, mas ainda são conceitos separados para mim: o required_version com min/max e ponto final impede a instalação do plugin lançando um erro e é carregado após essa verificação de compatibilidade. Se você quiser impedir que instâncias antigas do Discourse usem seu plugin, ainda é uma boa ideia incluir o required_version para a primeira versão mínima — nenhuma quantidade de busca por compatibilidade vai resolver isso :wink:

Não devem ser necessárias alterações de configuração no uso normal; as mudanças serão detectadas automaticamente. A tarefa rake é chamada no discourse_docker após a clonagem dos plugins, então, na ordem do launcher:

  • Plugins clonados
  • Verificação de compatibilidade e checkout (se aplicável)
  • Migração

No docker_manager, versões mais antigas do Discourse poderão atualizar os plugins para uma versão compatível. A interface aqui permanece a mesma, mas um plugin “atualizado” agora é definido de acordo com o arquivo de compatibilidade.

Resumindo: não são necessárias alterações em nenhum dos casos de uso para começar a aproveitar isso, caso seja essa a sua dúvida.

Por baixo dos panos, ele usa git show HEAD@{upstream}:.discourse-compatibility para ler o arquivo mais recente e git reset --hard #{checkout_version} para fazer o checkout da versão correta. Dessa forma, conseguimos usar a compatibilidade mais recente, e versões antigas do Discourse não ficarão presas a um arquivo de compatibilidade antigo (possivelmente inválido).

11 curtidas

Legal, obrigado por explicar isso.

Então, servi-me uma taça de :wine_glass: e tentei com o Plugin Custom Wizard.

Verifiquei cada tag uma por uma, em ordem reversa, começando por v2.6.0.beta1. Encontrei esses comandos do git úteis:

git tag --list \\ por exemplo, git tag --list 'v2.5.0*'
git checkout tags/tag \\ por exemplo, git checkout tags/v2.5.0.beta7

Não demorou muito para encontrar uma tag que não funcionava com a versão atual do plugin: v2.5.0.beta7 não inclui discourse/app/components/d-textarea, que o Custom Wizard tenta importar.

Então, encontrei o commit no plugin que adicionou essa importação, peguei o sha1 do commit anterior, fiz o checkout e testei (funcionou perfeitamente), e adicionei isso ao arquivo .discourse-compatibility:

v2.5.0.beta7: 802d74bab2ebe19a106f75275342dc2e9cc6066a

Em seguida, fiz push para uma branch com o código mais recente do plugin (uma branch para testes, não necessária normalmente), e reconstruí um servidor de teste Dockerizado com essa branch do plugin e a version definida como v2.5.0.beta7.

Isso não funcionou. Depois, percebi que, claro, a tarefa rake plugin:pull_compatible_all não existe em v2.5.0.beta7, então isso não vai funcionar retroativamente (culpo o :wine_glass:). Com certeza, nos logs do launcher vejo:

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

Mas é essa a ideia geral de como você imagina que isso seja usado?

Quanto ao required_version, encontrei isso aqui, pois o servidor de teste tinha o plugin discourse-legal-tools instalado, que possui um required_version de v2.5.0, então inicialmente falhou em v2.5.0.beta7. Acredito que vou transferir esse plugin para este novo sistema. Ainda vejo o required_version como útil para definir uma base absoluta, como você disse.

11 curtidas

Sim, isso está correto — como isso não foi incorporado ao núcleo do Discourse até agora, não funcionará em versões anteriores à 2.6.0.beta1 (atualmente). Ainda preciso portar isso para a versão estável e a última beta, para que possamos usá-lo na 2.5.0, mas não temos planos de portar para versões anteriores a essa.

11 curtidas

Apenas um acompanhamento aqui: agora adicionei um arquivo de compatibilidade ao Custom Wizard master e o usarei para travar o plugin, incluindo v2.6.0.beta1 (beta atual) e v2.5.0 (estável atual) quando isso for portado. Veja mais:

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

5 curtidas

Uma observação e uma pergunta aqui.

Primeiramente, apenas uma nota: a sintaxe para as tags de versão do Discourse no arquivo .discourse-compatibility é 2.5.0 (como no exemplo de @featheredtoast), e não v2.5.0. Se você incluir um v, receberá este erro:

Malformed version number string v2.6.0.beta1

Em segundo lugar, @featheredtoast apontou que o sistema de fixação (pinning) agora foi portado de volta para a branch stable. Eu perdi essa informação porque estava olhando para a tag v2.5.0.

Isso levanta uma pequena dúvida para mim. As branches do Discourse não correspondem diretamente às tags de versão do Discourse. A maioria dos sites que executam versões mais antigas do Discourse provavelmente está em uma branch, ou seja, stable, e não em uma versão lançada, ou seja, v2.5.0.

Se uma mudança disruptiva (para um plugin) também for adicionada a uma branch “mais antiga”, ou seja, stable, o que isso significa para as fixações (pins)? Provavelmente eu apenas não entendo completamente como o sistema de versionamento funciona.

6 curtidas

As versões correspondem à versão do Discourse definida no aplicativo, e não à tag do git.

Portanto, “2.5.0” neste contexto significa qualquer versão declarada como 2.5.0, até o novo incremento de versão para 2.5.1 (ou 2.6.0.beta1).

Se uma mudança que quebra a compatibilidade (para um plugin) também for adicionada à branch estável, isso de fato quebraria o plugin também. Todo o ponto da versionização é garantir que não estejamos introduzindo mudanças que quebrem a compatibilidade nessa versão, então isso é algo para ser discutido separadamente. Nossa intenção para a versão estável é fazer o backport apenas de correções de segurança e críticas (e de recursos menores, quando apropriado).

A intenção disso é termos a capacidade de calcular versões antigas funcionais de plugins em relação a versões antigas funcionais do Discourse. Isso não é de forma alguma uma solução mágica, mas nos oferece uma plataforma para fazê-lo, desde que sejamos realmente cuidadosos ao escolher o que fazer o backport.

7 curtidas

Isso funciona na maioria dos casos, a menos que você esteja clonando com --depth=1.

Em breve, implementarei um hook para chamar git fetch --depth 1 {upstream} commit se o destino não for encontrado inicialmente; caso contrário, ficaríamos presos fazendo uma clonagem parcial ou completa, o que não é ideal. Devemos ser capazes de buscar o que precisamos.

Edição: Atualizado aqui:

Também fiz o backport disso para as versões Beta e Stable, então agora deveremos ser capazes de fixar até mesmo com clonagens rasas.

6 curtidas

Peço desculpas antecipadamente, pois frequentemente recorro a isso um pouco tarde da noite, então não tenho certeza se estou 100% correto aqui, e isso pode muito bem ser algo que você já saiba. Estou registrando isso aqui em parte para minha própria sanidade, já que isso me confundiu algumas vezes agora.

Acredito que esse mecanismo seja efetivamente inviável para a branch beta se o plugin for usado em uma instância que você não controla (ou seja, é de código aberto) que está sendo atualizada da maneira comum. A maneira comum de atualizar é quando o administrador do site o faz quando solicitado na interface de administração.

Dado isso

E isso

E que tests-passed e beta têm a mesma versão do Discourse, mas não o mesmo código, por exemplo, ambos estão atualmente 2.6.0.beta2:

Isso resulta em:

  1. Para dar suporte à branch beta, você precisa fixar um commit na última versão beta, pois é essa que os sites na branch beta estarão usando.

  2. No entanto, se a última versão beta estiver no arquivo de compatibilidade, as instâncias executando tests-passed também usarão o commit fixado.

Isso significa que você não pode dar suporte ao uso padrão de tests-passed e beta ao mesmo tempo em um plugin de código aberto. Considerando que a maioria das pessoas que instalam plugins está em tests-passed, você efetivamente não pode dar suporte a beta por meio desse método.

Observe que isso funciona na prática para a branch stable, pois a stable tem uma versão do Discourse diferente de beta e tests-passed.

4 curtidas

É, eu já sabia disso, mas só será resolvido de verdade na próxima versão beta. Definitivamente, também devemos encontrar uma maneira de distinguir entre as versões beta e as versões em que os testes foram aprovados para isso.

Quando configurei o recurso, eu tinha em mente forks estáveis e problemáticos, e só percebi mais tarde que as versões mais recentes e as estáveis estavam compartilhando as mesmas versões.

6 curtidas

Obrigado pela confirmação.

Parece que a conclusão prática é que o processo não deve ser executado se a instância estiver rodando tests-passed. Você não pode incluir a versão tests-passed no arquivo pelos motivos descritos acima, então impedir o processo em tests-passed não alteraria seu comportamento atual.

Uma maneira de implementar isso seria

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

   ...
end

Isso tornaria possível usar a versão beta mais recente no arquivo para fins de fixar plugins em sites que estão rodando beta. E você poderia continuar dando suporte a tests-passed no último commit do plugin, ou seja, sem usar este arquivo. Se você estiver de acordo com isso, posso abrir um PR.

Alternativamente, sinto que a questão subjacente aqui é esta

Tenho certeza de que vocês já discutiram isso antes, mas seria possível fazer algo como

  • tests-passed: 2.6.0.tests-passed, ou seja, definir o PRE como tests-passed na branch tests-passed.

  • beta: 2.6.0.beta2, ou seja, como está atualmente

@jomaxro, você poderia me ajudar a entender isso?

7 curtidas

Sim, sou a favor de adicionar uma marcação adicional para a versão pré-beta em comparação com uma versão beta real, desde que seja fácil o suficiente de fazer. Isso ajudaria a resolver o problema subjacente aqui. Já vi outros projetos de software atualizar a versão para a próxima versão antes do lançamento (por exemplo, após lançar a beta1, atualizar a “versão” para beta2).

Tradicionalmente, no entanto, o Discourse não fez isso, então depende de qual método seja mais fácil de adotar para o projeto.

6 curtidas

Encontrei outro problema.

Esta alteração introduz $danger-low-mid na versão 2.6.0beta2.

Isso quebrou o plugin discourse-styleguide, que foi atualizado, e um arquivo .discourse-compatibility foi criado para manter o plugin no commit anterior para o Discourse 2.5.0.

Isso causa falhas no Discourse 2.5.1. Como essa alteração nunca será portada de volta para a versão estável, o arquivo discourse-compatibility precisaria ser atualizado a cada nova versão estável 2.5.x.

Cada arquivo discourse-compatibility de cada plugin precisaria ser atualizado a cada nova versão estável 2.5.x.

Alternativamente, o arquivo de compatibilidade poderia referenciar a versão 2.5.999, mantendo efetivamente o plugin no commit 1f86468b2c81b40e97f1bcd16ea4bb780634e2c7 durante toda a vida útil da série 2.5.x. Mas isso me parece muito hacky.

6 curtidas

Parece seguro travar na versão 2.6.0beta1, o que também seria mais correto, já que o problema foi introduzido na beta2. Faz sentido? Isso também cobriria todas as versões 2.5.

Sua solução “gambiarra” de 2.5.999 parece ser uma boa alternativa (embora bagunçada, como você mencionou). Tentei manter o travamento aqui o mais simples possível, mas você tem razão ao dizer que ainda não temos uma maneira real de declarar 2.5.x como uma versão travada válida.

Outra alternativa para esse caso específico de travar na última versão estável, mas mantendo as betas da próxima versão sem travamento, que pode ser um pouco mais sutil, mas me parece muito menos gambiarra para travar toda a branch 2.5.x, é fazer o travamento em 2.6.0.beta0 ou 2.6.0.alpha1, o que semanticamente significa travar tudo antes e até a versão entre 2.5.x e 2.6.0.beta1. Isso significa:

Tudo da versão 2.5.x está coberto pelo travamento.
Tudo da versão 2.6.0.beta(1+) continua sem travamento.

6 curtidas

Sim, para mim também parece menos hacky.

Mas tanto a solução 2.5.999 quanto a 2.6.0beta0 não cobrem um caso semelhante: e se um problema fosse introduzido na 2.6.0beta3 e fosse backportado para a 2.5.2?

Mais casos extremos, mais recursos ocultos: você pode realmente “desfazer” o pin com uma entrada em branco:

Com um arquivo de compatibilidade de:

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

Qualquer versão entre 2.4.2.beta1 e 2.4.4.beta6 não será fixada (pinned). Qualquer versão posterior, até 2.6.0.beta1, será fixada. Após isso, será desfixada novamente.

Internamente, qualquer coisa que seja avaliada como nil permanecerá inalterada ou na versão mais recente. Uma entrada em branco ou ~ é avaliada como nil (por meio do analisador YAML do Ruby), o que contorna a função de fixação.

8 curtidas

É possível usar um plugin de versão antiga em uma instância auto-hospedada?

1 curtida

Sim, você pode ajustar os comandos git clone em seu app.yml para clonar a versão que desejar. Observe que você precisa garantir que o Discourse também esteja fixado na versão desejada. E você também pode precisar fixar o discourse_docker a uma versão compatível com a versão do Discourse que você está executando. Se você estiver fazendo tudo isso, é mais fácil não atualizar.

2 curtidas