-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f3cd45b
commit 31590c4
Showing
7 changed files
with
227 additions
and
1 deletion.
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
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,48 @@ | ||
name: ✳️ Export analysis from Quicksight | ||
|
||
on: | ||
workflow_dispatch: | ||
inputs: | ||
environment: | ||
type: choice | ||
required: true | ||
description: AWS Environment | ||
options: [DEV, TEST, FEATURE, PRODUCTION, PRODUCTION-PREVIEW] | ||
analysisId: | ||
type: string | ||
required: true | ||
description: Id of the analysis to export (get from the url e.g. https://eu-west-2.quicksight.aws.amazon.com/sn/analyses/${analysisId}) | ||
bucketName: | ||
type: string | ||
required: false | ||
description: Name of the S3 bucket to use for the export (defaults to \${lowercase_environment}-dap-quicksight-exports) | ||
default: 'unset' | ||
filename: | ||
type: string | ||
required: false | ||
description: Filename of the export (defaults to export-\${analysis_id}-\${timestamp}.qs") | ||
default: 'unset' | ||
|
||
jobs: | ||
invoke-quicksight-export-lambda: | ||
# These permissions are needed to interact with GitHub's OIDC Token endpoint (enabling the aws-actions/configure-aws-credentials action) | ||
permissions: | ||
id-token: write | ||
contents: read | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Check out repository code | ||
uses: actions/checkout@v4 | ||
# - name: Assume AWS quicksight import export lambdas role | ||
# uses: aws-actions/configure-aws-credentials@v4 | ||
# with: | ||
# aws-region: eu-west-2 | ||
# role-to-assume: ${{ secrets[format('QUICKSIGHT_IMPORT_EXPORT_LAMBDAS_INVOKE_ROLE_{0}', inputs.environment)] }} | ||
- name: Invoke lambda | ||
run: | | ||
echo "Environment is ${{ inputs.environment }}" | ||
PAYLOAD=$(scripts/quicksight-export-payload.sh -e ${{ inputs.environment }} -a ${{ inputs.analysisId }} -b ${{ inputs.bucketName }} -f ${{ inputs.filename }}) | ||
echo "$PAYLOAD" | jq | ||
ENCODED=$(echo "$PAYLOAD" | openssl base64) | ||
#aws --region eu-west-2 lambda invoke --function-name quicksight-add-users --payload "$ENCODED" out.json | ||
#cat out.json |
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,48 @@ | ||
#!/bin/bash -e | ||
|
||
usage_and_exit() { | ||
echo "Usage: quicksight-export.sh -e ENVIRONMENT -a ANALYSIS_ID [-b BUCKET_NAME] [-f FILE_NAME] " | ||
echo "Arguments:" | ||
echo "-e environment quicksight environment (one of DEV, TEST, FEATURE, PRODUCTION or PRODUCTION-PREVIEW)" | ||
echo "-a analysis id id of the analysis to export (uuid)" | ||
echo "-b bucket name [optional] name of the S3 bucket to use for the export. defaults to \${lowercase_environment}-dap-quicksight-exports" | ||
echo "-f file name [optional] filename of the export. defaults to export-\${analysis_id}-\${timestamp}.qs" | ||
} | ||
|
||
while getopts "e:a:b:f:" option; do | ||
case "$option" in | ||
e) | ||
ENVIRONMENT="$OPTARG" | ||
;; | ||
a) | ||
ANALYSIS_ID="$OPTARG" | ||
;; | ||
b) | ||
BUCKET_NAME="$OPTARG" | ||
;; | ||
f) | ||
FILE_NAME="$OPTARG" | ||
;; | ||
*) | ||
echo "Unknown option $option" | ||
usage_and_exit | ||
;; | ||
esac | ||
done | ||
|
||
if [ -z "$ENVIRONMENT" ] || [ -z "$ANALYSIS_ID" ]; then | ||
echo -e "Error: Environment and/or analysis id not provided\n" | ||
usage_and_exit | ||
fi | ||
|
||
LOWERCASE_ENVIRONMENT=$(echo "$ENVIRONMENT" | tr '[:upper:]' '[:lower:]') | ||
|
||
if [ -z "$BUCKET_NAME" ] || [ "$BUCKET_NAME" == "unset" ]; then | ||
BUCKET_NAME="$LOWERCASE_ENVIRONMENT-dap-quicksight-exports" | ||
fi | ||
|
||
if [ -z "$FILE_NAME" ] || [ "$FILE_NAME" == "unset" ]; then | ||
FILE_NAME="export-$ANALYSIS_ID-$(date +"%Y-%m-%dT%H:%M:%S").qs" | ||
fi | ||
|
||
echo "{\"analysisId\":\"$ANALYSIS_ID\",\"bucketName\":\"$BUCKET_NAME\",\"filename\":\"$FILE_NAME\"}" |
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,3 @@ | ||
test('test', () => { | ||
expect(2 + 2).toEqual(4); | ||
}); |
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,105 @@ | ||
import { getLogger } from '../../shared/powertools'; | ||
import { ensureDefined, getAccountId, sleep } from '../../shared/utils/utils'; | ||
import { quicksightClient, s3Client } from '../../shared/clients'; | ||
import { DescribeAssetBundleExportJobCommand, StartAssetBundleExportJobCommand } from '@aws-sdk/client-quicksight'; | ||
import type { DescribeAssetBundleExportJobCommandOutput } from '@aws-sdk/client-quicksight'; | ||
import type { Context } from 'aws-lambda'; | ||
import { PutObjectCommand } from '@aws-sdk/client-s3'; | ||
|
||
const logger = getLogger('lambda/quicksight-export'); | ||
|
||
interface QuicksightExportEvent { | ||
analysisId: string; | ||
bucketName: string; | ||
filename: string; | ||
} | ||
|
||
export const handler = async (event: QuicksightExportEvent, context: Context): Promise<void> => { | ||
try { | ||
const accountId = getAccountId(context); | ||
logger.info('Starting quicksight export', { event }); | ||
const jobId = await startExportJob(event, accountId); | ||
const downloadUrl = await waitForExportToFinish(jobId, accountId); | ||
await uploadToS3(event, downloadUrl); | ||
} catch (error) { | ||
logger.error('Error in quicksight export', { error }); | ||
throw error; | ||
} | ||
}; | ||
|
||
const startExportJob = async (event: QuicksightExportEvent, accountId: string): Promise<string> => { | ||
const jobId = event.analysisId + Math.random().toString(36).substring(2); | ||
const response = await quicksightClient.send( | ||
new StartAssetBundleExportJobCommand({ | ||
AwsAccountId: accountId, | ||
AssetBundleExportJobId: jobId, | ||
ResourceArns: [event.analysisId], | ||
ExportFormat: 'QUICKSIGHT_JSON', | ||
IncludeAllDependencies: false, | ||
IncludePermissions: true, | ||
}), | ||
); | ||
if (response.Status === undefined || !response.Status.toString().startsWith('2')) { | ||
throw new Error(`Start export job request with id ${jobId} returned status code of ${response.Status}`); | ||
} | ||
logger.info(`Export started with id ${jobId}`); | ||
return jobId; | ||
}; | ||
|
||
const waitForExportToFinish = async (jobId: string, accountId: string): Promise<string> => { | ||
let describeResponse: DescribeAssetBundleExportJobCommandOutput | undefined; | ||
const timeoutMs = 120 * 1000; | ||
let timeRemaining = timeoutMs; | ||
while (timeRemaining > 0) { | ||
describeResponse = await describeExportJob(jobId, accountId); | ||
// return if SUCCESSFUL to stop waiting, break if FAILED to allow error to be thrown | ||
// other possible states are QUEUED_FOR_IMMEDIATE_EXECUTION and IN_PROGRESS where we do nothing as we want to continue waiting | ||
if (describeResponse.JobStatus === 'SUCCESSFUL') { | ||
return ensureDefined(() => describeResponse?.DownloadUrl); | ||
} else if (describeResponse.JobStatus === 'FAILED') { | ||
break; | ||
} | ||
timeRemaining -= 200; | ||
await sleep(200); | ||
} | ||
logger.error('Export job did not complete', { | ||
warnings: describeResponse?.Warnings, | ||
errors: describeResponse?.Errors, | ||
status: describeResponse?.JobStatus, | ||
}); | ||
throw new Error( | ||
`Export job did not complete in ${timeoutMs}ms - final status was ${JSON.stringify(describeResponse?.JobStatus)}`, | ||
); | ||
}; | ||
|
||
const describeExportJob = async ( | ||
exportJobId: string, | ||
accountId: string, | ||
): Promise<DescribeAssetBundleExportJobCommandOutput> => { | ||
try { | ||
return await quicksightClient.send( | ||
new DescribeAssetBundleExportJobCommand({ | ||
AssetBundleExportJobId: exportJobId, | ||
AwsAccountId: accountId, | ||
}), | ||
); | ||
} catch (error) { | ||
logger.error('Error checking status of export job', { error }); | ||
throw error; | ||
} | ||
}; | ||
|
||
const uploadToS3 = async (event: QuicksightExportEvent, downloadUrl: string): Promise<void> => { | ||
try { | ||
await s3Client.send( | ||
new PutObjectCommand({ | ||
Bucket: event.bucketName, | ||
Key: event.filename, | ||
Body: await fetch(downloadUrl).then(response => response.body ?? undefined), | ||
}), | ||
); | ||
} catch (error) { | ||
logger.error('Error uploading export bundle to S3', { error }); | ||
throw error; | ||
} | ||
}; |
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,3 @@ | ||
test('test', () => { | ||
expect(2 + 2).toEqual(4); | ||
}); |
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,19 @@ | ||
import { getLogger } from '../../shared/powertools'; | ||
|
||
const logger = getLogger('lambda/quicksight-import'); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
interface QuicksightImportEvent {} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
interface QuicksightImportResult {} | ||
|
||
export const handler = async (event: QuicksightImportEvent): Promise<QuicksightImportResult> => { | ||
try { | ||
logger.info('Starting quicksight import', { event }); | ||
return {}; | ||
} catch (error) { | ||
logger.error('Error in quicksight import', { error }); | ||
throw error; | ||
} | ||
}; |