Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa65e76c77 | |||
| cdd0985d22 | |||
| 35fa0f0c78 | |||
| 2f7654e260 | |||
| fdd0dcdd3b | |||
| 8842e834f3 | |||
| c1c2094b77 | |||
| 9cc27ae1af | |||
| 809b8391cc | |||
| b412108a2b | |||
| f8ace3125a | |||
| c8c45990bd | |||
| 11ae58c7e5 | |||
| 8c3d400ca4 | |||
| 88cdb6e6a0 | |||
| b4d5115676 | |||
| 7d818d5e51 | |||
| 7e44bce852 |
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 GitHub, Inc. and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Vendored from gitea.com/actions/gitea-upload-artifact commit 81f940d004763f986ba3582c007fd842dd5cb0d7.
|
||||
name: 'Upload a Build Artifact'
|
||||
description: 'Upload a build artifact that can be used by subsequent workflow steps'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
name:
|
||||
description: 'Artifact name'
|
||||
default: 'artifact'
|
||||
path:
|
||||
description: 'A file, directory or wildcard pattern that describes what to upload'
|
||||
required: true
|
||||
if-no-files-found:
|
||||
description: >
|
||||
The desired behavior if no files are found using the provided path.
|
||||
|
||||
Available Options:
|
||||
warn: Output a warning but do not fail the action
|
||||
error: Fail the action with an error message
|
||||
ignore: Do not output any warnings or errors, the action does not fail
|
||||
default: 'warn'
|
||||
retention-days:
|
||||
description: >
|
||||
Duration after which artifact will expire in days. 0 means using default retention.
|
||||
|
||||
Minimum 1 day.
|
||||
Maximum 90 days unless changed from the repository settings page.
|
||||
compression-level:
|
||||
description: >
|
||||
The level of compression for Zlib to be applied to the artifact archive.
|
||||
The value can range from 0 to 9:
|
||||
- 0: No compression
|
||||
- 1: Best speed
|
||||
- 6: Default compression (same as GNU Gzip)
|
||||
- 9: Best compression
|
||||
Higher levels will result in better compression, but will take longer to complete.
|
||||
For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
|
||||
default: '6'
|
||||
overwrite:
|
||||
description: >
|
||||
If true, an artifact with a matching name will be deleted before a new one is uploaded.
|
||||
If false, the action will fail if an artifact for the given name already exists.
|
||||
Does not fail if the artifact does not exist.
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
artifact-id:
|
||||
description: >
|
||||
A unique identifier for the artifact that was just uploaded. Empty if the artifact upload failed.
|
||||
|
||||
This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts
|
||||
artifact-url:
|
||||
description: >
|
||||
A download URL for the artifact that was just uploaded. Empty if the artifact upload failed.
|
||||
|
||||
This download URL only works for requests Authenticated with GitHub. Anonymous downloads will be prompted to first login.
|
||||
If an anonymous download URL is needed than a short time restricted URL can be generated using the download artifact API: https://docs.github.com/en/rest/actions/artifacts#download-an-artifact
|
||||
|
||||
This URL will be valid for as long as the artifact exists and the workflow run and repository exists. Once an artifact has expired this URL will no longer work.
|
||||
Common uses cases for such a download URL can be adding download links to artifacts in descriptions or comments on pull requests or issues.
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
File diff suppressed because one or more lines are too long
@@ -244,8 +244,6 @@ jobs:
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cleanup() { docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin
|
||||
docker build -f deploy/railway-api.Dockerfile -t "$IMAGE_REPOSITORY:api-$GITEA_SHA" .
|
||||
docker build -f deploy/railway-web.Dockerfile -t "$IMAGE_REPOSITORY:web-$GITEA_SHA" .
|
||||
@@ -271,9 +269,30 @@ jobs:
|
||||
artifacts/staging-images/manifest.env "$GITEA_SHA" "$IMAGE_REPOSITORY" >/dev/null
|
||||
|
||||
- name: Upload immutable staging image manifest
|
||||
uses: https://gitea.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: staging-image-manifest-${{ gitea.sha }}-${{ gitea.run_attempt }}
|
||||
path: artifacts/staging-images/manifest.env
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${ACTIONS_RUNTIME_TOKEN:-}"
|
||||
test -n "${ACTIONS_RESULTS_URL:-}"
|
||||
test -n "${GITHUB_RUN_ID:-}"
|
||||
test -n "${GITHUB_REPOSITORY:-}"
|
||||
workdir="$(pwd -P)"
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$workdir:$workdir" \
|
||||
--workdir "$workdir" \
|
||||
--env HOME=/tmp \
|
||||
--env "INPUT_NAME=staging-image-manifest-$GITEA_SHA-$GITEA_RUN_ATTEMPT" \
|
||||
--env INPUT_PATH=artifacts/staging-images/manifest.env \
|
||||
--env INPUT_OVERWRITE=false \
|
||||
--env ACTIONS_RUNTIME_TOKEN \
|
||||
--env ACTIONS_RESULTS_URL \
|
||||
--env GITHUB_RUN_ID \
|
||||
--env GITHUB_REPOSITORY \
|
||||
--env "GITHUB_SHA=$GITEA_SHA" \
|
||||
--env "GITHUB_WORKSPACE=$workdir" \
|
||||
node:22-bookworm-slim \
|
||||
node -e 'process.env["INPUT_IF-NO-FILES-FOUND"]="error"; process.env["INPUT_RETENTION-DAYS"]="30"; process.env["INPUT_COMPRESSION-LEVEL"]="6"; require("./.gitea/actions/upload-artifact/dist/index.js")'
|
||||
|
||||
- name: Logout ACR registry
|
||||
if: always()
|
||||
run: docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true
|
||||
|
||||
@@ -49,7 +49,6 @@ jobs:
|
||||
env:
|
||||
REQUESTED_SHA: ${{ gitea.event.workflow_run.head_sha || inputs.deploy_sha }}
|
||||
WORKFLOW_RUN_ID: ${{ gitea.event.workflow_run.id }}
|
||||
WORKFLOW_RUN_ATTEMPT: ${{ gitea.event.workflow_run.run_attempt }}
|
||||
REQUESTED_ROLLBACK: ${{ inputs.allow_rollback || 'false' }}
|
||||
GITEA_EVENT_NAME: ${{ gitea.event_name }}
|
||||
run: |
|
||||
@@ -62,7 +61,6 @@ jobs:
|
||||
fi
|
||||
|
||||
gate_run_id="${WORKFLOW_RUN_ID:-}"
|
||||
gate_run_attempt="${WORKFLOW_RUN_ATTEMPT:-}"
|
||||
if [[ "$GITEA_EVENT_NAME" == workflow_dispatch ]]; then
|
||||
runs="$(curl --fail --silent --show-error \
|
||||
--header "Authorization: token $GITEA_TOKEN" \
|
||||
@@ -75,10 +73,8 @@ jobs:
|
||||
)] | sort_by(.id) | reverse | first
|
||||
' <<<"$runs")"
|
||||
gate_run_id="$(jq -er '.id' <<<"$selected_run")"
|
||||
gate_run_attempt="$(jq -er '.run_attempt // 0' <<<"$selected_run")"
|
||||
fi
|
||||
[[ "$gate_run_id" =~ ^[0-9]+$ ]] || { echo "no successful exact-SHA staging quality gate run found" >&2; exit 1; }
|
||||
[[ "$gate_run_attempt" =~ ^[0-9]+$ ]] || { echo "invalid staging quality gate run attempt" >&2; exit 1; }
|
||||
|
||||
staging_head="$(git ls-remote https://git.copse.top/root/Jyotisha.git refs/heads/staging | awk '{print $1}')"
|
||||
[[ "$staging_head" =~ ^[0-9a-f]{40}$ ]]
|
||||
@@ -89,7 +85,6 @@ jobs:
|
||||
{
|
||||
echo "sha=$REQUESTED_SHA"
|
||||
echo "gate_run_id=$gate_run_id"
|
||||
echo "gate_run_attempt=$gate_run_attempt"
|
||||
echo "allow_rollback=$allow_rollback"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
|
||||
@@ -148,15 +143,25 @@ jobs:
|
||||
- name: Download gate-produced image manifest
|
||||
env:
|
||||
GATE_RUN_ID: ${{ steps.revision.outputs.gate_run_id }}
|
||||
GATE_RUN_ATTEMPT: ${{ steps.revision.outputs.gate_run_attempt }}
|
||||
DEPLOY_SHA: ${{ steps.revision.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
artifact_name="staging-image-manifest-$DEPLOY_SHA-$GATE_RUN_ATTEMPT"
|
||||
artifact_prefix="staging-image-manifest-$DEPLOY_SHA-"
|
||||
artifacts="$(curl --fail --silent --show-error \
|
||||
--header "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?name=$artifact_name")"
|
||||
artifact_id="$(jq -er --arg name "$artifact_name" '[.artifacts[] | select(.name == $name and .expired == false)] | first | .id' <<<"$artifacts")"
|
||||
"$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?limit=100")"
|
||||
selected_artifact="$(jq -cer --arg prefix "$artifact_prefix" '
|
||||
[(.artifacts // [])[]
|
||||
| select(.expired == false and (.name | startswith($prefix)))
|
||||
| . + {attempt: ((.name | ltrimstr($prefix)) | tonumber?)}
|
||||
| select(.attempt != null and .attempt >= 1)
|
||||
] | sort_by(.attempt, .id) | reverse | first
|
||||
' <<<"$artifacts")"
|
||||
artifact_name="$(jq -er '.name' <<<"$selected_artifact")"
|
||||
artifact_id="$(jq -er '.id' <<<"$selected_artifact")"
|
||||
artifact_attempt="${artifact_name#"$artifact_prefix"}"
|
||||
[[ "$artifact_name" == "$artifact_prefix"* ]]
|
||||
[[ "$artifact_attempt" =~ ^[1-9][0-9]*$ ]]
|
||||
[[ "$artifact_id" =~ ^[0-9]+$ ]]
|
||||
install -d -m 700 artifacts/staging-image
|
||||
curl --fail --silent --show-error --location \
|
||||
|
||||
@@ -54,13 +54,10 @@ jobs:
|
||||
)] | sort_by(.id) | reverse | first
|
||||
' <<<"$runs")"
|
||||
gate_run_id="$(jq -er '.id' <<<"$selected_run")"
|
||||
gate_run_attempt="$(jq -er '.run_attempt // 0' <<<"$selected_run")"
|
||||
[[ "$gate_run_id" =~ ^[0-9]+$ ]]
|
||||
[[ "$gate_run_attempt" =~ ^[0-9]+$ ]]
|
||||
{
|
||||
echo "sha=$DEPLOY_SHA"
|
||||
echo "gate_run_id=$gate_run_id"
|
||||
echo "gate_run_attempt=$gate_run_attempt"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout trusted main controller
|
||||
@@ -118,15 +115,25 @@ jobs:
|
||||
- name: Download gate-produced migration manifest
|
||||
env:
|
||||
GATE_RUN_ID: ${{ steps.revision.outputs.gate_run_id }}
|
||||
GATE_RUN_ATTEMPT: ${{ steps.revision.outputs.gate_run_attempt }}
|
||||
DEPLOY_SHA: ${{ steps.revision.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
artifact_name="staging-image-manifest-$DEPLOY_SHA-$GATE_RUN_ATTEMPT"
|
||||
artifact_prefix="staging-image-manifest-$DEPLOY_SHA-"
|
||||
artifacts="$(curl --fail --silent --show-error \
|
||||
--header "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?name=$artifact_name")"
|
||||
artifact_id="$(jq -er --arg name "$artifact_name" '[.artifacts[] | select(.name == $name and .expired == false)] | first | .id' <<<"$artifacts")"
|
||||
"$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?limit=100")"
|
||||
selected_artifact="$(jq -cer --arg prefix "$artifact_prefix" '
|
||||
[(.artifacts // [])[]
|
||||
| select(.expired == false and (.name | startswith($prefix)))
|
||||
| . + {attempt: ((.name | ltrimstr($prefix)) | tonumber?)}
|
||||
| select(.attempt != null and .attempt >= 1)
|
||||
] | sort_by(.attempt, .id) | reverse | first
|
||||
' <<<"$artifacts")"
|
||||
artifact_name="$(jq -er '.name' <<<"$selected_artifact")"
|
||||
artifact_id="$(jq -er '.id' <<<"$selected_artifact")"
|
||||
artifact_attempt="${artifact_name#"$artifact_prefix"}"
|
||||
[[ "$artifact_name" == "$artifact_prefix"* ]]
|
||||
[[ "$artifact_attempt" =~ ^[1-9][0-9]*$ ]]
|
||||
[[ "$artifact_id" =~ ^[0-9]+$ ]]
|
||||
install -d -m 700 artifacts/staging-image
|
||||
curl --fail --silent --show-error --location \
|
||||
|
||||
@@ -22,6 +22,14 @@ const giteaQualityWorkflow = new URL(
|
||||
"../../.gitea/workflows/backend-quality-gate.yml",
|
||||
import.meta.url,
|
||||
);
|
||||
const giteaUploadArtifactAction = new URL(
|
||||
"../../.gitea/actions/upload-artifact/action.yml",
|
||||
import.meta.url,
|
||||
);
|
||||
const giteaUploadArtifactEntrypoint = new URL(
|
||||
"../../.gitea/actions/upload-artifact/dist/index.js",
|
||||
import.meta.url,
|
||||
);
|
||||
const giteaDeployWorkflow = new URL(
|
||||
"../../.gitea/workflows/deploy-staging.yml",
|
||||
import.meta.url,
|
||||
@@ -156,8 +164,8 @@ test("Gitea quality gate validates before publishing an immutable ACR manifest",
|
||||
assert.equal((workflow.match(/timeout 180 docker pull "\$NODE_TOOL_SOURCE_IMAGE"/g) ?? []).length, 2);
|
||||
assert.equal((workflow.match(/docker tag "\$NODE_TOOL_SOURCE_IMAGE" "\$NODE_TOOL_IMAGE"/g) ?? []).length, 2);
|
||||
assert.equal((workflow.match(/cat > "\$tool_dir\/node" <<'EOF'/g) ?? []).length, 1);
|
||||
assert.equal((workflow.match(/--volume "\$workdir:\$workdir"/g) ?? []).length, 1);
|
||||
assert.equal((workflow.match(/--workdir "\$workdir"/g) ?? []).length, 1);
|
||||
assert.equal((workflow.match(/--volume "\$workdir:\$workdir"/g) ?? []).length, 2);
|
||||
assert.equal((workflow.match(/--workdir "\$workdir"/g) ?? []).length, 2);
|
||||
assert.equal((workflow.match(/ln -s node "\$tool_dir\/npm"/g) ?? []).length, 1);
|
||||
assert.equal((workflow.match(/>> "\$GITHUB_PATH"/g) ?? []).length, 2);
|
||||
assert.match(workflow, /node:22-bookworm-slim "\$\{0##\*\/\}" "\$@"/);
|
||||
@@ -201,8 +209,26 @@ test("Gitea quality gate validates before publishing an immutable ACR manifest",
|
||||
assert.match(workflow, /get\("os"\)=="linux"/);
|
||||
assert.match(workflow, /get\("architecture"\)=="amd64"/);
|
||||
assert.match(workflow, /node frontend\/scripts\/staging-image-manifest\.mjs/);
|
||||
assert.match(workflow, /uses: https:\/\/gitea\.com\/actions\/upload-artifact@v4/);
|
||||
assert.match(workflow, /name: staging-image-manifest-\$\{\{ gitea\.sha \}\}-\$\{\{ gitea\.run_attempt \}\}/);
|
||||
assert.match(workflow, /require\("\.\/\.gitea\/actions\/upload-artifact\/dist\/index\.js"\)/);
|
||||
assert.match(workflow, /--env INPUT_PATH=artifacts\/staging-images\/manifest\.env/);
|
||||
assert.match(workflow, /process\.env\["INPUT_IF-NO-FILES-FOUND"\]="error"/);
|
||||
assert.match(workflow, /process\.env\["INPUT_RETENTION-DAYS"\]="30"/);
|
||||
assert.match(workflow, /process\.env\["INPUT_COMPRESSION-LEVEL"\]="6"/);
|
||||
assert.doesNotMatch(workflow, /--env ['"]INPUT_(?:IF-NO-FILES-FOUND|RETENTION-DAYS|COMPRESSION-LEVEL)=/);
|
||||
assert.match(workflow, /--env ACTIONS_RUNTIME_TOKEN/);
|
||||
assert.match(workflow, /--env ACTIONS_RESULTS_URL/);
|
||||
assert.match(workflow, /--env ['"]GITHUB_WORKSPACE=\$workdir['"]/);
|
||||
assert.match(read(giteaUploadArtifactAction), /main: ['"]dist\/index\.js['"]/);
|
||||
assert.match(read(giteaUploadArtifactAction), /81f940d004763f986ba3582c007fd842dd5cb0d7/);
|
||||
assert.ok(existsSync(fileURLToPath(giteaUploadArtifactEntrypoint)));
|
||||
assert.match(workflow, /INPUT_NAME=staging-image-manifest-\$GITEA_SHA-\$GITEA_RUN_ATTEMPT/);
|
||||
assertOrder(workflow, [
|
||||
"docker login",
|
||||
"Record immutable linux-amd64 image manifest",
|
||||
"Upload immutable staging image manifest",
|
||||
"Logout ACR registry",
|
||||
]);
|
||||
assert.match(workflow, /Logout ACR registry\n\s+if: always\(\)/);
|
||||
assert.match(workflow, /\[\[ "\$GITEA_RUN_ATTEMPT" =~ \^\[0-9\]\+\$ \]\]/);
|
||||
assert.doesNotMatch(workflow, /STAGING_SSH_PRIVATE_KEY|run-staging-deploy\.sh|curl[^\n]+STAGING_URL/);
|
||||
assert.doesNotMatch(workflow, /(?:^|:)latest$/m);
|
||||
@@ -419,10 +445,8 @@ test("Gitea deploy and migration consume the exact successful gate artifact", ()
|
||||
assert.match(workflow, /\.head_branch == "staging"/);
|
||||
assert.match(workflow, /\.event == "push"/);
|
||||
assert.match(workflow, /\.conclusion == "success"/);
|
||||
assert.match(workflow, /\.run_attempt \/\/ 0/);
|
||||
assert.match(workflow, /\[\[ "\$gate_run_attempt" =~ \^\[0-9\]\+\$ \]\]/);
|
||||
assert.match(workflow, /sort_by\(\.id\) \| reverse \| first/);
|
||||
assert.match(workflow, /actions\/runs\/\$GATE_RUN_ID\/artifacts\?name=\$artifact_name/);
|
||||
assert.match(workflow, /actions\/runs\/\$GATE_RUN_ID\/artifacts\?limit=100/);
|
||||
assert.match(workflow, /actions\/artifacts\/\$artifact_id\/zip/);
|
||||
assert.match(workflow, /node frontend\/scripts\/staging-image-manifest\.mjs/);
|
||||
assert.match(workflow, /name: Checkout trusted main controller/);
|
||||
@@ -434,6 +458,34 @@ test("Gitea deploy and migration consume the exact successful gate artifact", ()
|
||||
}
|
||||
});
|
||||
|
||||
test("Gitea staging mutations resolve the manifest from actual gate-run artifacts", () => {
|
||||
for (const workflow of [read(giteaDeployWorkflow), read(giteaMigrationWorkflow)]) {
|
||||
assert.match(
|
||||
workflow,
|
||||
/artifact_prefix="staging-image-manifest-\$DEPLOY_SHA-"/,
|
||||
);
|
||||
assert.match(workflow, /\.expired == false/);
|
||||
assert.match(workflow, /\.name \| startswith\(\$prefix\)/);
|
||||
assert.match(workflow, /\.name \| ltrimstr\(\$prefix\)/);
|
||||
assert.match(workflow, /tonumber\?/);
|
||||
assert.match(workflow, /sort_by\(\.attempt, \.id\) \| reverse \| first/);
|
||||
assert.match(workflow, /artifact_name="\$\(jq -er '\.name'/);
|
||||
assert.match(workflow, /artifact_id="\$\(jq -er '\.id'/);
|
||||
assert.match(workflow, /artifact_attempt="\$\{artifact_name#"\$artifact_prefix"\}"/);
|
||||
assert.match(workflow, /\[\[ "\$artifact_name" == "\$artifact_prefix"\* \]\]/);
|
||||
assert.match(workflow, /\[\[ "\$artifact_attempt" =~ \^\[1-9\]\[0-9\]\*\$ \]\]/);
|
||||
assertOrder(workflow, [
|
||||
'artifact_prefix="staging-image-manifest-$DEPLOY_SHA-"',
|
||||
'actions/runs/$GATE_RUN_ID/artifacts?limit=100',
|
||||
'sort_by(.attempt, .id) | reverse | first',
|
||||
'artifact_name="$(jq -er',
|
||||
'actions/artifacts/$artifact_id/zip',
|
||||
]);
|
||||
assert.doesNotMatch(workflow, /run_attempt/i);
|
||||
assert.doesNotMatch(workflow, /staging-image-manifest-\$DEPLOY_SHA-1/);
|
||||
}
|
||||
});
|
||||
|
||||
test("Gitea deployment follows only a successful staging push gate and keeps rollback manual", () => {
|
||||
const workflow = read(giteaDeployWorkflow);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user