diff --git a/.claude/skills/release-info/SKILL.md b/.claude/skills/release-info/SKILL.md new file mode 100644 index 0000000..9f5603d --- /dev/null +++ b/.claude/skills/release-info/SKILL.md @@ -0,0 +1,100 @@ +--- +name: release-info +description: | + Display the current state of an Eclipse Che release across all phases. + Shows workflow status, artifact publication, branch creation, and PR status + for each component in the release process. + Triggers: "release info", "release status", "show release status", + "what's the status of release", "release-info 7.120.0" +version: 1.0.0 +tools: + - Read + - Bash +--- + +# Eclipse Che Release Status + +This skill displays comprehensive release status for all Eclipse Che components across all release phases. + +## Usage + +The user should provide a version number in the format `X.Y.Z` (e.g., 7.120.0). + +**Examples**: +- `release-info 7.120.0` +- "Show release status for 7.120.0" +- "What's the status of release 7.75.0?" + +## Execution + +### Step 1: Extract and Validate Version + +Extract the version from the user's input: +- Expected format: `MAJOR.MINOR.BUGFIX` (e.g., 7.120.0) +- Pattern: `\d+\.\d+\.\d+` +- If no version provided or invalid format, ask the user to provide one + +### Step 2: Check Environment + +Verify required environment variables are set: +- `CHE_BOT_GITHUB_TOKEN` - required +- `CHE_INCUBATOR_BOT_GITHUB_TOKEN` - optional (falls back to CHE_BOT_GITHUB_TOKEN) + +If `CHE_BOT_GITHUB_TOKEN` is not set, check if `GITHUB_TOKEN` is available and use that: + +```bash +if [[ -z "${CHE_BOT_GITHUB_TOKEN:-}" ]]; then + if [[ -n "${GITHUB_TOKEN:-}" ]]; then + export CHE_BOT_GITHUB_TOKEN="$GITHUB_TOKEN" + else + echo "Error: No GitHub token available." + echo "Set CHE_BOT_GITHUB_TOKEN or GITHUB_TOKEN environment variable." + fi +fi +``` + +### Step 3: Run release-info.sh + +Execute the main script from the repository root: + +```bash +cd /home/mkuznets/projects/claude/che-release +./scripts/release-info.sh +``` + +For debug output: +```bash +./scripts/release-info.sh --debug +``` + +### Step 4: Present Results + +Display the script output directly to the user. The script produces formatted output with: +- Per-phase grouping with descriptions +- Per-project status (DONE / IN PROGRESS / FAILED / NOT STARTED) +- Detailed item status for workflows (currently disabled), branches, artifacts, and PRs +- Summary with counts and blocking issues + +**Note**: Workflow status checking is currently disabled. The script will show workflows as "(checking disabled)" and determine project status based only on artifacts, branches, and PRs. + +## Error Handling + +- If the script is not found, report that the release-info scripts need to be set up +- If tokens are missing, guide the user on how to set them +- If the script fails, show the error output and suggest running with `--debug` + +## Configuration + +All project and phase configuration is in `che-release.yaml` at the repository root. +To add or modify projects, edit the `release-config` section in `che-release.yaml`. + +The configuration defines: +- **Phases**: Release phases with descriptions and dependencies +- **Projects**: For each project - name, repo, workflow ID, artifacts, branches, and PRs +- **Artifacts**: Images (Quay.io), NPM packages, GitHub releases, websites, and website version verification + - `image`: Quay.io container image (checks if tag exists) + - `npmjs`: NPM package (checks if version is published) + - `github-release`: GitHub release (checks if tag exists) + - `website`: Website URL (checks if accessible via HTTP 200) + - `website-version`: Website with version verification (checks if HTML element with class "version-menu-toggle" contains the release version or branch format) +- **Pull Requests**: Expected PRs with title patterns, target branches, and completion requirements diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ef0d284..f0a3fa0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Global Owners -* @mkuznyetsov @SDawley +* @mkuznyetsov @SDawley @svor diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f49fd6b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,16 @@ +name: CI + +on: [push, pull_request] + +jobs: + check: + runs-on: ubuntu-24.04 + steps: + - name: Clone source code + uses: actions/checkout@v6 + with: + fetch-depth: 1 + persist-credentials: false + - name: Run ShellCheck + run: | + find . -type f -name "*.sh" | xargs shellcheck --external-sources diff --git a/.github/workflows/release-announce.yml b/.github/workflows/release-announce.yml deleted file mode 100644 index ba14b5d..0000000 --- a/.github/workflows/release-announce.yml +++ /dev/null @@ -1,45 +0,0 @@ -# This Workflow announces a release in Eclipse Mattermost to https://mattermost.eclipse.org/eclipse/channels/eclipse-che-releases -name: Release - Send notification -on: - workflow_dispatch: - inputs: - version: - description: 'The version that is going to be announced. Should be in format 7.y.z' - required: true - default: '' -jobs: - build: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Verify branches - id: check - run: | - BRANCH=${{ github.event.inputs.version }} - BRANCH=${BRANCH%.*}.x - SCRIPT_OUTPUT=$(./utils/check-branches.sh "${BRANCH}") - if [[ "${SCRIPT_OUTPUT}" == *"[ERROR]"* ]]; then - #TODO find a working solution to output multiline text in the next step - exit 1 - fi - - name: Create success message - run: | - milestone=${{ github.event.inputs.version }} - milestone=${milestone%.*}; echo "milestone: ${milestone}" - echo "{\"text\":\":che-logo: Che ${{ github.event.inputs.version }} has been released.\n\n Please resolve or move unresolved issues assigned to this milestone: https://github.com/eclipse/che/milestones/${milestone}\"}" - echo "{\"text\":\":che-logo: Che ${{ github.event.inputs.version }} has been released.\n\n Please resolve or move unresolved issues assigned to this milestone: https://github.com/eclipse/che/milestones/${milestone}\"}" > mattermost.json - - name: Create failure message - if: ${{ failure() }} - run: | - echo "{\"text\":\":che-logo: Che ${{ github.event.inputs.version }} release final checks have failed:\n\n failed: https://github.com/eclipse-che/che-release/actions/workflows/release-send-mattermost-announcement.yml.\"}" - echo "{\"text\":\":che-logo: Che ${{ github.event.inputs.version }} release final checks have failed:\n\n failed: https://github.com/eclipse-che/che-release/actions/workflows/release-send-mattermost-announcement.yml.\"}" > mattermost.json - # TODO send to slack as MM no longer exists - # - name: Send message - # if: ${{ success() }} || ${{ failure() }} - # uses: mattermost/action-mattermost-notify@1.1.0 - # env: - # MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} - # MATTERMOST_CHANNEL: eclipse-che-releases - # MATTERMOST_USERNAME: che-bot diff --git a/.github/workflows/release-orchestrate-overall.yml b/.github/workflows/release-orchestrate-overall.yml index 78ae7cf..2e76b04 100644 --- a/.github/workflows/release-orchestrate-overall.yml +++ b/.github/workflows/release-orchestrate-overall.yml @@ -7,22 +7,14 @@ on: description: 'The version that is going to be released. Should be in format 7.y.z' required: true default: '' - releaseParent: - description: 'If true, will also release Che Parent.' - required: false - default: 'false' - versionParent: - description: 'The version of Che Parent' - required: false - default: '7.15.0' phases: description: ' # Comma-separated phases to perform (see README.md). -# Default: 1,2,3 +# Default: 1,2,3,4 # Omit phases that have successfully run. ' required: true - default: '1,2,3' + default: '1,2,3,4' jobs: pre_job: runs-on: ubuntu-22.04 @@ -44,15 +36,9 @@ jobs: if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: - fetch-depth: 0 - - name: Login to docker.io - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} - registry: docker.io + credentials: false - name: Login to quay.io uses: docker/login-action@v2 with: @@ -63,12 +49,6 @@ jobs: uses: actions/setup-python@v3 with: python-version: 3.9 - - uses: actions/setup-java@v3 - with: - java-version: '11' - java-package: jdk - architecture: x64 - distribution: adopt - name: Set up environment run: | python -m pip install --upgrade pip @@ -81,34 +61,21 @@ jobs: sudo apt-get update -y || true # install more dependencies sudo apt-get -y -q install wget curl bash git hub - java -version # want git >=2.24, hub >=2 hub --version # hub reports git version too # want >=5 bash --version - - # do we need any of these? - # docker-ce x86_64 3:19.03.14-3.el7 docker-ce-stable 24 M - # gcc-c++ x86_64 4.8.5-44.el7 base 7.2 M - # nodejs x86_64 2:10.23.0-1nodesource nodesource 20 M - # yarn noarch 1.22.5-1 yarn 1.2 M - name: Release run: | CHE_VERSION="${{ github.event.inputs.version }}" echo "CHE_VERSION=${CHE_VERSION}" PHASES="${{ github.event.inputs.phases }}" - PARENT_VERSION="${{ github.event.inputs.versionParent }}" - PARENT_RELEASE="${{ github.event.inputs.releaseParent }}" - export CHE_GITHUB_SSH_KEY=${{ secrets.CHE_GITHUB_SSH_KEY }} - export QUAY_ECLIPSE_CHE_USERNAME=${{ secrets.QUAY_USERNAME }} - export QUAY_ECLIPSE_CHE_PASSWORD=${{ secrets.QUAY_PASSWORD }} git config --global user.name "Mykhailo Kuznietsov" git config --global user.email "mkuznets@redhat.com" - export GITHUB_TOKEN=${{ secrets.CHE_BOT_GITHUB_TOKEN }} export CHE_BOT_GITHUB_TOKEN=${{ secrets.CHE_BOT_GITHUB_TOKEN }} export CHE_INCUBATOR_BOT_GITHUB_TOKEN=${{ secrets.CHE_INCUBATOR_BOT_GITHUB_TOKEN }} set -e - ./make-release.sh -v ${CHE_VERSION} -p ${PHASES} --parent-version ${PARENT_VERSION} + ./scripts/make-release.sh -v ${CHE_VERSION} -p ${PHASES} --parent-version ${PARENT_VERSION} diff --git a/.github/workflows/update-base-images.yml b/.github/workflows/update-base-images.yml deleted file mode 100644 index 4688aae..0000000 --- a/.github/workflows/update-base-images.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Update base images -on: - workflow_dispatch: - inputs: - branch: - description: 'The branch to update, eg., main or 7.56.x (or 20221111 for che-incubator/jetbrains-editor-images)' - required: true - default: 'main' - repos: - description: 'List of repos to check for updates, comma-separated' - required: true - #TODO when adding/removing from this list, see lists below in this file and also in ../../utils/approvePRs.sh and ../../utils/check-branches.sh - default: ' - che-incubator/chectl, - che-incubator/che-code, - che-incubator/configbump, - che-incubator/jetbrains-editor-images, - che-incubator/jetbrains-ide-dev-server, - che-incubator/kubernetes-image-puller, - che-incubator/kubernetes-image-puller-operator, - devfile/devworkspace-operator, - devfile/developer-images, - eclipse-che/che-dashboard, - eclipse-che/che-machine-exec, - eclipse-che/che-operator, - eclipse-che/che-plugin-registry, - eclipse-che/che-server, - eclipse/che - ' - schedule: - - cron: "0 0 * * 6" - -jobs: - build: - name: Check - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - projects: - - { repo: "che-incubator/chectl", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "che-incubator/che-code", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "che-incubator/configbump", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "che-incubator/jetbrains-editor-images", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "che-incubator/jetbrains-ide-dev-server", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "che-incubator/kubernetes-image-puller", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "che-incubator/kubernetes-image-puller-operator", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "devfile/devworkspace-operator", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "devfile/developer-images", user: "CHE_INCUBATOR_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "eclipse-che/che-dashboard", user: "CHE_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "eclipse-che/che-machine-exec", user: "CHE_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "eclipse-che/che-operator", user: "CHE_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 1 -f *Dockerfile --pr" } - - { repo: "eclipse-che/che-plugin-registry", user: "CHE_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "eclipse-che/che-server", user: "CHE_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - - { repo: "eclipse/che", user: "CHE_BOT_GITHUB_TOKEN", commandFlags: "-maxdepth 3 -f *Dockerfile --pr" } - steps: - - name: Set defaults (for use in crontab or workflow_dispatch) and check if we have a matching repo value - id: SETUP_REPOS - shell: bash - env: - DEFAULT_BRANCH: 'main' - DEFAULT_REPOS: ' - che-incubator/chectl, - che-incubator/che-code, - che-incubator/configbump, - che-incubator/jetbrains-editor-images, - che-incubator/jetbrains-ide-dev-server, - che-incubator/kubernetes-image-puller, - che-incubator/kubernetes-image-puller-operator, - devfile/devworkspace-operator, - devfile/developer-images, - eclipse-che/che-dashboard, - eclipse-che/che-devfile-registry, - eclipse-che/che-machine-exec, - eclipse-che/che-operator, - eclipse-che/che-plugin-registry, - eclipse-che/che-server, - eclipse/che - ' - run: | - echo "BRANCH=${{ github.event.inputs.branch || env.DEFAULT_BRANCH }}" >> $GITHUB_ENV - echo "REPOS=${{ github.event.inputs.repos || env.DEFAULT_REPOS }}" >> $GITHUB_ENV - echo GOT_REPO=false >> $GITHUB_OUTPUT - for r in $(echo ${{ github.event.inputs.repos || env.DEFAULT_REPOS }} | tr "," "\n"); do - if [[ $r == "${{ matrix.projects.repo }}" ]]; then - echo GOT_REPO=true >> $GITHUB_OUTPUT - break - fi - done - - name: "Checkout source code" - if: steps.SETUP_REPOS.outputs.GOT_REPO == 'true' - uses: actions/checkout@v3 - with: - repository: ${{ matrix.projects.repo }} - token: ${{ secrets[matrix.projects.user] }} - fetch-depth: 0 - - name: Login to registry.redhat.io - if: steps.SETUP_REPOS.outputs.GOT_REPO == 'true' - uses: docker/login-action@v2 - with: - registry: registry.redhat.io - username: ${{ secrets.CRW_BUILD_USER }} - password: ${{ secrets.CRW_BUILD_TOKEN }} - - name: Init - if: steps.SETUP_REPOS.outputs.GOT_REPO == 'true' - run: | - checkVersion() { - if [[ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ]]; then - # echo "[INFO] $3 version $2 >= $1, can proceed." - true - else - echo "[INFO] Installing $3 version >= $1 ..." - if [[ "$3" == "yq" ]]; then - pip install "$3" - else - sudo apt-get -y install "$3" - fi - fi - } - repos=$(echo ${{ env.REPOS }} | tr "," "\n") - for r in $repos; do - if [[ $r == "${{ matrix.projects.repo }}" ]]; then - # Install skopeo + yq - checkVersion 1.1 "$(skopeo --version | sed -e "s/skopeo version //")" skopeo - checkVersion 2.0 "$(yq --version | sed -e "s/yq //")" yq - pushd /tmp >/dev/null || exit 1 - curl -sSLO https://raw.githubusercontent.com/redhat-developer/codeready-workspaces/devspaces-3-rhel-8/product/updateBaseImages.sh - chmod +x updateBaseImages.sh - popd >/dev/null || exit 1 - export GITHUB_TOKEN=${{ secrets[matrix.projects.user] }} - git config --global user.name "Nick Boldt" - git config --global user.email "nboldt@redhat.com" - echo "running job for ${{ matrix.projects.repo }}" - mkdir -p /tmp/${{ matrix.projects.repo }} - /tmp/updateBaseImages.sh -b ${{ env.BRANCH }} ${{ matrix.projects.commandFlags }} | tee /tmp/${{ matrix.projects.repo }}/log.txt - break - fi - done - if [[ ! -d /tmp/${{ matrix.projects.repo }} ]]; then - echo "SKIP running job for ${{ matrix.projects.repo }} - not included in $repos" - fi - - name: PR link - id: SETUP_PR_LINK - shell: bash - run: | - echo GOT_PR=false >> $GITHUB_OUTPUT - if [[ -f /tmp/${{ matrix.projects.repo }}/log.txt ]]; then - URL=$(grep "${{ matrix.projects.repo }}/pull/" /tmp/${{ matrix.projects.repo }}/log.txt 2>/dev/null || true) - if [[ $URL ]]; then - echo "$URL" - echo "PR_URL=\"$URL\"" >> $GITHUB_ENV - echo GOT_PR=true >> $GITHUB_OUTPUT - fi - fi diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f294942 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,160 @@ +# AGENTS.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Purpose + +This is the Eclipse Che release orchestration repository. It automates the coordinated release of Eclipse Che components across multiple GitHub repositories in a specific order based on dependencies. + +## Release Commands + +### Orchestrate full release +```bash +./make-release.sh --version 7.75.0 --phases 1,2,3 +``` +- `--version` (-v): Version to release in format 7.y.z +- `--phases` (-p): Comma-separated list of phases to run (default: 1,2,3) + +### Create a release branch +```bash +./make-branch.sh --branch 7.75.x --branchfrom main --repo +``` +- Use `--force` to delete and recreate an existing branch + +## Release Architecture + +### Release Configuration + +The release configuration is defined in `che-release.yaml` at the repository root. This YAML file contains: +- **Phases**: All release phases with descriptions +- **Projects**: Project metadata including repository, workflow ID, expected artifacts +- **Workflows**: GitHub Actions workflow names and IDs to trigger +- **Artifacts**: Container images, NPM packages, GitHub releases, and websites to verify +- **Branches**: Expected branches for each project +- **Pull Requests**: Expected PRs with patterns, targets, and completion requirements + +The `scripts/release-info.sh` script reads this configuration to display comprehensive release status. + +### Multi-Phase Release Process + +The release process is divided into phases based on project dependencies: + +**Phase 1** - Independent projects (no Che dependencies): +- che-code +- jetbrains-ide-dev-server +- configbump +- che-machine-exec +- che-server +- devworkspace-generator (NPM package) +- kubernetes-image-puller (branch creation only) + +**Phase 2** - Projects depending on Phase 1: +- che-e2e (depends on che-server, devworkspace-generator) +- che-plugin-registry (depends on che-machine-exec) +- che-dashboard + +**Phase 3** - Operator (depends on all previous phases): +- che-operator + +**Phase 4+** - Downstream releases (manual steps required): +- community-operators PRs +- chectl CLI +- che-docs + +**Phase 5+** - Website and verification: +- che-website-publish +- release-check-unmerged-PRs + +### Version and Branch Strategy + +- **Version format**: 7.yy.z (e.g., 7.75.0, 7.75.1) +- **Branch format**: 7.yy.x (e.g., 7.75.x) +- **For .0 releases** (7.yy.0): Release from `main` branch +- **For bugfix releases** (7.yy.1+): Release from the corresponding .x branch (e.g., 7.75.x) + +### GitHub Action Invocation + +The `make-release.sh` script triggers GitHub Actions workflows in each project repository using the GitHub API. The `invokeAction()` function in `utils/util.sh`: + +1. Computes the workflow ID from the action name +2. Determines the correct branch to run from (main for .0 releases, .x branch for bugfixes) +3. Uses personal GitHub token `GITHUB_TOKEN` for authentication +4. Dispatches the workflow with version parameters + +### Verification Steps + +After each phase, the script verifies that artifacts were published before proceeding: + +- **Container images**: Checks Quay.io using `verifyContainerExistsWithTimeout()` +- **NPM packages**: Checks npmjs.org using `verifyNpmJsPackageExistsWithTimeout()` +- **Git branches**: Checks GitHub using `verifyBranchExistsWithTimeout()` +- **GitHub releases**: Checks for release tags via GitHub API +- **Websites**: Checks HTTP accessibility (status 200) +- **Website version**: Checks that the HTML element with class "version-menu-toggle" displays the correct version + +Verification retries every 20 seconds for the specified timeout period (30-60 minutes depending on the artifact). + +## Key Files + +- `che-release.yaml`: Release configuration defining all phases, projects, workflows, artifacts, and PRs +- `make-release.sh`: Main orchestration script +- `utils/util.sh`: Reusable functions for GitHub API calls, verification, error handling +- `.github/workflows/release-orchestrate-overall.yml`: GitHub workflow that runs make-release.sh +- `scripts/release-info.sh`: Status reporting script that reads from che-release.yaml +- `README.md`: Detailed release procedure and project status + +## Environment Variables Required + +When running manually (outside GitHub Actions): + +- `CHE_VERSION`: Version being released +- `CHE_GITHUB_SSH_KEY`: SSH key for Git operations (base64 encoded) +- `CHE_BOT_GITHUB_TOKEN`: Token for eclipse-che/* repos +- `CHE_INCUBATOR_BOT_GITHUB_TOKEN`: Token for che-incubator/* and devfile/* repos +- `QUAY_ECLIPSE_CHE_USERNAME`: Quay.io username +- `QUAY_ECLIPSE_CHE_PASSWORD`: Quay.io password + +## Blocker Issue Check + +For .0 releases, the script checks for open blocker issues in eclipse/che before proceeding: +```bash +curl -s "https://api.github.com/repos/eclipse/che/issues?labels=severity/blocker&state=open" +``` + +## Lint and Type-Check Commands + +### Single-file bash script checks + +**Syntax check**: +```bash +bash -n +``` +Validates bash syntax without executing the script. + +**ShellCheck** (if installed): +```bash +shellcheck +``` +Static analysis for shell scripts. Install with: `dnf install ShellCheck` or `apt-get install shellcheck` + +**Format check** (if shfmt installed): +```bash +shfmt -d +``` +Check formatting. Install from: https://github.com/mvdan/sh + +**All scripts at once**: +```bash +find . -name "*.sh" -type f -exec bash -n {} \; +``` + +## Common Troubleshooting + +- If a workflow fails, you can restart individual workflows or re-run specific phases by providing the phase number to `--phases` +- Sometimes you may need to regenerate tags or skip certain steps +- Modified workflow files can be tested from feature branches by triggering the workflow on that branch +- Che Operator PRs must be manually approved and merged before Phase 4+ can proceed + +## Red Hat Compliance and Responsible AI Rules + +See [redhat-compliance-and-responsible-ai.md](redhat-compliance-and-responsible-ai.md) and the Cursor rules file under `.cursor/rules/`. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..89635ca --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +See [AGENTS.md](AGENTS.md) for full project guidance, architecture, and conventions. \ No newline at end of file diff --git a/che-release.yaml b/che-release.yaml new file mode 100644 index 0000000..57d7230 --- /dev/null +++ b/che-release.yaml @@ -0,0 +1,279 @@ +--- +# Eclipse Che Release Configuration +# +# This file defines the multi-phase release process for Eclipse Che components. +# Used by: +# - scripts/release-info.sh: Display comprehensive release status +# - make-release.sh: Orchestrate releases (future enhancement) +# +# Structure: +# - phases: Release phases with dependencies (phase-1 through phase-6) +# - projects: For each project - repo, workflow, artifacts, branches, PRs +# - artifacts: Container images (Quay), NPM packages, GitHub releases, websites, website-version +# - image: Quay.io container image (checks if tag exists) +# - npmjs: NPM package (checks if version published) +# - github-release: GitHub release (checks if tag exists) +# - website: Website URL (checks if accessible) +# - website-version: Website with version verification (checks if HTML element with class +# "version-menu-toggle" contains the release version) +# - pull-requests: Expected PRs with title patterns and merge requirements +# +release-config: + phases: + phase-1: + description: "Independent projects (no Che dependencies)" + projects: + - name: che-code + repo: che-incubator/che-code + workflow: + name: "Release Che Code" + id: 34764281 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/che-incubator/che-code + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + - name: jetbrains-ide-dev-server + repo: che-incubator/jetbrains-ide-dev-server + workflow: + name: "Release JetBrains IDE Dev Server" + id: 120670986 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/che-incubator/che-idea-dev-server + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + - name: configbump + repo: che-incubator/configbump + workflow: + name: "Release Che Configbump" + id: 69757177 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/che-incubator/configbump + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + - name: che-machine-exec + repo: eclipse-che/che-machine-exec + workflow: + name: "Release Che Machine Exec" + id: 7369994 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/eclipse/che-machine-exec + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + - name: che-server + repo: eclipse-che/che-server + workflow: + name: "Release Che Server" + id: 9230035 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/eclipse/che-server + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + - name: devworkspace-generator + repo: devfile/devworkspace-generator + workflow: + name: "Release Che Devworkspace Generator" + id: 102323522 + parameters: + - key: version + value: "{version}" + artifacts: + - type: npmjs + name: "@eclipse-che/che-devworkspace-generator" + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + - name: kubernetes-image-puller + repo: che-incubator/kubernetes-image-puller + workflow: + name: "Create branch" + id: 5409996 + parameters: + - key: branch + value: "{branch}" + artifacts: [] + branches: + - "{branch}" + pull-requests: [] + + phase-2: + description: "Projects depending on Phase 1" + projects: + - name: che-e2e + repo: eclipse/che + workflow: + name: "Release Che E2E" + id: 5536792 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/eclipse/che-e2e + timeout: 30 + branches: + - "{branch}" + pull-requests: [] + + - name: che-plugin-registry + repo: eclipse-che/che-plugin-registry + workflow: + name: "Release Che Plugin Registry" + id: 4191251 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/eclipse/che-plugin-registry + timeout: 30 + branches: + - "{branch}" + pull-requests: [] + + - name: che-dashboard + repo: eclipse-che/che-dashboard + workflow: + name: "Release Che Dashboard" + id: 3152474 + parameters: + - key: version + value: "{version}" + artifacts: + - type: image + name: quay.io/eclipse/che-dashboard + timeout: 60 + branches: + - "{branch}" + pull-requests: [] + + phase-3: + description: "Operator (depends on all previous phases)" + projects: + - name: che-operator + repo: eclipse-che/che-operator + workflow: + name: "Release Che Operator" + id: 3593082 + parameters: + - key: version + value: "{version}" + pause: true + artifacts: + - type: image + name: quay.io/eclipse/che-operator + branches: + - "{branch}" + pull-requests: + - title-pattern: "{version} release" + target-branch: "{branch}" + required-for-completion: true + - title-pattern: "Copy {version} csv to main" + target-branch: main + required-for-completion: true + + phase-4: + description: "Downstream releases (manual steps required)" + projects: + - name: community-operator + repo: redhat-openshift-ecosystem/community-operators-prod + workflow: + name: "Release Community Operator PRs" + artifacts: [] + branches: [] + pull-requests: + - title-pattern: "operator eclipse-che ({version})" + target-repo: redhat-openshift-ecosystem/community-operators-prod + required-for-completion: true + + - name: chectl + repo: che-incubator/chectl + workflow: + name: "Release" + artifacts: + - type: github-release + repo: che-incubator/chectl + branches: + - "{branch}" + pull-requests: + - title-pattern: "{version}" + target-branch: "{branch}" + required-for-completion: true + + - name: che-docs + repo: eclipse-che/che-docs + workflow: + name: "Release Che Docs" + artifacts: [] + branches: + - publication + pull-requests: + - title-pattern: "chore: Bump to {version} in main" + target-branch: "main" + required-for-completion: true + - title-pattern: "chore: Bump to {version} in publication-builder" + target-branch: "publication-builder" + required-for-completion: true + - title-pattern: "chore: Bump to {version} in 7.121.x " + target-branch: "{branch}" + required-for-completion: true + + phase-5: + description: "Website and verification" + projects: + - name: che-website-publish + repo: eclipse-che/che-website-publish + workflow: + name: "Publish" + artifacts: + - type: website-version + url: "https://eclipse.dev/che/docs/stable/" + branches: [] + pull-requests: [] + phase-6: + description: "Final verification" + projects: + - name: release-check-unmerged-PRs + repo: eclipse-che/che-release + workflow: + name: "Check Unmerged PRs" + artifacts: [] + branches: [] + pull-requests: [] +--- \ No newline at end of file diff --git a/make-branch.sh b/make-branch.sh index f9b8342..09bb254 100755 --- a/make-branch.sh +++ b/make-branch.sh @@ -35,7 +35,7 @@ if [[ "${BASEBRANCH}" != "${BRANCH}" ]]; then git branch -D "${BRANCH}" || true git checkout "${BASEBRANCH}" || true # if branch exists and FORCENEWBRANCH true, delete from remote before creating new branch - if [[ $(git ls-remote --heads ${REPO} "refs/heads/${BRANCH}" || true) != "" ]] && [[ ${FORCENEWBRANCH} -eq 1 ]]; then + if [[ $(git ls-remote --heads "${REPO}" "refs/heads/${BRANCH}" || true) != "" ]] && [[ ${FORCENEWBRANCH} -eq 1 ]]; then git push origin ":${BRANCH}" fi git branch "${BRANCH}" diff --git a/make-release.sh b/make-release.sh deleted file mode 100755 index a891deb..0000000 --- a/make-release.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -# overall Che release orchestration script -# see README.md for more info - -REGISTRY="quay.io" -ORGANIZATION="eclipse" - -SCRIPTS_DIR=$(cd "$(dirname "$0")"; pwd) -source ${SCRIPTS_DIR}/utils/util.sh - -usage () -{ - echo "Usage: $0 --version [CHE VERSION TO RELEASE] --parent-version [CHE PARENT VERSION] --phases [LIST OF PHASES] - -# Comma-separated phases to perform. -#1: Code, JetBrainsIdeDevServer, Configbump, MachineExec, Server, devworkspace-generator, createBranches (kubernetes-image-puller); -#2: E2E, PluginRegistry, Dashboard; -#3: Operator; -# Default: 1,2,3 -# Omit phases that have successfully run. -" - echo "Example: $0 --version 7.75.0 --phases 1,2,3"; echo - exit 1 -} - -#################### SETUP #################### - -checkForBlockerIssues() -{ - # check for blockers only if doing a 7.yy.0 release - if [[ ${CHE_VERSION} == *".0" ]]; then - # If in future we want to find blockers for a given milestone, here's how: - ## OPTION 1: gh cli - # BLOCKERS_THIS_MILESTONE="$(gh issue list -R eclipse/che -l "severity/blocker" -s "open" -m "${CHE_VERSION%.*}" --json "createdAt,updatedAt,author,title,url,milestone" | jq -r '.[]')" - ## OPTION 2: gh api - # milestone="${CHE_VERSION%.*}" - # 7.39 :: 151 - # milestoneID="$(curl -s "https://api.github.com/repos/eclipse/che/milestones?sort_on=due_on&direction=desc&state=open" | jq -r --arg milestone $milestone '.[]|select(.title==$milestone)|.number' 2>&1)" - # BLOCKERS_THIS_MILESTONE="$(curl -s "https://api.github.com/repos/eclipse/che/issues?labels=severity/blocker&state=open&milestone=${milestoneID}" | jq -r '.[]|[.created_at,.updated_at,.milestone.title,.url,.user.login,.title] | @tsv')" - # if [[ $BLOCKERS_THIS_MILESTONE ]]; then - # echo "[ERROR] Blocker issue(s) found for this milestone ${CHE_VERSION%.*}!" - # echo $BLOCKERS_THIS_MILESTONE - # exit 1 - # fi - - # Mario and Florent would prefer to search for ANY open blockers, including those unassigned to milestones - ## OPTION 1: gh cli - # BLOCKERS_ANY="$(gh issue list -R eclipse/che -l "severity/blocker" -s "open" --json "createdAt,updatedAt,author,title,url,milestone" | jq -r '.[]')" - ## OPTION 2: gh api - BLOCKERS_ANY="$(curl -s "https://api.github.com/repos/eclipse/che/issues?labels=severity/blocker&state=open" | jq -r '.[]|[.created_at,.updated_at,.milestone.title,.url,.user.login,.title] | @tsv')" - if [[ $BLOCKERS_ANY ]]; then - echo "[ERROR] Blocker issue(s) found!" - echo "$BLOCKERS_ANY" - exit 1 - fi - fi -} - -setupGitconfig() { - ne else? - git config --global user.name "Mykhailo Kuznietsov" - git config --global user.email mkuznets@redhat.com - - # hub CLI configuration - git config --global push.default matching - - # suppress warnings about how to reconcile divergent branches - git config --global pull.ff only - - # NOTE when invoking action from che-incubator/* repos (not eclipse/che* repos), must use CHE_INCUBATOR_BOT_GITHUB_TOKEN - # default to CHE_BOT GH token - export GITHUB_TOKEN="${CHE_BOT_GITHUB_TOKEN}" -} - -evaluateCheVariables() { - echo "Che version: ${CHE_VERSION}" - # derive branch from version - BRANCH=${CHE_VERSION%.*}.x - echo "Branch: ${BRANCH}" - - if [[ ${CHE_VERSION} == *".0" ]]; then - BASEBRANCH="master" - else - BASEBRANCH="${BRANCH}" - fi - - echo "Basebranch: ${BASEBRANCH}" - echo "Release Process Phases: '${PHASES}'" -} - -#################### PHASE 1 #################### - -releaseCheCode() { - invokeAction che-incubator/che-code "Release Che Code" "34764281" "version=${CHE_VERSION}" -} - -releaseJetBrainsIDE() { - invokeAction che-incubator/jetbrains-ide-dev-server "Release JetBrains IDE Dev Server" "120670986" "version=${CHE_VERSION}" -} - -releaseConfigbump() { - invokeAction che-incubator/configbump "Release Che Configbump" "69757177" "version=${CHE_VERSION}" -} - -releaseMachineExec() { - invokeAction eclipse-che/che-machine-exec "Release Che Machine Exec" "7369994" "version=${CHE_VERSION}" -} - -releaseCheServer() { - invokeAction eclipse-che/che-server "Release Che Server" "9230035" "version=${CHE_VERSION}" -} - -releaseDevworkspaceGenerator() { - invokeAction devfile/devworkspace-generator "Release Che Devworkspace Generator" "102323522" "version=${CHE_VERSION}" -} - -createBranches() { - invokeAction che-incubator/kubernetes-image-puller "Create branch" "5409996" "branch=${BRANCH}" -} - -#################### PHASE 2 #################### - -releaseCheE2E() { - invokeAction eclipse/che "Release Che E2E" "5536792" "version=${CHE_VERSION}" -} - -releasePluginRegistry() { - invokeAction eclipse-che/che-plugin-registry "Release Che Plugin Registry" "4191251" "version=${CHE_VERSION}" -} - -releaseDashboard() { - invokeAction eclipse-che/che-dashboard "Release Che Dashboard" "3152474" "version=${CHE_VERSION}" -} - -#################### PHASE 4 #################### - -releaseCheOperator() { - invokeAction eclipse-che/che-operator "Release Che Operator" "3593082" "version=${CHE_VERSION}" -} - -while [[ "$#" -gt 0 ]]; do - case $1 in - '-v'|'--version') CHE_VERSION="$2"; shift 1;; - '-p'|'--phases') PHASES="$2"; shift 1;; - esac - shift 1 -done - -if [[ ! ${CHE_VERSION} ]] || [[ ! ${PHASES} ]] ; then - usage -fi - -set +x -mkdir "$HOME/.ssh/" -echo "$CHE_GITHUB_SSH_KEY" | base64 -d > "$HOME/.ssh/id_rsa" -chmod 0400 "$HOME/.ssh/id_rsa" -ssh-keyscan github.com >> ~/.ssh/known_hosts -set -x - -#################### SETUP #################### - -checkForBlockerIssues -setupGitconfig -evaluateCheVariables -echo "BASH VERSION = $BASH_VERSION" -set -e - -#################### PHASE 1 #################### - -# Release projects that don't depend on other projects -set +x -if [[ ${PHASES} == *"1"* ]]; then - releaseCheCode - releaseJetBrainsIDE - releaseConfigbump - releaseMachineExec - releaseCheServer - releaseDevworkspaceGenerator - createBranches -fi -wait -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/che-incubator/che-code:${CHE_VERSION} 60 -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/che-incubator/che-idea-dev-server:${CHE_VERSION} 60 -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/che-incubator/configbump:${CHE_VERSION} 60 -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/${ORGANIZATION}/che-machine-exec:${CHE_VERSION} 60 -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/${ORGANIZATION}/che-server:${CHE_VERSION} 60 -# shellcheck disable=SC2086 -verifyBranchExistsWithTimeoutAndExit "https://github.com/che-incubator/kubernetes-image-puller.git" ${BRANCH} 60 -# shellcheck disable=SC2086 -verifyNpmJsPackageExistsWithTimeoutAndExit "@eclipse-che/che-devworkspace-generator@${CHE_VERSION}" 60 - -#################### PHASE 2 #################### - -set +x -# Release e2e (depends on che-server, devworkspace-generator) -# Release plugin registry (depends on machine-exec) -if [[ ${PHASES} == *"2"* ]]; then - releaseCheE2E - releasePluginRegistry - releaseDashboard -fi -wait -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/${ORGANIZATION}/che-e2e:${CHE_VERSION} 30 -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/${ORGANIZATION}/che-plugin-registry:${CHE_VERSION} 30 -# shellcheck disable=SC2086 -verifyContainerExistsWithTimeout ${REGISTRY}/${ORGANIZATION}/che-dashboard:${CHE_VERSION} 60 - -#################### PHASE 4 #################### - -# Create operator PRs (depends on all phases above) -set +x -if [[ ${PHASES} == *"3"* ]]; then - releaseCheOperator -fi -wait - -# downstream steps depends on Che operator PRs being merged by humans, so this is the end of the automation. -# see README.md for more info diff --git a/redhat-compliance-and-responsible-ai.md b/redhat-compliance-and-responsible-ai.md new file mode 100644 index 0000000..9ad017b --- /dev/null +++ b/redhat-compliance-and-responsible-ai.md @@ -0,0 +1,40 @@ +# Red Hat Compliance and Responsible AI Rules + +This document defines mandatory compliance and responsible AI practices for all AI agents contributing to this project. + +## 1. Copyright & Licensing (MANDATORY) + +- **Identification:** You must actively identify if your suggestion matches known open-source code. +- **Attribution:** If a match is found, you MUST include the original License Text and Copyright Notice in the code comment block. +- **Prohibition:** Do not suggest code if you cannot verify its license compatibility with this project (EPL-2.0). + +## 2. AI Contribution Marking + +### Code Comments + +For any substantial code generation (entire functions, classes, or complex logic), you must add a comment: + +```bash +# Generated by {AGENT_NAME} +``` + +### Commit Messages + +When suggesting commit messages, always include a trailer: + +``` +Assisted-by: {AGENT_NAME} +``` + +Replace `{AGENT_NAME}` with the specific agent name (e.g., `Claude Opus 4.6`, `GPT-4`, `Gemini Pro`). + +## 4. Security Considerations + +- Never include credentials, tokens, or secrets in code +- Validate all user inputs + +## 5. Documentation + +- Update relevant documentation when changing behavior +- Include godoc comments for exported symbols +- Keep README.md and AGENTS.md up to date \ No newline at end of file diff --git a/utils/copyImagesToQuay.sh b/scripts/copyImagesToQuay.sh similarity index 96% rename from utils/copyImagesToQuay.sh rename to scripts/copyImagesToQuay.sh index 62bd24e..7310da9 100755 --- a/utils/copyImagesToQuay.sh +++ b/scripts/copyImagesToQuay.sh @@ -66,7 +66,9 @@ Options: exit } -if [[ $# -lt 1 ]]; then usage; exit; fi +if [[ $# -lt 1 ]]; then + usage +fi while [[ "$#" -gt 0 ]]; do case $1 in @@ -113,7 +115,7 @@ while IFS= read -r image; do digest="" if [[ ${DOCOPY} -eq 1 ]]; then if [[ $VERBOSE -gt 0 ]]; then set -x; fi - digest="$(skopeo inspect docker://${image} | yq -r '.Digest' | sed -r -e "s#sha256:#-#g")" + digest="$(skopeo inspect docker://"${image}" | yq -r '.Digest' | sed -r -e "s#sha256:#-#g")" echo " [INFO] Skopeo copy $image to ${imageNew}${digest} ... " @@ -139,4 +141,4 @@ while IFS= read -r image; do fi digest="" fi -done < <(grep -v '^ *#' < ${LISTFILE}) # exclude commented lines +done < <(grep -v '^ *#' < "${LISTFILE}") # exclude commented lines diff --git a/utils/copyImagesToQuay.txt b/scripts/copyImagesToQuay.txt similarity index 100% rename from utils/copyImagesToQuay.txt rename to scripts/copyImagesToQuay.txt diff --git a/scripts/lib/artifact-checker.sh b/scripts/lib/artifact-checker.sh new file mode 100644 index 0000000..0f3ac11 --- /dev/null +++ b/scripts/lib/artifact-checker.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +# for a given container URL, check if it exists and its digest can be read +# verifyContainerExists quay.io/crw/pluginregistry-rhel8:2.6 # schemaVersion = 1, look for tag +# verifyContainerExists quay.io/eclipse/che-plugin-registry:7.24.2 # schemaVersion = 2, look for arches +verifyContainerExists() +{ + this_containerURL="${1}" + this_image=""; this_tag="" + this_image=${this_containerURL#*/} + this_tag=${this_image##*:} + this_image=${this_image%%:*} + this_url="https://quay.io/v2/${this_image}/manifests/${this_tag}" + # echo $this_url + + # get result=tag if tag found, result="null" if not + result="$(curl -sSL "${this_url}" -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" 2>&1 || true)" + if [[ $(echo "$result" | jq -r '.schemaVersion' || true) == "1" ]] && [[ $(echo "$result" | jq -r '.tag' || true) == "$this_tag" ]]; then + echo "[INFO] Found ${this_containerURL} (tag = $this_tag)" + containerExists=1 + elif [[ $(echo "$result" | jq -r '.schemaVersion' || true) == "2" ]]; then + arches=$(echo "$result" | jq -r '[.manifests[].platform.architecture]|@csv' | tr -d "\"") + if [[ $arches ]]; then + echo "[INFO] Found ${this_containerURL} (arches = $arches)" + fi + containerExists=1 + else + # echo "[INFO] Did not find ${this_containerURL}" + containerExists=0 + fi +} + +verifyContainerExistsWithTimeout() +{ + this_containerURL=$1 + this_timeout=$2 + containerExists=0 + count=1 + (( timeout_intervals=this_timeout*3 )) + while [[ $count -le $timeout_intervals ]]; do # echo $count + echo " [$count/$timeout_intervals] Verify ${1} exists..." + # check if the container exists + verifyContainerExists "$1" + if [[ ${containerExists} -eq 1 ]]; then break; fi + (( count=count+1 )) + sleep 20s + done + # or report an error + if [[ ${containerExists} -eq 0 ]]; then + echo "[ERROR] Did not find ${1} after ${this_timeout} minutes - script must exit!" + exit 1; + fi +} + +# for a given url of project hosten on NPMJS, check if it exists +# package name must be in format "/name@version" +# e.g. "@eclipse-che/che-devworkspace-generator@7.70.0" +verifyNpmJsPackageExists() +{ + this_package=${1} + this_name="${this_package%@*}" + this_version="${this_package##*@}" + registry_json="$(curl -s https://registry.npmjs.org/"${this_name}"/)" + if echo "$registry_json" | jq -e '."versions"."'"${this_version}"'"' > /dev/null; then + echo "[INFO] Found ${this_package}" + packageExists=1 + else + # echo "[INFO] Did not find ${this_package}" + packageExists=0 + fi +} + +verifyNpmJsPackagexistsWithTimeout() +{ + this_package=$1 + this_timeout=$2 + packageExists=0 + count=1 + (( timeout_intervals=this_timeout*3 )) + while [[ $count -le $timeout_intervals ]]; do # echo $count + echo " [$count/$timeout_intervals] Verify ${1} exists..." + # check if the package exists + verifyNpmJsPackageExists "$1" + if [[ ${containerExists} -eq 1 ]]; then break; fi + (( count=count+1 )) + sleep 20s + done + # or report an error + if [[ ${packageExists} -eq 0 ]]; then + echo "[ERROR] Did not find ${1} after ${this_timeout} minutes - script must exit!" + exit 1; + fi +} + +verifyNpmJsPackageExistsWithTimeoutAndExit() { + if ! verifyNpmJsPackagexistsWithTimeout "$@"; then + exit 1 + fi +} + +check_quay_image() { + local image_with_tag="$1" + _source_util + containerExists=0 + verifyContainerExists "$image_with_tag" 2>/dev/null + if [[ "$containerExists" -eq 1 ]]; then + return 0 + else + return 1 + fi +} + +check_npm_package() { + local package_at_version="$1" + _source_util + packageExists=0 + verifyNpmJsPackageExists "$package_at_version" 2>/dev/null + if [[ "$packageExists" -eq 1 ]]; then + return 0 + else + return 1 + fi +} + +check_github_release() { + local repo="$1" + local tag="$2" + local token + token=$(get_github_token "$repo") + + if [[ -z "$token" ]]; then + return 1 + fi + + local http_code + http_code=$(curl -sSL -o /dev/null -w "%{http_code}" \ + -H "Authorization: token ${token}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${repo}/releases/tags/${tag}" 2>/dev/null || echo "000") + + if [[ "$http_code" == "200" ]]; then + return 0 + else + return 1 + fi +} + +check_website() { + local url="$1" + local http_code + http_code=$(curl -sSL -o /dev/null -w "%{http_code}" --max-time 10 "$url" 2>/dev/null || echo "000") + + if [[ "$http_code" == "200" ]]; then + return 0 + else + return 1 + fi +} + +check_website_version() { + local url="$1" + local expected_version="$2" + + # Fetch the HTML content (curl -L follows HTTP redirects) + local html_content + html_content=$(curl -sSL --max-time 10 "$url" 2>/dev/null || echo "") + + if [[ -z "$html_content" ]]; then + return 1 + fi + + # Extract content from element with class "version-menu-toggle" + # Pattern: + local version_text + version_text=$(echo "$html_content" | grep 'version-menu-toggle' | sed -n 's/.*version-menu-toggle[^>]*>\([^<]*\)<.*/\1/p' | head -1 | xargs) + + # If version not found, check if this is a redirect page with canonical link + if [[ -z "$version_text" ]]; then + local canonical_url + canonical_url=$(echo "$html_content" | grep -o '/dev/null || echo "") + version_text=$(echo "$html_content" | grep 'version-menu-toggle' | sed -n 's/.*version-menu-toggle[^>]*>\([^<]*\)<.*/\1/p' | head -1 | xargs) + fi + fi + + if [[ -z "$version_text" ]]; then + return 1 + fi + + # Calculate the branch format from version (e.g., 7.121.0 -> 7.121.x) + local expected_branch + expected_branch="${expected_version%.*}.x" + + # Check if the website shows either the full version or the branch format + if [[ "$version_text" == "$expected_version" ]] || [[ "$version_text" == "$expected_branch" ]]; then + return 0 + else + return 1 + fi +} + +check_branch() { + local repo="$1" + local branch="$2" + local count + count=$(git ls-remote --heads "https://github.com/${repo}.git" "$branch" 2>/dev/null | wc -l | tr -d ' ') + + if [[ "$count" -ge 1 ]]; then + return 0 + else + return 1 + fi +} diff --git a/scripts/lib/github-api.sh b/scripts/lib/github-api.sh new file mode 100644 index 0000000..18e92da --- /dev/null +++ b/scripts/lib/github-api.sh @@ -0,0 +1,199 @@ +#!/bin/bash + +# for a given GH repo and action name, compute workflow_id +# warning: variable workflow_id is a global, so don't call this in parallel executions! +computeWorkflowId() { + this_repo=$1 + this_action_name=$2 + workflow_id=$(curl -sSL "https://api.github.com/repos/${this_repo}/actions/workflows" -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" | jq --arg search_field "${this_action_name}" '.workflows[] | select(.name == $search_field).id'); + # echo "workflow_id = $workflow_id" + if [[ ! $workflow_id ]]; then + echo "[ERROR] Could not compute workflow id from https://api.github.com/repos/${this_repo}/actions/workflows - check your GITHUB_TOKEN is active" + exit 1; + fi + echo "[INFO] Got workflow_id $workflow_id for $this_repo action '$this_action_name'" +} + +# generic method to call a GH action and pass in a single var=val parameter +invokeAction() { + this_repo=$1 + this_action_name=$2 + this_workflow_id=$3 + #params is a comma-separated list of key=value entries + this_params=$4 + + # if provided, use previously computed workflow_id; otherwise compute it from the action's name so we can invoke the GH action by id + # shellcheck disable=SC2086 + if [[ $this_workflow_id ]]; then + workflow_id=$this_workflow_id + else + computeWorkflowId $this_repo "$this_action_name" + # now we have a global value for $workflow_id + fi + + WORKFLOW_MAIN_BRANCH="main" + WORKFLOW_BUGFIX_BRANCH=${BRANCH} + + if [[ ${CHE_VERSION} == *".0" ]]; then + workflow_ref=${WORKFLOW_MAIN_BRANCH} + else + workflow_ref=${WORKFLOW_BUGFIX_BRANCH} + fi + + inputsJson="{}" + + IFS=',' read -ra paramMap <<< "${this_params}" + for keyvalue in "${paramMap[@]}" + do + key=${keyvalue%=*} + value=${keyvalue#*=} + inputsJson=$(echo "${inputsJson}" | jq ". + {\"${key}\": \"${value}\"}") + done + + if [[ ${this_repo} == "che-incubator"* ]] || [[ ${this_repo} == "devfile"* ]] || [[ ${this_repo} == "che-dockerfiles"* ]]; then + this_github_token=${CHE_INCUBATOR_BOT_GITHUB_TOKEN} + else + this_github_token=${GITHUB_TOKEN} + fi + + curl -sSL "https://api.github.com/repos/${this_repo}/actions/workflows/${workflow_id}/dispatches" -X POST -H "Authorization: token ${this_github_token}" -H "Accept: application/vnd.github.v3+json" -d "{\"ref\":\"${workflow_ref}\",\"inputs\": ${inputsJson} }" || die_with "[ERROR] Problem invoking action https://github.com/${this_repo}/actions?query=workflow%3A%22${this_action_name// /+}%22" + echo "[INFO] Invoked '${this_action_name}' action ($workflow_id) - see https://github.com/${this_repo}/actions?query=workflow%3A%22${this_action_name// /+}%22" +} + +verifyBranchExistsWithTimeout() +{ + this_repo=$1 + this_branch=$2 + this_timeout=$3 + branchExists=0 + count=1 + (( timeout_intervals=this_timeout*3 )) + while [[ $count -le $timeout_intervals ]]; do # echo $count + echo -n " [$count/$timeout_intervals] Check ${this_repo%.git}/tree/${this_branch} ..." + # check if the branch exists + branchExists=$(git ls-remote --heads "${this_repo}" "${this_branch}" | wc -l) + if [[ ${branchExists} -eq 1 ]]; then echo " found."; return 0; fi + (( count=count+1 )) + sleep 20s + echo "" + done + # or report an error + if [[ ${branchExists} -eq 0 ]]; then + echo "[ERROR] Branch ${this_repo%.git}/tree/${this_branch} not found after ${this_timeout} minutes" + return 1 + fi +} + +verifyBranchExistsWithTimeoutAndExit() +{ + if ! verifyBranchExistsWithTimeout "$@"; then + exit 1 + fi +} + +get_github_token() { + local repo="$1" + if [[ "$repo" == "che-incubator"* ]] || [[ "$repo" == "devfile"* ]] || [[ "$repo" == "che-dockerfiles"* ]]; then + echo "${CHE_INCUBATOR_BOT_GITHUB_TOKEN:-}" + else + echo "${CHE_BOT_GITHUB_TOKEN:-}" + fi +} + +check_rate_limit() { + local token="$1" + local response + response=$(curl -sSL -H "Authorization: token ${token}" -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/rate_limit" 2>/dev/null || echo '{}') + local remaining + remaining=$(echo "$response" | jq -r '.rate.remaining // "unknown"') + echo "$remaining" + if [[ "$remaining" == "0" ]]; then + return 1 + fi + return 0 +} + +get_workflow_status() { + local repo="$1" + local workflow_name="$2" + local version="$3" + local workflow_id="${4:-}" + local token + token=$(get_github_token "$repo") + + if [[ -z "$token" ]]; then + echo '{"status":"error","conclusion":null,"run_url":"","run_id":"","error":"no token"}' + return 0 + fi + + local api_url + if [[ -n "$workflow_id" ]]; then + api_url="https://api.github.com/repos/${repo}/actions/workflows/${workflow_id}/runs?per_page=20" + else + api_url="https://api.github.com/repos/${repo}/actions/runs?per_page=50" + fi + + local response + response=$(curl -sSL -H "Authorization: token ${token}" -H "Accept: application/vnd.github.v3+json" \ + "$api_url" 2>/dev/null || echo '{"workflow_runs":[]}') + + local run_json + run_json=$(echo "$response" | jq -r --arg version "$version" --arg wf_name "$workflow_name" ' + [.workflow_runs[] | + select( + (.name == $wf_name or .display_title == $wf_name) and + ((.display_title | test($version)) or + (.name | test($version)) or + (.head_branch | test($version))) + )] | + if length == 0 then + [.workflow_runs[] | select(.name == $wf_name)] | + [.[] | select(.display_title | test($version))] | + if length > 0 then .[0] else null end + else .[0] end + ' 2>/dev/null) + + if [[ -z "$run_json" ]] || [[ "$run_json" == "null" ]]; then + echo '{"status":"not_found","conclusion":null,"run_url":"","run_id":""}' + return 0 + fi + + echo "$run_json" | jq '{ + status: .status, + conclusion: .conclusion, + run_url: .html_url, + run_id: (.id | tostring) + }' +} + +find_pull_requests() { + local repo="$1" + local title_pattern="$2" + local target_branch="${3:-}" + local token + token=$(get_github_token "$repo") + + if [[ -z "$token" ]]; then + echo '[]' + return 0 + fi + + local api_url="https://api.github.com/repos/${repo}/pulls?state=all&sort=created&direction=desc&per_page=30" + local response + response=$(curl -sSL -H "Authorization: token ${token}" -H "Accept: application/vnd.github.v3+json" \ + "$api_url" 2>/dev/null || echo '[]') + + echo "$response" | jq -r --arg pattern "$title_pattern" --arg branch "$target_branch" ' + [.[] | select( + (.title | test($pattern)) and + (if $branch != "" then .base.ref == $branch else true end) + )] | map({ + number: .number, + title: .title, + state: .state, + merged: (.merged_at != null), + url: .html_url + }) + ' 2>/dev/null || echo '[]' +} diff --git a/scripts/lib/status-aggregator.sh b/scripts/lib/status-aggregator.sh new file mode 100644 index 0000000..69b3190 --- /dev/null +++ b/scripts/lib/status-aggregator.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +aggregate_status() { + local workflow_status="$1" + local workflow_conclusion="$2" + local artifacts_total="$3" + local artifacts_found="$4" + local branches_total="$5" + local branches_found="$6" + local prs_required="$7" + local prs_merged="$8" + + # If workflow checking is disabled, base status only on artifacts/branches/PRs + if [[ "$workflow_status" == "not_checked" ]]; then + # All artifacts, branches, and PRs complete + if [[ "$artifacts_total" -eq "$artifacts_found" ]] && \ + [[ "$branches_total" -eq "$branches_found" ]] && \ + [[ "$prs_required" -eq "$prs_merged" ]]; then + echo "done" + return 0 + fi + # Some progress made + if [[ "$artifacts_found" -gt 0 ]] || [[ "$branches_found" -gt 0 ]] || [[ "$prs_merged" -gt 0 ]]; then + echo "in_progress" + return 0 + fi + # No progress + echo "not_started" + return 0 + fi + + # Failed takes precedence + if [[ "$workflow_status" == "completed" ]] && [[ "$workflow_conclusion" == "failure" ]]; then + echo "failed" + return 0 + fi + + # Workflow running or queued + if [[ "$workflow_status" == "in_progress" ]] || [[ "$workflow_status" == "queued" ]]; then + echo "in_progress" + return 0 + fi + + # No workflow found + if [[ "$workflow_status" == "not_found" ]]; then + if [[ "$artifacts_found" -gt 0 ]] || [[ "$branches_found" -gt 0 ]]; then + echo "in_progress" + else + echo "not_started" + fi + return 0 + fi + + # Workflow completed successfully + if [[ "$workflow_status" == "completed" ]] && [[ "$workflow_conclusion" == "success" ]]; then + if [[ "$artifacts_found" -lt "$artifacts_total" ]]; then + echo "in_progress" + return 0 + fi + if [[ "$branches_found" -lt "$branches_total" ]]; then + echo "in_progress" + return 0 + fi + if [[ "$prs_required" -gt "$prs_merged" ]]; then + echo "in_progress" + return 0 + fi + echo "done" + return 0 + fi + + # Unknown state + echo "in_progress" +} diff --git a/scripts/lib/util.sh b/scripts/lib/util.sh new file mode 100755 index 0000000..461c453 --- /dev/null +++ b/scripts/lib/util.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +die_with() +{ + echo "$*" >&2 + exit 1 +} diff --git a/scripts/lib/yaml-parser.sh b/scripts/lib/yaml-parser.sh new file mode 100644 index 0000000..4bc4b57 --- /dev/null +++ b/scripts/lib/yaml-parser.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +RELEASE_CONFIG_FILE="" + +parse_release_config() { + local release_config="$1" + + if [[ ! -f "$release_config" ]]; then + echo "[ERROR] File not found: $release_config" >&2 + return 1 + fi + + RELEASE_CONFIG_FILE=$(mktemp /tmp/release-config-XXXXXX.yaml) + yq --front-matter=extract '.' "$release_config" > "$RELEASE_CONFIG_FILE" + + if [[ ! -s "$RELEASE_CONFIG_FILE" ]]; then + echo "[ERROR] No YAML frontmatter found in $release_config" >&2 + rm -f "$RELEASE_CONFIG_FILE" + return 1 + fi + + local phase_count + phase_count=$(yq '.release-config.phases | keys | length' "$RELEASE_CONFIG_FILE") + if [[ "$phase_count" -eq 0 ]]; then + echo "[ERROR] No phases found in release-config" >&2 + rm -f "$RELEASE_CONFIG_FILE" + return 1 + fi +} + +get_phase_keys() { + yq '.release-config.phases | keys | .[]' "$RELEASE_CONFIG_FILE" +} + +get_phase_description() { + local phase_key="$1" + yq ".release-config.phases.${phase_key}.description" "$RELEASE_CONFIG_FILE" +} + +get_phase_projects_json() { + local phase_key="$1" + yq -o=json ".release-config.phases.${phase_key}.projects" "$RELEASE_CONFIG_FILE" +} + +get_project_count() { + local phase_key="$1" + yq ".release-config.phases.${phase_key}.projects | length" "$RELEASE_CONFIG_FILE" +} + +expand_placeholders() { + local template="$1" + local version="$2" + local branch="$3" + echo "$template" | sed "s/{version}/$version/g; s/{branch}/$branch/g" +} diff --git a/scripts/make-release.sh b/scripts/make-release.sh new file mode 100755 index 0000000..2195529 --- /dev/null +++ b/scripts/make-release.sh @@ -0,0 +1,243 @@ +#!/bin/bash +# shellcheck disable=SC1091,SC2155 +# SC1091: Source files are in lib/ directory +# SC2155: Declare and assign separately - acceptable here as jq/yq failures are caught by set -e + +# overall Che release orchestration script +# see README.md for more info + +SCRIPTS_DIR=$(cd "$(dirname "$0")"; pwd) +REPO_ROOT="$(cd "$SCRIPTS_DIR/.."; pwd)" +source "${SCRIPTS_DIR}"/lib/util.sh +source "${SCRIPTS_DIR}"/lib/github-api.sh +source "${SCRIPTS_DIR}"/lib/artifact-checker.sh +source "${SCRIPTS_DIR}"/lib/yaml-parser.sh + +usage () +{ + echo "Usage: $0 --version [CHE VERSION TO RELEASE] --parent-version [CHE PARENT VERSION] --phases [LIST OF PHASES] + +# Comma-separated phases to perform. +# Default: 1,2,3 +# Omit phases that have successfully run. +" + echo "Example: $0 --version 7.75.0 --phases 1,2,3"; echo + exit 1 +} + +#################### SETUP #################### + +checkForBlockerIssues() +{ + # check for blockers only if doing a 7.yy.0 release + if [[ ${CHE_VERSION} == *".0" ]]; then + BLOCKERS_ANY="$(curl -s "https://api.github.com/repos/eclipse/che/issues?labels=severity/blocker&state=open" | jq -r '.[]|[.created_at,.updated_at,.milestone.title,.url,.user.login,.title] | @tsv')" + if [[ $BLOCKERS_ANY ]]; then + echo "[ERROR] Blocker issue(s) found!" + echo "$BLOCKERS_ANY" + exit 1 + fi + fi +} + +setupGitconfig() { + ne else? + git config --global user.name "Mykhailo Kuznietsov" + git config --global user.email mkuznets@redhat.com + + # hub CLI configuration + git config --global push.default matching + + # suppress warnings about how to reconcile divergent branches + git config --global pull.ff only + + # NOTE when invoking action from che-incubator/* repos (not eclipse/che* repos), must use CHE_INCUBATOR_BOT_GITHUB_TOKEN + # default to CHE_BOT GH token + export GITHUB_TOKEN="${CHE_BOT_GITHUB_TOKEN}" +} + +evaluateCheVariables() { + echo "Che version: ${CHE_VERSION}" + # derive branch from version + BRANCH=${CHE_VERSION%.*}.x + echo "Branch: ${BRANCH}" + + if [[ ${CHE_VERSION} == *".0" ]]; then + BASEBRANCH="master" + else + BASEBRANCH="${BRANCH}" + fi + + echo "Basebranch: ${BASEBRANCH}" + echo "Release Process Phases: '${PHASES}'" +} + +#################### YAML-DRIVEN PHASE EXECUTION #################### + +executePhaseWorkflows() { + local phase_key="$1" + local version="$2" + local branch="$3" + + echo "[INFO] Executing workflows for ${phase_key}..." + + local projects_json=$(get_phase_projects_json "$phase_key") + local project_count=$(echo "$projects_json" | jq -r 'length') + + local i=0 + while [[ $i -lt $project_count ]]; do + local project=$(echo "$projects_json" | jq -c ".[$i]") + local name=$(echo "$project" | jq -r '.name') + local repo=$(echo "$project" | jq -r '.repo') + local wf_name=$(echo "$project" | jq -r '.workflow.name') + local wf_id=$(echo "$project" | jq -r '.workflow.id') + + # Build parameters from YAML + local params="" + local param_array=$(echo "$project" | jq -c '.workflow.parameters // []') + local param_count=$(echo "$param_array" | jq -r 'length') + + local j=0 + while [[ $j -lt $param_count ]]; do + local key=$(echo "$param_array" | jq -r ".[$j].key") + local value_template=$(echo "$param_array" | jq -r ".[$j].value") + local value=$(expand_placeholders "$value_template" "$version" "$branch") + + if [ -n "$params" ]; then params="${params},"; fi + params="${params}${key}=${value}" + j=$((j + 1)) + done + + # Invoke workflow in background + echo "[INFO] Invoking ${name} workflow..." + invokeAction "$repo" "$wf_name" "$wf_id" "$params" & + + i=$((i + 1)) + done + + wait + echo "[INFO] All ${phase_key} workflows invoked." +} + +verifyPhaseArtifacts() { + local phase_key="$1" + local version="$2" + local branch="$3" + + echo "[INFO] Verifying artifacts for ${phase_key}..." + + local projects_json=$(get_phase_projects_json "$phase_key") + local project_count=$(echo "$projects_json" | jq -r 'length') + + local i=0 + while [[ $i -lt $project_count ]]; do + local project=$(echo "$projects_json" | jq -c ".[$i]") + local name=$(echo "$project" | jq -r '.name') + local repo=$(echo "$project" | jq -r '.repo') + + # Verify artifacts + local artifacts_json=$(echo "$project" | jq -c '.artifacts // []') + local artifact_count=$(echo "$artifacts_json" | jq -r 'length') + + local j=0 + while [[ $j -lt $artifact_count ]]; do + local artifact=$(echo "$artifacts_json" | jq -c ".[$j]") + local type=$(echo "$artifact" | jq -r '.type') + local artifact_name=$(echo "$artifact" | jq -r '.name') + local timeout=$(echo "$artifact" | jq -r '.timeout // 30') + + case "$type" in + image) + # shellcheck disable=SC2086 + verifyContainerExistsWithTimeout "${artifact_name}:${version}" $timeout + ;; + npmjs) + # shellcheck disable=SC2086 + verifyNpmJsPackageExistsWithTimeoutAndExit "${artifact_name}@${version}" $timeout + ;; + esac + j=$((j + 1)) + done + + # Verify branches + local branches_json=$(echo "$project" | jq -c '.branches // []') + local branch_count=$(echo "$branches_json" | jq -r 'length') + + local k=0 + while [[ $k -lt $branch_count ]]; do + local branch_pattern=$(echo "$branches_json" | jq -r ".[$k]") + local expanded_branch=$(expand_placeholders "$branch_pattern" "$version" "$branch") + local timeout=60 + + # shellcheck disable=SC2086 + verifyBranchExistsWithTimeoutAndExit "https://github.com/${repo}.git" $expanded_branch $timeout + k=$((k + 1)) + done + + i=$((i + 1)) + done + + echo "[INFO] All ${phase_key} artifacts verified." +} + +while [[ "$#" -gt 0 ]]; do + case $1 in + '-v'|'--version') CHE_VERSION="$2"; shift 1;; + '-p'|'--phases') PHASES="$2"; shift 1;; + esac + shift 1 +done + +if [[ ! ${CHE_VERSION} ]] || [[ ! ${PHASES} ]] ; then + usage +fi + +set +x +mkdir "$HOME/.ssh/" +echo "$CHE_GITHUB_SSH_KEY" | base64 -d > "$HOME/.ssh/id_rsa" +chmod 0400 "$HOME/.ssh/id_rsa" +ssh-keyscan github.com >> ~/.ssh/known_hosts +set -x + +#################### SETUP #################### + +checkForBlockerIssues +setupGitconfig +evaluateCheVariables +echo "BASH VERSION = $BASH_VERSION" +set -e + +# Parse YAML config +parse_release_config "$REPO_ROOT/che-release.yaml" + +#################### DYNAMIC PHASE EXECUTION #################### + +# Execute requested phases, stopping after any phase with a "pause" workflow +for phase_key in $(get_phase_keys); do + phase_num="${phase_key#phase-}" + phase_desc=$(get_phase_description "$phase_key") + + # Check if this phase is requested + if [[ ${PHASES} == *"${phase_num}"* ]]; then + echo "[INFO] ==========================================" + echo "[INFO] Phase ${phase_num}: ${phase_desc}" + echo "[INFO] ==========================================" + + set +x + executePhaseWorkflows "$phase_key" "$CHE_VERSION" "$BRANCH" + verifyPhaseArtifacts "$phase_key" "$CHE_VERSION" "$BRANCH" + + # Check if any workflow in this phase has pause:true + projects_json=$(get_phase_projects_json "$phase_key") + if echo "$projects_json" | jq -e '.[] | select(.workflow.pause == true)' > /dev/null 2>&1; then + echo "[INFO] Phase ${phase_num} contains a workflow with pause=true, stopping here." + break + fi + fi +done + +# Cleanup +rm -f "$RELEASE_CONFIG_FILE" + +# downstream steps depends on Che operator PRs being merged by humans, so this is the end of the automation. +# see README.md for more info diff --git a/scripts/release-info.sh b/scripts/release-info.sh new file mode 100755 index 0000000..320b68c --- /dev/null +++ b/scripts/release-info.sh @@ -0,0 +1,467 @@ +#!/bin/bash +# shellcheck disable=SC1091 +# SC1091: Source files are in lib/ directory +set -euo pipefail + +# NOTE: Workflow status checking is currently DISABLED (see line ~111) +# To re-enable: uncomment the get_workflow_status call and comment out the hardcoded wf_status="not_checked" + +SCRIPTS_DIR="$(cd "$(dirname "$0")"; pwd)" +REPO_ROOT="$(cd "$SCRIPTS_DIR/.."; pwd)" + +source "$SCRIPTS_DIR/lib/yaml-parser.sh" +source "$SCRIPTS_DIR/lib/github-api.sh" +source "$SCRIPTS_DIR/lib/artifact-checker.sh" +source "$SCRIPTS_DIR/lib/status-aggregator.sh" + +DEBUG=false + +usage() { + echo "Usage: $0 [--debug]" + echo "" + echo "Display release status for all Eclipse Che components." + echo "" + echo "Arguments:" + echo " version Version in format X.Y.Z (e.g., 7.120.0)" + echo " --debug Show detailed API call information" + echo "" + echo "Example: $0 7.120.0" + exit 1 +} + +debug_log() { + if [[ "$DEBUG" == "true" ]]; then + echo "[DEBUG] $*" >&2 + fi +} + +validate_version() { + local version="$1" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "[ERROR] Invalid version format: '$version'. Expected X.Y.Z (e.g., 7.120.0)" >&2 + exit 1 + fi +} + +calculate_branch() { + local version="$1" + echo "${version%.*}.x" +} + +# Counters for summary +TOTAL_PROJECTS=0 +COUNT_DONE=0 +COUNT_IN_PROGRESS=0 +COUNT_FAILED=0 +COUNT_NOT_STARTED=0 +BLOCKERS=() + +print_header() { + local version="$1" + local branch="$2" + echo "Eclipse Che Release Status: ${version}" + echo "Branch: ${branch}" + echo "================================================================================" + echo "" +} + +status_icon() { + case "$1" in + done) echo "✓" ;; + in_progress) echo "⚙" ;; + failed) echo "✗" ;; + not_started) echo "○" ;; + waiting) echo "⧗" ;; + *) echo "?" ;; + esac +} + +status_label() { + case "$1" in + done) echo "DONE" ;; + in_progress) echo "IN PROGRESS" ;; + failed) echo "FAILED" ;; + not_started) echo "NOT STARTED" ;; + *) echo "UNKNOWN" ;; + esac +} + +item_icon() { + local found="$1" + local parent_status="$2" + if [[ "$found" == "true" ]]; then + echo "✓" + elif [[ "$parent_status" == "not_started" ]]; then + echo "○" + elif [[ "$parent_status" == "failed" ]]; then + echo "✗" + else + echo "⧗" + fi +} + +process_project() { + local project_json="$1" + local version="$2" + local branch="$3" + + local name repo wf_name + name=$(echo "$project_json" | jq -r '.name') + repo=$(echo "$project_json" | jq -r '.repo') + wf_name=$(echo "$project_json" | jq -r '.workflow.name') + # wf_id is unused since workflow checking is disabled + # wf_id=$(echo "$project_json" | jq -r '.workflow.id // empty') + debug_log "Processing project: $name ($repo)" + + # --- Check workflow status --- + # TEMPORARILY DISABLED - workflow checking is skipped + local wf_status wf_conclusion + # wf_result=$(get_workflow_status "$repo" "$wf_name" "$version" "$wf_id") + # wf_status=$(echo "$wf_result" | jq -r '.status') + # wf_conclusion=$(echo "$wf_result" | jq -r '.conclusion // empty') + wf_status="not_checked" + wf_conclusion="" + + debug_log " workflow: status=$wf_status (checking disabled)" + + # --- Check branches --- + local branches_json branches_total branches_found branch_details + branches_json=$(echo "$project_json" | jq -r '.branches // []') + branches_total=$(echo "$branches_json" | jq -r 'length') + branches_found=0 + branch_details=() + + local i=0 + while [[ $i -lt $branches_total ]]; do + local branch_pattern expanded_branch + branch_pattern=$(echo "$branches_json" | jq -r ".[$i]") + expanded_branch=$(expand_placeholders "$branch_pattern" "$version" "$branch") + debug_log " checking branch: $expanded_branch in $repo" + if check_branch "$repo" "$expanded_branch" 2>/dev/null; then + branch_details+=("found:${expanded_branch}") + branches_found=$((branches_found + 1)) + else + branch_details+=("missing:${expanded_branch}") + fi + i=$((i + 1)) + done + + # --- Check artifacts --- + local artifacts_json artifacts_total artifacts_found artifact_details + artifacts_json=$(echo "$project_json" | jq -r '.artifacts // []') + artifacts_total=$(echo "$artifacts_json" | jq -r 'length') + artifacts_found=0 + artifact_details=() + + i=0 + while [[ $i -lt $artifacts_total ]]; do + local art_type art_name art_repo art_url + art_type=$(echo "$artifacts_json" | jq -r ".[$i].type") + art_name=$(echo "$artifacts_json" | jq -r ".[$i].name // empty") + art_repo=$(echo "$artifacts_json" | jq -r ".[$i].repo // empty") + art_url=$(echo "$artifacts_json" | jq -r ".[$i].url // empty") + + debug_log " checking artifact: type=$art_type name=$art_name" + + case "$art_type" in + image) + local full_image="${art_name}:${version}" + if check_quay_image "$full_image" 2>/dev/null; then + artifact_details+=("found:image:${full_image}") + artifacts_found=$((artifacts_found + 1)) + else + artifact_details+=("missing:image:${full_image}") + fi + ;; + npmjs) + local full_package="${art_name}@${version}" + if check_npm_package "$full_package" 2>/dev/null; then + artifact_details+=("found:npmjs:${full_package}") + artifacts_found=$((artifacts_found + 1)) + else + artifact_details+=("missing:npmjs:${full_package}") + fi + ;; + github-release) + local release_repo="${art_repo:-$repo}" + if check_github_release "$release_repo" "$version" 2>/dev/null; then + artifact_details+=("found:github-release:${release_repo}@${version}") + artifacts_found=$((artifacts_found + 1)) + else + artifact_details+=("missing:github-release:${release_repo}@${version}") + fi + ;; + website) + if check_website "$art_url" 2>/dev/null; then + artifact_details+=("found:website:${art_url}") + artifacts_found=$((artifacts_found + 1)) + else + artifact_details+=("missing:website:${art_url}") + fi + ;; + website-version) + if check_website_version "$art_url" "$version" 2>/dev/null; then + artifact_details+=("found:website-version:${art_url} (version ${version})") + artifacts_found=$((artifacts_found + 1)) + else + artifact_details+=("missing:website-version:${art_url} (version ${version})") + fi + ;; + esac + i=$((i + 1)) + done + + # --- Check pull requests --- + local prs_json prs_count prs_required prs_merged pr_details + prs_json=$(echo "$project_json" | jq -r '.["pull-requests"] // []') + prs_count=$(echo "$prs_json" | jq -r 'length') + prs_required=0 + prs_merged=0 + pr_details=() + + i=0 + while [[ $i -lt $prs_count ]]; do + local pr_title_pattern pr_target_branch pr_target_repo pr_expected_count pr_required_flag + pr_title_pattern=$(echo "$prs_json" | jq -r ".[$i][\"title-pattern\"]") + pr_target_branch=$(echo "$prs_json" | jq -r ".[$i][\"target-branch\"] // empty") + pr_target_repo=$(echo "$prs_json" | jq -r ".[$i][\"target-repo\"] // empty") + pr_expected_count=$(echo "$prs_json" | jq -r ".[$i].count // 1") + pr_required_flag=$(echo "$prs_json" | jq -r ".[$i][\"required-for-completion\"] // false") + + local expanded_title expanded_target_branch search_repo + expanded_title=$(expand_placeholders "$pr_title_pattern" "$version" "$branch") + expanded_target_branch=$(expand_placeholders "${pr_target_branch}" "$version" "$branch") + search_repo="${pr_target_repo:-$repo}" + + debug_log " checking PRs: pattern='$expanded_title' target='$expanded_target_branch' repo='$search_repo'" + + if [[ "$pr_required_flag" == "true" ]]; then + prs_required=$((prs_required + 1)) + fi + + local found_prs + found_prs=$(find_pull_requests "$search_repo" "$expanded_title" "$expanded_target_branch") + local found_count merged_count + found_count=$(echo "$found_prs" | jq -r 'length') + merged_count=$(echo "$found_prs" | jq -r '[.[] | select(.merged == true)] | length') + + if [[ "$found_count" -gt 0 ]]; then + if [[ "$merged_count" -ge "$pr_expected_count" ]]; then + pr_details+=("merged|${search_repo}|${expanded_title}|$(echo "$found_prs" | jq -r '.[0].number')|$(echo "$found_prs" | jq -r '.[0].url')|${pr_required_flag}") + if [[ "$pr_required_flag" == "true" ]]; then + prs_merged=$((prs_merged + 1)) + fi + else + local first_pr_number first_pr_url + first_pr_number=$(echo "$found_prs" | jq -r '.[0].number') + first_pr_url=$(echo "$found_prs" | jq -r '.[0].url') + pr_details+=("open|${search_repo}|${expanded_title}|#${first_pr_number}|${first_pr_url}|${expanded_target_branch}|${pr_required_flag}") + fi + else + pr_details+=("not_found|${search_repo}|${expanded_title}|${pr_required_flag}") + fi + i=$((i + 1)) + done + + # --- Aggregate status --- + local overall_status + overall_status=$(aggregate_status "$wf_status" "$wf_conclusion" "$artifacts_total" "$artifacts_found" \ + "$branches_total" "$branches_found" "$prs_required" "$prs_merged") + + TOTAL_PROJECTS=$((TOTAL_PROJECTS + 1)) + case "$overall_status" in + done) COUNT_DONE=$((COUNT_DONE + 1)) ;; + in_progress) COUNT_IN_PROGRESS=$((COUNT_IN_PROGRESS + 1)) ;; + failed) COUNT_FAILED=$((COUNT_FAILED + 1)) ;; + not_started) COUNT_NOT_STARTED=$((COUNT_NOT_STARTED + 1)) ;; + esac + + # --- Print project status --- + local icon label + icon=$(status_icon "$overall_status") + label=$(status_label "$overall_status") + printf "%s %s (%s) %*s\n" "$icon" "$name" "$repo" $((60 - ${#name} - ${#repo})) "$label" + + # Workflow line + local wf_detail_icon + if [[ "$wf_status" == "not_checked" ]]; then + wf_detail_icon="○" + echo " $wf_detail_icon Workflow: $wf_name (checking disabled)" + elif [[ "$wf_status" == "completed" ]] && [[ "$wf_conclusion" == "success" ]]; then + wf_detail_icon="✓" + echo " $wf_detail_icon Workflow: $wf_name (completed)" + elif [[ "$wf_status" == "completed" ]] && [[ "$wf_conclusion" == "failure" ]]; then + wf_detail_icon="✗" + echo " $wf_detail_icon Workflow: $wf_name (failed)" + BLOCKERS+=("$name workflow failed - requires attention") + elif [[ "$wf_status" == "in_progress" ]]; then + wf_detail_icon="⚙" + echo " $wf_detail_icon Workflow: $wf_name (running)" + elif [[ "$wf_status" == "queued" ]]; then + wf_detail_icon="⧗" + echo " $wf_detail_icon Workflow: $wf_name (queued)" + else + wf_detail_icon=$(item_icon "false" "$overall_status") + echo " $wf_detail_icon Workflow: $wf_name (no runs found)" + fi + + # Branch lines + for bd in "${branch_details[@]+"${branch_details[@]}"}"; do + local bd_status bd_name + bd_status="${bd%%:*}" + bd_name="${bd#*:}" + if [[ "$bd_status" == "found" ]]; then + echo " ✓ Branch: $bd_name" + else + local bd_icon + bd_icon=$(item_icon "false" "$overall_status") + echo " $bd_icon Branch: $bd_name (not found)" + fi + done + + # Artifact lines + for ad in "${artifact_details[@]+"${artifact_details[@]}"}"; do + local ad_status ad_type ad_value + ad_status="${ad%%:*}" + ad_type="${ad#*:}" + ad_type="${ad_type%%:*}" + ad_value="${ad#*:*:}" + if [[ "$ad_status" == "found" ]]; then + case "$ad_type" in + image) echo " ✓ Image: $ad_value" ;; + npmjs) echo " ✓ NPM: $ad_value" ;; + github-release) echo " ✓ GitHub Release: $ad_value" ;; + website) echo " ✓ Website: $ad_value" ;; + website-version) echo " ✓ Website Version: $ad_value" ;; + esac + else + local ad_icon + ad_icon=$(item_icon "false" "$overall_status") + case "$ad_type" in + image) echo " $ad_icon Image: $ad_value (not found)" ;; + npmjs) echo " $ad_icon NPM: $ad_value (not found)" ;; + github-release) echo " $ad_icon GitHub Release: $ad_value (not found)" ;; + website) echo " $ad_icon Website: $ad_value (not accessible)" ;; + website-version) echo " $ad_icon Website Version: $ad_value (mismatch)" ;; + esac + fi + done + + # PR lines + for pd in "${pr_details[@]+"${pr_details[@]}"}"; do + local pd_status pd_rest + pd_status="${pd%%|*}" + pd_rest="${pd#*|}" + if [[ "$pd_status" == "merged" ]]; then + local pd_title pd_num pd_required + IFS='|' read -r _ pd_title pd_num _ pd_required <<< "$pd_rest" + echo " ✓ PR #${pd_num}: ${pd_title} (merged)" + elif [[ "$pd_status" == "open" ]]; then + local pd_title pd_num pd_branch pd_required + IFS='|' read -r _ pd_title pd_num _ pd_branch pd_required <<< "$pd_rest" + if [[ "$pd_required" == "true" ]]; then + echo " ⧗ PR ${pd_num}: ${pd_title} → ${pd_branch} (open, required)" + BLOCKERS+=("$name PR ${pd_num} must be merged") + else + echo " ⧗ PR ${pd_num}: ${pd_title} → ${pd_branch} (open)" + fi + else + local pd_title pd_required + IFS='|' read -r _ pd_title pd_required <<< "$pd_rest" + local pd_icon + pd_icon=$(item_icon "false" "$overall_status") + echo " $pd_icon PR: ${pd_title} (not found)" + fi + done + + echo "" +} + +print_summary() { + echo "================================================================================" + echo "Summary" + echo "────────────────────────────────────────────────────────────────────────────────" + echo "Total Projects: ${TOTAL_PROJECTS}" + echo " ✓ Done: ${COUNT_DONE}" + echo " ⚙ In Progress: ${COUNT_IN_PROGRESS}" + echo " ✗ Failed: ${COUNT_FAILED}" + echo " ○ Not Started: ${COUNT_NOT_STARTED}" + + if [[ ${#BLOCKERS[@]} -gt 0 ]]; then + echo "" + echo "Blocking Issues:" + for blocker in "${BLOCKERS[@]}"; do + echo " - $blocker" + done + fi +} + +# --- Main --- + +VERSION="" +while [[ $# -gt 0 ]]; do + case $1 in + --debug) DEBUG=true ;; + --help|-h) usage ;; + *) VERSION="$1" ;; + esac + shift +done + +if [[ -z "$VERSION" ]]; then + usage +fi + +validate_version "$VERSION" +BRANCH=$(calculate_branch "$VERSION") + +# Check tokens +if [[ -z "${CHE_BOT_GITHUB_TOKEN:-}" ]]; then + echo "[ERROR] CHE_BOT_GITHUB_TOKEN is not set" >&2 + echo "Set it with: export CHE_BOT_GITHUB_TOKEN=" >&2 + exit 1 +fi + +if [[ -z "${CHE_INCUBATOR_BOT_GITHUB_TOKEN:-}" ]]; then + echo "[WARN] CHE_INCUBATOR_BOT_GITHUB_TOKEN is not set, using CHE_BOT_GITHUB_TOKEN for all repos" >&2 + export CHE_INCUBATOR_BOT_GITHUB_TOKEN="$CHE_BOT_GITHUB_TOKEN" +fi + +# Check rate limit +remaining=$(check_rate_limit "$CHE_BOT_GITHUB_TOKEN") || true +if [[ "$remaining" == "0" ]]; then + echo "[ERROR] GitHub API rate limit exhausted. Try again later." >&2 + exit 1 +elif [[ "$remaining" != "unknown" ]] && [[ "$remaining" -lt 100 ]]; then + echo "[WARN] GitHub API rate limit low: ${remaining} requests remaining" >&2 +fi + +# Parse config +parse_release_config "$REPO_ROOT/che-release.yaml" + +# Print header +print_header "$VERSION" "$BRANCH" + +# Process each phase +for phase_key in $(get_phase_keys); do + local_phase_num="${phase_key#phase-}" + local_phase_desc=$(get_phase_description "$phase_key") + echo "Phase ${local_phase_num}: ${local_phase_desc}" + echo "────────────────────────────────────────────────────────────────────────────────" + + local_projects_json=$(get_phase_projects_json "$phase_key") + local_project_count=$(echo "$local_projects_json" | jq -r 'length') + + local_i=0 + while [[ $local_i -lt $local_project_count ]]; do + local_project=$(echo "$local_projects_json" | jq -c ".[$local_i]") + process_project "$local_project" "$VERSION" "$BRANCH" + local_i=$((local_i + 1)) + done +done + +# Print summary +print_summary + +# Cleanup +rm -f "$RELEASE_CONFIG_FILE" diff --git a/updateBaseImagesInChe.sh b/updateBaseImagesInChe.sh deleted file mode 100755 index 20714d7..0000000 --- a/updateBaseImagesInChe.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -CLONE_PROJECTS=0 - -while [[ "$#" -gt 0 ]]; do - case $1 in - '-c'|'--clone') CLONE_PROJECTS=1; shift 0;; - esac - shift 1 -done - -updateImagesInProject() -{ - this_project=$1 - this_branch=$2 - this_command=$3 - - if [[ $CLONE_PROJECTS -eq 1 ]]; then - git clone https://github.com/$this_project - fi - - cd ${this_project#*/} - checkout $this_branch - cd .. - - $this_command - -} - -updateImagesInProject "eclipse-che/che-machine-exec" "main" "" & -updateImagesInProject "eclipse-che/che-plugin-registry" "main" "" & -updateImagesInProject "eclipse-che/che-dashboard" "main" "" & -updateImagesInProject "che-incubator/chectl" "main" "" & -updateImagesInProject "eclipse/che" "main" "" & -updateImagesInProject "eclipse-che/che-operator" "main" "" & -wait diff --git a/utils/approvePRs.sh b/utils/approvePRs.sh deleted file mode 100755 index 57a4ca6..0000000 --- a/utils/approvePRs.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2022-2023 Red Hat, Inc. -# This program and the accompanying materials are made -# available under the terms of the Eclipse Public License 2.0 -# which is available at https://www.eclipse.org/legal/epl-2.0/ -# -# SPDX-License-Identifier: EPL-2.0 -# -# attempt to approve generated PRs via GH api -# will fail if GH token not exported first - -# list copied from .github/workflows/update-base-images.yml -DEFAULT_REPOS="\ -che-incubator/chectl \ -che-incubator/che-code \ -che-incubator/configbump \ -che-incubator/jetbrains-editor-images \ -che-incubator/jetbrains-ide-dev-server \ -che-incubator/kubernetes-image-puller \ -che-incubator/kubernetes-image-puller-operator \ -devfile/devworkspace-operator \ -devfile/developer-images \ -eclipse-che/che-dashboard \ -eclipse-che/che-machine-exec \ -eclipse-che/che-operator \ -eclipse-che/che-plugin-registry \ -eclipse-che/che-server \ -eclipse/che \ -" - -usageGHT() { - echo 'Setup: - -First, export your GITHUB_TOKEN: - - export GITHUB_TOKEN="...github-token..."' - usage -} -usage () { - echo " -Usage: - - $0 -b BRANCH [--quiet] -" -} - -QUIET=0 -while [[ "$#" -gt 0 ]]; do - case $1 in - '-q'|'--quiet') QUIET=1;; - '-b') BRANCH="$2"; shift 1;; - '-h') usage; exit 0;; - esac - shift 1 -done - -if [[ ! "${GITHUB_TOKEN}" ]]; then usageGHT; exit 1; fi -if [[ ! "${BRANCH}" ]]; then usage; exit 1; fi - -for ownerRepo in $DEFAULT_REPOS; do - if [[ $QUIET -eq 0 ]]; then echo "Check for open $ownerRepo PRs in branch ${BRANCH}"; fi - # get open PRs, reported by che-incubator bot, with head.ref like pr-update-base-images-1651279364 - curl -sSL -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${ownerRepo}/pulls?state=open&base=${BRANCH}" | jq -r \ - '.[] | select((.user.login == "che-incubator-bot") or (.user.login == "che-bot")) | select(.head.ref|test("pr-update-base-images-.|pr-main-to-")) | [.user.login, .head.ref, ._links.self.href, ._links.html.href] | @tsv' - # process PRs - unmerged_PRs_string="$(curl -sSL -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${ownerRepo}/pulls?state=open&base=${BRANCH}" | jq -r \ - '.[] | select((.user.login == "che-incubator-bot") or (.user.login == "che-bot")) | select(.head.ref|test("pr-update-base-images-.|pr-main-to-")) | ._links.self.href')" - unmerged_PRs=($unmerged_PRs_string); # echo $unmerged_PRs_string; echo "${#unmerged_PRs[@]}" - - if [[ ${#unmerged_PRs[@]} -gt 0 ]]; then - PR_URL=${unmerged_PRs[0]} - # approve it - reviewResult="$(curl -sSL -X POST -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" -d '{"event":"APPROVE"}' ${PR_URL}/reviews)" - if [[ $QUIET -eq 0 ]]; then - echo -n "${PR_URL}: " - echo $reviewResult | jq -r '[.state, .commit_id] | @tsv' - fi - # squash-merge it - mergeResult="$(curl -sSL -X PUT -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" -d '{"merge_method":"squash"}' ${PR_URL}/merge)" - echo -n "${PR_URL}: " - echo $mergeResult | jq -r '[.message, .sha] | @tsv' - - # if more than one, approve the first and close the older ones - if [[ ${#unmerged_PRs[@]} -gt 1 ]]; then - unset "unmerged_PRs[0]" - for PR_URL in "${unmerged_PRs[@]}"; do - closeResult="$(curl -sSL -X PATCH -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" -d '{"state":"closed"}' ${PR_URL})" - echo -n "${PR_URL}: " - echo $closeResult | jq -r '[.state, .closed_at] | @tsv' - done - fi - echo - fi -done diff --git a/utils/base32 b/utils/base32 deleted file mode 100755 index aa2afe1..0000000 --- a/utils/base32 +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -usage () { - echo "This is a replacement for base32 (from GNU coreutils), which is not easily available on ci-centos machines. - -Usage: - pipe content to be encoded into this executable, or pipe content with -d to decode. - -Examples: - echo 'some string that I used to know' | $0 # ONXW2ZJAON2HE2LOM4QHI2DBOQQESIDVONSWIIDUN4QGW3TPO4====== - echo 'ONXW2ZJAON2HE2LOM4QHI2DBOQQESIDVONSWIIDUN4QGW3TPO4======' | $0 -d # some string that I used to know -" - exit -} - -DECODE=0 -for i in "$@" -do -case $i in - -d) DECODE=1; shift;; - --help|--version) usage; shift;; -esac -done - -read -t 60 TEXT - -if [ $DECODE -eq 1 ];then - python3 -c "import base64; print(base64.b32decode(bytearray('$TEXT', 'ascii')).decode('utf-8'))" -else - python3 -c "import base64; print(base64.b32encode(bytearray('$TEXT', 'ascii')).decode('utf-8'))" -fi \ No newline at end of file diff --git a/utils/check-branches.sh b/utils/check-branches.sh deleted file mode 100755 index 6b5acac..0000000 --- a/utils/check-branches.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -e - - -# this script is checking all projects, so that they contain a branch, that is passed as parameter. - -BRANCH=$1 - -SCRIPTS_DIR=$(cd "$(dirname "$0")"; pwd) -source ${SCRIPTS_DIR}/util.sh - -REPO_LIST=( - che-incubator/chectl - che-incubator/configbump - che-incubator/kubernetes-image-puller - eclipse-che/che-dashboard - eclipse-che/che-machine-exec - eclipse-che/che-operator - eclipse-che/che-plugin-registry - eclipse-che/che-server - eclipse/che -) - -MISSING_BRANCHES= -numprojects=0 -set -e -for repo in "${REPO_LIST[@]}"; do - EXIT_CODE=0 - verifyBranchExistsWithTimeout "https://github.com/${repo}.git" "${BRANCH}" 1 || EXIT_CODE=$? - if [[ ${EXIT_CODE} -eq 1 ]]; then - MISSING_BRANCHES="${MISSING_BRANCHES} $repo" - fi - let numprojects=numprojects+1 -done -set -e - -if [ -n "${MISSING_BRANCHES}" ];then - echo "[ERROR] Branch ${BRANCH} is not present in following projects: ${MISSING_BRANCHES}" -else - echo "[INFO] Branch ${BRANCH} is present in all $numprojects Che projects" -fi diff --git a/utils/updateSecrets.chebot.txt b/utils/updateSecrets.chebot.txt deleted file mode 100644 index a744689..0000000 --- a/utils/updateSecrets.chebot.txt +++ /dev/null @@ -1,7 +0,0 @@ -# che-bot secret -eclipse-che/che-operator -eclipse-che/che-plugin-registry -eclipse-che/che-release -eclipse-che/che-server -eclipse/che -eclipse/che-docs diff --git a/utils/updateSecrets.cheincubatorbot.txt b/utils/updateSecrets.cheincubatorbot.txt deleted file mode 100644 index 76db998..0000000 --- a/utils/updateSecrets.cheincubatorbot.txt +++ /dev/null @@ -1,7 +0,0 @@ -# che-incubator secret -che-incubator/che-code -che-incubator/chectl -che-incubator/jetbrains-editor-images -eclipse-che/che-machine-exec -eclipse-che/che-operator -eclipse-che/che-release diff --git a/utils/updateSecrets.crw.txt b/utils/updateSecrets.crw.txt deleted file mode 100644 index 943f5cc..0000000 --- a/utils/updateSecrets.crw.txt +++ /dev/null @@ -1,7 +0,0 @@ -redhat-developer/devspaces -redhat-developer/devspaces-chectl -redhat-developer/devspaces-images - -# no secrets in these repos -# redhat-developer/devspaces-vscode-extensions -# redhat-developer/devspaces-jiralint diff --git a/utils/updateSecrets.dockerhub.txt b/utils/updateSecrets.dockerhub.txt deleted file mode 100644 index cfc69b9..0000000 --- a/utils/updateSecrets.dockerhub.txt +++ /dev/null @@ -1,12 +0,0 @@ -# dockerhub secrets -che-dockerfiles/che-sidecar-go -che-dockerfiles/che-sidecar-kubernetes-tooling -che-dockerfiles/che-sidecar-openshift-connector -che-incubator/configbump -eclipse-che/che-dashboard -eclipse-che/che-machine-exec -eclipse-che/che-operator -eclipse-che/che-plugin-registry -eclipse-che/che-release -eclipse-che/che-server -eclipse/che diff --git a/utils/updateSecrets.quay.txt b/utils/updateSecrets.quay.txt deleted file mode 100644 index 4ad9de4..0000000 --- a/utils/updateSecrets.quay.txt +++ /dev/null @@ -1,13 +0,0 @@ -# quay secrets -che-incubator/che-code -che-incubator/configbump -che-incubator/jetbrains-editor-images -che-incubator/kubernetes-image-puller -che-incubator/kubernetes-image-puller-operator -eclipse-che/che-dashboard -eclipse-che/che-machine-exec -eclipse-che/che-operator -eclipse-che/che-plugin-registry -eclipse-che/che-release -eclipse-che/che-server -eclipse/che diff --git a/utils/updateSecrets.sh b/utils/updateSecrets.sh deleted file mode 100755 index 32878c2..0000000 --- a/utils/updateSecrets.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/bash -e -# -# Copyright (c) 2021-2023 Red Hat, Inc. -# This program and the accompanying materials are made -# available under the terms of the Eclipse Public License 2.0 -# which is available at https://www.eclipse.org/legal/epl-2.0/ -# -# SPDX-License-Identifier: EPL-2.0 -# - -# update secrets from provided secrets file into the specified list of repos (see updateSecrets.txt for repo list) - - -usage () -{ -echo " -For a given list of Github repos, push secret name/value pairs in a secrets file to those repos. -Existing secrets will be overwritten. - -Repos file should include one org/repo per line. For example: - -eclipse-che/che-server -eclipse-che/che-code -... - -Secrets file should include one secret name and value, space-separated, per line. For example: -QUAY_USERNAME my-quay-username -QUAY_PASSWORD my-quay-password -... - -Usage: $0 -r [LIST OF REPOS FILE] -s [SECRETS FILE] -Example: $0 -r updateSecrets.txt -s /path/to/all-my-secrets.txt --list-secrets --update-secrets - -Options: - -v verbose output - --list-secrets before doing any changes, list the current secrets in the repo(s) - --update-secrets rather than displaying planned changes, DO actually push new/updated secrets - --help, -h help -" -exit -} - -if [[ $# -lt 1 ]]; then usage; exit; fi - -#defaults -WORKDIR=$(pwd) -LIST_SECRETS=0 -UPDATE_SECRETS=0 -REPOSFILE="updateSecrets.txt" - -while [[ "$#" -gt 0 ]]; do - case $1 in - '-w') WORKDIR="$2"; shift 1;; - '-r') REPOSFILE="$2"; shift 1;; - '-s') SECRETSFILE="$2"; shift 1;; - '-v') VERBOSE=1; shift 0;; - '--update-secrets') UPDATE_SECRETS=1;; - '--list-secrets') LIST_SECRETS=1;; - '--help'|'-h') usage;; - *) OTHER="${OTHER} $1";; - esac - shift 1 -done - -if [[ $UPDATE_SECRETS -eq 0 ]] && [[ $LIST_SECRETS -eq 0 ]]; then - echo "Error: must specify --list-secrets or --update-secrets (or both)"; usage -fi - -# check for valid files -if [[ ! "$SECRETSFILE" ]]; then echo "Error: Secrets file not set."; usage; fi -if [[ ! -r "$SECRETSFILE" ]] && [[ ! -r "${WORKDIR}/${SECRETSFILE}" ]]; then - echo "Error: Invalid repos file ${SECRETSFILE}."; usage -else - if [[ -r "${WORKDIR}/$SECRETSFILE" ]]; then - SECRETSFILE="${WORKDIR}/$SECRETSFILE" - fi -fi - -if [[ ! "$REPOSFILE" ]]; then echo "Error: Repos file not set."; usage; fi -if [[ ! -r "$REPOSFILE" ]] && [[ ! -r "${WORKDIR}/${REPOSFILE}" ]]; then - echo "Error: Invalid repos file ${REPOSFILE}."; usage -else - # exclude commented out lines in the repos file - if [[ -r "$REPOSFILE" ]]; then - REPOS=$(cat "$REPOSFILE" | grep -v -E "^#") - elif [[ -r "${WORKDIR}/$REPOSFILE" ]]; then - REPOS=$(cat "${WORKDIR}/$REPOSFILE" | grep -v -E "^#") - fi -fi - -PODMAN=$(command -v podman) -if [[ ! -x $PODMAN ]]; then - echo "[WARNING] podman is not installed." - PODMAN=$(command -v docker) - if [[ ! -x $PODMAN ]]; then - echo "[ERROR] docker is not installed. Aborting."; exit 1 - fi -fi - -# get the secret uploader tool and build it -# requires podman or docker to build the generator image -if [[ ! -d /tmp/github-secrets-generator ]]; then - cd /tmp; git clone git@github.com:nickboldt/github-secrets-generator.git -else - if [[ ! $($PODMAN images github-secrets-generator | grep github-secrets-generator) ]]; then - cd /tmp/github-secrets-generator && ./run.sh --build - fi -fi - -for repo in $REPOS; do - if [[ ${LIST_SECRETS} -eq 1 ]]; then - # list the current secrets, but don't change anything - /tmp/github-secrets-generator/run.sh -r ${repo} --list - fi - - if [[ ${UPDATE_SECRETS} -eq 1 ]]; then - /tmp/github-secrets-generator/run.sh -r ${repo} -f "${SECRETSFILE}" - else - echo "/tmp/github-secrets-generator/run.sh -r ${repo} -f ${SECRETSFILE}" - fi -done - diff --git a/utils/updateSecrets.txt b/utils/updateSecrets.txt deleted file mode 100644 index b482c3e..0000000 --- a/utils/updateSecrets.txt +++ /dev/null @@ -1,8 +0,0 @@ -# nothing to update in these repos -# eclipse/che-dev -# eclipse/che-lib -# eclipse/che-parent -# eclipse/che-plugin-broker -# eclipse/che-jwtproxy - -# see other updateSecrets.*.txt files for other projects to update, split by which secret(s) need updating \ No newline at end of file diff --git a/utils/util.sh b/utils/util.sh deleted file mode 100755 index a0e902e..0000000 --- a/utils/util.sh +++ /dev/null @@ -1,196 +0,0 @@ -die_with() -{ - echo "$*" >&2 - exit 1 -} - -# for a given GH repo and action name, compute workflow_id -# warning: variable workflow_id is a global, so don't call this in parallel executions! -computeWorkflowId() { - this_repo=$1 - this_action_name=$2 - workflow_id=$(curl -sSL "https://api.github.com/repos/${this_repo}/actions/workflows" -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" | jq --arg search_field "${this_action_name}" '.workflows[] | select(.name == $search_field).id'); - # echo "workflow_id = $workflow_id" - if [[ ! $workflow_id ]]; then - die_with "[ERROR] Could not compute workflow id from https://api.github.com/repos/${this_repo}/actions/workflows - check your GITHUB_TOKEN is active" - fi - echo "[INFO] Got workflow_id $workflow_id for $this_repo action '$this_action_name'" -} - -# generic method to call a GH action and pass in a single var=val parameter -invokeAction() { - this_repo=$1 - this_action_name=$2 - this_workflow_id=$3 - #params is a comma-separated list of key=value entries - this_params=$4 - - # if provided, use previously computed workflow_id; otherwise compute it from the action's name so we can invoke the GH action by id - # shellcheck disable=SC2086 - if [[ $this_workflow_id ]]; then - workflow_id=$this_workflow_id - else - computeWorkflowId $this_repo "$this_action_name" - # now we have a global value for $workflow_id - fi - - WORKFLOW_MAIN_BRANCH="main" - WORKFLOW_BUGFIX_BRANCH=${BRANCH} - - if [[ ${CHE_VERSION} == *".0" ]]; then - workflow_ref=${WORKFLOW_MAIN_BRANCH} - else - workflow_ref=${WORKFLOW_BUGFIX_BRANCH} - fi - - inputsJson="{}" - - IFS=',' read -ra paramMap <<< "${this_params}" - for keyvalue in "${paramMap[@]}" - do - key=${keyvalue%=*} - value=${keyvalue#*=} - inputsJson=$(echo "${inputsJson}" | jq ". + {\"${key}\": \"${value}\"}") - done - - if [[ ${this_repo} == "che-incubator"* ]] || [[ ${this_repo} == "devfile"* ]] || [[ ${this_repo} == "che-dockerfiles"* ]]; then - this_github_token=${CHE_INCUBATOR_BOT_GITHUB_TOKEN} - else - this_github_token=${GITHUB_TOKEN} - fi - - curl -sSL "https://api.github.com/repos/${this_repo}/actions/workflows/${workflow_id}/dispatches" -X POST -H "Authorization: token ${this_github_token}" -H "Accept: application/vnd.github.v3+json" -d "{\"ref\":\"${workflow_ref}\",\"inputs\": ${inputsJson} }" || die_with "[ERROR] Problem invoking action https://github.com/${this_repo}/actions?query=workflow%3A%22${this_action_name// /+}%22" - echo "[INFO] Invoked '${this_action_name}' action ($workflow_id) - see https://github.com/${this_repo}/actions?query=workflow%3A%22${this_action_name// /+}%22" -} - -verifyBranchExistsWithTimeout() -{ - this_repo=$1 - this_branch=$2 - this_timeout=$3 - branchExists=0 - count=1 - (( timeout_intervals=this_timeout*3 )) - while [[ $count -le $timeout_intervals ]]; do # echo $count - echo -n " [$count/$timeout_intervals] Check ${this_repo%.git}/tree/${this_branch} ..." - # check if the branch exists - branchExists=$(git ls-remote --heads "${this_repo}" "${this_branch}" | wc -l) - if [[ ${branchExists} -eq 1 ]]; then echo " found."; return 0; break; fi - (( count=count+1 )) - sleep 20s - echo "" - done - # or report an error - if [[ ${branchExists} -eq 0 ]]; then - echo "[ERROR] Branch ${this_repo%.git}/tree/${this_branch} not found after ${this_timeout} minutes" - return 1 - fi -} - -verifyBranchExistsWithTimeoutAndExit() -{ - verifyBranchExistsWithTimeout "$@" - if [[ $? -gt 0 ]]; then - exit 1 - fi -} - -# for a given container URL, check if it exists and its digest can be read -# verifyContainerExists quay.io/crw/pluginregistry-rhel8:2.6 # schemaVersion = 1, look for tag -# verifyContainerExists quay.io/eclipse/che-plugin-registry:7.24.2 # schemaVersion = 2, look for arches -verifyContainerExists() -{ - this_containerURL="${1}" - this_image=""; this_tag="" - this_image=${this_containerURL#*/} - this_tag=${this_image##*:} - this_image=${this_image%%:*} - this_url="https://quay.io/v2/${this_image}/manifests/${this_tag}" - # echo $this_url - - # get result=tag if tag found, result="null" if not - result="$(curl -sSL "${this_url}" -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" 2>&1 || true)" - if [[ $(echo "$result" | jq -r '.schemaVersion' || true) == "1" ]] && [[ $(echo "$result" | jq -r '.tag' || true) == "$this_tag" ]]; then - echo "[INFO] Found ${this_containerURL} (tag = $this_tag)" - containerExists=1 - elif [[ $(echo "$result" | jq -r '.schemaVersion' || true) == "2" ]]; then - arches=$(echo "$result" | jq -r '[.manifests[].platform.architecture]|@csv' | tr -d "\"") - if [[ $arches ]]; then - echo "[INFO] Found ${this_containerURL} (arches = $arches)" - fi - containerExists=1 - else - # echo "[INFO] Did not find ${this_containerURL}" - containerExists=0 - fi -} - -verifyContainerExistsWithTimeout() -{ - this_containerURL=$1 - this_timeout=$2 - containerExists=0 - count=1 - (( timeout_intervals=this_timeout*3 )) - while [[ $count -le $timeout_intervals ]]; do # echo $count - echo " [$count/$timeout_intervals] Verify ${1} exists..." - # check if the container exists - verifyContainerExists "$1" - if [[ ${containerExists} -eq 1 ]]; then break; fi - (( count=count+1 )) - sleep 20s - done - # or report an error - if [[ ${containerExists} -eq 0 ]]; then - echo "[ERROR] Did not find ${1} after ${this_timeout} minutes - script must exit!" - exit 1; - fi -} - - -# for a given url of project hosten on NPMJS, check if it exists -# package name must be in format "/name@version" -# e.g. "@eclipse-che/che-devworkspace-generator@7.70.0" -verifyNpmJsPackageExists() -{ - this_package=${1} - this_name="${this_package%@*}" - this_version="${this_package##*@}" - registry_json="$(curl -s https://registry.npmjs.org/${this_name}/)" - if echo "$registry_json" | jq -e '."versions"."'${this_version}'"'; then - echo "[INFO] Found ${this_package}" - packageExists=1 - else - # echo "[INFO] Did not find ${this_package}" - packageExists=0 - fi -} - -verifyNpmJsPackagexistsWithTimeout() -{ - this_package=$1 - this_timeout=$2 - packageExists=0 - count=1 - (( timeout_intervals=this_timeout*3 )) - while [[ $count -le $timeout_intervals ]]; do # echo $count - echo " [$count/$timeout_intervals] Verify ${1} exists..." - # check if the package exists - verifyNpmJsPackageExists "$1" - if [[ ${containerExists} -eq 1 ]]; then break; fi - (( count=count+1 )) - sleep 20s - done - # or report an error - if [[ ${packageExists} -eq 0 ]]; then - echo "[ERROR] Did not find ${1} after ${this_timeout} minutes - script must exit!" - exit 1; - fi -} - -verifyNpmJsPackageExistsWithTimeoutAndExit() { - verifyNpmJsPackagexistsWithTimeout "$@" - if [[ $? -gt 0 ]]; then - exit 1 - fi -} \ No newline at end of file