diff --git a/.github/workflows/cloudpod_release.yml b/.github/workflows/cloudpod_release.yml new file mode 100644 index 0000000..c7f8208 --- /dev/null +++ b/.github/workflows/cloudpod_release.yml @@ -0,0 +1,53 @@ +on: + workflow_dispatch: + inputs: + release-tag: + type: string + required: true + push: + paths-ignore: + - 'README.md' + branches: + - main + +permissions: + contents: write + +name: Create Release +jobs: + build: + uses: ./.github/workflows/setup.yml + secrets: inherit + with: + store-cloudpod: "true" + localstack-version: ${{ inputs.release-tag || 'latest'}} + upload: + needs: build + name: Upload Release Asset + runs-on: ubuntu-latest + steps: + - name: Download Pod Artifacts + uses: actions/download-artifact@v3 + with: + name: cloudpod + + - name: Display structure of downloaded files + run: ls -R + + - name: Prepare Release Notes + run: | + echo "This release includes the Cloud Pod of the sample created with LocalStack Version \`${{ inputs.release-tag || 'latest'}}\`." > Release.txt + echo "### MySQL" >> Release.txt + echo "The pod was created with `mysql` engine." >> Release.txt + echo "You can click the Launchpad to inject the \`mysql\` version of the pod into your running LocalStack instance:" >> Release.txt + echo "[![LocalStack Pods Launchpad](https://localstack.cloud/gh/launch-pod-badge.svg)](https://app.localstack.cloud/launchpad?url=https://github.com/$GITHUB_REPOSITORY/releases/download/${{ inputs.release-tag || 'latest'}}/release-pod-mysql.zip)" >> Release.txt + + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + tag_name: "${{ inputs.release-tag || 'latest'}}" + name: "Cloud Pod for LocalStack Version '${{ inputs.release-tag || 'latest'}}'" + body_path: ./Release.txt + files: | + ./release-pod-mysql.zip \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ecfef68..9796b37 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: LS_LOG=trace localstack start -d # Wait 30 seconds for the LocalStack container to become ready before timing out echo "Waiting for LocalStack startup..." - localstack wait -t 15 + localstack wait -t 30 echo "Startup complete" - name: Deploy the application @@ -61,21 +61,9 @@ jobs: make bootstrap make deploy - - name: Smoke Test + - name: Run Smoke Tests run: | - awslocal --version - awslocal lambda invoke --cli-binary-format raw-in-base64-out --function-name my-lambda-rds-query-helper --payload '{"sqlQuery": "show tables", "secretName":"/rdsinitexample/rds/creds/mysql-01"}' output - echo "show tables:" - cat output - awslocal lambda invoke --cli-binary-format raw-in-base64-out --function-name my-lambda-rds-query-helper --payload '{"sqlQuery": "select Author from Books", "secretName":"/rdsinitexample/rds/creds/mysql-01"}' output - echo "select Author from Books:" - cat output - return_status=$(cat output | jq -r .status) - if [ "SUCCESS" != ${return_status} ]; then - echo "unexpected response: ${return_status}" - cat output - exit 1 - fi + ./test-helper/smoke-test.sh - name: Show Logs if: always() diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml new file mode 100644 index 0000000..0cbeadd --- /dev/null +++ b/.github/workflows/setup.yml @@ -0,0 +1,77 @@ +name: Setup Sample with LocalStack +on: + workflow_call: + inputs: + store-cloudpod: + required: true + type: string + localstack-version: + required: true + type: string + +jobs: + setup-localstack: + name: Setup infrastructure + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Setup Nodejs + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install dependencies + run: | + make install + + - name: Start LocalStack + env: + LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }} + LOCALSTACK_VOLUME_DIR: ${{ github.workspace }}/ls_test + run: | + mkdir ls_test + ls -la ls_test + docker pull localstack/localstack-pro:${{ inputs.localstack-version }} + # Start LocalStack in the background + LS_LOG=trace localstack start -d + # Wait 30 seconds for the LocalStack container to become ready before timing out + echo "Waiting for LocalStack startup..." + localstack wait -t 30 + echo "Startup complete" + + - name: Deploy the application + env: + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + run: | + make build + make bootstrap + make deploy + + # TODO should we run smoke tests before creating the pod? + - name: Run Smoke Tests + run: | + ./test-helper/smoke-test.sh + + - name: Save the Cloud Pod + if: ${{ inputs.store-cloudpod == 'true' }} + uses: HarshCasper/cloud-pod-save@v0.1.0 + with: + name: 'release-pod-mysql.zip' + location: 'disk' + + - name: Upload Pod as Artifact + if: ${{ inputs.store-cloudpod == 'true' }} + uses: actions/upload-artifact@v3 + with: + name: cloudpod + path: release-pod-mysql.zip + retention-days: 1 diff --git a/.github/workflows/test_cloudpods.yml b/.github/workflows/test_cloudpods.yml new file mode 100644 index 0000000..bf6d482 --- /dev/null +++ b/.github/workflows/test_cloudpods.yml @@ -0,0 +1,112 @@ +name: Test Released Cloud Pods + +on: + schedule: + # β€œAt 00:00 on Saturday.” + - cron: "0 0 * * 6" + workflow_dispatch: + +permissions: + contents: write + +jobs: + get-releases: + name: Retrieve Released Cloud Pods + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + output=$(gh api repos/$GITHUB_REPOSITORY/releases | jq '[.[].tag_name]') + output=$(echo $output | tr '\n' ' ') + echo "matrix=$output" >> $GITHUB_OUTPUT + + test-pod-release: + needs: get-releases + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + # checkout to run the smoke-test.sh + - name: Checkout + uses: actions/checkout@v3 + + - name: Retrieve Pod + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # TODO the download url seems to follow the pattern $GITHUB_REPOSITORY/releases/download/{TAG}/{ASSET_NAME} + # alternatively we can query the asset-id, and browser_download_url, but it seems like an overhead + # asset_id=$(gh api repos/$GITHUB_REPOSITORY/releases/tags/latest | jq -r '.assets[]' | jq --arg DB $DB -c 'select(.name=="release-pod-\( $DB ).zip") | .id) + # download_url=$(gh api repos/$GITHUB_REPOSITORY/releases/assets/$asset_id | jq -r ".browser_download_url") + download_url="https://github.com/$GITHUB_REPOSITORY/releases/download/${{ matrix.tag }}/release-pod-${{ matrix.db }}.zip" + curl -L $download_url --output release-pod.zip + ls -la + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install Dependencies + run: | + pip install localstack awscli-local + + - name: Start LocalStack + env: + LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }} + LOCALSTACK_VOLUME_DIR: ${{ github.workspace }}/ls_test + DEBUG: 1 + POD_LOAD_CLI_TIMEOUT: 300 + run: | + mkdir ls_test + ls -la ls_test + docker pull localstack/localstack-pro:${{ matrix.tag }} + # Start LocalStack in the background + localstack start -d + # Wait 30 seconds for the LocalStack container to become ready before timing out + echo "Waiting for LocalStack startup..." + localstack wait -t 30 + echo "Startup complete" + + - name: Inject Pod + run: | + localstack pod load file://release-pod.zip + state=$(awslocal rds describe-db-instances | jq -r ".DBInstances[0].DBInstanceStatus") + while [ "$state" = creating ]; do + sleep 1 + state=$(awslocal rds describe-db-instances | jq -r ".DBInstances[0].DBInstanceStatus") + done + + - name: Run Smoke Tests + run: | + ./test-helper/smoke-test.sh + + - name: Show Logs + if: failure() + run: | + localstack logs + + - name: Send a Slack notification + if: failure() || github.event_name != 'pull_request' + uses: ravsamhq/notify-slack-action@v2 + with: + status: ${{ job.status }} + token: ${{ secrets.GITHUB_TOKEN }} + notification_title: "{workflow} has {status_message}" + message_format: "{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>" + footer: "Linked Repo <{repo_url}|{repo}> | <{run_url}|View Workflow run>" + notify_when: "failure" + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + - name: Prevent Workflows from getting Stale + if: always() + uses: gautamkrishnar/keepalive-workflow@v1 + with: + # this message should prevent automatic triggering of workflows + # see https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs + commit_message: "[skip ci] Automated commit by Keepalive Workflow to keep the repository active" diff --git a/README.md b/README.md index 5337c8d..4b6b049 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,50 @@ The implementation can be found in `lib/resource-initializer.ts`. More details about the original sample can be found in the AWS blog post β€” [Use AWS CDK to initialize Amazon RDS instances](https://aws.amazon.com/blogs/infrastructure-and-automation/use-aws-cdk-to-initialize-amazon-rds-instances/). + +## Cloud Pods + +[Cloud Pods](https://docs.localstack.cloud/user-guide/tools/cloud-pods/) are a mechanism that allows you to take a snapshot of the state in your current LocalStack instance, persist it to a storage backend, and easily share it with your team members. + +You can convert your current AWS infrastructure state to a Cloud Pod using the `localstack` CLI. +Check out our [Getting Started guide](https://docs.localstack.cloud/user-guide/tools/cloud-pods/getting-started/) and [LocalStack Cloud Pods CLI reference](https://docs.localstack.cloud/user-guide/tools/cloud-pods/pods-cli/) to learn more about Cloud Pods and how to use them. + +To inject a Cloud Pod you can use [Cloud Pods Launchpad](https://docs.localstack.cloud/user-guide/tools/cloud-pods/launchpad/) wich quickly injects Cloud Pods into your running LocalStack container. + +> **NOTE**: The Cloud Pod linked here, was created with the `latest` LocalStack version, make sure you also run `latest` when injecting. +Further, LocalStack needs to be started with the flag `RDS_MYSQL_DOCKER=1`. For different flavors check the available [releases](https://github.com/localstack/amazon-rds-init-cdk/releases). + +Click here [![LocalStack Pods Launchpad](https://localstack.cloud/gh/launch-pod-badge.svg)](https://app.localstack.cloud/launchpad?url=https://github.com/localstack/amazon-rds-init-cdk/releases/download/latest/release-pod-mysql.zip) to launch the Cloud Pods Launchpad and inject the Cloud Pod for this application by clicking the `Inject` button. + +![Cloud Pod injection with the Cloud Pod Launchpad](images/screenshot_launchpad.png) + +Alternatively, you can inject the pod by using the `localstack` CLI. +First, you need to download the pod you want to inject from the [releases](https://github.com/localstack/amazon-rds-init-cdk/releases). +Then run: + +```sh +localstack pod load file://$(pwd)/release-pod-mysql.zip +``` + + +### Troubleshooting Cloud Pod Injection + +If you are on MacOS using the Docker Desktop App, and you want to inject the mysql pod, you might need to change some settings. + +The error message in LocalStack is visible when you enable debugging (`DEBUG=1`): + +``` +Different lower_case_table_names settings for server ('2') and data dictionary ('0'). +Data Dictionary initialization failed. +``` + +To fix this, go to the settings of the Docker Desktop App -> General, and then select `osxfs (Legacy)` for file sharing: + +![Change the Docker Desktop Setting](images/screenshot_docker_desktop_setting.png) + +Apply the changes, and restart LocalStack before attempting to inject the pod again. + + ## Contributing We appreciate your interest in contributing to our project and are always looking for new ways to improve the developer experience. We welcome feedback, bug reports, and even feature ideas from the community. diff --git a/images/screenshot_docker_desktop_setting.png b/images/screenshot_docker_desktop_setting.png new file mode 100644 index 0000000..05529ad Binary files /dev/null and b/images/screenshot_docker_desktop_setting.png differ diff --git a/images/screenshot_launchpad.png b/images/screenshot_launchpad.png new file mode 100644 index 0000000..8b6e34c Binary files /dev/null and b/images/screenshot_launchpad.png differ diff --git a/test-helper/smoke-test.sh b/test-helper/smoke-test.sh new file mode 100755 index 0000000..d143c53 --- /dev/null +++ b/test-helper/smoke-test.sh @@ -0,0 +1,26 @@ +# simple smoke test: runs two queries using the lambda that should be deployed +# which connects to the database +# it fails if the lambda cannot be called, or the database is not accessible/the tables are not available + +awslocal lambda invoke --cli-binary-format raw-in-base64-out --function-name my-lambda-rds-query-helper --payload '{"sqlQuery": "show tables", "secretName":"/rdsinitexample/rds/creds/mysql-01"}' output1 +echo "show tables:" +cat output1 + +awslocal lambda invoke --cli-binary-format raw-in-base64-out --function-name my-lambda-rds-query-helper --payload '{"sqlQuery": "select Author from Books", "secretName":"/rdsinitexample/rds/creds/mysql-01"}' output2 +echo "select Author from Books:" +cat output2 + + +return_status1=$(cat output1 | jq -r .status) +if [ "SUCCESS" != ${return_status1} ]; then + echo "unexpected response for query1: ${return_status1}" + cat output1 + exit 1 +fi + +return_status2=$(cat output2 | jq -r .status) +if [ "SUCCESS" != ${return_status2} ]; then + echo "unexpected response for query2: ${return_status2}" + cat output2 + exit 1 +fi \ No newline at end of file