You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/08/28 17:15:19 UTC
(grails-core) branch fix/forge-aws-workflow-simple-7.0 created (now b3683f6d5e)
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a change to branch fix/forge-aws-workflow-simple-7.0
in repository https://gitbox.apache.org/repos/asf/grails-core.git
at b3683f6d5e Simplify Forge AWS GitHub Actions deploy
This branch includes the following new commits:
new b3683f6d5e Simplify Forge AWS GitHub Actions deploy
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(grails-core) 01/01: Simplify Forge AWS GitHub Actions deploy
Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch fix/forge-aws-workflow-simple-7.0
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit b3683f6d5eeaf2351f92b1f8ecc85add98d870f5
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Fri Aug 28 13:13:29 2026 -0400
Simplify Forge AWS GitHub Actions deploy
Keep only the slot input. Build the branch selected in Use workflow from.
Hard-code region and stack name. Package without running Forge tests.
Give the deploy role cloudformation:GetTemplate so UpdateEnvironment works.
Stop re-declaring the Shadow plugin; configure shadowJar when Micronaut applies it.
---
.github/workflows/forge-deploy-aws.yml | 170 +++++++----------------
grails-forge/docs/aws-elastic-beanstalk.md | 2 +-
grails-forge/grails-forge-web-netty/build.gradle | 15 +-
grails-forge/infrastructure/shared.yaml | 5 +-
4 files changed, 65 insertions(+), 127 deletions(-)
diff --git a/.github/workflows/forge-deploy-aws.yml b/.github/workflows/forge-deploy-aws.yml
index 16196af33d..cbc32afcbe 100644
--- a/.github/workflows/forge-deploy-aws.yml
+++ b/.github/workflows/forge-deploy-aws.yml
@@ -13,12 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-# GitHub registers workflow_dispatch workflows from the default branch. Before
-# first deployment, add this workflow at the same path on the default branch,
-# then dispatch it with 7.0.x selected as the run ref.
+# Dispatch from the maintenance branch that should be built (Use workflow from).
+# Choose the slot. Region, stack name, and JDK come from that branch.
name: "Forge - AWS Elastic Beanstalk Deploy"
-run-name: "Forge AWS ${{ inputs.slot }} deployment${{ inputs.release && format(' - {0}', inputs.release) || '' }}"
+run-name: "Forge AWS ${{ inputs.slot }} ${{ github.ref_name }}"
on:
workflow_dispatch:
@@ -33,20 +32,6 @@ on:
- next
- prev
- prev-snapshot
- release:
- description: 'Optional release identifier for the deployment label'
- required: false
- type: string
- shared_stack:
- description: 'CloudFormation stack exporting Forge deployment resources'
- default: grails-forge-shared
- required: false
- type: string
- aws_region:
- description: 'AWS region containing the shared stack and Elastic Beanstalk application'
- default: us-east-1
- required: false
- type: string
permissions:
contents: read
@@ -60,24 +45,35 @@ jobs:
deploy:
name: "Deploy ${{ inputs.slot }} to AWS Elastic Beanstalk"
runs-on: ubuntu-24.04
- timeout-minutes: 75
+ timeout-minutes: 40
env:
- AWS_REGION: ${{ inputs.aws_region }}
- AWS_DEFAULT_REGION: ${{ inputs.aws_region }}
+ AWS_REGION: us-east-1
+ AWS_DEFAULT_REGION: us-east-1
AWS_PAGER: ""
DEPLOY_ROLE_ARN: ${{ vars.AWS_FORGE_DEPLOY_ROLE_ARN }}
- RELEASE: ${{ inputs.release }}
- SHARED_STACK: ${{ inputs.shared_stack }}
+ SHARED_STACK: grails-forge-shared
SLOT: ${{ inputs.slot }}
steps:
- - name: "Checkout repository"
+ - name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - name: "Setup Corretto 17"
+ - name: "Read Java version"
+ id: jdk
+ shell: bash
+ run: |
+ set -euo pipefail
+ java_version="$(sed -n 's/^javaVersion=//p' gradle.properties | tr -d '\r' | awk 'NR==1')"
+ if [[ -z "${java_version}" ]]; then
+ echo "gradle.properties must define javaVersion." >&2
+ exit 1
+ fi
+ printf 'version=%s\n' "${java_version}" >> "${GITHUB_OUTPUT}"
+
+ - name: "Setup Corretto"
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: corretto
- java-version: '17'
+ java-version: ${{ steps.jdk.outputs.version }}
- name: "Setup Gradle"
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
@@ -90,84 +86,42 @@ jobs:
run: |
set -euo pipefail
case "${SLOT}" in
- latest)
- hostname='latest.grails.org'
- ;;
- snapshot)
- hostname='snapshot.grails.org'
- ;;
- next)
- hostname='next.grails.org'
- ;;
- prev)
- hostname='prev.grails.org'
- ;;
- prev-snapshot)
- hostname='prev-snapshot.grails.org'
- ;;
+ latest) hostname='latest.grails.org' ;;
+ snapshot) hostname='snapshot.grails.org' ;;
+ next) hostname='next.grails.org' ;;
+ prev) hostname='prev.grails.org' ;;
+ prev-snapshot) hostname='prev-snapshot.grails.org' ;;
*)
echo "Unsupported deployment slot" >&2
exit 1
;;
esac
-
- sanitized_release="$(printf '%s' "${RELEASE}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9._-' '-')"
- sanitized_release="${sanitized_release#-}"
- sanitized_release="${sanitized_release%-}"
- label_prefix="${SLOT}"
- if [[ -n "${sanitized_release}" ]]; then
- label_prefix="${label_prefix}-${sanitized_release}"
- fi
- label_suffix="-${GITHUB_SHA:0:12}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
- label_prefix="${label_prefix:0:$((100 - ${#label_suffix}))}"
- version_label="${label_prefix%-}${label_suffix}"
-
+ source_sha="$(git rev-parse HEAD)"
+ version_label="${SLOT}-${source_sha:0:12}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
printf 'hostname=%s\n' "${hostname}" >> "${GITHUB_OUTPUT}"
printf 'version-label=%s\n' "${version_label}" >> "${GITHUB_OUTPUT}"
printf 's3-key=artifacts/%s/%s.zip\n' "${SLOT}" "${version_label}" >> "${GITHUB_OUTPUT}"
- - name: "Test and package Forge web application"
+ - name: "Package Forge for Elastic Beanstalk"
working-directory: grails-forge
shell: bash
- run: >
- ./gradlew
- grails-forge-api:test
- grails-forge-web-netty:test
- grails-forge-web-netty:awsElasticBeanstalk
- env:
- TEST_BUILD_REPRODUCIBLE: "true"
-
- - name: "Verify Elastic Beanstalk package"
- shell: bash
- run: |
- set -euo pipefail
- artifact='grails-forge/grails-forge-web-netty/build/distributions/grails-forge-web-netty-aws.zip'
- test -s "${artifact}"
-
- - name: "Verify AWS OIDC role configuration"
- shell: bash
- run: |
- set -euo pipefail
- if [[ -z "${DEPLOY_ROLE_ARN}" ]]; then
- echo "AWS_FORGE_DEPLOY_ROLE_ARN must be configured as a repository variable." >&2
- exit 1
- fi
- if [[ ! "${DEPLOY_ROLE_ARN}" =~ ^arn:aws:iam::[0-9]{12}:role/.+$ ]]; then
- echo "AWS_FORGE_DEPLOY_ROLE_ARN must be an IAM role ARN." >&2
- exit 1
- fi
+ run: ./gradlew grails-forge-web-netty:awsElasticBeanstalk
- name: "Configure AWS credentials through OIDC"
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
with:
role-to-assume: ${{ env.DEPLOY_ROLE_ARN }}
- aws-region: ${{ inputs.aws_region }}
+ aws-region: us-east-1
- name: "Discover shared deployment resources"
id: shared
shell: bash
run: |
set -euo pipefail
+ if [[ -z "${DEPLOY_ROLE_ARN}" ]]; then
+ echo "AWS_FORGE_DEPLOY_ROLE_ARN must be configured as a repository variable." >&2
+ exit 1
+ fi
artifact_bucket="$(aws cloudformation describe-stacks --stack-name "${SHARED_STACK}" --query "Stacks[0].Outputs[?OutputKey=='ArtifactBucketName'].OutputValue | [0]" --output text)"
application_name="$(aws cloudformation describe-stacks --stack-name "${SHARED_STACK}" --query "Stacks[0].Outputs[?OutputKey=='ApplicationName'].OutputValue | [0]" --output text)"
load_balancer_dns_name="$(aws cloudformation describe-stacks --stack-name "${SHARED_STACK}" --query "Stacks[0].Outputs[?OutputKey=='SharedLoadBalancerDnsName'].OutputValue | [0]" --output text)"
@@ -183,17 +137,7 @@ jobs:
printf 'load-balancer-dns-name=%s\n' "${load_balancer_dns_name}" >> "${GITHUB_OUTPUT}"
printf 'environment-name=%s-%s\n' "${application_name}" "${SLOT}" >> "${GITHUB_OUTPUT}"
- - name: "Upload immutable Elastic Beanstalk artifact"
- shell: bash
- env:
- ARTIFACT_BUCKET: ${{ steps.shared.outputs.artifact-bucket }}
- S3_KEY: ${{ steps.target.outputs.s3-key }}
- run: |
- set -euo pipefail
- artifact='grails-forge/grails-forge-web-netty/build/distributions/grails-forge-web-netty-aws.zip'
- aws s3 cp "${artifact}" "s3://${ARTIFACT_BUCKET}/${S3_KEY}" --only-show-errors
-
- - name: "Deploy, verify, and roll back on failure"
+ - name: "Upload and deploy"
shell: bash
env:
APPLICATION_NAME: ${{ steps.shared.outputs.application-name }}
@@ -205,23 +149,21 @@ jobs:
VERSION_LABEL: ${{ steps.target.outputs.version-label }}
run: |
set -Eeuo pipefail
+ artifact='grails-forge/grails-forge-web-netty/build/distributions/grails-forge-web-netty-aws.zip'
+ test -s "${artifact}"
+ aws s3 cp "${artifact}" "s3://${ARTIFACT_BUCKET}/${S3_KEY}" --only-show-errors
+
update_started=false
- rollback_available=false
previous_version="$(aws elasticbeanstalk describe-environments --environment-names "${ENVIRONMENT_NAME}" --query 'Environments[0].VersionLabel' --output text)"
+ rollback_available=true
if [[ -z "${previous_version}" || "${previous_version}" == 'None' || "${previous_version}" == 'Sample Application' ]]; then
- echo "No prior application version is available. A failed first deployment cannot be rolled back automatically."
- else
- rollback_available=true
+ rollback_available=false
fi
wait_for_ready_green() {
local operation="$1"
local expected_version="$2"
- local status
- local health
- local current_version
- local attempt
-
+ local status health current_version attempt
for attempt in {1..90}; do
read -r status health current_version < <(aws elasticbeanstalk describe-environments --environment-names "${ENVIRONMENT_NAME}" --query 'Environments[0].[Status,Health,VersionLabel]' --output text)
if [[ "${status}" == 'Ready' && "${health}" == 'Green' && "${current_version}" == "${expected_version}" ]]; then
@@ -233,7 +175,6 @@ jobs:
fi
sleep 10
done
-
echo "${operation} did not reach Status=Ready, Health=Green, and VersionLabel=${expected_version}." >&2
return 1
}
@@ -242,14 +183,8 @@ jobs:
local exit_code=$?
trap - ERR
if [[ "${update_started}" == true && "${rollback_available}" == true ]]; then
- echo "Deployment failed. Rolling back ${ENVIRONMENT_NAME} to its previous application version."
- if aws elasticbeanstalk update-environment --environment-name "${ENVIRONMENT_NAME}" --version-label "${previous_version}" >/dev/null && wait_for_ready_green 'Rollback' "${previous_version}"; then
- echo "Rollback completed."
- else
- echo "Rollback did not reach Status=Ready and Health=Green. Manual intervention is required." >&2
- fi
- elif [[ "${update_started}" == true ]]; then
- echo "Deployment failed, but no prior application version is available for rollback." >&2
+ echo "Deployment failed. Rolling back ${ENVIRONMENT_NAME}."
+ aws elasticbeanstalk update-environment --environment-name "${ENVIRONMENT_NAME}" --version-label "${previous_version}" >/dev/null || true
fi
exit "${exit_code}"
}
@@ -264,20 +199,17 @@ jobs:
application_version_status=''
for attempt in {1..30}; do
application_version_status="$(aws elasticbeanstalk describe-application-versions --application-name "${APPLICATION_NAME}" --version-labels "${VERSION_LABEL}" --query 'ApplicationVersions[0].Status' --output text)"
+ application_version_status="${application_version_status^^}"
case "${application_version_status}" in
- Processed)
- break
- ;;
- Failed)
+ PROCESSED) break ;;
+ FAILED)
echo "Elastic Beanstalk application version processing failed." >&2
exit 1
;;
- *)
- sleep 10
- ;;
+ *) sleep 10 ;;
esac
done
- if [[ "${application_version_status}" != 'Processed' ]]; then
+ if [[ "${application_version_status}" != 'PROCESSED' ]]; then
echo "Elastic Beanstalk application version processing did not complete within the polling limit." >&2
exit 1
fi
diff --git a/grails-forge/docs/aws-elastic-beanstalk.md b/grails-forge/docs/aws-elastic-beanstalk.md
index b5178761d8..fe9cd77375 100644
--- a/grails-forge/docs/aws-elastic-beanstalk.md
+++ b/grails-forge/docs/aws-elastic-beanstalk.md
@@ -133,7 +133,7 @@ The output is `grails-forge-web-netty/build/distributions/grails-forge-web-netty
## First Deployment Before DNS
-GitHub registers `workflow_dispatch` workflows from the repository's default branch. Before the first deployment, add `.github/workflows/forge-deploy-aws.yml` at the same path on the default branch. Dispatch it with `7.0.x` selected as the run ref so the branch-scoped OIDC trust remains valid.
+GitHub registers `workflow_dispatch` from the default branch. Choose **Use workflow from** as the maintenance line to build, then choose the slot. Region, stack name, and JDK are not inputs. The deploy role trusts `refs/heads/*.x` and `refs/tags/v*` in `apache/grails-core`.
After the shared and five environment stacks exist, manually dispatch the workflow once per slot. The workflow reads `AWS_FORGE_DEPLOY_ROLE_ARN` as a repository variable, assumes it with GitHub OIDC, derives `<ApplicationName>-<slot>`, packages the bundle, uploads it to the shared artifact bucket, creates the application version, and waits for that version to be `Processed` before updating the environment.
diff --git a/grails-forge/grails-forge-web-netty/build.gradle b/grails-forge/grails-forge-web-netty/build.gradle
index f9d99bdadb..0c936caffd 100644
--- a/grails-forge/grails-forge-web-netty/build.gradle
+++ b/grails-forge/grails-forge-web-netty/build.gradle
@@ -73,20 +73,23 @@ tasks.named('dockerBuild') {
images = [findProperty('dockerImageName') ?: 'grailsforge']
}
-TaskProvider<ShadowJar> shadowJarTask = tasks.named('shadowJar', ShadowJar)
-shadowJarTask.configure {
- reproducibleFileOrder = true
- preserveFileTimestamps = false
+['com.gradleup.shadow', 'com.github.johnrengelman.shadow'].each { pluginId ->
+ pluginManager.withPlugin(pluginId) {
+ tasks.named('shadowJar', ShadowJar).configure {
+ reproducibleFileOrder = true
+ preserveFileTimestamps = false
+ }
+ }
}
TaskProvider<Zip> awsElasticBeanstalk = tasks.register('awsElasticBeanstalk', Zip) {
- dependsOn(shadowJarTask)
+ dependsOn('shadowJar')
archiveFileName.set('grails-forge-web-netty-aws.zip')
destinationDirectory.set(layout.buildDirectory.dir('distributions'))
reproducibleFileOrder = true
preserveFileTimestamps = false
- from(shadowJarTask.flatMap { it.archiveFile }) {
+ from({ tasks.named('shadowJar').get().archiveFile.get() }) {
rename { 'app.jar' }
}
from(layout.projectDirectory.dir('aws')) {
diff --git a/grails-forge/infrastructure/shared.yaml b/grails-forge/infrastructure/shared.yaml
index ba8d473673..5c57aa65f7 100644
--- a/grails-forge/infrastructure/shared.yaml
+++ b/grails-forge/infrastructure/shared.yaml
@@ -277,7 +277,10 @@ Resources:
- Fn::Sub: arn:${AWS::Partition}:elasticbeanstalk:${AWS::Region}:${AWS::AccountId}:environment/${ApplicationName}/${ApplicationName}-prev
- Fn::Sub: arn:${AWS::Partition}:elasticbeanstalk:${AWS::Region}:${AWS::AccountId}:environment/${ApplicationName}/${ApplicationName}-prev-snapshot
- Fn::Sub: arn:${AWS::Partition}:elasticbeanstalk:${AWS::Region}:${AWS::AccountId}:applicationversion/${ApplicationName}/*
- - Action: cloudformation:DescribeStacks
+ - Action:
+ - cloudformation:DescribeStacks
+ - cloudformation:DescribeStackResources
+ - cloudformation:GetTemplate
Effect: Allow
Resource: '*'
Version: '2012-10-17'