Use SHA-256 for checksums #55
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
name: deploy | |
on: | |
issue_comment: | |
types: [ created ] | |
env: | |
ARTIFACT_NAME: 'lambda' | |
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }} | |
AWS_REGION: ${{ vars.AWS_REGION }} | |
LAMBDA_FUNCTION: 'alexa-london-travel' | |
TERM: xterm | |
permissions: | |
contents: read | |
jobs: | |
setup: | |
runs-on: ubuntu-latest | |
if: | | |
github.event.issue.pull_request != '' && | |
github.event.repository.fork == false && | |
github.triggering_actor == github.event.repository.owner.login && | |
startsWith(github.event.comment.body, '/deploy') | |
outputs: | |
comment-id: ${{ steps.post-comment.outputs.result }} | |
environment-name: ${{ steps.set-outputs.outputs.environment-name }} | |
environment-url: ${{ steps.set-outputs.outputs.environment-url }} | |
function-description: ${{ steps.set-outputs.outputs.function-description }} | |
function-name: ${{ steps.set-outputs.outputs.function-name }} | |
ref: ${{ steps.set-outputs.outputs.ref }} | |
workflow-url: ${{ steps.set-outputs.outputs.workflow-url }} | |
permissions: | |
actions: read | |
contents: read | |
pull-requests: write | |
steps: | |
- name: Get environment name | |
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 | |
id: get-environment-name | |
with: | |
result-encoding: string | |
script: | | |
const owner = context.payload.repository.owner.login; | |
const repo = context.payload.repository.name; | |
const username = context.payload.comment.user.login; | |
try { | |
await github.rest.repos.checkCollaborator({ | |
owner, | |
repo, | |
username, | |
}); | |
} catch (err) { | |
throw new Error(`Error: @${username} is not a repository collaborator.`); | |
} | |
const regex = /\/deploy ([a-zA-Z\d\-\_]+)/; | |
const arguments = regex.exec(context.payload.comment.body); | |
if (arguments === null || arguments.length < 2) { | |
throw new Error('Invalid command'); | |
} | |
const environment = arguments[1].trim(); | |
return environment; | |
- name: 'Download artifact from #${{ github.event.issue.number }}' | |
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 | |
id: download-artifact | |
env: | |
DOWNLOAD_PATH: ${{ github.workspace }} | |
PULL_NUMBER: ${{ github.event.issue.number }} | |
WORKFLOW_NAME: 'build' | |
with: | |
script: | | |
const artifactName = process.env.ARTIFACT_NAME; | |
const pull_number = process.env.PULL_NUMBER; | |
const workflowName = process.env.WORKFLOW_NAME; | |
const workingDirectory = process.env.DOWNLOAD_PATH; | |
const owner = context.repo.owner; | |
const repo = context.repo.repo; | |
core.debug(`Getting pull request ${owner}/${repo}#${pull_number}`); | |
const { data: pull } = await github.rest.pulls.get({ | |
owner, | |
repo, | |
pull_number, | |
}); | |
if (!pull) { | |
throw new Error(`Pull request ${owner}/${repo}#${pull_number} not found.`); | |
} | |
const head_sha = pull.head.sha; | |
core.debug(`Getting workflow runs for ${owner}/${repo}#${pull_number}@${head_sha}`); | |
const { data: workflows } = await github.rest.actions.listWorkflowRunsForRepo({ | |
owner, | |
repo, | |
event: 'pull_request', | |
head_sha, | |
status: 'success', | |
}); | |
const workflow = workflows.workflow_runs.find((run) => run.name === workflowName); | |
if (!workflow) { | |
throw new Error(`No successful workflow run found for ${owner}/${repo}@${head_sha.slice(0, 7)} with name ${workflowName}.`); | |
} | |
const run_id = workflow.id; | |
core.debug(`Getting artifacts for workflow run ${owner}/${repo} number ${run_id}`); | |
const { data: allArtifacts } = await github.rest.actions.listWorkflowRunArtifacts({ | |
owner, | |
repo, | |
run_id, | |
}); | |
core.debug(`Found ${allArtifacts.length} artifact(s)`); | |
const [artifact] = allArtifacts.artifacts.filter((artifact) => artifact.name === artifactName); | |
if (!artifact) { | |
throw new Error(`No ${artifactName} artifact found in workflow run with ID ${run_id}.`); | |
} | |
const artifact_id = artifact.id; | |
core.debug(`Downloading artifact ${artifactName} with ID ${artifact_id}`); | |
const { data: download } = await github.rest.actions.downloadArtifact({ | |
owner, | |
repo, | |
artifact_id, | |
archive_format: 'zip', | |
}); | |
const fs = require('fs'); | |
const path = require('path'); | |
const fileName = `${artifactName}.zip`; | |
if (!fs.existsSync(workingDirectory)) { | |
core.debug(`Creating working directory ${workingDirectory}`); | |
await fs.promises.mkdir(workingDirectory); | |
} | |
const downloadPath = path.join(workingDirectory, fileName); | |
core.debug(`Writing ZIP ${downloadPath} to disk`); | |
await fs.promises.writeFile(downloadPath, Buffer.from(download)); | |
core.setOutput('ref', head_sha); | |
core.setOutput('run-number', workflow.run_number); | |
core.setOutput('zip-path', downloadPath); | |
- name: Extract the artifact | |
id: extract-artifact | |
env: | |
ARTIFACT_PATH: ${{ steps.download-artifact.outputs.zip-path }} | |
STAGING_PATH: '${{ github.workspace }}/staging' | |
run: | | |
mkdir --parents "${STAGING_PATH}" | |
unzip -q "${ARTIFACT_PATH}" -d "${STAGING_PATH}" | |
echo "lambda-artifact-path=${STAGING_PATH}" >> "$GITHUB_OUTPUT" | |
- name: Publish deployment package | |
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 | |
with: | |
name: ${{ env.ARTIFACT_NAME }} | |
path: ${{ steps.extract-artifact.outputs.lambda-artifact-path }} | |
if-no-files-found: error | |
- name: Set outputs | |
id: set-outputs | |
run: | | |
environment_name="${{ steps.get-environment-name.outputs.result }}" | |
environment_url="${{ github.server_url }}/${{ github.repository }}/deployments/${environment_name}" | |
function_description="Deploy build ${{ steps.download-artifact.outputs.run-number }} to AWS Lambda via GitHub Actions" | |
ref="${{ steps.download-artifact.outputs.ref }}" | |
workflow_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
if [ "${environment_name}" == "production" ] | |
then | |
function_name="${LAMBDA_FUNCTION}" | |
else | |
function_name="${LAMBDA_FUNCTION}-${environment_name}" | |
fi | |
{ | |
echo "environment-name=${environment_name}" | |
echo "environment-url=${environment_url}" | |
echo "function-description=${function_description}" | |
echo "function-name=${function_name}" | |
echo "ref=${ref}" | |
echo "workflow-url=${workflow_url}" | |
} >> "$GITHUB_OUTPUT" | |
- name: Post comment | |
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 | |
id: post-comment | |
env: | |
ENVIRONMENT_NAME: ${{ steps.set-outputs.outputs.environment-name }} | |
ENVIRONMENT_URL: ${{ steps.set-outputs.outputs.environment-url }} | |
WORKFLOW_URL: ${{ steps.set-outputs.outputs.workflow-url }} | |
with: | |
result-encoding: string | |
script: | | |
const owner = context.payload.repository.owner.login; | |
const repo = context.payload.repository.name; | |
const issue_number = context.issue.number; | |
const environment = process.env.ENVIRONMENT_NAME; | |
const environment_url = process.env.ENVIRONMENT_URL; | |
const workflow_url = process.env.WORKFLOW_URL; | |
const { data: comment } = await github.rest.issues.createComment({ | |
owner, | |
repo, | |
issue_number, | |
body: `Starting [deployment](${workflow_url}) to [${environment}](${environment_url}) :rocket:`, | |
}); | |
return comment.id; | |
deploy: | |
name: ${{ needs.setup.outputs.environment-name }} | |
needs: [ setup ] | |
concurrency: '${{ needs.setup.outputs.environment-name }}_environment' | |
runs-on: ubuntu-latest | |
env: | |
FUNCTION_NAME: ${{ needs.setup.outputs.function-name }} | |
LAMBDA_ARCHITECTURES: 'arm64' | |
LAMBDA_DESCRIPTION: ${{ needs.setup.outputs.function-description }} | |
LAMBDA_HANDLER: 'LondonTravel.Skill::MartinCostello.LondonTravel.Skill.AlexaFunctionHandler::HandleAsync' | |
LAMBDA_MEMORY: 256 | |
LAMBDA_ROLE: ${{ vars.AWS_LAMBDA_ROLE }} | |
LAMBDA_RUNTIME: 'provided.al2' | |
LAMBDA_TIMEOUT: 10 | |
environment: | |
name: ${{ needs.setup.outputs.environment-name }} | |
permissions: | |
id-token: write | |
pull-requests: write | |
steps: | |
- name: Download artifacts | |
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 | |
with: | |
name: ${{ env.ARTIFACT_NAME }} | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 | |
with: | |
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/github-actions-deploy | |
role-session-name: ${{ github.event.repository.name }}-${{ github.run_id }}-deploy-${{ needs.setup.outputs.environment-name }} | |
aws-region: ${{ env.AWS_REGION }} | |
- name: Update function code | |
run: | | |
aws lambda update-function-code \ | |
--function-name "${FUNCTION_NAME}" \ | |
--architectures "${LAMBDA_ARCHITECTURES}" \ | |
--zip-file "fileb://./${LAMBDA_FUNCTION}.zip" \ | |
> /dev/null | |
- name: Wait for function code update | |
run: | | |
aws lambda wait function-updated-v2 \ | |
--function-name "${FUNCTION_NAME}" \ | |
> /dev/null | |
- name: Update function configuration | |
run: | | |
aws lambda update-function-configuration \ | |
--function-name "${FUNCTION_NAME}" \ | |
--description "${LAMBDA_DESCRIPTION}" \ | |
--handler "${LAMBDA_HANDLER}" \ | |
--memory-size "${LAMBDA_MEMORY}" \ | |
--role "${LAMBDA_ROLE}" \ | |
--runtime "${LAMBDA_RUNTIME}" \ | |
--timeout "${LAMBDA_TIMEOUT}" \ | |
> /dev/null | |
- name: Wait for function configuration update | |
run: | | |
aws lambda wait function-updated-v2 \ | |
--function-name "${FUNCTION_NAME}" \ | |
> /dev/null | |
- name: Post comment | |
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 | |
if: ${{ !cancelled() }} | |
env: | |
COMMENT_ID: ${{ needs.setup.outputs.comment-id }} | |
ENVIRONMENT_NAME: ${{ needs.setup.outputs.environment-name }} | |
ENVIRONMENT_URL: ${{ needs.setup.outputs.environment-url }} | |
OUTCOME: ${{ job.status }} | |
WORKFLOW_URL: ${{ needs.setup.outputs.workflow-url }} | |
with: | |
script: | | |
const owner = context.payload.repository.owner.login; | |
const repo = context.payload.repository.name; | |
const comment_id = process.env.COMMENT_ID; | |
const environment = process.env.ENVIRONMENT_NAME; | |
const environment_url = process.env.ENVIRONMENT_URL; | |
const workflow_url = process.env.WORKFLOW_URL; | |
const succeeded = process.env.OUTCOME === 'success'; | |
const outcome = succeeded ? 'successful' : 'failed'; | |
const emoji = succeeded ? ':white_check_mark:' : ':x:'; | |
await github.rest.issues.updateComment({ | |
owner, | |
repo, | |
comment_id, | |
body: `[Deployment](${workflow_url}) to [${environment}](${environment_url}) ${outcome} ${emoji}`, | |
}); | |
tests: | |
name: tests-${{ needs.setup.outputs.environment-name }} | |
needs: [ setup, deploy ] | |
runs-on: ubuntu-latest | |
concurrency: '${{ needs.setup.outputs.environment-name }}_environment' | |
env: | |
DOTNET_CLI_TELEMETRY_OPTOUT: true | |
DOTNET_GENERATE_ASPNET_CERTIFICATE: false | |
DOTNET_MULTILEVEL_LOOKUP: 0 | |
DOTNET_NOLOGO: true | |
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 | |
DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: 1 | |
NUGET_XMLDOC_MODE: skip | |
permissions: | |
id-token: write | |
pull-requests: write | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 | |
with: | |
ref: ${{ needs.setup.outputs.ref }} | |
- name: Setup .NET SDK | |
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 | |
with: | |
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/github-actions-test | |
role-session-name: ${{ github.event.repository.name }}-${{ github.run_id }}-tests-${{ needs.setup.outputs.environment-name }} | |
aws-region: ${{ env.AWS_REGION }} | |
- name: Run end-to-end tests | |
shell: pwsh | |
run: dotnet test ./test/LondonTravel.Skill.EndToEndTests --configuration Release --logger "GitHubActions;report-warnings=false" | |
env: | |
LAMBDA_FUNCTION_NAME: ${{ needs.setup.outputs.function-name }} | |
- name: Post comment | |
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 | |
if: ${{ !cancelled() }} | |
env: | |
COMMENT_ID: ${{ needs.setup.outputs.comment-id }} | |
ENVIRONMENT_NAME: ${{ needs.setup.outputs.environment-name }} | |
ENVIRONMENT_URL: ${{ needs.setup.outputs.environment-url }} | |
OUTCOME: ${{ job.status }} | |
WORKFLOW_URL: ${{ needs.setup.outputs.workflow-url }} | |
with: | |
script: | | |
const owner = context.payload.repository.owner.login; | |
const repo = context.payload.repository.name; | |
const comment_id = process.env.COMMENT_ID; | |
const environment = process.env.ENVIRONMENT_NAME; | |
const environment_url = process.env.ENVIRONMENT_URL; | |
const workflow_url = process.env.WORKFLOW_URL; | |
const succeeded = process.env.OUTCOME === 'success'; | |
const outcome = succeeded ? 'passed' : 'failed'; | |
const emoji = succeeded ? ':white_check_mark:' : ':x:'; | |
await github.rest.issues.updateComment({ | |
owner, | |
repo, | |
comment_id, | |
body: `:test_tube: [Tests](${workflow_url}) for deployment to [${environment}](${environment_url}) ${outcome} ${emoji}`, | |
}); |