525 lines
15 KiB
PHP
525 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* S3 & CloudFront - content only
|
|
* app/Views/docs/s3-cloudfront.php
|
|
*/
|
|
?>
|
|
|
|
<p>
|
|
This page documents the current dev-side asset deployment script that uploads
|
|
files to an S3 bucket and then invalidates a selected CloudFront distribution.
|
|
The script is interactive and is intended for operator-driven publishing rather
|
|
than unattended release automation.
|
|
</p>
|
|
|
|
<div class="callout info">
|
|
<span>i</span>
|
|
<div>
|
|
<strong>Dev-focused workflow</strong>
|
|
The process below is documented from the Windows batch script currently used
|
|
for S3 upload plus CloudFront invalidation in the dev workflow.
|
|
</div>
|
|
</div>
|
|
|
|
<h2 id="overview">Overview</h2>
|
|
|
|
<div class="mermaid-wrapper">
|
|
<div class="mermaid">
|
|
flowchart TD
|
|
A[Start batch script] --> B[List S3 buckets]
|
|
B --> C[Select bucket]
|
|
C --> D[List CloudFront distributions]
|
|
D --> E[Select distribution]
|
|
E --> F[Confirm selection]
|
|
F --> G[Delete existing S3 files]
|
|
G --> H[Sync new files]
|
|
H --> I[Create CloudFront invalidation]
|
|
I --> J[Wait 45 seconds]
|
|
J --> K[Poll invalidation status]
|
|
K --> L[Finish when Completed]
|
|
</div>
|
|
</div>
|
|
|
|
<h2 id="prerequisites">Prerequisites</h2>
|
|
|
|
<p>
|
|
Before running the script, the local machine must already be able to execute
|
|
AWS CLI commands successfully.
|
|
</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr><th>Requirement</th><th>Purpose</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>aws s3 ls</code></td>
|
|
<td>Lists available buckets for the operator to choose from.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>aws cloudfront list-distributions</code></td>
|
|
<td>Lists distributions so the operator can choose the target invalidation.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>AWS credentials already configured</td>
|
|
<td>The script assumes AWS CLI authentication is already working.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Correct working directory</td>
|
|
<td><code>aws s3 sync . ...</code> uploads from the current folder, so the script must be run from the directory containing the files to publish.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2 id="selection-flow">Selection flow</h2>
|
|
|
|
<p>
|
|
The script begins with two interactive selections:
|
|
</p>
|
|
|
|
<ol class="steps">
|
|
<li>
|
|
<strong>Select the target S3 bucket</strong>
|
|
<p>It runs <code>aws s3 ls</code>, numbers the available buckets, and stores the selected bucket as <code>S3_BUCKET</code>.</p>
|
|
</li>
|
|
<li>
|
|
<strong>Select the target CloudFront distribution</strong>
|
|
<p>It runs <code>aws cloudfront list-distributions</code> and shows the distribution ID, domain name, and comment for each available entry.</p>
|
|
</li>
|
|
<li>
|
|
<strong>Confirm before deploy</strong>
|
|
<p>The operator can proceed, re-choose the bucket/distribution pair, or exit before any destructive operation starts.</p>
|
|
</li>
|
|
</ol>
|
|
|
|
<p>
|
|
The current UI-side mapping used for bucket to CloudFront pairing is:
|
|
</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr><th>S3 bucket</th><th>CloudFront distribution ID</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>uat-benefits-app-bucket</code></td>
|
|
<td><code>EUBZ8CDSV9KZZ</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>uat-hr-app-bucket</code></td>
|
|
<td><code>E9TNPRI9ITM1M</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>benefits-app-bucket</code></td>
|
|
<td><code>E1MKRK4U5MZ3BD</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>live-hr-app-bucket</code></td>
|
|
<td><code>E3TE01DPKHTD8B</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p>
|
|
These pairs are aligned with the bucket-to-distribution mapping currently used
|
|
in <code>app/Views/fedeploy.php</code>.
|
|
</p>
|
|
|
|
<div class="callout warning">
|
|
<span>!</span>
|
|
<div>
|
|
<strong>Interactive by design</strong>
|
|
This script is not written as a fixed one-click pipeline. It deliberately
|
|
pauses for operator selection and confirmation before deployment begins.
|
|
</div>
|
|
</div>
|
|
|
|
<h2 id="deployment-steps">Deployment steps</h2>
|
|
|
|
<p>
|
|
Once confirmed, the script executes five sequential steps:
|
|
</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr><th>Step</th><th>What it does</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>1/5</code></td>
|
|
<td>Deletes the existing contents of the selected S3 bucket with <code>aws s3 rm --recursive</code>.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>2/5</code></td>
|
|
<td>Syncs the current directory into the bucket with <code>aws s3 sync</code>, excluding <code>*.bat</code> and <code>*.bat.*</code>.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>3/5</code></td>
|
|
<td>Creates a CloudFront invalidation for <code>/*</code> and captures the invalidation ID.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>4/5</code></td>
|
|
<td>Waits 45 seconds before the first status check.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>5/5</code></td>
|
|
<td>Polls invalidation status until it becomes <code>Completed</code> or retries are exhausted.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<pre><code class="language-batch">aws s3 rm "%S3_BUCKET%" --recursive
|
|
aws s3 sync . "%S3_BUCKET%" --exclude "*.bat" --exclude "*.bat.*"
|
|
aws cloudfront create-invalidation --distribution-id %DIST_ID% --paths "/*"</code></pre>
|
|
|
|
<div class="callout danger">
|
|
<span>!</span>
|
|
<div>
|
|
<strong>Bucket cleanup is destructive</strong>
|
|
The script removes existing files from the selected S3 bucket before syncing
|
|
the new content. Confirm the selected bucket carefully before proceeding.
|
|
</div>
|
|
</div>
|
|
|
|
<h2 id="invalidation-polling">Invalidation polling</h2>
|
|
|
|
<p>
|
|
The script includes explicit status handling for CloudFront invalidation:
|
|
</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr><th>Setting</th><th>Value</th><th>Purpose</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>WAIT_SECONDS</code></td>
|
|
<td><code>45</code></td>
|
|
<td>Initial wait before the first invalidation status check.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>MAX_STATUS_RETRIES</code></td>
|
|
<td><code>20</code></td>
|
|
<td>Upper bound for repeated status polling attempts.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Retry delay</td>
|
|
<td><code>15</code> seconds</td>
|
|
<td>Pause between repeated invalidation status checks.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p>
|
|
If AWS returns an error or the status is empty, the script retries until the
|
|
maximum retry count is reached. If the returned status becomes
|
|
<code>Completed</code>, the deployment is treated as successful.
|
|
</p>
|
|
|
|
<pre><code class="language-text">Completed
|
|
InProgress
|
|
</code></pre>
|
|
|
|
<h2 id="operational-notes">Operational notes</h2>
|
|
|
|
<ol class="steps">
|
|
<li>
|
|
<strong>Run from the correct publish directory</strong>
|
|
<p>The script syncs the current directory, so it should be started only from the folder whose contents should go to S3.</p>
|
|
</li>
|
|
<li>
|
|
<strong>Verify the chosen bucket and distribution before confirming</strong>
|
|
<p>The confirmation step exists to prevent publishing to the wrong S3 bucket or invalidating the wrong distribution.</p>
|
|
</li>
|
|
<li>
|
|
<strong>Keep batch files out of the published output</strong>
|
|
<p>The script explicitly excludes batch files during sync so deployment helpers are not uploaded into the target bucket.</p>
|
|
</li>
|
|
<li>
|
|
<strong>Watch for invalidation completion</strong>
|
|
<p>The script does not finish immediately after creating the invalidation; it waits and polls until the status is complete or retries are exhausted.</p>
|
|
</li>
|
|
</ol>
|
|
|
|
<div class="callout success">
|
|
<span>+</span>
|
|
<div>
|
|
<strong>Practical use</strong>
|
|
Use this workflow when dev static assets or frontend build output must be
|
|
refreshed in S3 and then propagated through CloudFront without waiting for
|
|
normal cache expiry.
|
|
</div>
|
|
</div>
|
|
|
|
<h2 id="full-script">Full script</h2>
|
|
|
|
<p>
|
|
Full reference copy of the current Windows batch script:
|
|
</p>
|
|
|
|
<pre><code class="language-batch">@echo off
|
|
setlocal EnableDelayedExpansion
|
|
|
|
REM ==============================
|
|
REM CONFIGURATION
|
|
REM ==============================
|
|
set WAIT_SECONDS=45
|
|
set MAX_STATUS_RETRIES=20
|
|
|
|
REM ==============================
|
|
REM SELECT S3 BUCKET
|
|
REM ==============================
|
|
:SELECT_BUCKET
|
|
cls
|
|
echo =========================================
|
|
echo AVAILABLE S3 BUCKETS
|
|
echo =========================================
|
|
echo.
|
|
|
|
set count=0
|
|
for /f "tokens=3 delims= " %%a in ('aws s3 ls') do (
|
|
set /a count+=1
|
|
set bucket[!count!]=%%a
|
|
echo !count!. %%a
|
|
)
|
|
|
|
if %count%==0 (
|
|
echo [ERROR] No S3 buckets found.
|
|
pause
|
|
exit /b 1
|
|
)
|
|
|
|
echo.
|
|
set /p bucketChoice=Select bucket number:
|
|
|
|
if not defined bucket[%bucketChoice%] (
|
|
echo [ERROR] Invalid selection.
|
|
ping -n 3 127.0.0.1 >nul
|
|
goto SELECT_BUCKET
|
|
)
|
|
|
|
set S3_BUCKET=s3://!bucket[%bucketChoice%]!
|
|
|
|
REM ==============================
|
|
REM SELECT CLOUDFRONT DISTRIBUTION
|
|
REM ==============================
|
|
:SELECT_CF
|
|
cls
|
|
echo =========================================
|
|
echo AVAILABLE CLOUDFRONT DISTRIBUTIONS
|
|
echo =========================================
|
|
echo.
|
|
|
|
set cfcount=0
|
|
for /f "tokens=1,2,3 delims= " %%a in ('aws cloudfront list-distributions --query "DistributionList.Items[*].[Id,DomainName,Comment]" --output text') do (
|
|
set /a cfcount+=1
|
|
set cfid[!cfcount!]=%%a
|
|
echo !cfcount!. ID: %%a
|
|
echo Domain: %%b
|
|
echo Desc : %%c
|
|
echo.
|
|
)
|
|
|
|
if %cfcount%==0 (
|
|
echo [ERROR] No CloudFront distributions found.
|
|
pause
|
|
exit /b 1
|
|
)
|
|
|
|
set /p cfChoice=Select CloudFront number:
|
|
|
|
if not defined cfid[%cfChoice%] (
|
|
echo [ERROR] Invalid selection.
|
|
ping -n 3 127.0.0.1 >nul
|
|
goto SELECT_CF
|
|
)
|
|
|
|
set DIST_ID=!cfid[%cfChoice%]!
|
|
|
|
REM ==============================
|
|
REM CONFIRM SELECTION
|
|
REM ==============================
|
|
:CONFIRM
|
|
cls
|
|
echo =========================================
|
|
echo CONFIRM YOUR SELECTION
|
|
echo =========================================
|
|
echo.
|
|
echo S3 Bucket : %S3_BUCKET%
|
|
echo CloudFront : %DIST_ID%
|
|
echo.
|
|
echo 1. Proceed
|
|
echo 2. Re-choose
|
|
echo 3. Exit
|
|
echo.
|
|
|
|
set /p confirmChoice=Enter choice:
|
|
|
|
if "%confirmChoice%"=="1" goto DEPLOY
|
|
if "%confirmChoice%"=="2" goto SELECT_BUCKET
|
|
if "%confirmChoice%"=="3" exit /b 0
|
|
|
|
goto CONFIRM
|
|
|
|
|
|
REM ==============================
|
|
REM DEPLOYMENT FLOW
|
|
REM ==============================
|
|
:DEPLOY
|
|
cls
|
|
echo =========================================
|
|
echo DEPLOYMENT STARTED
|
|
echo =========================================
|
|
echo.
|
|
|
|
REM --------------------------------------------------
|
|
REM STEP 1: REMOVE EXISTING S3 FILES
|
|
REM --------------------------------------------------
|
|
echo [1/5] Removing existing files from %S3_BUCKET%...
|
|
aws s3 rm "%S3_BUCKET%" --recursive
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo [ERROR] S3 DELETE FAILED. Aborting.
|
|
exit /b 1
|
|
)
|
|
echo [OK] S3 cleanup completed.
|
|
echo.
|
|
|
|
REM --------------------------------------------------
|
|
REM STEP 2: SYNC NEW FILES
|
|
REM FIX B1+B2: Quoted S3_BUCKET, exclude .bat files from sync
|
|
REM --------------------------------------------------
|
|
echo [2/5] Uploading new files to %S3_BUCKET%...
|
|
aws s3 sync . "%S3_BUCKET%" --exclude "*.bat" --exclude "*.bat.*"
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo [ERROR] S3 SYNC FAILED. Aborting.
|
|
exit /b 1
|
|
)
|
|
echo [OK] S3 sync completed.
|
|
echo.
|
|
|
|
REM --------------------------------------------------
|
|
REM STEP 3: CREATE CLOUDFRONT INVALIDATION
|
|
REM FIX B3: Pre-clear variable before reading temp file
|
|
REM FIX B4: Use for /f to read temp file -- strips \r automatically
|
|
REM --------------------------------------------------
|
|
echo [3/5] Creating CloudFront invalidation...
|
|
|
|
set INVALIDATION_ID=
|
|
aws cloudfront create-invalidation --distribution-id %DIST_ID% --paths "/*" --query "Invalidation.Id" --output text > "%TEMP%\cf_inv_id.txt" 2>"%TEMP%\cf_inv_err.txt"
|
|
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo [ERROR] CloudFront invalidation creation failed.
|
|
echo --- AWS Error Output ---
|
|
type "%TEMP%\cf_inv_err.txt"
|
|
del "%TEMP%\cf_inv_id.txt" >nul 2>&1
|
|
del "%TEMP%\cf_inv_err.txt" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
|
|
REM for /f auto-strips \r\n giving a clean value -- fixes the critical \r bug
|
|
for /f "usebackq delims=" %%i in ("%TEMP%\cf_inv_id.txt") do set INVALIDATION_ID=%%i
|
|
del "%TEMP%\cf_inv_id.txt" >nul 2>&1
|
|
del "%TEMP%\cf_inv_err.txt" >nul 2>&1
|
|
|
|
if "%INVALIDATION_ID%"=="" (
|
|
echo [ERROR] Invalidation ID was empty after creation. Aborting.
|
|
exit /b 1
|
|
)
|
|
|
|
echo [OK] Invalidation created.
|
|
echo Invalidation ID : %INVALIDATION_ID%
|
|
echo Distribution ID : %DIST_ID%
|
|
echo.
|
|
|
|
REM --------------------------------------------------
|
|
REM STEP 4: WAIT 45 SECONDS
|
|
REM --------------------------------------------------
|
|
set /a PING_COUNT=%WAIT_SECONDS%+1
|
|
echo [4/5] Waiting %WAIT_SECONDS% seconds before polling...
|
|
ping -n %PING_COUNT% 127.0.0.1 >nul
|
|
echo [OK] Wait complete.
|
|
echo.
|
|
|
|
REM --------------------------------------------------
|
|
REM STEP 5: POLL INVALIDATION STATUS
|
|
REM FIX B5: for /f strips \r so STATUS matches "Completed" correctly
|
|
REM FIX B6: Retry counter prevents infinite loop on AWS errors
|
|
REM --------------------------------------------------
|
|
set retryCount=0
|
|
|
|
:CHECK_STATUS
|
|
cls
|
|
echo =========================================
|
|
echo Checking CloudFront Invalidation Status
|
|
echo =========================================
|
|
echo.
|
|
echo Invalidation ID : %INVALIDATION_ID%
|
|
echo Distribution ID : %DIST_ID%
|
|
echo.
|
|
|
|
set STATUS=
|
|
aws cloudfront get-invalidation --distribution-id %DIST_ID% --id %INVALIDATION_ID% --query "Invalidation.Status" --output text > "%TEMP%\cf_status.txt" 2>"%TEMP%\cf_status_err.txt"
|
|
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo [WARN] AWS call failed. Error output:
|
|
type "%TEMP%\cf_status_err.txt"
|
|
del "%TEMP%\cf_status.txt" >nul 2>&1
|
|
del "%TEMP%\cf_status_err.txt" >nul 2>&1
|
|
set /a retryCount+=1
|
|
if !retryCount! GEQ %MAX_STATUS_RETRIES% (
|
|
echo [ERROR] Max retries ^(%MAX_STATUS_RETRIES%^) reached. Deployment status unknown.
|
|
exit /b 1
|
|
)
|
|
echo Retrying in 15 seconds... [Attempt !retryCount!/%MAX_STATUS_RETRIES%]
|
|
ping -n 16 127.0.0.1 >nul
|
|
goto CHECK_STATUS
|
|
)
|
|
|
|
for /f "usebackq delims=" %%s in ("%TEMP%\cf_status.txt") do set STATUS=%%s
|
|
del "%TEMP%\cf_status.txt" >nul 2>&1
|
|
del "%TEMP%\cf_status_err.txt" >nul 2>&1
|
|
|
|
if "%STATUS%"=="" (
|
|
set /a retryCount+=1
|
|
echo [WARN] Empty status received.
|
|
if !retryCount! GEQ %MAX_STATUS_RETRIES% (
|
|
echo [ERROR] Max retries ^(%MAX_STATUS_RETRIES%^) reached. Aborting.
|
|
exit /b 1
|
|
)
|
|
echo Retrying in 15 seconds... [Attempt !retryCount!/%MAX_STATUS_RETRIES%]
|
|
ping -n 16 127.0.0.1 >nul
|
|
goto CHECK_STATUS
|
|
)
|
|
|
|
echo Current Status : %STATUS%
|
|
echo.
|
|
|
|
if /I "%STATUS%"=="Completed" (
|
|
echo =========================================
|
|
echo INVALIDATION COMPLETED SUCCESSFULLY
|
|
echo =========================================
|
|
goto END_SCRIPT
|
|
)
|
|
|
|
set /a retryCount+=1
|
|
if !retryCount! GEQ %MAX_STATUS_RETRIES% (
|
|
echo [ERROR] Max retries ^(%MAX_STATUS_RETRIES%^) reached. Last status: %STATUS%
|
|
exit /b 1
|
|
)
|
|
|
|
echo [INFO] Still "%STATUS%". Checking again in 15 seconds... [Attempt !retryCount!/%MAX_STATUS_RETRIES%]
|
|
ping -n 16 127.0.0.1 >nul
|
|
goto CHECK_STATUS
|
|
|
|
REM ==============================
|
|
REM DONE
|
|
REM ==============================
|
|
:END_SCRIPT
|
|
echo.
|
|
echo =========================================
|
|
echo DEPLOYMENT FINISHED SUCCESSFULLY
|
|
echo =========================================
|
|
echo.
|
|
pause
|
|
exit /b 0</code></pre>
|