From a8265aa6e07939229c92d66d7ee859eb0bd974c5 Mon Sep 17 00:00:00 2001 From: Deac Date: Wed, 20 May 2026 18:32:57 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20initial=20commit=20=E2=80=94=20kubectl/?= =?UTF-8?q?configure,=20infisical/fetch-secret,=20helm/upgrade=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/actions/docker/README.md | 29 ++++++++ .gitea/actions/docker/action.yml | 58 ++++++++++++++++ .gitea/actions/git/create_tag/README.md | 22 ++++++ .gitea/actions/git/create_tag/action.yml | 23 +++++++ .gitea/actions/helm/diff/README.md | 26 +++++++ .gitea/actions/helm/diff/action.yml | 35 ++++++++++ .../helm/set_deployment_image/README.md | 26 +++++++ .../helm/set_deployment_image/action.yml | 31 +++++++++ .gitea/actions/helm/template/README.md | 26 +++++++ .gitea/actions/helm/template/action.yml | 31 +++++++++ .gitea/actions/helm/upgrade/README.md | 24 +++++++ .gitea/actions/helm/upgrade/action.yml | 32 +++++++++ .../actions/infisical/fetch-secret/README.md | 34 ++++++++++ .../actions/infisical/fetch-secret/action.yml | 68 +++++++++++++++++++ .gitea/actions/infra/update_version/README.md | 23 +++++++ .../actions/infra/update_version/action.yml | 42 ++++++++++++ .gitea/actions/kubectl/configure/README.md | 23 +++++++ .gitea/actions/kubectl/configure/action.yml | 33 +++++++++ .gitea/actions/node/README.md | 26 +++++++ .gitea/actions/node/action.yml | 56 +++++++++++++++ .gitea/actions/test/npm/README.md | 29 ++++++++ .gitea/actions/test/npm/action.yml | 40 +++++++++++ .gitea/actions/trivy/image_scan/README.md | 23 +++++++ .gitea/actions/trivy/image_scan/action.yml | 21 ++++++ .gitea/actions/trivy/namespace_scan/README.md | 21 ++++++ .../actions/trivy/namespace_scan/action.yml | 14 ++++ Makefile | 26 +++++++ README.md | 47 +++++++++++++ 28 files changed, 889 insertions(+) create mode 100644 .gitea/actions/docker/README.md create mode 100644 .gitea/actions/docker/action.yml create mode 100644 .gitea/actions/git/create_tag/README.md create mode 100644 .gitea/actions/git/create_tag/action.yml create mode 100644 .gitea/actions/helm/diff/README.md create mode 100644 .gitea/actions/helm/diff/action.yml create mode 100644 .gitea/actions/helm/set_deployment_image/README.md create mode 100644 .gitea/actions/helm/set_deployment_image/action.yml create mode 100644 .gitea/actions/helm/template/README.md create mode 100644 .gitea/actions/helm/template/action.yml create mode 100644 .gitea/actions/helm/upgrade/README.md create mode 100644 .gitea/actions/helm/upgrade/action.yml create mode 100644 .gitea/actions/infisical/fetch-secret/README.md create mode 100644 .gitea/actions/infisical/fetch-secret/action.yml create mode 100644 .gitea/actions/infra/update_version/README.md create mode 100644 .gitea/actions/infra/update_version/action.yml create mode 100644 .gitea/actions/kubectl/configure/README.md create mode 100644 .gitea/actions/kubectl/configure/action.yml create mode 100644 .gitea/actions/node/README.md create mode 100644 .gitea/actions/node/action.yml create mode 100644 .gitea/actions/test/npm/README.md create mode 100644 .gitea/actions/test/npm/action.yml create mode 100644 .gitea/actions/trivy/image_scan/README.md create mode 100644 .gitea/actions/trivy/image_scan/action.yml create mode 100644 .gitea/actions/trivy/namespace_scan/README.md create mode 100644 .gitea/actions/trivy/namespace_scan/action.yml create mode 100644 Makefile create mode 100644 README.md diff --git a/.gitea/actions/docker/README.md b/.gitea/actions/docker/README.md new file mode 100644 index 0000000..fba6a89 --- /dev/null +++ b/.gitea/actions/docker/README.md @@ -0,0 +1,29 @@ +# Docker Build and Push + + +## Description + +Build a Docker image and push it to the Gitea container registry + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `REGISTRY` |

Container registry hostname

| `false` | `gitea.pixelparasol.com` | +| `REGISTRY_USERNAME` |

Registry login username

| `false` | `deac` | +| `REGISTRY_TOKEN` |

Registry login token or password

| `true` | `""` | +| `IMAGE_PATH` |

Full registry image path (e.g. gitea.pixelparasol.com/stat-tackler/stat-tackler-api)

| `true` | `""` | +| `IMAGE_TAG` |

Tag to apply in addition to latest (e.g. stage-)

| `true` | `""` | +| `PLATFORMS` |

Comma-separated buildx platform list

| `false` | `linux/amd64,linux/arm/v7,linux/arm64` | +| `ARTIFACT_NAME` |

Name of the build artifact to download

| `false` | `dist` | +| `ARTIFACT_PATH` |

Destination path for the downloaded artifact

| `false` | `dist` | +| `TAG_LATEST` |

Also tag and push the image as latest

| `false` | `false` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/docker/action.yml b/.gitea/actions/docker/action.yml new file mode 100644 index 0000000..3275052 --- /dev/null +++ b/.gitea/actions/docker/action.yml @@ -0,0 +1,58 @@ +name: Docker Build and Push +description: Build a Docker image and push it to the Gitea container registry +inputs: + REGISTRY: + description: "Container registry hostname" + default: "gitea.pixelparasol.com" + REGISTRY_USERNAME: + description: "Registry login username" + default: "deac" + REGISTRY_TOKEN: + description: "Registry login token or password" + required: true + IMAGE_PATH: + description: "Full registry image path (e.g. gitea.pixelparasol.com/stat-tackler/stat-tackler-api)" + required: true + IMAGE_TAG: + description: "Tag to apply in addition to latest (e.g. stage-)" + required: true + PLATFORMS: + description: "Comma-separated buildx platform list" + default: "linux/amd64,linux/arm/v7,linux/arm64" + ARTIFACT_NAME: + description: "Name of the build artifact to download" + default: "dist" + ARTIFACT_PATH: + description: "Destination path for the downloaded artifact" + default: "dist" + TAG_LATEST: + description: "Also tag and push the image as latest" + default: "false" + +runs: + using: composite + steps: + - name: Login to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.REGISTRY }} + username: ${{ inputs.REGISTRY_USERNAME }} + password: ${{ inputs.REGISTRY_TOKEN }} + + - name: Download Build Artifact + uses: actions/download-artifact@v3 + with: + name: ${{ inputs.ARTIFACT_NAME }} + path: ${{ inputs.ARTIFACT_PATH }} + + - name: Docker Build and Push + shell: sh + run: | + TAGS="-t ${{ inputs.IMAGE_PATH }}:${{ inputs.IMAGE_TAG }}" + if [ "${{ inputs.TAG_LATEST }}" = "true" ]; then + TAGS="$TAGS -t ${{ inputs.IMAGE_PATH }}:latest" + fi + docker buildx build -f Dockerfile . \ + --platform ${{ inputs.PLATFORMS }} \ + --push \ + $TAGS diff --git a/.gitea/actions/git/create_tag/README.md b/.gitea/actions/git/create_tag/README.md new file mode 100644 index 0000000..b03a8a9 --- /dev/null +++ b/.gitea/actions/git/create_tag/README.md @@ -0,0 +1,22 @@ +# Create and Push Git Tag + + +## Description + +Creates and pushes a git tag in the current repository + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `tag` |

The tag name to create

| `true` | `""` | +| `token` |

Gitea token with repository write access

| `true` | `""` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/git/create_tag/action.yml b/.gitea/actions/git/create_tag/action.yml new file mode 100644 index 0000000..972e0e7 --- /dev/null +++ b/.gitea/actions/git/create_tag/action.yml @@ -0,0 +1,23 @@ +name: Create and Push Git Tag +description: Creates and pushes a git tag in the current repository +inputs: + tag: + description: "The tag name to create" + required: true + token: + description: "Gitea token with repository write access" + required: true + +runs: + using: composite + steps: + - name: Create and push tag + shell: sh + env: + TAG: ${{ inputs.tag }} + TOKEN: ${{ inputs.token }} + run: | + git config user.email "gitea-actions@gitea.pixelparasol.com" + git config user.name "Gitea Actions" + git tag "${TAG}" + git push "https://gitea-actions:${TOKEN}@gitea.pixelparasol.com/${{ gitea.repository }}.git" "${TAG}" diff --git a/.gitea/actions/helm/diff/README.md b/.gitea/actions/helm/diff/README.md new file mode 100644 index 0000000..a488f3f --- /dev/null +++ b/.gitea/actions/helm/diff/README.md @@ -0,0 +1,26 @@ +# Helm Diff Deployment + + +## Description + +Diff a Helm chart for a deployment in a Kubernetes cluster + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `DEPLOYMENT_NAME` |

The Kubernetes Deployment to update

| `true` | `""` | +| `DEPLOYMENT_NAMESPACE` |

The Kubernetes namespace of the Deployment

| `true` | `""` | +| `IMAGE_PATH` |

The registry path to the image

| `true` | `""` | +| `IMAGE_TAG` |

The image tag to deploy

| `true` | `""` | +| `CONTAINER_NAME` |

The container component to update

| `true` | `""` | +| `VALUES_FILE` |

The values file to use

| `false` | `./helm/values.yaml` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/helm/diff/action.yml b/.gitea/actions/helm/diff/action.yml new file mode 100644 index 0000000..95d776a --- /dev/null +++ b/.gitea/actions/helm/diff/action.yml @@ -0,0 +1,35 @@ +name: Helm Diff Deployment +description: Diff a Helm chart for a deployment in a Kubernetes cluster +inputs: + DEPLOYMENT_NAME: + description: "The Kubernetes Deployment to update" + required: true + DEPLOYMENT_NAMESPACE: + description: "The Kubernetes namespace of the Deployment" + required: true + IMAGE_PATH: + description: "The registry path to the image" + required: true + IMAGE_TAG: + description: "The image tag to deploy" + required: true + CONTAINER_NAME: + description: "The container component to update" + required: true + VALUES_FILE: + description: "The values file to use" + default: "./helm/values.yaml" + +runs: + using: composite + steps: + - name: Install Helm Diff + shell: sh + run: | + helm plugin list | grep -q diff || helm plugin install https://github.com/databus23/helm-diff + - name: Helm Diff + shell: sh + run: | + CMD="helm diff upgrade ${{ inputs.DEPLOYMENT_NAME }} ./helm -n ${{ inputs.DEPLOYMENT_NAMESPACE }} --values ${{ inputs.VALUES_FILE }} --set deploy.${{ inputs.CONTAINER_NAME }}.tag=${{ inputs.IMAGE_TAG }} --set image.repository=${{ inputs.IMAGE_PATH }} --context 5" + echo "Running: $CMD" + eval "$CMD" \ No newline at end of file diff --git a/.gitea/actions/helm/set_deployment_image/README.md b/.gitea/actions/helm/set_deployment_image/README.md new file mode 100644 index 0000000..068eaad --- /dev/null +++ b/.gitea/actions/helm/set_deployment_image/README.md @@ -0,0 +1,26 @@ +# Helm Upgrade Deployment Image + + +## Description + +Set the image for a deployment in a Kubernetes + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `DEPLOYMENT_NAME` |

The Kubernetes Deployment to update

| `true` | `""` | +| `DEPLOYMENT_NAMESPACE` |

The Kubernetes namespace of the Deployment

| `true` | `""` | +| `IMAGE_PATH` |

The registry path to the image

| `true` | `""` | +| `IMAGE_TAG` |

The image tag to deploy

| `true` | `""` | +| `CONTAINER_NAME` |

The container component to update

| `true` | `""` | +| `VALUES_FILE` |

The values file to use

| `false` | `./helm/values.yaml` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/helm/set_deployment_image/action.yml b/.gitea/actions/helm/set_deployment_image/action.yml new file mode 100644 index 0000000..c136688 --- /dev/null +++ b/.gitea/actions/helm/set_deployment_image/action.yml @@ -0,0 +1,31 @@ +name: Helm Upgrade Deployment Image +description: Set the image for a deployment in a Kubernetes +inputs: + DEPLOYMENT_NAME: + description: "The Kubernetes Deployment to update" + required: true + DEPLOYMENT_NAMESPACE: + description: "The Kubernetes namespace of the Deployment" + required: true + IMAGE_PATH: + description: "The registry path to the image" + required: true + IMAGE_TAG: + description: "The image tag to deploy" + required: true + CONTAINER_NAME: + description: "The container component to update" + required: true + VALUES_FILE: + description: "The values file to use" + default: "./helm/values.yaml" + +runs: + using: composite + steps: + - name: Helm Set Image + shell: sh + run: | + CMD="helm upgrade ${{ inputs.DEPLOYMENT_NAME }} ./helm -n ${{ inputs.DEPLOYMENT_NAMESPACE }} --values ${{ inputs.VALUES_FILE }} --set deploy.${{ inputs.CONTAINER_NAME }}.tag=${{ inputs.IMAGE_TAG }} --set image.repository=${{ inputs.IMAGE_PATH }}" + echo "Running: $CMD" + eval "$CMD" \ No newline at end of file diff --git a/.gitea/actions/helm/template/README.md b/.gitea/actions/helm/template/README.md new file mode 100644 index 0000000..f305cd5 --- /dev/null +++ b/.gitea/actions/helm/template/README.md @@ -0,0 +1,26 @@ +# Helm Template Deployment + + +## Description + +Template a Helm chart for a deployment in a Kubernetes cluster + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `DEPLOYMENT_NAME` |

The Kubernetes Deployment to update

| `true` | `""` | +| `DEPLOYMENT_NAMESPACE` |

The Kubernetes namespace of the Deployment

| `true` | `""` | +| `IMAGE_PATH` |

The registry path to the image

| `true` | `""` | +| `IMAGE_TAG` |

The image tag to deploy

| `true` | `""` | +| `CONTAINER_NAME` |

The container component to update

| `true` | `""` | +| `VALUES_FILE` |

The values file to use

| `false` | `./helm/values.yaml` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/helm/template/action.yml b/.gitea/actions/helm/template/action.yml new file mode 100644 index 0000000..c84ad47 --- /dev/null +++ b/.gitea/actions/helm/template/action.yml @@ -0,0 +1,31 @@ +name: Helm Template Deployment +description: Template a Helm chart for a deployment in a Kubernetes cluster +inputs: + DEPLOYMENT_NAME: + description: "The Kubernetes Deployment to update" + required: true + DEPLOYMENT_NAMESPACE: + description: "The Kubernetes namespace of the Deployment" + required: true + IMAGE_PATH: + description: "The registry path to the image" + required: true + IMAGE_TAG: + description: "The image tag to deploy" + required: true + CONTAINER_NAME: + description: "The container component to update" + required: true + VALUES_FILE: + description: "The values file to use" + default: "./helm/values.yaml" + +runs: + using: composite + steps: + - name: Helm Template + shell: sh + run: | + CMD="helm template ${{ inputs.DEPLOYMENT_NAME }} ./helm -n ${{ inputs.DEPLOYMENT_NAMESPACE }} --values ${{ inputs.VALUES_FILE }} --set deploy.${{ inputs.CONTAINER_NAME }}.tag=${{ inputs.IMAGE_TAG }} --set image.repository=${{ inputs.IMAGE_PATH }}" + echo "Running: $CMD" + eval "$CMD" \ No newline at end of file diff --git a/.gitea/actions/helm/upgrade/README.md b/.gitea/actions/helm/upgrade/README.md new file mode 100644 index 0000000..7072a11 --- /dev/null +++ b/.gitea/actions/helm/upgrade/README.md @@ -0,0 +1,24 @@ +# Helm Upgrade + + +## Description + +Login to an OCI registry, update chart dependencies, and run helm upgrade for the chart in the current directory + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `DEPLOYMENT_NAME` |

The Helm release name and target namespace

| `true` | `""` | +| `REGISTRY` |

OCI registry hostname for helm dependency login

| `true` | `""` | +| `REGISTRY_USERNAME` |

Username for OCI registry login

| `true` | `""` | +| `REGISTRY_TOKEN` |

Token for OCI registry login

| `true` | `""` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/helm/upgrade/action.yml b/.gitea/actions/helm/upgrade/action.yml new file mode 100644 index 0000000..b09b748 --- /dev/null +++ b/.gitea/actions/helm/upgrade/action.yml @@ -0,0 +1,32 @@ +name: Helm Upgrade +description: Login to an OCI registry, update chart dependencies, and run helm upgrade for the chart in the current directory +inputs: + DEPLOYMENT_NAME: + description: "The Helm release name and target namespace" + required: true + REGISTRY: + description: "OCI registry hostname for helm dependency login" + required: true + REGISTRY_USERNAME: + description: "Username for OCI registry login" + required: true + REGISTRY_TOKEN: + description: "Token for OCI registry login" + required: true + +runs: + using: composite + steps: + - name: Helm OCI Login + shell: sh + run: | + echo "${{ inputs.REGISTRY_TOKEN }}" | helm registry login ${{ inputs.REGISTRY }} \ + --username ${{ inputs.REGISTRY_USERNAME }} \ + --password-stdin + + - name: Helm Upgrade + shell: sh + run: | + helm dependency update + echo "Running: helm upgrade ${{ inputs.DEPLOYMENT_NAME }} ./ -n ${{ inputs.DEPLOYMENT_NAME }}" + helm upgrade ${{ inputs.DEPLOYMENT_NAME }} ./ -n ${{ inputs.DEPLOYMENT_NAME }} diff --git a/.gitea/actions/infisical/fetch-secret/README.md b/.gitea/actions/infisical/fetch-secret/README.md new file mode 100644 index 0000000..7b68dd6 --- /dev/null +++ b/.gitea/actions/infisical/fetch-secret/README.md @@ -0,0 +1,34 @@ +# Fetch Secret from Infisical + + +## Description + +Fetches a single secret value from Infisical using a machine identity token + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `INFISICAL_TOKEN` |

Machine identity access token

| `true` | `""` | +| `SECRET_NAME` |

The secret key to fetch

| `true` | `""` | +| `INFISICAL_HOST` |

Infisical API base URL

| `false` | `https://infisical.pixelparasol.com` | +| `WORKSPACE_ID` |

Infisical project UUID

| `false` | `""` | +| `ENVIRONMENT` |

Infisical environment slug

| `false` | `prod` | +| `SECRET_PATH` |

Folder path within the environment

| `false` | `/` | + + + +## Outputs + +| name | description | +| --- | --- | +| `value` |

The fetched secret value

| + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/infisical/fetch-secret/action.yml b/.gitea/actions/infisical/fetch-secret/action.yml new file mode 100644 index 0000000..9e44a73 --- /dev/null +++ b/.gitea/actions/infisical/fetch-secret/action.yml @@ -0,0 +1,68 @@ +name: Fetch Secret from Infisical +description: Fetches a single secret value from Infisical using a machine identity token +inputs: + INFISICAL_TOKEN: + description: "Machine identity access token" + required: true + SECRET_NAME: + description: "The secret key to fetch" + required: true + INFISICAL_HOST: + description: "Infisical API base URL" + required: false + default: "https://infisical.pixelparasol.com" + WORKSPACE_ID: + description: "Infisical project UUID" + required: true + ENVIRONMENT: + description: "Infisical environment slug" + required: false + default: "prod" + SECRET_PATH: + description: "Folder path within the environment" + required: false + default: "/" +outputs: + value: + description: "The fetched secret value" + value: ${{ steps.fetch.outputs.value }} + +runs: + using: composite + steps: + - name: Fetch secret + id: fetch + shell: sh + run: | + if ! command -v jq >/dev/null 2>&1; then + apk add --no-cache jq 2>/dev/null \ + || apt-get install -y -q --no-install-recommends jq 2>/dev/null \ + || { echo "Error: jq not available and could not be installed" >&2; exit 1; } + fi + + HTTP_STATUS=$(curl -s -o /tmp/_infisical_resp.json -w "%{http_code}" \ + -H "Authorization: Bearer ${{ inputs.INFISICAL_TOKEN }}" \ + "${{ inputs.INFISICAL_HOST }}/api/v3/secrets/raw/${{ inputs.SECRET_NAME }}?workspaceId=${{ inputs.WORKSPACE_ID }}&environment=${{ inputs.ENVIRONMENT }}&secretPath=${{ inputs.SECRET_PATH }}") + + if [ "$HTTP_STATUS" != "200" ]; then + echo "Error: Infisical returned HTTP $HTTP_STATUS for secret '${{ inputs.SECRET_NAME }}'" >&2 + echo "Response: $(cat /tmp/_infisical_resp.json)" >&2 + rm -f /tmp/_infisical_resp.json + exit 1 + fi + + RESPONSE=$(cat /tmp/_infisical_resp.json) + rm -f /tmp/_infisical_resp.json + + VALUE=$(echo "$RESPONSE" | jq -r '.secret.secretValue') + + if [ -z "$VALUE" ] || [ "$VALUE" = "null" ]; then + echo "Error: secret '${{ inputs.SECRET_NAME }}' is empty or not found" >&2 + exit 1 + fi + + DELIMITER="INFISICAL_EOF_$$" + echo "value<<${DELIMITER}" >> "$GITHUB_OUTPUT" + echo "$VALUE" >> "$GITHUB_OUTPUT" + echo "${DELIMITER}" >> "$GITHUB_OUTPUT" + echo "Successfully fetched secret '${{ inputs.SECRET_NAME }}'" diff --git a/.gitea/actions/infra/update_version/README.md b/.gitea/actions/infra/update_version/README.md new file mode 100644 index 0000000..f7a95fa --- /dev/null +++ b/.gitea/actions/infra/update_version/README.md @@ -0,0 +1,23 @@ +# Update Infra Version + + +## Description + +Updates the service tag in the stat-tackler-infra releases/versions.yaml + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `token` |

Gitea token with read/write access to the infra repo

| `true` | `""` | +| `service` |

Service name key in versions.yaml (e.g. stat-tackler-api)

| `true` | `""` | +| `tag` |

The image tag to set for the service

| `true` | `""` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/infra/update_version/action.yml b/.gitea/actions/infra/update_version/action.yml new file mode 100644 index 0000000..de45f39 --- /dev/null +++ b/.gitea/actions/infra/update_version/action.yml @@ -0,0 +1,42 @@ +name: Update Infra Version +description: Updates the service tag in the stat-tackler-infra releases/versions.yaml +inputs: + token: + description: "Gitea token with read/write access to the infra repo" + required: true + service: + description: "Service name key in versions.yaml (e.g. stat-tackler-api)" + required: true + tag: + description: "The image tag to set for the service" + required: true + +runs: + using: composite + steps: + - name: Clone infra repo + shell: sh + run: | + git clone https://gitea-actions:${{ inputs.token }}@gitea.pixelparasol.com/stat-tackler/stat-tackler-infra.git infra + + - name: Install yq + shell: sh + run: | + wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + chmod +x /usr/local/bin/yq + + - name: Update tag in versions.yaml + shell: sh + run: | + cd infra + yq e ".services.${{ inputs.service }}.tag = \"${{ inputs.tag }}\"" -i releases/versions.yaml + + - name: Commit and push + shell: sh + run: | + cd infra + git config user.email "gitea-actions@gitea.pixelparasol.com" + git config user.name "Gitea Actions" + git add releases/versions.yaml + git commit -m "chore: update ${{ inputs.service }} to ${{ inputs.tag }}" + git push diff --git a/.gitea/actions/kubectl/configure/README.md b/.gitea/actions/kubectl/configure/README.md new file mode 100644 index 0000000..476d4c1 --- /dev/null +++ b/.gitea/actions/kubectl/configure/README.md @@ -0,0 +1,23 @@ +# Configure Kubectl + + +## Description + +Configure kubectl for use with Kubernetes + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `K8S_CONFIG` |

The RAW Kubernetes config

| `true` | `""` | +| `K8S_NAMESPACE` |

The K8S namespace

| `true` | `""` | +| `K8S_CONTEXT` |

The K8S context

| `true` | `""` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/kubectl/configure/action.yml b/.gitea/actions/kubectl/configure/action.yml new file mode 100644 index 0000000..ef2f3c9 --- /dev/null +++ b/.gitea/actions/kubectl/configure/action.yml @@ -0,0 +1,33 @@ +name: Configure Kubectl +description: Configure kubectl for use with Kubernetes +inputs: + K8S_CONFIG: + description: "The RAW Kubernetes config" + required: true + K8S_NAMESPACE: + description: "The K8S namespace" + required: true + K8S_CONTEXT: + description: "The K8S context" + required: true + +runs: + using: composite + steps: + - name: Configure kubectl + shell: sh + run: | + echo "Remove existing kubeconfig" + rm -f ~/.kube/config + + echo "Re-creating .kube directory" + mkdir -p ~/.kube + + echo "Set kubeconfig" + echo "${{ inputs.K8S_CONFIG }}" > ~/.kube/config + + echo "Set kubeconfig context" + kubectl config set-context ${{ inputs.K8S_CONTEXT }} --cluster=${{ inputs.K8S_CONTEXT }} --namespace=${{ inputs.K8S_NAMESPACE }} + + echo "Use kubeconfig context ${{ inputs.K8S_CONTEXT }}" + kubectl config use-context ${{ inputs.K8S_CONTEXT }} diff --git a/.gitea/actions/node/README.md b/.gitea/actions/node/README.md new file mode 100644 index 0000000..613091b --- /dev/null +++ b/.gitea/actions/node/README.md @@ -0,0 +1,26 @@ +# Node Build + + +## Description + +Install dependencies, build, and upload a build artifact + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `INSTALL_CMD` |

Install command

| `false` | `npm ci` | +| `BUILD_SCRIPT` |

npm script to run for the main build

| `false` | `build:stage` | +| `EXTRA_BUILD_SCRIPT` |

Optional additional npm script to run after the main build

| `false` | `""` | +| `ARTIFACT_NAME` |

Name to give the uploaded artifact

| `false` | `dist` | +| `ARTIFACT_PATH` |

Path to upload as the artifact

| `false` | `dist` | +| `COPY_PRISMA_ENGINE` |

Copy the Prisma query engine binaries into the build directory

| `false` | `false` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/node/action.yml b/.gitea/actions/node/action.yml new file mode 100644 index 0000000..6c44430 --- /dev/null +++ b/.gitea/actions/node/action.yml @@ -0,0 +1,56 @@ +name: Node Build +description: Install dependencies, build, and upload a build artifact +inputs: + INSTALL_CMD: + description: "Install command" + default: "npm ci" + BUILD_SCRIPT: + description: "npm script to run for the main build" + default: "build:stage" + EXTRA_BUILD_SCRIPT: + description: "Optional additional npm script to run after the main build" + default: "" + ARTIFACT_NAME: + description: "Name to give the uploaded artifact" + default: "dist" + ARTIFACT_PATH: + description: "Path to upload as the artifact" + default: "dist" + COPY_PRISMA_ENGINE: + description: "Copy the Prisma query engine binaries into the build directory" + default: "false" + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/setup-node@v5 + with: + node-version-file: package.json + + - name: Install + shell: sh + run: ${{ inputs.INSTALL_CMD }} + + - name: Build + shell: sh + run: | + npm run ${{ inputs.BUILD_SCRIPT }} + if [ -n "${{ inputs.EXTRA_BUILD_SCRIPT }}" ]; then + npm run ${{ inputs.EXTRA_BUILD_SCRIPT }} + fi + + - name: Copy Prisma Client Engine + if: inputs.COPY_PRISMA_ENGINE == 'true' + shell: sh + run: | + mkdir -p build/prisma + cp node_modules/.prisma/client/libquery_engine-* build/prisma/ + + - name: Upload Build Artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.ARTIFACT_NAME }} + path: ${{ inputs.ARTIFACT_PATH }} diff --git a/.gitea/actions/test/npm/README.md b/.gitea/actions/test/npm/README.md new file mode 100644 index 0000000..6a715ff --- /dev/null +++ b/.gitea/actions/test/npm/README.md @@ -0,0 +1,29 @@ +# test/npm + +Composite action: install dependencies and run an npm test script. + +## Inputs + +| Input | Description | Default | +|---|---|---| +| `INSTALL_CMD` | Install command | `npm ci` | +| `TEST_SCRIPT` | npm script to run (must exist in `package.json`) | `test` | +| `WORKING_DIRECTORY` | Directory to run commands in | `.` | + +## Usage + +```yaml +- uses: stat-tackler/stat-tackler-infra/.gitea/actions/test/npm@main + with: + TEST_SCRIPT: test:unit +``` + +### Common test scripts by project + +| Project | Script | Runner | +|---|---|---| +| `stat-tackler-api` | `test` | Jest | +| `stat-tackler-scorekeeper` | `test:unit` | Vitest | +| `stat-tackler-scorekeeper` | `test:integration` | Vitest | +| `stat-tackler-scorekeeper` | `test:run` | Vitest (unit + integration) | +| `stat-tackler-scorekeeper` | `test:coverage` | Vitest | diff --git a/.gitea/actions/test/npm/action.yml b/.gitea/actions/test/npm/action.yml new file mode 100644 index 0000000..28352a0 --- /dev/null +++ b/.gitea/actions/test/npm/action.yml @@ -0,0 +1,40 @@ +name: NPM Test +description: Install dependencies and run npm tests + +inputs: + INSTALL_CMD: + description: "Install command" + default: "npm ci" + TEST_SCRIPT: + description: "npm script to run (must exist in package.json)" + default: "test" + WORKING_DIRECTORY: + description: "Directory to run commands in" + default: "." + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/setup-node@v5 + with: + node-version-file: package.json + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: ${{ inputs.WORKING_DIRECTORY }}/node_modules + key: node-modules-${{ hashFiles(format('{0}/package-lock.json', inputs.WORKING_DIRECTORY)) }} + restore-keys: node-modules- + + - name: Install + shell: sh + working-directory: ${{ inputs.WORKING_DIRECTORY }} + run: ${{ inputs.INSTALL_CMD }} + + - name: Test + shell: sh + working-directory: ${{ inputs.WORKING_DIRECTORY }} + run: npm run ${{ inputs.TEST_SCRIPT }} diff --git a/.gitea/actions/trivy/image_scan/README.md b/.gitea/actions/trivy/image_scan/README.md new file mode 100644 index 0000000..1612c98 --- /dev/null +++ b/.gitea/actions/trivy/image_scan/README.md @@ -0,0 +1,23 @@ +# Trivy Scan Image + + +## Description + +Scan a container image with Trivy + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `IMAGE_PATH` |

The registry path to the image to scan

| `true` | `""` | +| `IMAGE_TAG` |

The image tag to scan

| `true` | `""` | +| `FAIL_HARD` |

Boolean: true will fail the build if vulnerabilities are found, false will not

| `false` | `false` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/trivy/image_scan/action.yml b/.gitea/actions/trivy/image_scan/action.yml new file mode 100644 index 0000000..b7427da --- /dev/null +++ b/.gitea/actions/trivy/image_scan/action.yml @@ -0,0 +1,21 @@ +name: Trivy Scan Image +description: Scan a container image with Trivy +inputs: + IMAGE_PATH: + description: "The registry path to the image to scan" + required: true + IMAGE_TAG: + description: "The image tag to scan" + required: true + FAIL_HARD: + description: "Boolean: true will fail the build if vulnerabilities are found, false will not" + required: false + default: 'false' + +runs: + using: composite + steps: + - name: Scan Container Registry Image + shell: sh + run: | + trivy image ${{ inputs.IMAGE_PATH }}:${{ inputs.IMAGE_TAG }} --report=all --exit-code=${{ contains(fromJSON('["true"]'), inputs.FAIL_HARD) && '1' || '0' }} --severity CRITICAL,HIGH diff --git a/.gitea/actions/trivy/namespace_scan/README.md b/.gitea/actions/trivy/namespace_scan/README.md new file mode 100644 index 0000000..cfbf84d --- /dev/null +++ b/.gitea/actions/trivy/namespace_scan/README.md @@ -0,0 +1,21 @@ +# Trivy Scan K8S Namespace + + +## Description + +Scan kubernetes namespace for vulnerabilities + + + +## Inputs + +| name | description | required | default | +| --- | --- | --- | --- | +| `NAMESPACE` |

The Kubernetes namespace to scan

| `true` | `""` | + + + +## Runs + +This action is a `composite` action. + diff --git a/.gitea/actions/trivy/namespace_scan/action.yml b/.gitea/actions/trivy/namespace_scan/action.yml new file mode 100644 index 0000000..1ce2895 --- /dev/null +++ b/.gitea/actions/trivy/namespace_scan/action.yml @@ -0,0 +1,14 @@ +name: Trivy Scan K8S Namespace +description: Scan kubernetes namespace for vulnerabilities +inputs: + NAMESPACE: + description: "The Kubernetes namespace to scan" + required: true + +runs: + using: composite + steps: + - name: Scan Kubernetes Namespace + shell: sh + run: | + trivy k8s --namespace ${{ inputs.NAMESPACE }} --report=all all diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f6afcab --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +ACTIONS := \ + .gitea/actions/docker \ + .gitea/actions/node \ + .gitea/actions/git/create_tag \ + .gitea/actions/helm/diff \ + .gitea/actions/helm/set_deployment_image \ + .gitea/actions/helm/template \ + .gitea/actions/infra/update_version \ + .gitea/actions/kubectl/configure \ + .gitea/actions/trivy/image_scan \ + .gitea/actions/trivy/namespace_scan + +.PHONY: help docs + +help: ## Show this help + @awk 'BEGIN {FS = ":.*##"; printf "Usage: make \033[36m\033[0m [REGION=us-east-2]\n"} \ + /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0,5) } \ + /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-16s\033[0m %s\n", $$1, $$2 }' \ + $(MAKEFILE_LIST) + +##@ Setup +docs: ## Create litellm-secrets (prompts for master_key) + @for dir in $(ACTIONS); do \ + echo "Generating docs for $$dir"; \ + (cd $$dir && action-docs --no-banner --update-readme README.md); \ + done diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e58cb7 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# stat-tackler-infra + +Central deployment repository for the stat-tackler platform. All production releases are coordinated from here. + +## How to cut a production release + +1. Update `releases/versions.yaml` with the semver tag you want deployed for each service. +2. Open a PR to `main` and merge it. +3. Publish a release in Gitea. The deploy workflow fires automatically and rolls out every service in the versions file. + +To skip a service in a given release, comment it out in `versions.yaml`. + +## Repository layout + +``` +.gitea/ + actions/kubectl/configure/ # Reusable kubectl setup action + workflows/ + deploy-prod.yaml # Coordinated production deployment +releases/ + versions.yaml # Source of truth for what is deployed to production +``` + +## Services + +| Service | Repo | +|---|---| +| API | `stat-tackler-api` | +| Auth UI | `stat-tackler-auth` | +| Scorekeeper UI | `stat-tackler-scorekeeper` | +| Admin UI | `stat-tackler-admin` | +| Marketing site | `stat-tackler-marketing` | +| Email relay | `stat-tackler-email-relay` | +| MCP server | `stat-tackler-mcp` | + +Each service owns its own Helm chart (`./helm/`) and handles its own staging deploys. This repo only manages coordinated production releases. + +## Runner requirements + +The `helm` runner must have `helm`, `kubectl`, and `yq` available. + +## Required secrets + +| Secret | Purpose | +|---|---| +| `K8S_TROWBRIDGE_K0S0_CONFIG` | Kubeconfig for the production cluster | +| `REGISTRY_AGENT_TOKEN` | Gitea token with read access to all service repos and the container registry |