From d8073ffac2ef7438c2d379a967634a177a0f128e Mon Sep 17 00:00:00 2001 From: Daniil Fedotov Date: Wed, 20 Mar 2024 17:08:10 -0400 Subject: [PATCH 1/6] build(release): add pre-release workflow to creat version bump PR --- .github/workflows/pre-release.yml | 48 +++++++++++++++++++++++++++++++ .goreleaser.yml | 1 + 2 files changed, 49 insertions(+) create mode 100644 .github/workflows/pre-release.yml diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000000..0946210d9d --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,48 @@ +name: Pre release + +on: + workflow_dispatch: + inputs: + release_tag: + description: 'Image tag in the format x.x.x' + required: true + type: string + +env: + RELEASE_TAG: ${{ inputs.release_tag }} + PRERELEASE_DOCS_BRANCH: 'dg8d45z' + +jobs: + ## TODO we can add a condition like github.actor.role == 'Maintainer' to limit trigger to maintainers only + create_pr: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - name: checkout_repo + uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 # necessary for CURRENT_TAG tracing + - name: fetch_tags + run: git fetch --tags origin + - name: bump_version + run: | + export CURRENT_TAG=$(git describe --abbrev=0 --tags) + echo ./build/bump_version.sh "${CURRENT_TAG}" "${RELEASE_TAG}" + ./build/bump_version.sh "${CURRENT_TAG}" "${RELEASE_TAG}" + - name: commit_changes + run: | + git config --global user.name 'Kasten Production' + git config --global user.email 'infra@kasten.io' + git checkout -B "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" + git add -A + git commit -s -m "pre-release: Update version to ${RELEASE_TAG}" + - name: create_pr + run: | + git push origin "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" + gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" --body "Update version to ${RELEASE_TAG}" --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer tdmanv,pavannd1,viveksinghggits --label kueue + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + diff --git a/.goreleaser.yml b/.goreleaser.yml index 45b4ce2b3a..c9b1d4c37b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -153,5 +153,6 @@ changelog: exclude: - '^docs:' - '^test:' + - '^pre-release:' archives: - allow_different_binary_count: true From 3ddd799ada88f40173fbfabdf553b365128fbad0 Mon Sep 17 00:00:00 2001 From: Daniil Fedotov Date: Wed, 27 Mar 2024 18:27:51 -0400 Subject: [PATCH 2/6] Add release instructions --- RELEASE.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..38eb1addec --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,137 @@ +## Automated Release + +Release process: + +- Verify the release version by looking at the [releases page](https://github.com/kanisterio/kanister/releases) on the Kanister repo. +- Trigger the [pre-release](#pre-release-workflow) workflow with the desired version number (e.g. bump the minor version portion 0.106.0 -> 0.107.0) +- Review and validate created PR that it doesn't have any unintended changes +- Make sure to validate that all merged PRs in the release have [release notes](#release-notes) + - Make sure that CHANGELOG.md and CHANGELOG_CURRENT.md contain release notes for the release version + - **NOTE** While we establish the new process of release notes, it may be required to add notes in pre-release step by commiting them into pre-release branch +- Approve and merge the pre-release PR (it will be merged by `kueue` when approved) +- Merging of pre-release PR will trigger the `kanister/release` pipeline in codefresh +- The Kanister release job will publish a new tag, update documentation, build all the docker images, and push them to the [ghcr.io](https://github.com/orgs/kanisterio/packages) registry. +- After completing the Kanister part, the job will open a PR on K10 to update the Kanister version in K10 docs, tests, and the base image of the kanister-tools docker image. Example PR: https://github.com/kastenhq/k10/pull/23956 +- Once the job is complete, a Slack notification will be sent to the kanister channel on the Kasten workspace. + - **NOTE** We need to update the GVS blueprint version in the kio/kanister/blueprint.go file to ensure that the new kanister-tools image will be used for Kopia operations. Push a commit to the PR opened above to do this. Example: https://github.com/kastenhq/k10/pull/23956/commits/83a62ccb17af52fd331012239dea97e02180817b +- Once the PR is approved, the Kanister release is complete. +- Additionally, we could also approve the K10 go.mod update to bring in the latest changes in K10. Example PR: https://github.com/kastenhq/k10/pull/24589 + +### Pre-release workflow + +`pre-release` workflow serves to create a new version of kanister, it updates version number and creates a PR in kanister repo. + +The workflow can be triggered using workflow dispatch from the `Actions` tab in the repo: https://github.com/kanisterio/kanister/actions/workflows/pre-release.yml +It has a required input of `release_tag`, which should be set to a version next to the current version. + +This will result in creating a PR with a version bump, like https://github.com/kanisterio/kanister/pull/2629 +**NOTE** PR description would look like `pre-release: Update version to ...` + +This PR would contain an update of version in various files. + + +### Release notes + +When working on pre-release PR the person doing the release should check that merged PRs have release notes when necessary. + +It can be checked by running a diff with previous release and looking at the PR commits: + +``` +git log 0.106.0..HEAD --invert-grep --grep='deps' --grep='docs' --grep='build' | grep -oh '^.*\(#[0-9]*\)' +``` + +And verifying that PRs have release notes. + +This is a bit tedious, but should be less of an issue as we establish a process of adding release notes. + + +## Handling Failures + +### Retry + +Depending on where the release failed, some of the following steps may have succeeded: + +- Creation of a new tag in the kanister repo +- PR raised in kanister to update kanister tools +- PR raised in K10 to update kanister tools version + +Before rerunning the release job manually, do the following: + +- Delete the tag manually from GitHub +- Close the PRs created (to avoid confusion) + +## Manual Release (Only needed when automation is broken) + +Prerequisites: + +- Make sure you have admin access or access to push tags on Kanister +- Make sure you have push access to the K10 repo +- Make sure you have access to [push images to GHCR](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) + +### Kanister Repo + +1. Create a release tag + +``` +$ export RELEASE_TAG="0.43.0" +$ git checkout master +$ git pull origin master +$ git tag -a "${RELEASE_TAG}" -m "Release version"; +$ git push origin "${RELEASE_TAG}" +``` + +2. Release binaries and docker images + +``` +$ make reno-report +$ make gorelease CHANGELOG_FILE=./CHANGELOG.md +``` + +3. Update and release docs and helms charts + +``` +$ export PREV_TAG="0.42.0" +$ git checkout -b "kan-docs-${RELEASE_TAG}" +$ ./build/bump_version.sh "${PREV_TAG}" "${RELEASE_TAG}" +$ git add -A +$ git commit -m"Kanister docs update to version ${RELEASE_TAG}" +$ git push origin kan-docs-${RELEASE_TAG} + +``` + +Go ahead and raise PR against master through GitHub UI + +### K10 Repo + +4. Refresh artifacts + +``` +$ build.sh docker_run invalidate_cloudfront -e kanisterrelease + +``` + +5. Bump kanister tools version in K10 + +``` +$ export RELEASE_TAG="0.43.0" +$ export PREV_TAG="0.41.0" +$ git checkout -b "kantools-bump-${RELEASE_TAG}" +$ /path/to/kanister/build/bump_version.sh "${PREV_TAG}" "${RELEASE_TAG}" . +$ git add -A +$ git commit -m"Update kanister tools version to ${RELEASE_TAG}" +$ git push origin kantools-bump-${RELEASE_TAG} +``` + +Go ahead and raise PR against master through GitHub UI + +### Publish Kanister Release + +Finally, go to https://github.com/kanisterio/kanister/releases, and publish the draft release + +## Post-release Checks + +- Verify the Kanister [repo](https://github.com/kanisterio/kanister/releases) for the new release tag. + +- Verify if the docker [images](https://github.com/orgs/kanisterio/packages?repo_name=kanister) have a new tag. NOTE: Not all docker images are relevant. + TODO: Add a list of the most relevant docker images to be verified here. + From cedb1a845f375920b78e7476edb03e9c1d84bf0d Mon Sep 17 00:00:00 2001 From: Daniil Fedotov Date: Wed, 10 Apr 2024 18:18:01 -0400 Subject: [PATCH 3/6] Add changelog generation to pre-release workflow --- .github/workflows/main.yaml | 16 ++++++++++++++++ .github/workflows/pre-release.yml | 13 +++++++++++-- .gitignore | 2 ++ Makefile | 3 +++ build/gorelease.sh | 3 +++ build/reno_report.sh | 13 ++++++++----- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f9ddddd9ca..e6bb83fb85 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -44,6 +44,22 @@ jobs: - name: restore_gosum run: echo "${{needs.gomod.outputs.gosum}}" > go.sum - run: make golint + + reno_lint: + runs-on: ubuntu-20.04 + needs: gomod + steps: + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + fetch-depth: 0 + - name: reset_git_extension + run: git config --unset-all extensions.worktreeconfig + - name: reno_lint + run: make reno-lint + ## Reno lint does not catch some errors which make reno report fail + - name: reno_report_check + run: make reno-report + test: runs-on: ubuntu-20.04 needs: gomod diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 0946210d9d..0f6e607279 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -32,6 +32,7 @@ jobs: export CURRENT_TAG=$(git describe --abbrev=0 --tags) echo ./build/bump_version.sh "${CURRENT_TAG}" "${RELEASE_TAG}" ./build/bump_version.sh "${CURRENT_TAG}" "${RELEASE_TAG}" + make reno-report VERSION="${RELEASE_TAG}" - name: commit_changes run: | git config --global user.name 'Kasten Production' @@ -39,10 +40,18 @@ jobs: git checkout -B "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" git add -A git commit -s -m "pre-release: Update version to ${RELEASE_TAG}" + - name: push_changes + run: git push origin "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" + - name: create_pr_body + run: | + echo "Update version to ${RELEASE_TAG}" > PR_BODY_FILE + echo "" >> PR_BODY_FILE + echo "Please check the changelog for the following merges:" >> PR_BODY_FILE + export CURRENT_TAG=$(git describe --abbrev=0 --tags) + git log ${CURRENT_TAG}..kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG} --pretty="- %h: %s" | grep -v ': test' | grep -v ': doc' | grep -v ': build' | grep -v ': deps' >> PR_BODY_FILE - name: create_pr run: | - git push origin "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" - gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" --body "Update version to ${RELEASE_TAG}" --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer tdmanv,pavannd1,viveksinghggits --label kueue + gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" -F PR_BODY_FILE --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer tdmanv,pavannd1,viveksinghggits,hairyhum --label kueue env: GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.gitignore b/.gitignore index ddda7de854..14bb7c3793 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ **/*.swp /.idea /releasenotes/config.yaml +CHANGELOG.rst +CHANGELOG_CURRENT.rst diff --git a/Makefile b/Makefile index 2e64f66cb1..22fdb41de5 100644 --- a/Makefile +++ b/Makefile @@ -293,3 +293,6 @@ reno-new: reno-report: @$(MAKE) run CMD="./build/reno_report.sh $(VERSION)" + +reno-lint: + @$(MAKE) run CMD="reno lint" diff --git a/build/gorelease.sh b/build/gorelease.sh index 61ca8fd15d..52eeca322a 100755 --- a/build/gorelease.sh +++ b/build/gorelease.sh @@ -26,6 +26,9 @@ then exit 1 fi +## Set default changelog file +CHANGELOG_FILE=${CHANGELOG_FILE:-./CHANGELOG_CURRENT.md} + RELEASE_NOTES="" if [ -n "${CHANGELOG_FILE:-}" ] then diff --git a/build/reno_report.sh b/build/reno_report.sh index 3720e556f8..a3303bce29 100755 --- a/build/reno_report.sh +++ b/build/reno_report.sh @@ -37,8 +37,11 @@ rst2md ./CHANGELOG.rst --output ./CHANGELOG.md ## It will be replaced by `unreleased_version_title` setting in the actual report file UNRELEASED_VERSION=$(reno list 2>/dev/null | grep -E "^[0-9]+\.[0-9]+\.[0-9]+\-[0-9]+") -## Generate rst report -echo reno report --version=${UNRELEASED_VERSION} --output ./CHANGELOG_CURRENT.rst -reno report --version=${UNRELEASED_VERSION} --output ./CHANGELOG_CURRENT.rst -## Convert rst to markdown -rst2md ./CHANGELOG_CURRENT.rst --output ./CHANGELOG_CURRENT.md +if [ -n "${UNRELEASED_VERSION}" ] +then + ## Generate rst report + echo reno report --version=${UNRELEASED_VERSION} --output ./CHANGELOG_CURRENT.rst + reno report --version=${UNRELEASED_VERSION} --output ./CHANGELOG_CURRENT.rst + ## Convert rst to markdown + rst2md ./CHANGELOG_CURRENT.rst --output ./CHANGELOG_CURRENT.md +fi From e36d10c89db25b96e0e4238830b9d601cde36738 Mon Sep 17 00:00:00 2001 From: Daniil Fedotov Date: Fri, 12 Apr 2024 12:57:44 -0400 Subject: [PATCH 4/6] Mention posting release announcement --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index 38eb1addec..b7d62565b2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -15,6 +15,7 @@ Release process: - Once the job is complete, a Slack notification will be sent to the kanister channel on the Kasten workspace. - **NOTE** We need to update the GVS blueprint version in the kio/kanister/blueprint.go file to ensure that the new kanister-tools image will be used for Kopia operations. Push a commit to the PR opened above to do this. Example: https://github.com/kastenhq/k10/pull/23956/commits/83a62ccb17af52fd331012239dea97e02180817b - Once the PR is approved, the Kanister release is complete. +- Post release announcement in kanister slack and https://groups.google.com/g/kanisterio - Additionally, we could also approve the K10 go.mod update to bring in the latest changes in K10. Example PR: https://github.com/kastenhq/k10/pull/24589 ### Pre-release workflow From f89eb354f34664c9b5cd752496329ce797b922cf Mon Sep 17 00:00:00 2001 From: Daniil Fedotov Date: Fri, 10 May 2024 11:48:20 -0400 Subject: [PATCH 5/6] Move RELEASE.md to a different PR --- .github/workflows/pre-release.yml | 2 +- RELEASE.md | 138 ------------------------------ 2 files changed, 1 insertion(+), 139 deletions(-) delete mode 100644 RELEASE.md diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 0f6e607279..8dce5277f9 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -51,7 +51,7 @@ jobs: git log ${CURRENT_TAG}..kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG} --pretty="- %h: %s" | grep -v ': test' | grep -v ': doc' | grep -v ': build' | grep -v ': deps' >> PR_BODY_FILE - name: create_pr run: | - gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" -F PR_BODY_FILE --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer tdmanv,pavannd1,viveksinghggits,hairyhum --label kueue + gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" -F PR_BODY_FILE --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer pavannd1,viveksinghggits,hairyhum --label kueue env: GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index b7d62565b2..0000000000 --- a/RELEASE.md +++ /dev/null @@ -1,138 +0,0 @@ -## Automated Release - -Release process: - -- Verify the release version by looking at the [releases page](https://github.com/kanisterio/kanister/releases) on the Kanister repo. -- Trigger the [pre-release](#pre-release-workflow) workflow with the desired version number (e.g. bump the minor version portion 0.106.0 -> 0.107.0) -- Review and validate created PR that it doesn't have any unintended changes -- Make sure to validate that all merged PRs in the release have [release notes](#release-notes) - - Make sure that CHANGELOG.md and CHANGELOG_CURRENT.md contain release notes for the release version - - **NOTE** While we establish the new process of release notes, it may be required to add notes in pre-release step by commiting them into pre-release branch -- Approve and merge the pre-release PR (it will be merged by `kueue` when approved) -- Merging of pre-release PR will trigger the `kanister/release` pipeline in codefresh -- The Kanister release job will publish a new tag, update documentation, build all the docker images, and push them to the [ghcr.io](https://github.com/orgs/kanisterio/packages) registry. -- After completing the Kanister part, the job will open a PR on K10 to update the Kanister version in K10 docs, tests, and the base image of the kanister-tools docker image. Example PR: https://github.com/kastenhq/k10/pull/23956 -- Once the job is complete, a Slack notification will be sent to the kanister channel on the Kasten workspace. - - **NOTE** We need to update the GVS blueprint version in the kio/kanister/blueprint.go file to ensure that the new kanister-tools image will be used for Kopia operations. Push a commit to the PR opened above to do this. Example: https://github.com/kastenhq/k10/pull/23956/commits/83a62ccb17af52fd331012239dea97e02180817b -- Once the PR is approved, the Kanister release is complete. -- Post release announcement in kanister slack and https://groups.google.com/g/kanisterio -- Additionally, we could also approve the K10 go.mod update to bring in the latest changes in K10. Example PR: https://github.com/kastenhq/k10/pull/24589 - -### Pre-release workflow - -`pre-release` workflow serves to create a new version of kanister, it updates version number and creates a PR in kanister repo. - -The workflow can be triggered using workflow dispatch from the `Actions` tab in the repo: https://github.com/kanisterio/kanister/actions/workflows/pre-release.yml -It has a required input of `release_tag`, which should be set to a version next to the current version. - -This will result in creating a PR with a version bump, like https://github.com/kanisterio/kanister/pull/2629 -**NOTE** PR description would look like `pre-release: Update version to ...` - -This PR would contain an update of version in various files. - - -### Release notes - -When working on pre-release PR the person doing the release should check that merged PRs have release notes when necessary. - -It can be checked by running a diff with previous release and looking at the PR commits: - -``` -git log 0.106.0..HEAD --invert-grep --grep='deps' --grep='docs' --grep='build' | grep -oh '^.*\(#[0-9]*\)' -``` - -And verifying that PRs have release notes. - -This is a bit tedious, but should be less of an issue as we establish a process of adding release notes. - - -## Handling Failures - -### Retry - -Depending on where the release failed, some of the following steps may have succeeded: - -- Creation of a new tag in the kanister repo -- PR raised in kanister to update kanister tools -- PR raised in K10 to update kanister tools version - -Before rerunning the release job manually, do the following: - -- Delete the tag manually from GitHub -- Close the PRs created (to avoid confusion) - -## Manual Release (Only needed when automation is broken) - -Prerequisites: - -- Make sure you have admin access or access to push tags on Kanister -- Make sure you have push access to the K10 repo -- Make sure you have access to [push images to GHCR](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) - -### Kanister Repo - -1. Create a release tag - -``` -$ export RELEASE_TAG="0.43.0" -$ git checkout master -$ git pull origin master -$ git tag -a "${RELEASE_TAG}" -m "Release version"; -$ git push origin "${RELEASE_TAG}" -``` - -2. Release binaries and docker images - -``` -$ make reno-report -$ make gorelease CHANGELOG_FILE=./CHANGELOG.md -``` - -3. Update and release docs and helms charts - -``` -$ export PREV_TAG="0.42.0" -$ git checkout -b "kan-docs-${RELEASE_TAG}" -$ ./build/bump_version.sh "${PREV_TAG}" "${RELEASE_TAG}" -$ git add -A -$ git commit -m"Kanister docs update to version ${RELEASE_TAG}" -$ git push origin kan-docs-${RELEASE_TAG} - -``` - -Go ahead and raise PR against master through GitHub UI - -### K10 Repo - -4. Refresh artifacts - -``` -$ build.sh docker_run invalidate_cloudfront -e kanisterrelease - -``` - -5. Bump kanister tools version in K10 - -``` -$ export RELEASE_TAG="0.43.0" -$ export PREV_TAG="0.41.0" -$ git checkout -b "kantools-bump-${RELEASE_TAG}" -$ /path/to/kanister/build/bump_version.sh "${PREV_TAG}" "${RELEASE_TAG}" . -$ git add -A -$ git commit -m"Update kanister tools version to ${RELEASE_TAG}" -$ git push origin kantools-bump-${RELEASE_TAG} -``` - -Go ahead and raise PR against master through GitHub UI - -### Publish Kanister Release - -Finally, go to https://github.com/kanisterio/kanister/releases, and publish the draft release - -## Post-release Checks - -- Verify the Kanister [repo](https://github.com/kanisterio/kanister/releases) for the new release tag. - -- Verify if the docker [images](https://github.com/orgs/kanisterio/packages?repo_name=kanister) have a new tag. NOTE: Not all docker images are relevant. - TODO: Add a list of the most relevant docker images to be verified here. - From e8e87b3b5e2f12cd6cbdb3ad5afb02f2ab50d69e Mon Sep 17 00:00:00 2001 From: Daniil Fedotov Date: Thu, 13 Jun 2024 16:59:08 -0400 Subject: [PATCH 6/6] Use dynamic github token --- .github/workflows/pre-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 8dce5277f9..40c509cd2e 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -53,5 +53,5 @@ jobs: run: | gh pr create --title "pre-release: Update version to ${RELEASE_TAG}" -F PR_BODY_FILE --head "kan-docs-${PRERELEASE_DOCS_BRANCH}-${RELEASE_TAG}" --base master --reviewer pavannd1,viveksinghggits,hairyhum --label kueue env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}