fix(ci): change ephemeral env to use github labels instead of comments (#31340)

This commit is contained in:
Daniel Vaz Gaspar 2025-01-29 13:57:01 +00:00 committed by GitHub
parent a87a13c3ab
commit e4bdb28ba2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 190 additions and 211 deletions

View File

@ -1,145 +1,124 @@
name: Ephemeral env workflow name: Ephemeral env workflow
# Example manual trigger: gh workflow run ephemeral-env.yml --ref fix_ephemerals --field comment_body="/testenv up" --field issue_number=666 # Example manual trigger:
# gh workflow run ephemeral-env.yml --ref fix_ephemerals --field label_name="testenv-up" --field issue_number=666
on: on:
issue_comment: pull_request_target:
types: [created] types:
- labeled
workflow_dispatch: workflow_dispatch:
inputs: inputs:
comment_body: label_name:
description: 'Comment body to simulate /testenv command' description: 'Label name to simulate label-based /testenv trigger'
required: true required: true
default: '/testenv up' default: 'testenv-up'
issue_number: issue_number:
description: 'Issue or PR number' description: 'Issue or PR number'
required: true required: true
jobs: jobs:
ephemeral-env-comment: ephemeral-env-label:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.inputs.issue_number || github.event.issue.number || github.run_id }}-comment group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}-label
cancel-in-progress: true cancel-in-progress: true
name: Evaluate ephemeral env comment trigger (/testenv) name: Evaluate ephemeral env label trigger
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
permissions: permissions:
pull-requests: write pull-requests: write
outputs: outputs:
slash-command: ${{ steps.eval-body.outputs.result }} slash-command: ${{ steps.eval-label.outputs.result }}
feature-flags: ${{ steps.eval-feature-flags.outputs.result }} feature-flags: ${{ steps.eval-feature-flags.outputs.result }}
sha: ${{ steps.get-sha.outputs.sha }}
env: env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
steps: steps:
- name: Debug - name: Check for the "testenv-up" label
run: | id: eval-label
echo "Comment on PR #${{ github.event.issue.number }} by ${{ github.event.issue.user.login }}, ${{ github.event.comment.author_association }}" run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
LABEL_NAME="${{ github.event.inputs.label_name }}"
else
LABEL_NAME="${{ github.event.label.name }}"
fi
- name: Eval comment body for /testenv slash command echo "Evaluating label: $LABEL_NAME"
uses: actions/github-script@v7
env:
COMMENT_BODY: ${{ github.event.inputs.comment_body || github.event.comment.body }}
id: eval-body
with:
result-encoding: string
script: |
const pattern = /^\/testenv (up|down)/;
const result = pattern.exec(process.env.COMMENT_BODY || '');
return result === null ? 'noop' : result[1];
- name: Looking for feature flags if [[ "$LABEL_NAME" == "testenv-up" ]]; then
uses: actions/github-script@v7 echo "result=up" >> $GITHUB_OUTPUT
env: else
COMMENT_BODY: ${{ github.event.inputs.comment_body || github.event.comment.body }} echo "result=noop" >> $GITHUB_OUTPUT
id: eval-feature-flags exit 1
with: fi
script: |
const pattern = /FEATURE_(\w+)=(\w+)/g;
let results = [];
[...process.env.COMMENT_BODY.matchAll(pattern)].forEach(match => {
const config = {
name: `SUPERSET_FEATURE_${match[1]}`,
value: match[2],
};
results.push(config);
});
return results;
- name: Limit to committers - name: Get event SHA
if: > id: get-sha
steps.eval-body.outputs.result != 'noop' && run: |
github.event_name == 'issue_comment' && echo "sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
github.event.comment.author_association != 'MEMBER' &&
github.event.comment.author_association != 'OWNER' - name: Looking for feature flags in PR description
uses: actions/github-script@v7 uses: actions/github-script@v7
with: id: eval-feature-flags
github-token: ${{ github.token }} with:
script: | script: |
const errMsg = '@${{ github.event.comment.user.login }} Ephemeral environment creation is currently limited to committers.'; const description = context.payload.pull_request
github.rest.issues.createComment({ ? context.payload.pull_request.body || ''
issue_number: ${{ github.event.issue.number }}, : context.payload.inputs.pr_description || '';
owner: context.repo.owner,
repo: context.repo.repo, const pattern = /FEATURE_(\w+)=(\w+)/g;
body: errMsg let results = [];
}); [...description.matchAll(pattern)].forEach(match => {
core.setFailed(errMsg); const config = {
name: `SUPERSET_FEATURE_${match[1]}`,
value: match[2],
};
results.push(config);
});
return results;
- name: Reply with confirmation comment
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const action = '${{ steps.eval-label.outputs.result }}';
const user = context.actor;
const runId = context.runId;
const workflowUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
const issueNumber = context.payload.pull_request
? context.payload.pull_request.number
: context.payload.inputs.issue_number;
if (!issueNumber) {
throw new Error("Issue number is not available.");
}
const body = `@${user} Processing your ephemeral environment request [here](${workflowUrl}). Action: **${action}**.`;
- name: Reply with confirmation comment
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueNumber = ${{ github.event.inputs.issue_number || github.event.issue.number }};
const user = '${{ github.event.comment.user.login || github.actor }}';
const action = '${{ steps.eval-body.outputs.result }}';
const runId = context.runId;
const workflowUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
const body = `@${user} Processing your ephemeral environment request [here](${workflowUrl}).`;
if (action !== 'noop') {
await github.rest.issues.createComment({ await github.rest.issues.createComment({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
issue_number: issueNumber, issue_number: issueNumber,
body, body,
}); });
}
else {
core.setFailed('No ephemeral environment action detected.');
}
ephemeral-docker-build: ephemeral-docker-build:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.inputs.issue_number || github.event.issue.number || github.run_id }}-build group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}-build
cancel-in-progress: true cancel-in-progress: true
needs: ephemeral-env-comment needs: ephemeral-env-label
if: needs.ephemeral-env-comment.outputs.slash-command == 'up' if: needs.ephemeral-env-label.outputs.slash-command == 'up'
name: ephemeral-docker-build name: ephemeral-docker-build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Get Info from comment - name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )"
uses: actions/github-script@v7
id: get-pr-info
with:
script: |
const request = {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
};
core.info(`Getting PR #${request.pull_number} from ${request.owner}/${request.repo}`);
const pr = await github.rest.pulls.get(request);
return pr.data;
- name: Debug
id: get-sha
run: |
echo "sha=${{ fromJSON(steps.get-pr-info.outputs.result).head.sha }}" >> $GITHUB_OUTPUT
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} : ${{steps.get-sha.outputs.sha}} )"
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: ${{ steps.get-sha.outputs.sha }} ref: ${{ needs.ephemeral-env-label.outputs.sha }}
persist-credentials: false persist-credentials: false
- name: Setup Docker Environment - name: Setup Docker Environment
@ -181,14 +160,14 @@ jobs:
env: env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: superset-ci ECR_REPOSITORY: superset-ci
IMAGE_TAG: apache/superset:${{ steps.get-sha.outputs.sha }}-ci IMAGE_TAG: apache/superset:${{ needs.ephemeral-env-label.outputs.sha }}-ci
run: | run: |
docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
docker push -a $ECR_REGISTRY/$ECR_REPOSITORY docker push -a $ECR_REGISTRY/$ECR_REPOSITORY
ephemeral-env-up: ephemeral-env-up:
needs: [ephemeral-env-comment, ephemeral-docker-build] needs: [ephemeral-env-label, ephemeral-docker-build]
if: needs.ephemeral-env-comment.outputs.slash-command == 'up' if: needs.ephemeral-env-label.outputs.slash-command == 'up'
name: Spin up an ephemeral environment name: Spin up an ephemeral environment
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
permissions: permissions:
@ -196,120 +175,120 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2 aws-region: us-west-2
- name: Login to Amazon ECR - name: Login to Amazon ECR
id: login-ecr id: login-ecr
uses: aws-actions/amazon-ecr-login@v2 uses: aws-actions/amazon-ecr-login@v2
- name: Check target image exists in ECR - name: Check target image exists in ECR
id: check-image id: check-image
continue-on-error: true continue-on-error: true
run: | run: |
aws ecr describe-images \ aws ecr describe-images \
--registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \ --registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \
--repository-name superset-ci \ --repository-name superset-ci \
--image-ids imageTag=pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci --image-ids imageTag=pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
- name: Fail on missing container image - name: Fail on missing container image
if: steps.check-image.outcome == 'failure' if: steps.check-image.outcome == 'failure'
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
script: | script: |
const errMsg = '@${{ github.event.comment.user.login }} Container image not yet published for this PR. Please try again when build is complete.'; const errMsg = '@${{ github.event.comment.user.login }} Container image not yet published for this PR. Please try again when build is complete.';
github.rest.issues.createComment({ github.rest.issues.createComment({
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }}, issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: errMsg body: errMsg
}); });
core.setFailed(errMsg); core.setFailed(errMsg);
- name: Fill in the new image ID in the Amazon ECS task definition - name: Fill in the new image ID in the Amazon ECS task definition
id: task-def id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1 uses: aws-actions/amazon-ecs-render-task-definition@v1
with: with:
task-definition: .github/workflows/ecs-task-definition.json task-definition: .github/workflows/ecs-task-definition.json
container-name: superset-ci container-name: superset-ci
image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
- name: Update env vars in the Amazon ECS task definition - name: Update env vars in the Amazon ECS task definition
run: | run: |
cat <<< "$(jq '.containerDefinitions[0].environment += ${{ needs.ephemeral-env-comment.outputs.feature-flags }}' < ${{ steps.task-def.outputs.task-definition }})" > ${{ steps.task-def.outputs.task-definition }} cat <<< "$(jq '.containerDefinitions[0].environment += ${{ needs.ephemeral-env-label.outputs.feature-flags }}' < ${{ steps.task-def.outputs.task-definition }})" > ${{ steps.task-def.outputs.task-definition }}
- name: Describe ECS service - name: Describe ECS service
id: describe-services id: describe-services
run: | run: |
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
- name: Create ECS service - name: Create ECS service
id: create-service id: create-service
if: steps.describe-services.outputs.active != 'true' if: steps.describe-services.outputs.active != 'true'
env: env:
ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974 ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974
ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91 ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91
run: | run: |
aws ecs create-service \ aws ecs create-service \
--cluster superset-ci \ --cluster superset-ci \
--service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service \ --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service \
--task-definition superset-ci \ --task-definition superset-ci \
--launch-type FARGATE \ --launch-type FARGATE \
--desired-count 1 \ --desired-count 1 \
--platform-version LATEST \ --platform-version LATEST \
--network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \ --network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \
--tags key=pr,value=${{ github.event.inputs.issue_number || github.event.issue.number }} key=github_user,value=${{ github.actor }} --tags key=pr,value=${{ github.event.inputs.issue_number || github.event.issue.number }} key=github_user,value=${{ github.actor }}
- name: Deploy Amazon ECS task definition - name: Deploy Amazon ECS task definition
id: deploy-task id: deploy-task
uses: aws-actions/amazon-ecs-deploy-task-definition@v2 uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with: with:
task-definition: ${{ steps.task-def.outputs.task-definition }} task-definition: ${{ steps.task-def.outputs.task-definition }}
service: pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service service: pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service
cluster: superset-ci cluster: superset-ci
wait-for-service-stability: true wait-for-service-stability: true
wait-for-minutes: 10 wait-for-minutes: 10
- name: List tasks - name: List tasks
id: list-tasks id: list-tasks
run: | run: |
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
- name: Get network interface - name: Get network interface
id: get-eni id: get-eni
run: | run: |
echo "eni=$(aws ecs describe-tasks --cluster superset-ci --tasks ${{ steps.list-tasks.outputs.task }} | jq '.tasks | .[0] | .attachments | .[0] | .details | map(select(.name==\"networkInterfaceId\")) | .[0] | .value')" >> $GITHUB_OUTPUT echo "eni=$(aws ecs describe-tasks --cluster superset-ci --tasks ${{ steps.list-tasks.outputs.task }} | jq '.tasks[0].attachments[0].details | map(select(.name==\"networkInterfaceId\"))[0].value')" >> $GITHUB_OUTPUT
- name: Get public IP - name: Get public IP
id: get-ip id: get-ip
run: | run: |
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
- name: Comment (success) - name: Comment (success)
if: ${{ success() }} if: ${{ success() }}
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
github-token: ${{github.token}} github-token: ${{github.token}}
script: | script: |
github.rest.issues.createComment({ github.rest.issues.createComment({
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }}, issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are `admin`/`admin`. Please allow several minutes for bootstrapping and startup.' body: '@${{ github.actor }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are `admin`/`admin`. Please allow several minutes for bootstrapping and startup.'
}) })
- name: Comment (failure) - name: Comment (failure)
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
github-token: ${{github.token}} github-token: ${{github.token}}
script: | script: |
github.rest.issues.createComment({ github.rest.issues.createComment({
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }}, issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment creation failed. Please check the Actions logs for details.' body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment creation failed. Please check the Actions logs for details.'
}) })