-
Notifications
You must be signed in to change notification settings - Fork 14.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
build: Ephemeral environments for PRs via slash command #13189
Merged
Merged
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
32dbdfe
First pass at ephemeral env, new Docker ci target
robdiciuccio 71607e0
Add service checks, get public IP
robdiciuccio e39bed5
Separate issue_comment and workflow_run jobs
robdiciuccio 37468b3
Refactor workflows
robdiciuccio 18ca7c5
Adjust comment author association
robdiciuccio 6614cd7
Checkout code
robdiciuccio 034fa3a
Fix image name, manage service desired task count
robdiciuccio 7af1a6b
Use merge commit sha
robdiciuccio d0b8c11
Fix IP output, add failure comment
robdiciuccio 5a07e42
Refactor comment parsing & env spinup
robdiciuccio aa3bfd2
Check container image publish status
robdiciuccio 4069f7b
Parse AWS account ID from registry URL
robdiciuccio 20d53d0
Use PR number rather than variable merge commit SHA for image tag
robdiciuccio 705ad06
Fix docker push conditional
robdiciuccio 63bc1e5
Push multiple tags to ECR
robdiciuccio 5219469
Fix comment author check
robdiciuccio 21a26e8
Refactor comment body check
robdiciuccio 2e69ca0
Provision service with active task to get correct IP
robdiciuccio a7db63f
/testenv up
robdiciuccio 6e5e572
Add @mentions to PR comments, env var cleanup
robdiciuccio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
name: Push ephmereral env image | ||
|
||
on: | ||
workflow_run: | ||
workflows: ["Docker"] | ||
types: | ||
- completed | ||
|
||
jobs: | ||
docker_ephemeral_env: | ||
name: Push ephemeral env Docker image to ECR | ||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: 'Download artifact' | ||
uses: actions/github-script@v3.1.0 | ||
with: | ||
script: | | ||
const artifacts = await github.actions.listWorkflowRunArtifacts({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
run_id: ${{ github.event.workflow_run.id }}, | ||
}); | ||
|
||
core.info('*** artifacts') | ||
core.info(JSON.stringify(artifacts)) | ||
|
||
const matchArtifact = artifacts.data.artifacts.filter((artifact) => { | ||
return artifact.name == "build" | ||
})[0]; | ||
if(!matchArtifact) return core.setFailed("Build artifacts not found") | ||
|
||
const download = await github.actions.downloadArtifact({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
artifact_id: matchArtifact.id, | ||
archive_format: 'zip', | ||
}); | ||
var fs = require('fs'); | ||
fs.writeFileSync('${{github.workspace}}/build.zip', Buffer.from(download.data)); | ||
|
||
- run: unzip build.zip | ||
|
||
- name: Display downloaded files (debug) | ||
run: ls -la | ||
|
||
- name: Get SHA | ||
id: get-sha | ||
run: echo "::set-output name=sha::$(cat ./SHA)" | ||
|
||
- name: Get PR | ||
id: get-pr | ||
run: echo "::set-output name=num::$(cat ./PR-NUM)" | ||
|
||
- name: Configure AWS credentials | ||
uses: aws-actions/configure-aws-credentials@v1 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-region: us-west-2 | ||
|
||
- name: Login to Amazon ECR | ||
id: login-ecr | ||
uses: aws-actions/amazon-ecr-login@v1 | ||
|
||
- name: Load, tag and push image to ECR | ||
id: push-image | ||
env: | ||
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} | ||
ECR_REPOSITORY: superset-ci | ||
SHA: ${{ steps.get-sha.outputs.sha }} | ||
IMAGE_TAG: pr-${{ steps.get-pr.outputs.num }} | ||
SUPERSET_LOAD_EXAMPLES: yes | ||
run: | | ||
docker load < $SHA.tar.gz | ||
docker tag $SHA $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG | ||
docker tag $SHA $ECR_REGISTRY/$ECR_REPOSITORY:$SHA | ||
docker push -a $ECR_REGISTRY/$ECR_REPOSITORY |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
{ | ||
"containerDefinitions": [ | ||
{ | ||
"name": "superset-ci", | ||
"image": "apache/superset:latest", | ||
"cpu": 0, | ||
"links": [], | ||
"portMappings": [ | ||
{ | ||
"containerPort": 8080, | ||
"hostPort": 8080, | ||
"protocol": "tcp" | ||
} | ||
], | ||
"essential": true, | ||
"entryPoint": [], | ||
"command": [], | ||
"environment": [ | ||
{ | ||
"name": "SUPERSET_LOAD_EXAMPLES", | ||
"value": "yes" | ||
}, | ||
{ | ||
"name": "SUPERSET_PORT", | ||
"value": "8080" | ||
} | ||
], | ||
"mountPoints": [], | ||
"volumesFrom": [], | ||
"logConfiguration": { | ||
"logDriver": "awslogs", | ||
"options": { | ||
"awslogs-group": "/ecs/superset-ci", | ||
"awslogs-region": "us-west-2", | ||
"awslogs-stream-prefix": "ecs" | ||
} | ||
} | ||
} | ||
], | ||
"family": "superset-ci", | ||
"taskRoleArn": "ecsTaskExecutionRole", | ||
"executionRoleArn": "ecsTaskExecutionRole", | ||
"networkMode": "awsvpc", | ||
"volumes": [], | ||
"placementConstraints": [], | ||
"requiresCompatibilities": [ | ||
"FARGATE" | ||
], | ||
"cpu": "512", | ||
"memory": "1024" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
name: Ephemeral env workflow | ||
|
||
on: | ||
issue_comment: | ||
types: [created] | ||
|
||
jobs: | ||
ephemeral_env_comment: | ||
if: github.event.issue.pull_request | ||
name: Evaluate ephemeral env comment trigger (/testenv) | ||
runs-on: ubuntu-latest | ||
outputs: | ||
slash-command: ${{ steps.eval-body.outputs.result }} | ||
|
||
steps: | ||
- name: Debug | ||
run: | | ||
echo "Comment on PR #${{ github.event.issue.number }} by ${{ github.event.issue.user.login }}, ${{ github.event.comment.author_association }}" | ||
echo "Comment body: ${{ github.event.comment.body }}" | ||
|
||
- name: Eval comment body for /testenv slash command | ||
uses: actions/github-script@v3 | ||
id: eval-body | ||
with: | ||
result-encoding: string | ||
script: | | ||
const pattern = /^\/testenv (up|down)/ | ||
const result = pattern.exec(context.payload.comment.body) | ||
return result === null ? 'noop' : result[1] | ||
|
||
- name: Limit to committers | ||
if: > | ||
steps.eval-body.outputs.result != 'noop' && | ||
github.event.comment.author_association != 'MEMBER' && | ||
github.event.comment.author_association != 'OWNER' | ||
uses: actions/github-script@v3 | ||
with: | ||
github-token: ${{secrets.GITHUB_TOKEN}} | ||
script: | | ||
const errMsg = 'Ephemeral environment creation is currently limited to committers.' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a |
||
github.issues.createComment({ | ||
issue_number: ${{ github.event.issue.number }}, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: errMsg | ||
}) | ||
core.setFailed(errMsg) | ||
|
||
ephemeral_env_up: | ||
needs: ephemeral_env_comment | ||
if: needs.ephemeral_env_comment.outputs.slash-command == 'up' | ||
name: Spin up an ephemeral environment | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
persist-credentials: false | ||
|
||
- name: Configure AWS credentials | ||
uses: aws-actions/configure-aws-credentials@v1 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-region: us-west-2 | ||
|
||
- name: Login to Amazon ECR | ||
id: login-ecr | ||
uses: aws-actions/amazon-ecr-login@v1 | ||
|
||
- name: Check target image exists in ECR | ||
id: check-image | ||
continue-on-error: true | ||
run: | | ||
aws ecr describe-images \ | ||
--registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \ | ||
--repository-name superset-ci \ | ||
--image-ids imageTag=pr-${{ github.event.issue.number }} | ||
|
||
- name: Fail on missing container image | ||
if: steps.check-image.outcome == 'failure' | ||
uses: actions/github-script@v3 | ||
with: | ||
github-token: ${{secrets.GITHUB_TOKEN}} | ||
script: | | ||
const errMsg = 'Container image not yet published for this PR. Please try again when build is complete.' | ||
github.issues.createComment({ | ||
issue_number: ${{ github.event.issue.number }}, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: errMsg | ||
}) | ||
core.setFailed(errMsg) | ||
|
||
- name: Fill in the new image ID in the Amazon ECS task definition | ||
id: task-def | ||
uses: aws-actions/amazon-ecs-render-task-definition@v1 | ||
with: | ||
task-definition: .github/workflows/ecs-task-definition.json | ||
container-name: superset-ci | ||
image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.issue.number }} | ||
|
||
- name: Describe ECS service | ||
id: describe-services | ||
run: | | ||
echo "::set-output name=active::$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" | ||
|
||
- name: Create ECS service | ||
if: steps.describe-services.outputs.active != 'true' | ||
id: create-service | ||
env: | ||
ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974 | ||
ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91 | ||
run: | | ||
aws ecs create-service \ | ||
--cluster superset-ci \ | ||
--service-name pr-${{ github.event.issue.number }}-service \ | ||
--task-definition superset-ci \ | ||
--launch-type FARGATE \ | ||
--desired-count 1 \ | ||
--platform-version LATEST \ | ||
--network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \ | ||
--tags key=pr,value=${{ github.event.issue.number }} key=github_user,value=${{ github.actor }} | ||
|
||
- name: Deploy Amazon ECS task definition | ||
id: deploy-task | ||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 | ||
with: | ||
task-definition: ${{ steps.task-def.outputs.task-definition }} | ||
service: pr-${{ github.event.issue.number }}-service | ||
cluster: superset-ci | ||
wait-for-service-stability: true | ||
wait-for-minutes: 10 | ||
|
||
- name: List tasks | ||
id: list-tasks | ||
run: | | ||
echo "::set-output name=task::$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.issue.number }}-service | jq '.taskArns | first')" | ||
|
||
- name: Get network interface | ||
id: get-eni | ||
run: | | ||
echo "::set-output name=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')" | ||
|
||
- name: Get public IP | ||
id: get-ip | ||
run: | | ||
echo "::set-output name=ip::$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" | ||
|
||
- name: Comment (success) | ||
if: ${{ success() }} | ||
uses: actions/github-script@v3 | ||
with: | ||
github-token: ${{secrets.GITHUB_TOKEN}} | ||
script: | | ||
github.issues.createComment({ | ||
issue_number: ${{ github.event.issue.number }}, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: '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) | ||
if: ${{ failure() }} | ||
uses: actions/github-script@v3 | ||
with: | ||
github-token: ${{secrets.GITHUB_TOKEN}} | ||
script: | | ||
github.issues.createComment({ | ||
issue_number: ${{ github.event.issue.number }}, | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
body: 'Ephemeral environment creation failed. Please check the Actions logs for details.' | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#!/usr/bin/env bash | ||
# | ||
# Licensed to the Apache Software Foundation (ASF) under one or more | ||
# contributor license agreements. See the NOTICE file distributed with | ||
# this work for additional information regarding copyright ownership. | ||
# The ASF licenses this file to You under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# export SUPERSET_LOAD_EXAMPLES="yes" | ||
|
||
/app/docker/docker-init.sh | ||
|
||
# TODO: copy config overrides from ENV vars | ||
|
||
# TODO: run celery in detached state | ||
|
||
# start up the web server | ||
gunicorn \ | ||
--bind "0.0.0.0:${SUPERSET_PORT}" \ | ||
--access-logfile '-' \ | ||
--error-logfile '-' \ | ||
--workers 1 \ | ||
--worker-class gthread \ | ||
--threads 8 \ | ||
--timeout 60 \ | ||
--limit-request-line 0 \ | ||
--limit-request-field_size 0 \ | ||
"${FLASK_APP}" |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it make sense to save the already built image from previously step and rely on file names to get both SHA and PR number?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the
--target ci
build here is already using the cached layers from the previous builds, there's not much of a gain in using those images directly. We could embed both the PR num and SHA in the filename, but this felt a bit cleaner, and wouldn't save any measurable time in theworkflow_run
step.