diff --git a/.github/actions/create-release-pr/action.yaml b/.github/actions/create-release-pr/action.yaml new file mode 100644 index 00000000000..1112889062c --- /dev/null +++ b/.github/actions/create-release-pr/action.yaml @@ -0,0 +1,31 @@ +name: "Create release pr" +description: "Updates get_all_manifests.sh with relavent tags and creates a pr" +inputs: + pr-branch: + required: true + description: "Branch containing the changes to create pr" + commit-message: + required: true + description: "Commit message for the pr" + title: + required: true + description: "Title of the pr" +runs: + using: "composite" + steps: + - name: Update branches in get_all_manifest.sh + uses: actions/github-script@v7 + with: + script: | + const script = require('./.github/scripts/get-release-branches.js') + script({github, core}) + - name: Update versions + shell: bash + run: ./.github/scripts/update-manifests-tags.sh ${{ env.CODEFLARE }} ${{ env.KUBERAY }} ${{ env.KUEUE }} ${{ env.DSP }} ${{ env.DASHBOARD }} ${{ env.NOTEBOOK-CONTROLLER }} ${{ env.NOTEBOOK-CONTROLLER }} ${{ env.NOTEBOOKS }} ${{ env.TRUSTYAI }} ${{ env.MODELMESH }} ${{ env.ODH-MODEL-CONTROLLER }} ${{ env.KSERVE }} ${{ env.MODEL-REGISTRY }} + - name: Create release pr + uses: peter-evans/create-pull-request@v6 + with: + commit-message: ${{ inputs.commit-message }} + branch: ${{ inputs.pr-branch }} + delete-branch: true + title: ${{ inputs.title }} \ No newline at end of file diff --git a/.github/actions/set-shared-env/action.yaml b/.github/actions/set-shared-env/action.yaml new file mode 100644 index 00000000000..c4631dbfd3d --- /dev/null +++ b/.github/actions/set-shared-env/action.yaml @@ -0,0 +1,40 @@ +name: "Set Shared env vars" +description: "Gets repo env and sets in workflow env" +inputs: + pat-token: + required: true + description: "A personal access token to read repo envs." +runs: + using: "composite" + steps: + - name: Get and set env variables + uses: actions/github-script@v7 + with: + github-token: ${{ inputs.pat-token }} + script: | + try{ + async function getAndSetVariables(){ + const { data: versionData } = await github.request('GET /repos/{owner}/{repo}/actions/variables/{name}', { + owner: context.repo.owner, + repo: context.repo.repo, + name: 'VERSION', + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + const { data: trackerUrlData } = await github.request('GET /repos/{owner}/{repo}/actions/variables/{name}', { + owner: context.repo.owner, + repo: context.repo.repo, + name: 'TRACKER_URL', + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + console.log(`The VERSION is ${versionData.value} and the TRACKER_URL is ${trackerUrlData.value}`) + core.exportVariable('VERSION', versionData.value); + core.exportVariable('TRACKER_URL', trackerUrlData.value); + } + getAndSetVariables() + }catch(e){ + core.setFailed(`Action failed with error ${e}`); + } \ No newline at end of file diff --git a/.github/scripts/get-component-release-notes.js b/.github/scripts/get-component-release-notes.js new file mode 100644 index 00000000000..5586bfccf43 --- /dev/null +++ b/.github/scripts/get-component-release-notes.js @@ -0,0 +1,39 @@ +module.exports = ({ github, core }) => { + const { TRACKER_URL } = process.env + console.log(`The TRACKER_URL is ${TRACKER_URL}`) + const arr = TRACKER_URL.split("/") + const owner = arr[3] + const repo = arr[4] + const issue_number = arr[6] + + github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner, + repo, + issue_number, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + 'Accept': 'application/vnd.github.text+json' + } + }).then((result) => { + const allowedComponents = ["dashboard", "notebooks", "notebook-controller", "trustyai", "kserve", "modelmesh-serving", "model-registry", "kueue", "codeflare", "kuberay", "dsp"] + let outputStr = "## Component Release Notes\n" + result.data.forEach((issue) => { + issueCommentBody = issue.body_text + if (issueCommentBody.includes("#Release#")) { + let components = issueCommentBody.split("\n") + components = components.splice(2, components.length - 1) + components.forEach(component => { + [componentName, branchUrl, tagUrl] = component.split("|") + if (allowedComponents.includes(componentName)) { + outputStr += `- **${componentName.charAt(0).toUpperCase() + componentName.slice(1)}**: ${tagUrl}\n` + } + }) + } + }) + console.log("Created component release notes successfully...") + core.setOutput('release-notes-body', outputStr); + }).catch(e => { + core.setFailed(`Action failed with error ${e}`); + }) +} + diff --git a/.github/scripts/get-release-branches.js b/.github/scripts/get-release-branches.js new file mode 100644 index 00000000000..5a29f1b38fc --- /dev/null +++ b/.github/scripts/get-release-branches.js @@ -0,0 +1,38 @@ +module.exports = ({ github, core }) => { + const { TRACKER_URL } = process.env + console.log(`The tracker url is: ${TRACKER_URL}`) + + const arr = TRACKER_URL.split("/") + const owner = arr[3] + const repo = arr[4] + const issue_number = arr[6] + + github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner, + repo, + issue_number, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + 'Accept': 'application/vnd.github.text+json' + } + }).then((result) => { + result.data.forEach((issue) => { + issueCommentBody = issue.body_text + if (issueCommentBody.includes("#Release#")) { + let components = issueCommentBody.split("\n") + components = components.splice(2, components.length - 1) + components.forEach(component => { + [componentName, branchUrl] = component.split("|") + const splitArr = branchUrl.split("/") + const idx = splitArr.indexOf("tree") + const branchName = splitArr.slice(idx + 1).join("/") + core.exportVariable(componentName.toUpperCase(), branchName); + }) + } + }) + console.log("Read release/tag from tracker issue successfully...") + }).catch(e => { + core.setFailed(`Action failed with error ${e}`); + }) +} + diff --git a/.github/scripts/update-manifests-tags.sh b/.github/scripts/update-manifests-tags.sh new file mode 100755 index 00000000000..87cfbf91243 --- /dev/null +++ b/.github/scripts/update-manifests-tags.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -euo pipefail + +update_tags(){ +MANIFEST_STR=$(cat get_all_manifests.sh | grep $1 | sed 's/ //g') + readarray -d ":" -t STR_ARR <<< "$MANIFEST_STR" + RES="" + for i in "${!STR_ARR[@]}"; do + if [ $i == 2 ]; then + RES+=$2":" + else + RES+=${STR_ARR[$i]}":" + fi + done + echo "${RES::-2}" + sed -i -r "s|.*$1.*| ${RES::-2}|" get_all_manifests.sh +} + +declare -A COMPONENT_VERSION_MAP=( + ["\"codeflare\""]=$1 + ["\"ray\""]=$2 + ["\"kueue\""]=$3 + ["\"data-science-pipelines-operator\""]=$4 + ["\"odh-dashboard\""]=$5 + ["\"kf-notebook-controller\""]=$6 + ["\"odh-notebook-controller\""]=$7 + ["\"notebooks\""]=$8 + ["\"trustyai\""]=$9 + ["\"model-mesh\""]=$10 + ["\"odh-model-controller\""]=$11 + ["\"kserve\""]=$12 + ["\"modelregistry\""]=$13 +) + +for key in ${!COMPONENT_VERSION_MAP[@]}; do + update_tags ${key} ${COMPONENT_VERSION_MAP[${key}]} +done \ No newline at end of file diff --git a/.github/scripts/update-variables.js b/.github/scripts/update-variables.js new file mode 100644 index 00000000000..2e8624489ef --- /dev/null +++ b/.github/scripts/update-variables.js @@ -0,0 +1,31 @@ +module.exports = ({ github, context, core }) => { + const { VERSION, TRACKER_URL } = process.env + try { + console.log(`Variables to update are: VERSION: ${VERSION} and TRACKER_URL:${TRACKER_URL}`) + async function updateVariables() { + await github.request('PATCH /repos/{owner}/{repo}/actions/variables/{name}', { + owner: context.repo.owner, + repo: context.repo.repo, + name: 'VERSION', + value: VERSION, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + + await github.request('PATCH /repos/{owner}/{repo}/actions/variables/{name}', { + owner: context.repo.owner, + repo: context.repo.repo, + name: 'TRACKER_URL', + value: TRACKER_URL, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + } + updateVariables() + console.log("Updated variables successfully...") + } catch (e) { + core.setFailed(`Action failed with error ${e}`); + } +} \ No newline at end of file diff --git a/.github/scripts/validate-semver.sh b/.github/scripts/validate-semver.sh new file mode 100755 index 00000000000..c02740b10a5 --- /dev/null +++ b/.github/scripts/validate-semver.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -euo pipefail + +sem_ver_pattern="^[vV](0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + +die () { + echo >&2 "$@" + exit 1 +} + +validate_semantic_versioning() { + version=$1 + + if [[ ${version} == "" ]]; then + die "Undefined version. Please use semantic versioning https://semver.org/." + fi + + # Ensure defined version matches semver rules + if [[ ! "${version}" =~ $sem_ver_pattern ]]; then + die "\`${version}\` you defined as a version does not match semantic versioning. Please make sure it conforms with https://semver.org/ and make sure it starts with v prefix." + fi +} \ No newline at end of file diff --git a/.github/scripts/wait-for-checks.sh b/.github/scripts/wait-for-checks.sh new file mode 100755 index 00000000000..e3f01b2d47c --- /dev/null +++ b/.github/scripts/wait-for-checks.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# @param $1 - PR number or URL +# wait for a bit until pr is created, otherwise it throws an error "no checks reported on the 'odh-release/e2e-test' branch" +set -euo pipefail + +sleep 10 + +while $(gh pr checks "$1" | grep -q -v 'tide' | grep -q 'pending'); do + printf ":stopwatch: PR checks still pending, retrying in 10 seconds...\n" + sleep 10 +done + +if $(gh pr checks "$1" | grep -q 'fail'); then + printf "!!PR checks failed!!\n" + exit 1 +fi + +if $(gh pr checks "$1" | grep -q 'pass'); then + printf "!!PR checks passed!!\n" + exit 0 +fi + +printf "!!An unknown error occurred!!\n" +exit 1 \ No newline at end of file diff --git a/.github/workflows/release-branch.yaml b/.github/workflows/release-branch.yaml new file mode 100644 index 00000000000..cdcdb2c237d --- /dev/null +++ b/.github/workflows/release-branch.yaml @@ -0,0 +1,31 @@ +name: "Release: Create release branch" +on: + pull_request: + types: + - closed +jobs: + create-release-branch: + if: github.event.pull_request.merged && startsWith(github.event.pull_request.title, 'ODH Release') && endsWith(github.event.pull_request.title, 'Version Update') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Set env variables + uses: ./.github/actions/set-shared-env + with: + pat-token: ${{ steps.generate-token.outputs.token }} + - name: Create release branch + run: | + git checkout -b odh-${{ env.VERSION }} + git push -f origin odh-${{ env.VERSION }} + - name: Create release pr to release branch + uses: ./.github/actions/create-release-pr + with: + pr-branch: "odh-release/branch-update" + title: "ODH ${{ env.VERSION }} Release" + commit-message: "ODH ${{ env.VERSION }} Release" + # reviewers: "VaishnaviHire,zdtsw,AjayJagan,ykaliuta" TODO \ No newline at end of file diff --git a/.github/workflows/release-gh-publish.yaml b/.github/workflows/release-gh-publish.yaml new file mode 100644 index 00000000000..e2bee576cb7 --- /dev/null +++ b/.github/workflows/release-gh-publish.yaml @@ -0,0 +1,88 @@ +name: "Release: GH and operatorhub publish" +on: + pull_request: + types: + - closed +jobs: + gh-release: + if: github.event.pull_request.merged && startsWith(github.event.pull_request.title, 'ODH') && endsWith(github.event.pull_request.title, 'Release') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Set env variables + uses: ./.github/actions/set-shared-env + with: + pat-token: ${{ steps.generate-token.outputs.token }} + - name: Create and push version tags + run: | + git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com + git config --global user.name github-actions[bot] + git checkout odh-${{ env.VERSION }} + git tag -a -m v${{ env.VERSION }} v${{ env.VERSION }} + git push origin v${{ env.VERSION }} + - name: Get component release notes + id: release-notes + uses: actions/github-script@v7 + with: + script: | + const script = require('./.github/scripts/get-component-release-notes.js') + script({github, core}) + - name: Create GH release + uses: softprops/action-gh-release@v2 + with: + body: ${{ steps.release-notes.outputs.release-notes-body }} + tag_name: v${{ env.VERSION }} + generate_release_notes: true + append_body: true + make_latest: true + create-community-operators-pr: + needs: [gh-release] + name: Create community operators prod pr # https://github.com/redhat-openshift-ecosystem/community-operators-prod + runs-on: ubuntu-latest + steps: + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Set env variables + uses: opendatahub-io/opendatahub-operator/.github/actions/set-shared-env@incubation + with: + pat-token: ${{ steps.generate-token.outputs.token }} + - name: Checkout opendatahub-operator + uses: actions/checkout@v4 + with: + path: ./opendatahub-operator + ref: v${{ env.VERSION }} + - name: Checkout redhat-openshift-ecosystem/community-operators-prod + uses: actions/checkout@v4 + with: + token: # We need a token with repo rights + repository: redhat-openshift-ecosystem/community-operators-prod # replaced with redhat-openshift-ecosystem/community-operators-prod + path: ./community-operators-prod + - name: Copy bundle files and add annotation + run : | + ls -la + # cd community-operators-prod + # mkdir -p community-operators-prod/operators/opendatahub-operator/${{ env.VERSION }} + # ls -la + # cp -a ../opendatahub-operator/bundle/. operators/opendatahub-operator/${{ env.VERSION }}/ + # echo " + # # OpenShift specific version + # com.redhat.openshift.versions: v4.9" >> operators/opendatahub-operator/${{ env.VERSION }}/metadata/annotations.yaml + # sed -i -e "s|image: REPLACE_IMAGE:latest.*|image: quay.io/opendatahub/opendatahub-operator:v${{ env.VERSION }}|g" operators/opendatahub-operator/${{ env.VERSION }}/manifests/opendatahub-operator.clusterserviceversion.yaml + # git status + - name: Create community operators prod pr + uses: peter-evans/create-pull-request@v6 + with: + path: ./community-operators-prod + token: # We need a token with repo rights + push-to-fork: opendatahub-io/community-operators-prod # Here we can fork community-operators-prod into opendatahub-io and use opendatahub-io/community-operators-prod. eg:https://github.com/maistra/community-operators-prod + commit-message: ODH Release v${{ env.VERSION }} + delete-branch: true + title: operator opendatahub-operator (${{ env.VERSION }}) \ No newline at end of file diff --git a/.github/workflows/release-test-e2e.yaml b/.github/workflows/release-test-e2e.yaml new file mode 100644 index 00000000000..9537e9bd3e5 --- /dev/null +++ b/.github/workflows/release-test-e2e.yaml @@ -0,0 +1,58 @@ +name: "Release: Test e2e" +on: + workflow_dispatch: + inputs: + version: + type: string + description: The version to update. + required: true + tracker-url: + type: string + description: The URL to tracker issue. + required: true +env: + VERSION: ${{ inputs.version }} + TRACKER_URL: ${{ inputs.tracker-url }} +jobs: + create-e2e-test-pr: + runs-on: ubuntu-latest + name: Create e2e test pr and update image tags + steps: + - uses: actions/checkout@v4 + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Validate semver + run: ./.github/scripts/validate-semver.sh v${{ env.VERSION }} + - name: Update repo variables + uses: actions/github-script@v7 + with: + github-token: ${{ steps.generate-token.outputs.token }} # A personal access token with workflow+repo permissions or token created at runtime from a github app + script: | + const script = require('./.github/scripts/update-variables.js') + script({github, context, core}) + - name: Create test release pr + uses: ./.github/actions/create-release-pr + with: + pr-branch: "odh-release/e2e-test" + title: "[DO NOT MERGE] Test ${{ env.VERSION }} Release" + commit-message: "Test ${{ env.VERSION }} Release" + - name: Wait for checks to pass + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + run: ./.github/scripts/wait-for-checks.sh ${{ env.PULL_REQUEST_NUMBER }} + - name: Close PR + uses: peter-evans/close-pull@v3 + with: + token: ${{ steps.generate-token.outputs.token }} + pull-request-number: ${{ env.PULL_REQUEST_NUMBER }} + comment: Auto-closing pull request after success checks + delete-branch: true + - name: Push version tag to quay.io + run: | + podman login -u -p ${{ secrets.PODMAN_PASSWORD }} quay.io/opendatahub # Store PODMAN_PASSWORD of opendatahub-io here as a secret. + podman pull quay.io/opendatahub/opendatahub-operator:pr-${{ env.PULL_REQUEST_NUMBER }} + podman push quay.io/opendatahub/opendatahub-operator:pr-${{ env.PULL_REQUEST_NUMBER }} quay.io/opendatahub/opendatahub-operator:${{ env.VERSION }} + echo "Successfully push tags to podman with version: ${{ env.VERSION }}" \ No newline at end of file diff --git a/.github/workflows/release-update-versions.yaml b/.github/workflows/release-update-versions.yaml new file mode 100644 index 00000000000..d9f7bbc8590 --- /dev/null +++ b/.github/workflows/release-update-versions.yaml @@ -0,0 +1,46 @@ +name: "Release: Update versions" +on: + workflow_run: + workflows: ["Release: Test e2e"] + types: [completed] +jobs: + update-versions: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Set env variables for actions + uses: ./.github/actions/set-shared-env + with: + pat-token: ${{ steps.generate-token.outputs.token }} + - name: Update versions in relevant files + run: | + NEW_VERSION=${{ env.VERSION }} + CURRENT_VERSION=$(cat Makefile | grep -w "VERSION ?=" | cut -d ' ' -f 3) + sed -i -e "s/^VERSION ?=.*/VERSION ?= $NEW_VERSION/g" Makefile + sed -i -e "s|containerImage.*|containerImage: quay.io/opendatahub/opendatahub-operator:v$NEW_VERSION|g" config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml + sed -i -e "s|createdAt.*|createdAt: \"$(date +"%Y-%-m-%dT00:00:00Z")\"|g" config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml + sed -i -e "s|name: opendatahub-operator.v.*|name: opendatahub-operator.v$NEW_VERSION|g" config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml + sed -i -e "s|version: $CURRENT_VERSION.*|version: $NEW_VERSION|g" config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml + sed -i -e "s|replaces.*|replaces: opendatahub-operator.v$CURRENT_VERSION|g" config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml + sed -i -e "s|olm.skipRange:.*|olm.skipRange: \'>=$CURRENT_VERSION <$NEW_VERSION\'|g" config/manifests/bases/opendatahub-operator.clusterserviceversion.yaml + - name: Run make bundle + run: make bundle + - name: Clean up + run: | + sed -i -e "s|image: quay.io/opendatahub/opendatahub-operator:latest.*|image: REPLACE_IMAGE:latest|g" bundle/manifests/opendatahub-operator.clusterserviceversion.yaml + rm ./config/manager/kustomization.yaml + - name: Create version update pr + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ steps.generate-token.outputs.token }} + commit-message: "ODH Release ${{ env.VERSION }}" + branch: odh-release/version-update + delete-branch: true + title: "ODH Release ${{ env.VERSION }}: Version Update" + # reviewers: "VaishnaviHire,zdtsw,AjayJagan,ykaliuta" TODO \ No newline at end of file diff --git a/Makefile b/Makefile index b3b5b678673..30114d3137b 100644 --- a/Makefile +++ b/Makefile @@ -236,7 +236,7 @@ KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/k .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - test -s $(KUSTOMIZE) || { curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | sh -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + test -s $(KUSTOMIZE) || { curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/config/manager/kustomization.yaml.in b/config/manager/kustomization.yaml.in deleted file mode 100644 index f956c36fbe6..00000000000 --- a/config/manager/kustomization.yaml.in +++ /dev/null @@ -1,15 +0,0 @@ -resources: -- manager.yaml - -generatorOptions: - disableNameSuffixHash: true - -configMapGenerator: -- files: - - controller_manager_config.yaml - name: manager-config -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -images: -- name: controller - newName: REPLACE_IMAGE