chart and workflow
Build and Publish Docker Image / Build and Push Docker Image (push) Failing after 36s Details

This commit is contained in:
Isaac Johnson 2026-09-08 06:26:57 -05:00
parent 80200ca1f3
commit a5ee339319
13 changed files with 635 additions and 0 deletions

View File

@ -0,0 +1,89 @@
name: Build and Publish Docker Image
on:
push:
branches:
- main
jobs:
build-and-push:
name: Build and Push Docker Image
runs-on: my_custom_label
container: node:22
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Prepare Env for Docker
run: |
whoami
which docker || true
apt update
cat /etc/os-release
apt install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
focal stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- name: Get Version from version.ini
id: get_version
run: |
# Extract version value from version.ini
VERSION=$(awk -F'=[ \t]*' '/^version[ \t]*=/ {gsub(/[ \t"'\''\r]/, "", $2); print $2}' version.ini)
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build Dockerfile
run: |
export BUILDIMGTAG="`cat Dockerfile | tail -n1 | sed 's/^.*\///g'`"
docker build -t $BUILDIMGTAG:${{ env.VERSION }} .
docker images
- name: Tag and Push (Harbor)
run: |
export BUILDIMGTAG="`cat Dockerfile | tail -n1 | sed 's/^.*\///g'`"
export FINALBUILDTAG="`cat Dockerfile | tail -n1 | sed 's/^#//g'`"
docker tag $BUILDIMGTAG:${{ env.VERSION }} $FINALBUILDTAG:${{ env.VERSION }}
docker images
echo $CR_PAT | docker login harbor.freshbrewed.science -u $CR_USER --password-stdin
docker push $FINALBUILDTAG:${{ env.VERSION }}
env: # Or as an environment variable
CR_PAT: ${{ secrets.CR_PAT }}
CR_USER: ${{ secrets.CR_USER }}
- name: Tag and Push (Dockerhub)
run: |
export BUILDIMGTAG="`cat Dockerfile | tail -n1 | sed 's/^.*\///g'`"
docker tag $BUILDIMGTAG:${{ env.VERSION }} $DHUSER/$BUILDIMGTAG:${{ env.VERSION }}
docker images
echo $DHPAT | docker login -u $DHUSER --password-stdin
docker push $DHUSER/$BUILDIMGTAG:${{ env.VERSION }}
env: # Or as an environment variable
DHPAT: ${{ secrets.DHPAT }}
DHUSER: ${{ secrets.DHUSER }}
- name: Prepare Env for Helm OCI Push (Helm v3 reference only)
run: |
if [ ! -f /tmp/linux-amd64/helm ]; then
apt update
DEBIAN_FRONTEND=noninteractive apt-get install -y unzip curl || true
wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz -O /tmp/helm.tar.gz || true
cd /tmp
tar xzvf helm.tar.gz
chmod +x linux-amd64/helm
fi
- name: Package Helm Chart
run: |
set -x
/tmp/linux-amd64/helm package ./helm-chart
export HLMPKG=`ls -tr *.tgz | tail -n1 | tr -d '\n'`
/tmp/linux-amd64/helm registry login harbor.freshbrewed.science -u $CR_USER -p $CR_PAT
/tmp/linux-amd64/helm push ./$HLMPKG oci://harbor.freshbrewed.science/chartrepo/botWebWars
env: # Or as an environment variable
CR_PAT: ${{ secrets.CR_PAT }}
CR_USER: ${{ secrets.CR_USER }}

6
helm-chart/Chart.yaml Normal file
View File

@ -0,0 +1,6 @@
apiVersion: v2
name: botwebwars
description: A Helm chart for botWebWars, an autonomous tactical bot arena.
type: application
version: 0.1.2
appVersion: "1.1.0"

148
helm-chart/INSTALL.md Normal file
View File

@ -0,0 +1,148 @@
# botWebWars Helm Chart
This Helm chart provides a Kubernetes deployment for **botWebWars**, an autonomous tactical bot arena.
## Prerequisites
- A running Kubernetes cluster (v1.19+ recommended).
- [Helm v3+](https://helm.sh/) installed.
- (Optional) Metrics Server installed in your cluster for detailed container CPU & memory usage.
## Optional Kubeconfig & Secret Setup
The application does not require Kubernetes API access by default. Leave `kubeconfig.enabled` and `rbac.create` disabled unless you have added a feature that needs cluster access.
You can configure and set up the `kubeconfig` secret using one of the following methods:
### Method 1: Create a Kubernetes Secret manually with `kubectl` (Recommended)
1. Create a secret containing your `.kube/config` file:
```bash
kubectl create secret generic botWebWars-kubeconfig --from-file=config=$HOME/.kube/config
```
2. Install or upgrade the Helm chart specifying the existing secret:
```bash
helm install botWebWars ./helm-chart \
--set kubeconfig.enabled=true \
--set kubeconfig.secretName=botWebWars-kubeconfig
```
### Method 2: Create the Secret via Helm using `--set-file`
You can have Helm create the `Secret` resource directly from your local `kubeconfig` file during installation:
```bash
helm install botWebWars ./helm-chart \
--set kubeconfig.enabled=true \
--set kubeconfig.createSecret=true \
--set-file kubeconfig.content=$HOME/.kube/config
```
### Method 3: Use a custom `values.yaml` file
Create a custom values file (e.g. `my-values.yaml`):
```yaml
kubeconfig:
enabled: true
createSecret: true
secretName: "botWebWars-kubeconfig"
secretKey: "config"
mountPath: "/root/.kube/config"
content: |
apiVersion: v1
clusters:
- cluster:
server: https://127.0.0.1:6443
name: my-cluster
...
```
Then install the chart using:
```bash
helm install botWebWars ./helm-chart -f my-values.yaml
```
---
## Quick Start
### 1. Install the Chart
Default installation:
```bash
helm install botwebwars ./helm-chart
```
### 2. Custom Configuration
You can override default settings by passing a custom `values.yaml` file or using `--set`:
```bash
helm install botWebWars ./helm-chart -f custom-values.yaml
```
Example: Enable Ingress and change image tag:
```bash
helm install botWebWars ./helm-chart \
--set image.tag="1.0.0" \
--set ingress.enabled=true \
--set ingress.hosts[0].host="botWebWars.example.com"
```
### 3. Upgrade the Release
```bash
helm upgrade botwebwars ./helm-chart
```
### 4. Uninstall
```bash
helm uninstall botwebwars
```
---
## Configuration Parameters
| Parameter | Description | Default |
|-----------|-------------|---------|
| `replicaCount` | Number of application replicas | `1` |
| `image.repository` | Docker image repository | `botWebWars` |
| `image.tag` | Docker image tag | `latest` |
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
| `kubeconfig.enabled` | Enable mounting kubeconfig Secret and `KUBECONFIG` env var | `true` |
| `kubeconfig.secretName` | Name of existing Secret containing kubeconfig file | `""` (defaults to `<release-fullname>-kubeconfig`) |
| `kubeconfig.createSecret` | Create Secret resource via Helm from `kubeconfig.content` | `false` |
| `kubeconfig.content` | Raw kubeconfig content when `createSecret` is true | `""` |
| `kubeconfig.secretKey` | Secret data key containing kubeconfig file content | `"config"` |
| `kubeconfig.mountPath` | Container path to mount kubeconfig file and set `KUBECONFIG` env | `"/root/.kube/config"` |
| `service.type` | Kubernetes service type | `ClusterIP` |
| `service.port` | Service port exposed | `80` |
| `service.targetPort` | Container application port | `8000` |
| `rbac.create` | Create ClusterRole & ClusterRoleBinding for K8s API access | `false` |
| `serviceAccount.create` | Create ServiceAccount for the deployment | `true` |
| `serviceAccount.name` | Explicit ServiceAccount name | `""` |
| `ingress.enabled` | Enable Ingress resource | `false` |
| `resources` | Pod CPU/Memory resource limits & requests | `{ limits: { cpu: 200m, memory: 256Mi }, requests: { cpu: 100m, memory: 128Mi } }` |
| `persistence.enabled` | Enable optional PersistentVolumeClaim | `false` |
---
## Optional RBAC
If cluster access is later required, set `rbac.create: true` and configure the optional kubeconfig Secret. The default game server does not require either.
## Verification
Check the deployment status:
```bash
kubectl get pods -l app.kubernetes.io/name=botWebWars
kubectl get svc -l app.kubernetes.io/name=botWebWars
```

View File

@ -0,0 +1,74 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "botWebWars.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "botWebWars.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "botWebWars.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "botWebWars.labels" -}}
helm.sh/chart: {{ include "botWebWars.chart" . }}
{{ include "botWebWars.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "botWebWars.selectorLabels" -}}
app.kubernetes.io/name: {{ include "botWebWars.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "botWebWars.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "botWebWars.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the kubeconfig secret to use
*/}}
{{- define "botWebWars.kubeconfigSecretName" -}}
{{- if .Values.kubeconfig.secretName }}
{{- .Values.kubeconfig.secretName }}
{{- else }}
{{- printf "%s-kubeconfig" (include "botWebWars.fullname" .) }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,15 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "botWebWars.fullname" . }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["nodes", "pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
{{- end }}

View File

@ -0,0 +1,16 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "botWebWars.fullname" . }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "botWebWars.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "botWebWars.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@ -0,0 +1,98 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "botWebWars.fullname" . }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "botWebWars.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "botWebWars.selectorLabels" . | nindent 8 }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "botWebWars.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.kubeconfig.enabled }}
env:
- name: KUBECONFIG
value: {{ .Values.kubeconfig.mountPath | quote }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if or .Values.persistence.enabled .Values.kubeconfig.enabled }}
volumeMounts:
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
{{- if .Values.kubeconfig.enabled }}
- name: kubeconfig-volume
mountPath: {{ .Values.kubeconfig.mountPath | quote }}
subPath: {{ .Values.kubeconfig.secretKey | default "config" | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- if or .Values.persistence.enabled .Values.kubeconfig.enabled }}
volumes:
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "botWebWars.fullname" . }}-data
{{- end }}
{{- if .Values.kubeconfig.enabled }}
- name: kubeconfig-volume
secret:
secretName: {{ include "botWebWars.kubeconfigSecretName" . }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,45 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "botWebWars.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if .pathType }}
pathType: {{ .pathType }}
{{- end }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,17 @@
{{- if .Values.persistence.enabled -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "botWebWars.fullname" . }}-data
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.accessMode | default "ReadWriteOnce" }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- if .Values.persistence.storageClassName }}
storageClassName: {{ .Values.persistence.storageClassName | quote }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,12 @@
{{- if and .Values.kubeconfig.enabled .Values.kubeconfig.createSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "botWebWars.kubeconfigSecretName" . }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
type: Opaque
stringData:
{{ .Values.kubeconfig.secretKey | default "config" }}: |
{{- .Values.kubeconfig.content | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "botWebWars.fullname" . }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "botWebWars.selectorLabels" . | nindent 4 }}

View File

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "botWebWars.serviceAccountName" . }}
labels:
{{- include "botWebWars.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

84
helm-chart/values.yaml Normal file
View File

@ -0,0 +1,84 @@
# Default values for botWebWars.
replicaCount: 1
image:
repository: botWebWars
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "latest"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Kubeconfig configuration & secret mounting
kubeconfig:
# The app uses its own game API and does not need a kubeconfig by default.
enabled: false
# Name of existing secret containing kubeconfig.
# If empty and createSecret is true, defaults to "<fullname>-kubeconfig"
secretName: ""
# Set to true to create a Secret resource from 'content'
createSecret: false
# Content of the kubeconfig file when createSecret is true
content: ""
# Key name inside the secret containing kubeconfig data
secretKey: "config"
# Path inside container where kubeconfig is mounted
mountPath: "/root/.kube/config"
rbac:
create: false
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
service:
type: ClusterIP
port: 80
targetPort: 8000
annotations: {}
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: botwebwars.local
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
persistence:
enabled: false
size: 1Gi
accessMode: ReadWriteOnce
storageClassName: ""
nodeSelector: {}
tolerations: []
affinity: {}