Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/12.java-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 12 - Java Edition:建置與測試(CI)
#
# 教學重點
# - paths 過濾:只有 src-java/** 有變更時才跑,避免 C# 的變更觸發 Java CI
# - setup-java 的 cache: maven 就足以加速相依套件下載,不需要自己寫 actions/cache
# - Artifact 只上傳「要部署的那一個檔案」(src-java/target/simpleweb.jar),
# 不要把整個 target/ 丟上去
name: 12.java-build

on:
push:
paths:
- 'src-java/**'
- '.github/workflows/12.java-build.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
cache: maven
cache-dependency-path: src-java/pom.xml

- name: Build and test
working-directory: src-java
run: ./mvnw -B verify

# if-no-files-found: error → finalName 若被改掉,這裡會直接失敗而不是靜靜地上傳空的 artifact
- name: Upload deployable jar
uses: actions/upload-artifact@v7
with:
name: simpleweb-jar
path: src-java/target/simpleweb.jar
if-no-files-found: error
retention-days: 7

- name: Write job summary
run: |
{
echo "## SimpleWeb Java Edition"
echo ""
echo "- 產出物:\`src-java/target/simpleweb.jar\`"
echo "- 大小:$(du -h src-java/target/simpleweb.jar | cut -f1)"
echo "- SHA256:\`$(sha256sum src-java/target/simpleweb.jar | cut -d' ' -f1)\`"
echo "- 來源 commit:\`${GITHUB_SHA:0:7}\`"
} >> "$GITHUB_STEP_SUMMARY"
186 changes: 186 additions & 0 deletions .github/workflows/13.java-vm-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# 13 - Java Edition:部署到 VM(手動觸發)
#
# 教學重點
# - 只用 workflow_dispatch:部署是「人決定什麼時候做」的動作,不要綁在 push 上
# - environment 由執行時輸入的參數決定;正式環境的保護(required reviewers、
# wait timer、限制分支)請在 GitHub Environment 設定,不要寫死在 workflow 裡
# - permissions 分 job 給:build 需要 contents: write(發佈 Release asset),
# deploy 只需要 contents: read + id-token: write(OIDC)
# - az vm run-command:不用開 SSH port、不用保管私鑰就能在 VM 上執行指令
#
# 部署載荷怎麼上 VM?
# 把 jar 發佈成「滾動式」的 public pre-release asset,VM 端用 curl 匿名下載,
# 不需要額外的儲存體或憑證。(私有 repo 請改用 SAS URL 或自架儲存體。)
#
# 執行前需要設定(Repository 或 Environment 層級):
# variables:AZURE_RESOURCE_GROUP、AZURE_VM_NAME、VM_PUBLIC_IP
# secrets :AZURE_CLIENT_ID、AZURE_TENANT_ID、AZURE_SUBSCRIPTION_ID
# VM 端 :simpleweb 使用者、/opt/simpleweb/{test,prod} 目錄、
# simpleweb-test / simpleweb-prod 兩個 systemd unit
# (unit 需 EnvironmentFile=/opt/simpleweb/<env>/app.env)
name: 13.java-vm-deploy

on:
workflow_dispatch:
inputs:
environment:
description: 要部署的環境
type: choice
required: true
default: test
options:
- test
- production

permissions:
contents: read

# 滾動式 asset 只有一份,同時跑兩個部署會互相覆蓋;用 concurrency 讓部署一次只跑一個。
# cancel-in-progress: false → 排隊等前一個做完,不要把部署到一半的 run 砍掉。
concurrency:
group: java-vm-deploy
cancel-in-progress: false

env:
RELEASE_TAG: java-build-latest

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write # 只有這個 job 需要寫入權限,用來發佈 Release asset

steps:
- uses: actions/checkout@v7

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
cache: maven
cache-dependency-path: src-java/pom.xml

- name: Build and test
working-directory: src-java
run: ./mvnw -B verify

# 滾動式 pre-release:tag 固定,asset 每次覆蓋,VM 端的下載網址永遠一樣
- name: Publish deployable
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
gh release view "$RELEASE_TAG" >/dev/null 2>&1 \
|| gh release create "$RELEASE_TAG" --prerelease \
--title "SimpleWeb Java rolling build" \
--notes "由 13.java-vm-deploy 自動更新的最新可部署版本"
gh release upload "$RELEASE_TAG" src-java/target/simpleweb.jar --clobber

deploy:
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
id-token: write # OIDC 換發 token 必要!少了這行 azure/login 一定失敗
# 這一行讓 run 出現在 Environments 頁面,也是套用保護規則的依據。
# 選 production 時若設了 required reviewers,job 會停在 Waiting 等人核准。
environment:
name: ${{ inputs.environment }}
url: http://${{ vars.VM_PUBLIC_IP }}:${{ inputs.environment == 'production' && '8081' || '8080' }}
env:
APP_DIR: ${{ inputs.environment == 'production' && '/opt/simpleweb/prod' || '/opt/simpleweb/test' }}
SERVICE_NAME: ${{ inputs.environment == 'production' && 'simpleweb-prod' || 'simpleweb-test' }}
APP_PORT: ${{ inputs.environment == 'production' && '8081' || '8080' }}
APP_ENVIRONMENT: ${{ inputs.environment }}

steps:
# 先確認設定齊全,錯誤訊息比「az 指令跑到一半失敗」清楚很多
- name: Check required configuration
env:
AZURE_RESOURCE_GROUP: ${{ vars.AZURE_RESOURCE_GROUP }}
AZURE_VM_NAME: ${{ vars.AZURE_VM_NAME }}
VM_PUBLIC_IP: ${{ vars.VM_PUBLIC_IP }}
run: |
set -eu
missing=''
for name in AZURE_RESOURCE_GROUP AZURE_VM_NAME VM_PUBLIC_IP; do
eval "value=\${$name:-}"
[ -n "$value" ] || missing="$missing $name"
done
if [ -n "$missing" ]; then
echo "缺少必要的 variables:$missing" >&2
exit 1
fi

# 使用 OIDC 登入 Azure(GitHub 這邊不需要存任何長期的 Azure 密碼)
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

# 訓練用訂閱常有 auto-shutdown;部署前先確保 VM 正在執行
- name: Ensure VM is running
run: |
az vm start \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--name "${{ vars.AZURE_VM_NAME }}"

# 注意:VM 上的預設 shell 不是 bash,所以只用 POSIX 的 set -eu(沒有 pipefail)
# run-command 即使腳本失敗也可能回報成功,所以腳本最後印出 DEPLOY_OK,由這裡驗證。
- name: Deploy to ${{ inputs.environment }} VM
env:
JAR_URL: ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ env.RELEASE_TAG }}/simpleweb.jar
run: |
output="$(az vm run-command invoke \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--name "${{ vars.AZURE_VM_NAME }}" \
--command-id RunShellScript \
--scripts "
set -eu
curl -fsSL --retry 3 -o '$APP_DIR/simpleweb.jar.new' '$JAR_URL'
install -o simpleweb -g simpleweb -m 0644 '$APP_DIR/simpleweb.jar.new' '$APP_DIR/simpleweb.jar'
rm -f '$APP_DIR/simpleweb.jar.new'
printf 'APP_ENVIRONMENT=%s\nAPP_BUILD_SHA=%s\nAPP_BUILD_TIME=%s\n' '$APP_ENVIRONMENT' '${{ github.sha }}' \"\$(date -u +%FT%TZ)\" > '$APP_DIR/app.env'
chown simpleweb:simpleweb '$APP_DIR/app.env'
chmod 0644 '$APP_DIR/app.env'
systemctl restart '$SERVICE_NAME'
echo DEPLOY_OK
" \
--query "value[0].message" -o tsv)"
echo "$output"
if ! printf '%s' "$output" | grep -q 'DEPLOY_OK'; then
echo "VM 上的部署腳本沒有跑完,請看上面的訊息" >&2
exit 1
fi

# 部署完一定要驗證,而且要比對 buildSha,
# 否則「舊版程式還活著」也會回 200,workflow 就會綠燈騙人
- name: Smoke test
run: |
body=''
for i in $(seq 1 10); do
body="$(curl -fsS "http://${{ vars.VM_PUBLIC_IP }}:${APP_PORT}/api/info" || true)"
if printf '%s' "$body" | grep -q "$GITHUB_SHA"; then
echo "$body"
echo "${APP_ENVIRONMENT} 環境部署成功"
exit 0
fi
echo "等待應用程式啟動並回報新版本... ($i/10)" && sleep 6
done
echo "應用程式在時間內沒有回報這次部署的 commit" >&2
echo "最後一次回應:$body" >&2
exit 1

- name: Write job summary
if: always()
run: |
{
echo "## Deploy (${APP_ENVIRONMENT})"
echo ""
echo "- 目標目錄:\`${APP_DIR}\`"
echo "- systemd unit:\`${SERVICE_NAME}\`"
echo "- 驗證網址:http://${{ vars.VM_PUBLIC_IP }}:${APP_PORT}/api/info"
echo "- 部署 commit:\`${GITHUB_SHA:0:7}\`"
} >> "$GITHUB_STEP_SUMMARY"
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,12 @@ override.tf.json
*_override.tf.json
.terraformrc
terraform.rc
.terraform.lock.hcl
.terraform.lock.hcl

# Java / Maven (src-java)
target/
.mvn/wrapper/maven-wrapper.jar
*.iml
.classpath
.project
.settings/
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ A demo ASP.NET Core 10.0 web application showcasing modern DevOps practices, clo
- [Docker Compose (Optional)](#docker-compose-optional)
- [CI/CD Pipelines](#cicd-pipelines)
- [GitHub Actions](#github-actions)
- [Java Edition](#java-edition)
- [Java Workflows](#java-workflows)
- [Infrastructure as Code](#infrastructure-as-code)
- [Terraform](#terraform)
- [Bicep](#bicep)
Expand Down Expand Up @@ -77,6 +79,12 @@ SimpleWeb/
│ ├── SimpleWeb.UnitTest/ # Unit tests
│ ├── SimpleWeb.IntegrationTest/ # Integration tests
│ └── SimpleWeb.UITest/ # UI automation tests
├── src-java/ # Java edition (Spring Boot 4.1.1 / Java 21)
│ ├── pom.xml # Maven project (builds target/simpleweb.jar)
│ ├── mvnw / mvnw.cmd / .mvn/ # Maven Wrapper
│ ├── Dockerfile # Container definition
│ ├── src/ # Application and test sources
│ └── README.md # Java edition documentation (Traditional Chinese)
├── ci/ # Azure DevOps pipeline definitions
│ ├── 01.build.yml # Basic build pipeline
│ ├── 01.prwithlimitbranch.yml # PR validation with branch restriction
Expand Down Expand Up @@ -112,7 +120,9 @@ SimpleWeb/
├── 08.aks.yml # Deploy to Azure Kubernetes Service
├── 09.terraform.build.yml # Terraform build workflow
├── 09.terraform.release.yml # Terraform release workflow
└── 10.bicep.yml # Bicep deployment workflow
├── 10.bicep.yml # Bicep deployment workflow
├── 12.java-build.yml # Java edition build and test
└── 13.java-vm-deploy.yml # Java edition manual VM deployment
```

## Prerequisites
Expand Down Expand Up @@ -281,6 +291,36 @@ The project includes comprehensive GitHub Actions workflows (`.github/workflows/
| `09.terraform.release.yml` | Deploy infrastructure with Terraform and deploy app |
| `10.bicep.yml` | Deploy Azure infrastructure with Bicep |

## Java Edition

Alongside the ASP.NET Core application in `src/`, this repository also contains a **Java edition**
of SimpleWeb in [`src-java/`](src-java/) — a Spring Boot 4.1.1 / Java 21 application that exposes
the same kind of environment and build information. Both editions are built and deployed
independently, so the same DevOps practices can be demonstrated on two technology stacks.

```bash
cd src-java

# Build, test and package (produces target/simpleweb.jar)
./mvnw -B verify

# Run locally on http://localhost:8080
./mvnw spring-boot:run
```

Endpoints: `/` (home page), `/api/info` (JSON), `/actuator/health`, `/actuator/info`.

Full documentation (Traditional Chinese) — local run, tests, Docker, environment variables and the
generic VM deployment layout under `/opt/simpleweb/{test,prod}` — is in
[`src-java/README.md`](src-java/README.md).

### Java Workflows

| Workflow | Description |
|----------|-------------|
| `12.java-build.yml` | Build and test the Java edition on pushes touching `src-java/**` |
| `13.java-vm-deploy.yml` | Manual (`workflow_dispatch`) deployment of the Java edition to a Linux VM, targeting the `test` or `production` GitHub Environment |

## Infrastructure as Code

### Terraform
Expand Down
39 changes: 38 additions & 1 deletion README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [執行測試](#執行測試)
- [Docker 支援](#docker-支援)
- [CI/CD 管線](#cicd-管線)
- [Java 版本](#java-版本)
- [基礎架構即程式碼](#基礎架構即程式碼)
- [Kubernetes 部署](#kubernetes-部署)
- [貢獻指南](#貢獻指南)
Expand Down Expand Up @@ -56,6 +57,12 @@ SimpleWeb/
│ ├── SimpleWeb.UnitTest/ # 單元測試
│ ├── SimpleWeb.IntegrationTest/ # 整合測試
│ └── SimpleWeb.UITest/ # UI 自動化測試
├── src-java/ # Java 版本(Spring Boot 4.1.1 / Java 21)
│ ├── pom.xml # Maven 專案(產出 target/simpleweb.jar)
│ ├── mvnw / mvnw.cmd / .mvn/ # Maven Wrapper
│ ├── Dockerfile # 容器定義
│ ├── src/ # 應用程式與測試原始碼
│ └── README.md # Java 版本說明文件
├── ci/ # Azure DevOps 管線定義
│ ├── 01.build.yml # 基本建置管線
│ ├── 01.prwithlimitbranch.yml # PR 驗證(限制分支)
Expand Down Expand Up @@ -91,7 +98,9 @@ SimpleWeb/
├── 08.aks.yml # 部署至 Azure Kubernetes Service
├── 09.terraform.build.yml # Terraform 建置工作流程
├── 09.terraform.release.yml # Terraform 發布工作流程
└── 10.bicep.yml # Bicep 部署工作流程
├── 10.bicep.yml # Bicep 部署工作流程
├── 12.java-build.yml # Java 版本建置與測試
└── 13.java-vm-deploy.yml # Java 版本手動 VM 部署
```

## 環境需求
Expand Down Expand Up @@ -260,6 +269,34 @@ docker-compose down
| `09.terraform.release.yml` | 使用 Terraform 部署基礎架構並部署應用程式 |
| `10.bicep.yml` | 使用 Bicep 部署 Azure 基礎架構 |

## Java 版本

除了 `src/` 的 ASP.NET Core 應用程式之外,本儲存庫還包含 SimpleWeb 的 **Java 版本**,
位於 [`src-java/`](src-java/):以 Spring Boot 4.1.1 / Java 21 實作,提供同樣的環境與建置資訊。
兩個版本各自獨立建置與部署,用來示範同一套 DevOps 實踐如何套用在不同技術堆疊上。

```bash
cd src-java

# 建置、測試並打包(產出 target/simpleweb.jar)
./mvnw -B verify

# 在 http://localhost:8080 本機執行
./mvnw spring-boot:run
```

端點:`/`(首頁)、`/api/info`(JSON)、`/actuator/health`、`/actuator/info`。

完整說明(本機執行、測試、Docker、環境變數,以及 `/opt/simpleweb/{test,prod}` 的通用 VM
部署配置)請見 [`src-java/README.md`](src-java/README.md)。

### Java 工作流程

| 工作流程 | 說明 |
|----------|-------------|
| `12.java-build.yml` | `src-java/**` 有變更時建置並測試 Java 版本 |
| `13.java-vm-deploy.yml` | 手動(`workflow_dispatch`)部署 Java 版本至 Linux VM,可選擇 `test` 或 `production` GitHub Environment |

## 基礎架構即程式碼

### Terraform
Expand Down
Loading
Loading