Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd723dbd05 | |||
| 55d7345854 | |||
| 87ce0ec6ee | |||
| 53da6fe547 | |||
| 6adc52830f | |||
| e6176999c1 | |||
| ec03bcf778 | |||
| e535a75649 | |||
| a44aef5381 |
Vendored
+27
-28
@@ -1,5 +1,5 @@
|
|||||||
# FleetDM Stack - Gitea Actions
|
# FleetDM Stack - Gitea Actions
|
||||||
# CI: lint on every push
|
# CI: lint on every push (skips docs-only changes)
|
||||||
# Semantic Release: auto-bump version on push to main/master
|
# Semantic Release: auto-bump version on push to main/master
|
||||||
# - merge from feature/* branch → major bump
|
# - merge from feature/* branch → major bump
|
||||||
# - any other commit (fix, chore, etc.) → patch bump
|
# - any other commit (fix, chore, etc.) → patch bump
|
||||||
@@ -12,6 +12,14 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- master
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'README.md'
|
||||||
|
- 'STATUS.md'
|
||||||
|
- 'AGENTS.md'
|
||||||
|
- 'TASKS.md'
|
||||||
|
- '.gitignore'
|
||||||
|
- 'djinni-*/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -51,24 +59,7 @@ jobs:
|
|||||||
git clone https://${{ gitea.actor }}:${{ gitea.token }}@git.produktor.io/${{ gitea.repository }}.git .
|
git clone https://${{ gitea.actor }}:${{ gitea.token }}@git.produktor.io/${{ gitea.repository }}.git .
|
||||||
git fetch --tags
|
git fetch --tags
|
||||||
|
|
||||||
- name: Check if release-worthy changes
|
|
||||||
id: changes
|
|
||||||
run: |
|
|
||||||
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD)
|
|
||||||
echo "Changed files:"
|
|
||||||
echo "$CHANGED"
|
|
||||||
# Skip release if only docs, markdown, or non-chart files changed
|
|
||||||
RELEASE_FILES=$(echo "$CHANGED" | grep -vE '^(docs/|README\.md|STATUS\.md|AGENTS\.md|TASKS\.md|\.gitignore|djinni-)' || true)
|
|
||||||
if [ -z "$RELEASE_FILES" ]; then
|
|
||||||
echo "Only docs/meta files changed — skipping release."
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "Chart/workflow files changed — proceeding with release."
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Determine version bump
|
- name: Determine version bump
|
||||||
if: steps.changes.outputs.skip != 'true'
|
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1)
|
LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1)
|
||||||
@@ -77,13 +68,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Latest tag: $LATEST_TAG"
|
echo "Latest tag: $LATEST_TAG"
|
||||||
|
|
||||||
# Strip 'v' prefix and split
|
|
||||||
VER="${LATEST_TAG#v}"
|
VER="${LATEST_TAG#v}"
|
||||||
MAJOR=$(echo "$VER" | cut -d. -f1)
|
MAJOR=$(echo "$VER" | cut -d. -f1)
|
||||||
MINOR=$(echo "$VER" | cut -d. -f2)
|
MINOR=$(echo "$VER" | cut -d. -f2)
|
||||||
PATCH=$(echo "$VER" | cut -d. -f3)
|
PATCH=$(echo "$VER" | cut -d. -f3)
|
||||||
|
|
||||||
# Check if this commit is a merge from a feature/* branch
|
|
||||||
COMMIT_MSG=$(git log -1 --format='%s' ${{ gitea.sha }})
|
COMMIT_MSG=$(git log -1 --format='%s' ${{ gitea.sha }})
|
||||||
echo "Commit message: $COMMIT_MSG"
|
echo "Commit message: $COMMIT_MSG"
|
||||||
|
|
||||||
@@ -91,7 +80,6 @@ jobs:
|
|||||||
if echo "$COMMIT_MSG" | grep -qiE "^Merge.*feature/"; then
|
if echo "$COMMIT_MSG" | grep -qiE "^Merge.*feature/"; then
|
||||||
IS_FEATURE="true"
|
IS_FEATURE="true"
|
||||||
fi
|
fi
|
||||||
# Also check parent branches for merge commits
|
|
||||||
if git log -1 --format='%P' ${{ gitea.sha }} | grep -q ' '; then
|
if git log -1 --format='%P' ${{ gitea.sha }} | grep -q ' '; then
|
||||||
MERGE_BRANCH=$(git log -1 --format='%s' ${{ gitea.sha }} | grep -oE "feature/[^ '\"]*" || true)
|
MERGE_BRANCH=$(git log -1 --format='%s' ${{ gitea.sha }} | grep -oE "feature/[^ '\"]*" || true)
|
||||||
if [ -n "$MERGE_BRANCH" ]; then
|
if [ -n "$MERGE_BRANCH" ]; then
|
||||||
@@ -116,19 +104,16 @@ jobs:
|
|||||||
echo "bump_type=${BUMP}" >> "$GITHUB_OUTPUT"
|
echo "bump_type=${BUMP}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Install Helm
|
- name: Install Helm
|
||||||
if: steps.changes.outputs.skip != 'true'
|
|
||||||
run: |
|
run: |
|
||||||
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||||
|
|
||||||
- name: Update Chart.yaml version
|
- name: Update Chart.yaml version
|
||||||
if: steps.changes.outputs.skip != 'true'
|
|
||||||
run: |
|
run: |
|
||||||
sed -i "s/^version: .*/version: ${{ steps.version.outputs.new_version }}/" fleetdm-stack/Chart.yaml
|
sed -i "s/^version: .*/version: ${{ steps.version.outputs.new_version }}/" fleetdm-stack/Chart.yaml
|
||||||
echo "Chart.yaml version set to ${{ steps.version.outputs.new_version }}"
|
echo "Chart.yaml version set to ${{ steps.version.outputs.new_version }}"
|
||||||
grep '^version:' fleetdm-stack/Chart.yaml
|
grep '^version:' fleetdm-stack/Chart.yaml
|
||||||
|
|
||||||
- name: Package chart
|
- name: Package chart
|
||||||
if: steps.changes.outputs.skip != 'true'
|
|
||||||
run: |
|
run: |
|
||||||
helm dependency update fleetdm-stack/
|
helm dependency update fleetdm-stack/
|
||||||
helm package fleetdm-stack/
|
helm package fleetdm-stack/
|
||||||
@@ -136,8 +121,25 @@ jobs:
|
|||||||
mv fleetdm-stack-*.tgz .tmp/
|
mv fleetdm-stack-*.tgz .tmp/
|
||||||
ls -la .tmp/
|
ls -la .tmp/
|
||||||
|
|
||||||
|
- name: Mirror FleetDM image to Gitea registry
|
||||||
|
run: |
|
||||||
|
CRANE_VER="v0.20.3"
|
||||||
|
curl -fsSL "https://github.com/google/go-containerregistry/releases/download/${CRANE_VER}/go-containerregistry_Linux_x86_64.tar.gz" \
|
||||||
|
| tar -xz -C /usr/local/bin crane
|
||||||
|
|
||||||
|
APP_VER=$(grep '^appVersion:' fleetdm-stack/Chart.yaml | awk '{print $2}' | tr -d '"')
|
||||||
|
CHART_TAG="${{ steps.version.outputs.new_tag }}"
|
||||||
|
SRC="docker.io/fleetdm/fleet:v${APP_VER}"
|
||||||
|
OWNER=$(echo "${{ gitea.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
DST="git.produktor.io/${OWNER}/flamingo-tech-test"
|
||||||
|
|
||||||
|
crane auth login git.produktor.io -u "${{ gitea.actor }}" -p "${{ secrets.REPO_TOKEN }}"
|
||||||
|
crane copy "${SRC}" "${DST}:${APP_VER}"
|
||||||
|
crane tag "${DST}:${APP_VER}" "${CHART_TAG}"
|
||||||
|
crane tag "${DST}:${APP_VER}" "latest"
|
||||||
|
echo "Mirrored ${SRC} → ${DST}:{${APP_VER},${CHART_TAG},latest}"
|
||||||
|
|
||||||
- name: Create tag
|
- name: Create tag
|
||||||
if: steps.changes.outputs.skip != 'true'
|
|
||||||
run: |
|
run: |
|
||||||
git config user.name "Gitea Actions"
|
git config user.name "Gitea Actions"
|
||||||
git config user.email "actions@git.produktor.io"
|
git config user.email "actions@git.produktor.io"
|
||||||
@@ -145,14 +147,12 @@ jobs:
|
|||||||
git push https://${{ gitea.actor }}:${{ gitea.token }}@git.produktor.io/${{ gitea.repository }}.git "${{ steps.version.outputs.new_tag }}"
|
git push https://${{ gitea.actor }}:${{ gitea.token }}@git.produktor.io/${{ gitea.repository }}.git "${{ steps.version.outputs.new_tag }}"
|
||||||
|
|
||||||
- name: Create Gitea Release
|
- name: Create Gitea Release
|
||||||
if: steps.changes.outputs.skip != 'true'
|
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.version.outputs.new_tag }}"
|
TAG="${{ steps.version.outputs.new_tag }}"
|
||||||
BUMP="${{ steps.version.outputs.bump_type }}"
|
BUMP="${{ steps.version.outputs.bump_type }}"
|
||||||
API="https://git.produktor.io/api/v1/repos/${{ gitea.repository }}/releases"
|
API="https://git.produktor.io/api/v1/repos/${{ gitea.repository }}/releases"
|
||||||
TOKEN="${{ gitea.token }}"
|
TOKEN="${{ gitea.token }}"
|
||||||
|
|
||||||
# Create release
|
|
||||||
RELEASE=$(curl -sf -X POST "$API" \
|
RELEASE=$(curl -sf -X POST "$API" \
|
||||||
-H "Authorization: token $TOKEN" \
|
-H "Authorization: token $TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -160,7 +160,6 @@ jobs:
|
|||||||
RELEASE_ID=$(echo "$RELEASE" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
|
RELEASE_ID=$(echo "$RELEASE" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
|
||||||
echo "Created release ID: $RELEASE_ID"
|
echo "Created release ID: $RELEASE_ID"
|
||||||
|
|
||||||
# Upload chart package
|
|
||||||
for f in .tmp/*.tgz; do
|
for f in .tmp/*.tgz; do
|
||||||
FNAME=$(basename "$f")
|
FNAME=$(basename "$f")
|
||||||
curl -sf -X POST "$API/$RELEASE_ID/assets?name=$FNAME" \
|
curl -sf -X POST "$API/$RELEASE_ID/assets?name=$FNAME" \
|
||||||
|
|||||||
Vendored
+1
@@ -5,3 +5,4 @@ fleetdm-stack/charts/*.tgz
|
|||||||
.helm/
|
.helm/
|
||||||
*.log
|
*.log
|
||||||
/*-flamingo
|
/*-flamingo
|
||||||
|
.idea
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# FleetDM Stack — Flamingo DevOps Assignment
|
# 🦩 FleetDM Stack
|
||||||
|
|
||||||
Helm chart deploying **FleetDM Server** with **MySQL** and **Redis** to Kubernetes. Suitable for local development (Kind/Minikube) and adaptable for production.
|
Helm chart deploying **FleetDM Server** with **MySQL** and **Redis** to Kubernetes. Suitable for local development (Kind/Minikube) and adaptable for production.
|
||||||
|
|
||||||
@@ -23,6 +23,8 @@ make verify
|
|||||||
make port-forward
|
make port-forward
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### 1. Create local cluster
|
### 1. Create local cluster
|
||||||
@@ -63,6 +65,10 @@ make port-forward FLEET_PORT=9090
|
|||||||
Open **https://localhost:8585** in your browser (accept the self-signed certificate).
|
Open **https://localhost:8585** in your browser (accept the self-signed certificate).
|
||||||
Fleet setup wizard will guide you through initial configuration.
|
Fleet setup wizard will guide you through initial configuration.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Teardown
|
## Teardown
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -162,5 +168,5 @@ tech-task/
|
|||||||
|
|
||||||
The architectural design document for "Company Inc." is in `docs/`:
|
The architectural design document for "Company Inc." is in `docs/`:
|
||||||
|
|
||||||
- [Architecture Design Document](docs/architecture-design-company-inc.md) — 1–2 page design (convert to PDF for submission)
|
- [Architecture Design Document](docs/architecture-design-company-inc.md) — 1-2 page design (convert to PDF for submission)
|
||||||
- [High-Level Diagram](docs/architecture-hld.md) — Mermaid diagrams (infra, CI/CD, network security)
|
- [High-Level Diagram](docs/architecture-hld.md) — Mermaid diagrams (infra, CI/CD, network security)
|
||||||
|
|||||||
@@ -125,13 +125,55 @@ flowchart LR
|
|||||||
|
|
||||||
**Cost impact:** Near-zero — both slots share the same node pool; the idle slot consumes minimal resources until traffic is switched. Argo Rollouts automates the full lifecycle within ArgoCD.
|
**Cost impact:** Near-zero — both slots share the same node pool; the idle slot consumes minimal resources until traffic is switched. Argo Rollouts automates the full lifecycle within ArgoCD.
|
||||||
|
|
||||||
### 4.5 Containerisation and CI/CD
|
### 4.5 Containerisation Strategy
|
||||||
|
|
||||||
|
#### Image Building Process
|
||||||
|
|
||||||
|
Each service (Flask backend, React frontend) has its own **multi-stage Dockerfile**:
|
||||||
|
|
||||||
|
1. **Build stage** — installs dependencies and compiles artefacts in a full SDK image (e.g. `python:3.12`, `node:20`).
|
||||||
|
2. **Runtime stage** — copies only the built artefacts into a minimal base image (e.g. `python:3.12-slim`, `nginx:alpine`). This cuts image size by 60–80% and removes build tools from the attack surface.
|
||||||
|
3. **Non-root user** — the runtime stage runs as a dedicated unprivileged user (`appuser`), never as root.
|
||||||
|
4. **Reproducible builds** — dependency lock files (`requirements.txt` / `package-lock.json`) are copied and installed before application code to maximise Docker layer caching.
|
||||||
|
|
||||||
|
**Tagging convention:** images are tagged with the **git SHA** for traceability and a `latest` alias for convenience. Semantic version tags (e.g. `v1.3.0`) are added on release.
|
||||||
|
|
||||||
|
#### Container Registry Management
|
||||||
|
|
||||||
|
All container images are stored in **GCP Artifact Registry** in the `company-inc-shared` project:
|
||||||
|
|
||||||
|
- **Single source of truth** — one registry serves both staging and production via cross-project IAM pull permissions.
|
||||||
|
- **Vulnerability scanning** — Artifact Registry's built-in scanning is enabled; CI fails if critical CVEs are detected.
|
||||||
|
- **Image retention policy** — keep the latest 10 tagged images per service; automatically garbage-collect untagged manifests older than 30 days.
|
||||||
|
- **Access control** — CI service account has `roles/artifactregistry.writer`; GKE node service accounts have `roles/artifactregistry.reader`. No human push access.
|
||||||
|
|
||||||
|
*For self-hosted Git platforms (e.g. Gitea), the built-in OCI container registry can serve the same role at zero additional cost, with Trivy added as a CI step for vulnerability scanning.*
|
||||||
|
|
||||||
|
#### Deployment Pipelines (CI/CD Integration)
|
||||||
|
|
||||||
|
The pipeline follows a **GitOps** model with clear separation between CI and CD:
|
||||||
|
|
||||||
|
| Phase | Tool | What happens |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| **Lint & Test** | Gitea / GitHub Actions | Unit tests, linting, Helm lint on every push |
|
||||||
|
| **Build & Push** | Gitea / GitHub Actions | `docker build` → tag with git SHA → push to registry |
|
||||||
|
| **Security Scan** | Trivy (in CI) | Scan image for OS and library CVEs; block on critical findings |
|
||||||
|
| **Manifest Update** | CI job | Update image tag in the GitOps manifests repo (or Helm values) |
|
||||||
|
| **Sync & Deploy** | ArgoCD | Detects manifest drift → triggers blue-green rollout via Argo Rollouts |
|
||||||
|
| **Promotion** | Argo Rollouts | Automated analysis (metrics, health checks) → promote or rollback |
|
||||||
|
|
||||||
|
**Key properties:**
|
||||||
|
- **CI never touches the cluster directly** — it only builds images and updates manifests. ArgoCD is the sole deployer.
|
||||||
|
- **Rollback is instant** — revert the manifest repo to the previous commit; ArgoCD syncs automatically.
|
||||||
|
- **Audit trail** — every deployment maps to a git commit in the manifests repo.
|
||||||
|
|
||||||
|
### 4.6 CI/CD Summary
|
||||||
|
|
||||||
| Aspect | Approach |
|
| Aspect | Approach |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| **Image build** | Dockerfile per service; multi-stage builds; non-root user |
|
| **Image build** | Multi-stage Dockerfile; layer caching; non-root; git-SHA tags |
|
||||||
| **Registry** | Artifact Registry in `company-inc-shared` |
|
| **Registry** | Artifact Registry in `company-inc-shared` (or Gitea built-in OCI registry) |
|
||||||
| **CI** | GitHub/Gitea Actions — build, test, security scan |
|
| **CI** | Gitea / GitHub Actions — lint, test, build, scan, push |
|
||||||
| **CD** | ArgoCD + Argo Rollouts — GitOps with blue-green strategy |
|
| **CD** | ArgoCD + Argo Rollouts — GitOps with blue-green strategy |
|
||||||
| **Secrets** | External Secrets Operator + GCP Secret Manager |
|
| **Secrets** | External Secrets Operator + GCP Secret Manager |
|
||||||
|
|
||||||
|
|||||||
@@ -94,10 +94,12 @@ flowchart LR
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
Dev[Developer] -->|push| Repo[Git Repo]
|
Dev[Developer] -->|push| Repo[Git Repo]
|
||||||
Repo -->|webhook| CI[CI Pipeline<br/>lint · test · build]
|
Repo -->|webhook| CI[CI Pipeline<br/>lint · test · build]
|
||||||
CI -->|push image| Registry[Artifact Registry]
|
CI -->|docker build + push| Registry[Container Registry<br/>Artifact Registry / Gitea OCI]
|
||||||
|
CI -->|scan image| Trivy[Trivy<br/>CVE scan]
|
||||||
CI -->|update manifests| GitOps[GitOps Repo]
|
CI -->|update manifests| GitOps[GitOps Repo]
|
||||||
GitOps -->|sync| Argo[ArgoCD]
|
GitOps -->|sync| Argo[ArgoCD]
|
||||||
Argo -->|blue-green deploy| GKE[GKE Cluster]
|
Argo -->|blue-green deploy| GKE[GKE Cluster]
|
||||||
|
GKE -->|pull image| Registry
|
||||||
```
|
```
|
||||||
|
|
||||||
## Network Security Layers
|
## Network Security Layers
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
Reference in New Issue
Block a user