여러분 안녕하세요 , 오래된 Discourse 인스턴스에 설치할 때 플러그인과 테마가 특정 버전을 고정(pinning)할 수 있도록 도와주는 새로운 기능을 병합했습니다.
이제 플러그인 또는 테마 저장소의 루트 디렉토리에 .discourse-compatibility 파일을 포함할 수 있으며, 이 파일은 오래된 Discourse 버전으로 설치할 때 어떤 버전을 체크아웃할지 지정합니다.
배경
어떤 플러그인과 테마가 어떤 Discourse 버전과 호환되는지 기억하는 것은 번거로운 일입니다. 관리자라면 플러그인 커밋 히스토리를 읽지 않고도 자신의 Discourse 설치에 적합한 버전을 쉽게 찾아낼 수 있어야 합니다. 또한 플러그인 또는 테마 개발자라면 후방 호환성이 깨지는 변경사항을 적용하면서도 오래된 설치 환경이 깨지지 않도록 설치 버전을 관리할 수 있어야 합니다.
Discourse 소프트웨어 업데이트는 매우 빠르게 배포되는데, 이는 훌륭한 점이지만 많은 플러그인을 사용하는 Discourse 인스턴스를 유지보수하는 것을 때로는 매우 어렵게 만듭니다. 특히 현재 안정판(stable)과 같은 다른 릴리스 주기/버션을 따르는 경우 더욱 그렇습니다. 여기서 제 계획은 안정판이나 다른 릴리스 주기를 따르는 사용자를 위한 업데이트 프로세스를 원활하게 하는 생태계를 조성하고, 해당 사이트 관리자가 대상 Discourse 버전과 호환되는 플러그인 버전을 빠르고 자동으로 가져올 수 있는 방법을 제공하는 것입니다.
원본 공지 (위에서 링크한 문서로 대체됨)
구현
먼저 주목할 점은: 여기서는 Discourse 코어의 태그에 의존합니다. Discourse의 베타 버전이 충분히 자주 배포되므로, 이를 기준으로 플러그인 버전을 고정할 수 있기 때문입니다. git 해시로 고정하는 것은 여러 가지 이유로 악몽과도 같으므로, 가장 가까운 베타/안정 태그를 가져오기 위해 git describe를 사용합니다.
플러그인 또는 테마의 루트 디렉토리에 .discourse-compatibility이라는 이름의 버전 호환성 파일을 지원합니다. 이 파일은 호환성 매핑을 지정하는 내림차순(새로운 Discourse 버전이 먼저)으로 정렬된 목록입니다.
각 플러그인/테마에 대해 업그레이드 또는 재빌드 시, 현재 Discourse 버전과 같거나 그보다 최신인 항목을 찾을 때까지 더 늦은 날짜의 이름이 붙은 커밋/브랜치/태그를 체크아웃하는 것이 계속됩니다.
예를 들어, 위의 버전 파일에서 현재 Discourse 버전이 2.4.6.beta12라면 파일을 스캔하여 2.5.0.beta2 항목을 선택합니다.
현재 Discourse 버전이 2.4.4.beta6라면 2.4.4.beta6에 해당하는 항목을 선택합니다.
더 최신 버전이 없으면 현재 체크아웃된 버전을 유지합니다.
예를 들어, 2.5.0.beta3의 경우 고정(pinning)이 발생하지 않습니다.
더 이전 버전이 없으면 버전 파일에 나열된 가장 오래된 버전을 체크아웃합니다.
예를 들어, 2.2.1.beta22의 경우 주어진 “버전” 중 가장 오래된 항목인 2.4.2.beta1 항목을 체크아웃합니다.
여기서의 목표는 향후 테스트 통과(tests-passed) 상태가 아닌 대체 배포 환경을 유지보수하는 어려움을 줄이고, 언제 어디서 업그레이드할지에 대해 관리자에게 유연성을 제공하는 것입니다. 이를 위해 플러그인과 테마 개발자가 후방 호환성이 깨지는 변경사항을 개발하더라도 오래된 버전의 Discourse 설치 환경에 영향을 주지 않도록 하는 방법을 제공합니다.
이 방향성이 좋습니다. 즉, 사이트 관리자가 별도로 조치를 취할 필요가 없이 플러그인 자체에서 이 문제를 관리할 수 있게 해줍니다.
초기 질문이 몇 가지 있습니다:
플러그인 활성화 시 기존에 존재하는 required_version 플러그인 메타데이터 체크는 유지됩니까? 그리고 이것이 이 기능과 어떻게 상호작용할 것으로 보시나요(아예 관련이 없다면)?
현재 이 기능이 rake 태스크 형태로 추가되어 있는 것을 확인했습니다. 이것이 discourse_docker(즉, launcher)와 docker_manager와 어떤 관련이 있으며, 의도된 사용 방법은 무엇인가요? 두 저장소 모두에 추가 사항을 넣으셨는데, 두 환경에서 어떻게 동작하도록 의도된 것인지 설명해 주실 수 있을까요?
네, 바로 그겁니다. 플러그인 개발자가 후방 호환성을 추가할 수 있도록 해서 관리자가 신경 쓸 일이 없게 하자는 거죠!
현재 required_version 플러그인 메타데이터를 변경하거나 제거할 계획은 없습니다. 두 가지는 관련이 있지만 제 생각에는 여전히 별개의 개념입니다. required_version의 min/max는 플러그인 설치를 에러를 통해 완전히 차단하며, 이 호환성 확인 단계 이후에 로드됩니다. 만약 아주 오래된 Discourse 인스턴스가 플러그인을 사용하는 것을 막고 싶다면, 첫 번째 최소 버전의 required_version을 포함하는 것이 여전히 좋은 방법이라고 생각합니다. 아무리 호환성을 찾아봐도 그 문제를 해결할 수는 없으니까요
일반적인 사용 환경에서는 설정 변경이 필요하지 않으며, 변경 사항이 자동으로 적용됩니다. rake 태스크는 플러그인이 클론된 후 discourse_docker에서 호출되므로, 런처의 순서는 다음과 같습니다:
플러그인 클론
호환성 확인 및 체크아웃 (해당하는 경우)
migrate
docker_manager에서는 이전 버전의 Discourse도 플러그인을 호환 가능한 버전으로 업데이트할 수 있습니다. 여기서는 UI가 동일하게 유지되지만, "최신 상태(up to date)"인 플러그인은 이제 호환성 파일에 따라 결정됩니다.
요약하자면, 이것이 궁금하신 거라면 두 가지 사용 사례 모두 이 기능을 활용하기 시작하는 데 필요한 변경 사항은 없습니다.
내부적으로는 git show HEAD@{upstream}:.discourse-compatibility를 사용하여 최신 파일을 읽고, git reset --hard #{checkout_version}를 사용하여 올바른 버전으로 체크아웃합니다. 이렇게 하면 최신 호환성 정보를 사용할 수 있고, 오래된 Discourse 버전이 오래된(혹은 잘못된) 호환성 파일에 묶여 있게 되는 것을 방지할 수 있습니다.
그 다음, 이를 최신 플러그인 코드가 포함된 브랜치(일반적으로는 필요 없는 테스트용 브랜치)에 푸시하고, 해당 플러그인 브랜치와 version이 v2.5.0.beta7으로 설정된 Docker화 테스트 서버를 다시 구축했습니다.
하지만 작동하지 않았습니다. 그때 문득, v2.5.0.beta7에는 plugin:pull_compatible_all rake 태스크가 존재하지 않는다는 것이 떠올랐고, 따라서 이는 사후적으로 적용되는 것이 불가능하다는 것을 깨달았습니다(이건 탓이겠죠). 역시나 런처 로그에서 다음 내용을 확인할 수 있었습니다:
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_version이 v2.5.0이었기 때문에 v2.5.0.beta7에서 처음 실패했습니다. 이 플러그인을 이 새로운 시스템으로 이전할 것 같습니다. 말씀하신 대로 절대적인 기준선(baseline)을 설정하는 용도로 required_version이 여전히 유용할 수 있다고 생각합니다.
네, 맞습니다. 이 기능이 아직 Discourse 코어에 포함되지 않았기 때문에, 현재로서는 2.6.0.beta1보다 오래된 버전에서는 작동하지 않습니다. 저는 이 기능을 안정판과 최신 베타판으로 이식해야 하므로, 2.5.0에서는 사용할 수 있게 되겠지만, 그 이전 버전으로는 이식할 계획이 없습니다.
여기서 후속 보고를 드리자면, 이제 Custom Wizard 마스터 브랜치에 호환성 파일을 추가했으며, 이 기능이 이식될 때 해당 파일을 사용하여 플러그인을 고정할 예정입니다. 고정 대상 버전에는 v2.6.0.beta1(현재 베타 버전)과 v2.5.0(현재 안정 버전)이 포함됩니다. 자세한 내용은 아래 링크를 참고하세요:
첫째, .discourse-compatibility 파일에서 discourse 버전 태그의 문법은 2.5.0(@featheredtoast의 예시와 같이)이며, v2.5.0이 아닙니다. v를 사용하면 다음과 같은 오류가 발생합니다:
Malformed version number string v2.6.0.beta1
둘째, @featheredtoast 님이 지적하신 대로 고정(pinning) 시스템이 이제 stable로 백포트되었습니다. 저는 v2.5.0 태그를 보고 있었기 때문에 이 사실을 놓쳤습니다.
이로 인해 저에게는 약간의 질문이 생겼습니다. Discourse의 브랜치는 discourse의 릴리스 태그와 직접적으로 대응되지 않습니다. 구버전의 discourse를 실행 중인 대부분의 사이트는 릴리스(예: v2.5.0)가 아니라 브랜치, 즉 stable을 사용하고 있을 가능성이 높습니다.
플러그인에 대한 호환성 깨짐(breaking change)이 stable과 같은 “구버전” 브랜치에도 추가된다면, 이는 고정(pins)에 어떤 의미를 갖습니까? 아마도 버전 관리 시스템이 어떻게 작동하는지를 완전히 이해하지 못하고 있는 것 같습니다.
따라서 이 맥락에서 "2.5.0"은 2.5.1(또는 2.6.0.beta1)로 버전이 올라갈 때까지 2.5.0으로 선언된 모든 버전을 의미합니다.
(플러그인에 대한) 호환성 깨짐(breaking change)이 stable 브랜치에도 추가된다면, 실제로 플러그기가 깨질 것입니다. 버전 관리의 핵심은 해당 버전에서 호환성 깨짐을 도입하지 않고 있다는 것을 비교적 확신할 수 있게 하기 위함이며, 이는 별도로 강력히 항의해야 할 사항입니다. stable 브랜치의 의도는 보안 및 치명적 오류 수정(적절할 경우 소규모 기능 추가)만 백포트하는 것입니다.
이것의 의도는 플러그인의 이전 정상 작동 버전을 Discourse의 이전 정상 작동 버전과 대조하여 계산할 수 있는 능력을 갖추기 위함입니다. 이는 만능 해결책은 아니지만, 백포트할 내용을 신중하게 선택한다면 이를 수행할 수 있는 기반을 제공해 줍니다.
beta 브랜치를 지원하려면, beta 브랜치를 사용하는 사이트들이 사용할 것이므로 최신 beta 릴리스의 커밋을 고정(pin)해야 합니다.
그러나 최신 beta 릴리스가 호환성 파일에 있는 경우, tests-passed를 실행하는 인스턴스도 고정된 커밋을 사용하게 됩니다.
이는 오픈 소스 플러그인에서 tests-passed와 beta의 표준 사용법을 동시에 지원하지 못한다는 것을 의미합니다. 플러그인을 설치하는 대부분의 사용자가 tests-passed를 사용한다는 점을 고려하면, 이 방법을 통해 beta를 효과적으로 지원하지 못하는 셈입니다.
참고로 stable 브랜치의 경우 실제로는 작동합니다. stable은 beta와 tests-passed와 다른 Discourse 버전을 가지기 때문입니다.
효과적으로 결론은 인스턴스가 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 브랜치에서 PRE를 tests-passed로 설정.
네, 실제 베타 릴리스와 사전 베타 릴리스를 구분하기 위해 추가 마킹을 도입하는 데 찬성합니다. 단, 구현이 충분히 용이할 경우 말입니다. 이렇게 하면 여기서 근본적인 문제를 해결하는 데 도움이 될 것입니다. 다른 소프트웨어 프로젝트에서는 릴리스 전에 버전을 다음 버전으로 올리는 방식을 사용하는 것을 본 적이 있습니다(예: beta1을 릴리스한 후 "version"을 beta2로 올리는 방식).
다만 전통적으로 Discourse는 그렇게 하지 않았기 때문에, 프로젝트가 채택하기 가장 쉬운 방식이 무엇인지에 따라 달라집니다.
이것이 Discourse 2.5.1에서 깨집니다. 이 변경 사항은 안정판(stable)으로 백포트되지 않을 것이므로, discourse-compatibility 파일은 모든 새로운 2.5.x 안정판 버전마다 업데이트되어야 합니다.
모든 플러그인의 모든 discourse-compatibility 파일이 모든 새로운 2.5.x 안정판 버전마다 업데이트되어야 합니다.
대안으로, 호환성 파일이 버전 2.5.999를 참조하도록 하여, 2.5.x의 전체 수명 동안 플러그인을 커밋 1f86468b2c81b40e97f1bcd16ea4bb780634e2c7에 유지할 수 있습니다. 하지만 제 생각에는 이 방식이 매우 해킹(hacky)처럼 보입니다.
2.6.0beta1로 고정(pinning)하는 것이 안전할 것 같고, 문제가 beta2에서 도입되었으므로 더 정확할 것 같습니다. 이 말이 맞나요? 이렇게 하면 2.5 버전 전체도 커버됩니다.
제안하신 2.5.999라는 “해킹적인(hacky)” 해결책은 깔끔한 우회책이 될 것 같습니다(물론 언급하셨듯이 다소 지저분하긴 하겠지만). 저는 여기서 고정 방식을 최대한 단순하게 유지하려 했었습니다. 하지만 말씀하신 대로 2.5.x를 유효한 고정 버전으로 명시하는 진정한 방법은 여전히 부족합니다.
마지막 안정판(stable)을 고정하되 다음 버전의 베타는 고정하지 않는 그 특정 사례에 대한 또 다른 우회책이 있습니다. 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+) 버전은 여전히 고정되지 않은 상태로 남습니다.
네, app.yml의 git clone 명령어를 조정하여 원하는 버전을 클론할 수 있습니다. 단, Discourse도 원하는 버전에 고정되어 있는지 확인해야 합니다. 또한, 실행 중인 Discourse 버전과 호환되는 버전으로 discourse_docker도 고정해야 할 수 있습니다. 이 모든 작업을 수행해야 한다면, 업그레이드를 하지 않는 것이 더 쉽습니다.