nhance/app/Views/docs/s3-cloudfront.php
2026-05-18 12:28:19 +05:30

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>