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
106 changes: 106 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Deploy

on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
default: "staging"
type: choice
options:
- staging
- prod
image_tag:
description: "Image tag to deploy (defaults to sha-<current commit>)"
required: false
default: ""

jobs:
deploy:
runs-on: ubuntu-latest
concurrency:
group: developer-portal-deploy-${{ inputs.environment }}
cancel-in-progress: false
permissions:
contents: write
env:
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Resolve deployment metadata
run: |
set -euo pipefail
env_name="${{ inputs.environment }}"
image_tag="${{ inputs.image_tag }}"
if [[ -z "${image_tag}" ]]; then
image_tag="sha-$(git rev-parse --short=12 HEAD)"
fi

if [[ "${env_name}" == "prod" ]]; then
app_name="developer-portal"
else
app_name="developer-portal-staging"
fi

if [[ ! "${image_tag}" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "invalid image tag: ${image_tag}" >&2
exit 1
fi

{
echo "DEPLOY_ENV=${env_name}"
echo "IMAGE_TAG=${image_tag}"
echo "ARGOCD_APP=${app_name}"
echo "GITOPS_CHANGED=false"
} >> "$GITHUB_ENV"

- name: Update kustomize image tag
run: |
python3 - <<'PY'
from pathlib import Path
import os
import re

env_name = os.environ["DEPLOY_ENV"]
image_tag = os.environ["IMAGE_TAG"]
path = Path(f"k8s/{env_name}/kustomization.yaml")
text = path.read_text()
updated = re.sub(r"(newTag:\s*)(\S+)", rf"\1{image_tag}", text, count=1)
if updated == text:
raise SystemExit("failed to update image tag in " + str(path))
path.write_text(updated)
PY

- name: Commit GitOps change
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "k8s/${DEPLOY_ENV}/kustomization.yaml"
if git diff --cached --quiet; then
echo "No GitOps change to commit."
echo "GITOPS_CHANGED=false" >> "$GITHUB_ENV"
exit 0
fi
echo "GITOPS_CHANGED=true" >> "$GITHUB_ENV"
git commit -m "deploy(${DEPLOY_ENV}): developer-portal ${IMAGE_TAG}"
git push origin "HEAD:${{ github.ref_name }}"

- name: Install Argo CD CLI
if: ${{ env.GITOPS_CHANGED == 'true' && env.ARGOCD_SERVER != '' && env.ARGOCD_AUTH_TOKEN != '' }}
run: |
curl -fsSL -o /tmp/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
install -m 0755 /tmp/argocd /usr/local/bin/argocd

- name: Trigger Argo CD sync
if: ${{ env.GITOPS_CHANGED == 'true' && env.ARGOCD_SERVER != '' && env.ARGOCD_AUTH_TOKEN != '' }}
run: |
argocd --server "${ARGOCD_SERVER}" --auth-token "${ARGOCD_AUTH_TOKEN}" --grpc-web app sync "${ARGOCD_APP}"
argocd --server "${ARGOCD_SERVER}" --auth-token "${ARGOCD_AUTH_TOKEN}" --grpc-web app wait "${ARGOCD_APP}" --health --sync
65 changes: 65 additions & 0 deletions .github/workflows/elixir-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Elixir Quality

permissions:
contents: read
pull-requests: write
statuses: write

on:
push:
branches:
- main
paths:
- 'lib/**'
- 'config/**'
- 'test/**'
- 'mix.exs'
- 'mix.lock'
- '.formatter.exs'
- '.credo.exs'
- '.credo.base.exs'
- '.credo.ex_dna.exs'
- '.credo.ex_slop.exs'
- '.sobelow-conf'
- '.deps_audit_ignore'
- '.tool-versions'
- 'scripts/elixir_quality.sh'
- '.github/workflows/elixir-quality.yml'
pull_request:
branches:
- main
paths:
- 'lib/**'
- 'config/**'
- 'test/**'
- 'mix.exs'
- 'mix.lock'
- '.formatter.exs'
- '.credo.exs'
- '.credo.base.exs'
- '.credo.ex_dna.exs'
- '.credo.ex_slop.exs'
- '.sobelow-conf'
- '.deps_audit_ignore'
- '.tool-versions'
- 'scripts/elixir_quality.sh'
- '.github/workflows/elixir-quality.yml'

jobs:
quality:
name: Elixir Quality
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Run Elixir quality contract
run: |
docker run --rm \
-v "${PWD}:${PWD}" \
-w "${PWD}" \
-e HEX_HOME=/tmp/hex \
-e MIX_HOME=/tmp/mix \
-e MIX_BUILD_PATH=/tmp/developer_portal_quality/_build \
docker.io/library/elixir:1.19.4-otp-28 \
bash -lc './scripts/elixir_quality.sh --project . --phoenix --skip-dialyzer'
62 changes: 62 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: CI

on:
push:
branches:
- main
pull_request:

jobs:
build:
runs-on: ubuntu-latest
concurrency:
group: developer-portal-ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
BUILDBUDDY_ORG_API_KEY: ${{ secrets.BUILDBUDDY_ORG_API_KEY }}
BAZELISK_HOME: ${{ github.workspace }}/.cache/bazelisk

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install Bazelisk
run: ./scripts/install-bazelisk.sh

- name: Install OpenSpec CLI
run: npm install --global @fission-ai/openspec

- name: Verify Bazel version
run: |
set -euo pipefail
expected="$(tr -d '\r\n' < .bazelversion)"
actual="$(bazel --version | awk '{print $NF}')"
test "${actual}" = "${expected}"

- name: Configure BuildBuddy remote settings
run: ./scripts/write_buildbuddy_bazelrc.sh --require-key

- name: Bazel build
run: bazel build --config=no_remote --verbose_failures //:compile //:release_tar //docker:developer_portal_image

- name: Bazel test
run: bazel test --config=no_remote --verbose_failures //:mix_test

- name: OpenSpec validation
run: |
set -euo pipefail
openspec validate --specs --strict

for change in openspec/changes/*; do
if [ ! -d "${change}" ]; then
continue
fi

if [ "$(basename "${change}")" = "archive" ]; then
continue
fi

openspec validate "$(basename "${change}")" --strict
done
74 changes: 74 additions & 0 deletions .github/workflows/publish-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Publish Image

on:
push:
branches:
- main
paths:
- 'assets/**'
- 'build/**'
- 'config/**'
- 'docker/**'
- 'lib/**'
- 'mix.exs'
- 'mix.lock'
- 'MODULE.bazel'
- 'BUILD.bazel'
- '.bazelrc'
- '.bazelversion'
- 'buildbuddy.yaml'
- 'rel/**'
- 'priv/**'
- 'scripts/**'
workflow_dispatch:

jobs:
publish:
runs-on: ubuntu-latest
concurrency:
group: developer-portal-publish-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
BUILDBUDDY_ORG_API_KEY: ${{ secrets.BUILDBUDDY_ORG_API_KEY }}
OCI_REGISTRY: registry.carverauto.dev
OCI_USERNAME: ${{ secrets.HARBOR_ROBOT_USERNAME }}
OCI_TOKEN: ${{ secrets.HARBOR_ROBOT_SECRET }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
BAZELISK_HOME: ${{ github.workspace }}/.cache/bazelisk

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install Bazelisk
run: ./scripts/install-bazelisk.sh

- name: Verify Bazel version
run: |
set -euo pipefail
expected="$(tr -d '\r\n' < .bazelversion)"
actual="$(bazel --version | awk '{print $NF}')"
test "${actual}" = "${expected}"

- name: Configure BuildBuddy remote settings
run: ./scripts/write_buildbuddy_bazelrc.sh --require-key

- name: Verify publish credentials
run: |
set -euo pipefail
if [[ -z "${OCI_USERNAME:-}" || -z "${OCI_TOKEN:-}" ]]; then
echo "OCI_USERNAME and OCI_TOKEN secrets must be configured for image publishing." >&2
exit 1
fi

- name: Configure registry auth for rules_oci
run: ./buildbuddy_setup_docker_auth.sh

- name: Bazel test
run: bazel test --config=no_remote --verbose_failures //:mix_test

- name: Build and publish image
run: bazel run --config=no_remote --verbose_failures //docker:developer_portal_image_push
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ServiceRadar Developer Portal

Source of truth: https://github.com/carverauto/developer

Phoenix/LiveView application for `developer.serviceradar.cloud`, plus the OpenSpec and Kubernetes GitOps scaffolding for the developer portal.

## Local Development
Expand All @@ -26,6 +28,11 @@ The app requires the usual Phoenix runtime config in deployed environments:

Kubernetes manifests live under `k8s/` with `base`, `staging`, and `prod` overlays. The deployment derives DB access from the shared `developer-portal-db-credentials` CNPG bootstrap secret plus `PG*` config in `developer-portal-config`, and uses `developer-portal-secrets` for `SECRET_KEY_BASE`. Argo CD also gets a PreSync migration Job from `k8s/base/migration-job.yaml` so schema changes run before the Deployment rolls. See [k8s/README.md](/home/mfreeman/src/community/k8s/README.md).

CI, image publish, and deploy run on GitHub Actions (`.github/workflows`).
Required repository secrets: `BUILDBUDDY_ORG_API_KEY`, `HARBOR_ROBOT_USERNAME`,
`HARBOR_ROBOT_SECRET`, `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN`,
`ARGOCD_SERVER`, `ARGOCD_AUTH_TOKEN`.

## Build and Release

Bazel is the primary build interface for this repository. Local and CI workflows should use:
Expand Down
2 changes: 1 addition & 1 deletion k8s/argocd-application-staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ metadata:
spec:
project: default
source:
repoURL: https://git.carverauto.dev/carverauto/developer.git
repoURL: https://github.com/carverauto/developer.git
targetRevision: HEAD
path: k8s/staging
destination:
Expand Down
2 changes: 1 addition & 1 deletion k8s/argocd-application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ metadata:
spec:
project: default
source:
repoURL: https://git.carverauto.dev/carverauto/developer.git
repoURL: https://github.com/carverauto/developer.git
targetRevision: HEAD
path: k8s/prod
destination:
Expand Down
Loading