Add container publishing and env-based runtime
This commit is contained in:
@@ -23,3 +23,33 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: go build .
|
||||
|
||||
publish:
|
||||
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y docker.io
|
||||
|
||||
- name: Login to Gitea Registry
|
||||
env:
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
registry_host="${GITHUB_SERVER_URL#http://}"
|
||||
registry_host="${registry_host#https://}"
|
||||
printf '%s' "${REGISTRY_PASSWORD}" | docker login "${registry_host}" -u "${GITHUB_REPOSITORY_OWNER}" --password-stdin
|
||||
|
||||
- name: Build and Push Image
|
||||
run: |
|
||||
registry_host="${GITHUB_SERVER_URL#http://}"
|
||||
registry_host="${registry_host#https://}"
|
||||
image="${registry_host}/${GITHUB_REPOSITORY}"
|
||||
docker build -t "${image}:main" -t "${image}:sha-${GITHUB_SHA}" .
|
||||
docker push "${image}:main"
|
||||
docker push "${image}:sha-${GITHUB_SHA}"
|
||||
|
||||
@@ -12,11 +12,13 @@ Required when `SEND_EMAIL=true`:
|
||||
|
||||
Recommended settings:
|
||||
|
||||
- `ALMA_CREDS_FILE=/config/alma.creds`
|
||||
- `ALMA_ASSIGNMENTS_URL=https://example.invalid/children/student-id/assignments`
|
||||
- `ALMA_SCHEDULE_URL=https://example.invalid/children/student-id/schedule`
|
||||
- `ALMA_START_DATE=2026-01-20`
|
||||
- `ALMA_UPCOMING_DAYS=14`
|
||||
- `ALMA_CREDS_FILE=/config/alma.creds`
|
||||
- `ALMA_USERNAME`
|
||||
- `ALMA_PASSWORD`
|
||||
- `SMTP_PORT=587`
|
||||
- `SMTP_STARTTLS=true`
|
||||
- `SMTP_USERNAME`
|
||||
@@ -26,7 +28,10 @@ Recommended settings:
|
||||
|
||||
Any setting can also be supplied via a `*_FILE` variant such as `SMTP_PASSWORD_FILE`.
|
||||
|
||||
The Alma credentials file format is:
|
||||
The Alma credentials can be supplied either by:
|
||||
|
||||
- `ALMA_USERNAME` and `ALMA_PASSWORD`
|
||||
- `ALMA_CREDS_FILE` containing:
|
||||
|
||||
```yaml
|
||||
username: your-alma-username
|
||||
@@ -48,8 +53,9 @@ It runs on pushes to `main` and pull requests, and currently:
|
||||
|
||||
- runs `go test ./...`
|
||||
- runs `go build .`
|
||||
- builds and pushes `:main` and `:sha-<commit>` container tags on pushes to `main`
|
||||
|
||||
The workflow expects a runner with the `ubuntu-latest` label. The cluster runner deployed for this repo provides that label.
|
||||
The workflow expects a runner with the `ubuntu-latest` label and a repository Actions secret named `REGISTRY_PASSWORD` that can push to the Gitea container registry. The cluster runner deployed for this repo provides the required runner label.
|
||||
|
||||
## Container
|
||||
|
||||
@@ -63,4 +69,4 @@ The container image uses a static Go binary in `scratch`, with only the CA bundl
|
||||
|
||||
## Kubernetes
|
||||
|
||||
Use a Secret for Alma and SMTP credentials. The example manifest in `cronjob.example.yaml` mounts Alma credentials at `/config/alma.creds` and reads SMTP credentials from secret-backed environment variables.
|
||||
Use a Secret for Alma and SMTP credentials. The example manifest in `cronjob.example.yaml` reads all runtime settings from Kubernetes secrets and does not require a credentials file mount.
|
||||
|
||||
+9
-41
@@ -2,53 +2,21 @@ apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: alma-assignments-reporter
|
||||
namespace: default
|
||||
namespace: email
|
||||
spec:
|
||||
timeZone: America/Los_Angeles
|
||||
schedule: "0 14 * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: alma-assignments-reporter-registry
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: reporter
|
||||
image: example.invalid/alma-assignments-reporter:latest
|
||||
env:
|
||||
- name: ALMA_ASSIGNMENTS_URL
|
||||
value: https://example.invalid/children/student-id/assignments
|
||||
- name: ALMA_SCHEDULE_URL
|
||||
value: https://example.invalid/children/student-id/schedule
|
||||
- name: ALMA_START_DATE
|
||||
value: "2026-01-20"
|
||||
- name: ALMA_UPCOMING_DAYS
|
||||
value: "14"
|
||||
- name: ALMA_CREDS_FILE
|
||||
value: /config/alma.creds
|
||||
- name: SMTP_HOST
|
||||
value: smtp.email.svc.cluster.local
|
||||
- name: SMTP_PORT
|
||||
value: "587"
|
||||
- name: SMTP_STARTTLS
|
||||
value: "false"
|
||||
- name: EMAIL_FROM
|
||||
value: alma-reporter@example.invalid
|
||||
- name: EMAIL_TO
|
||||
value: parent@example.invalid
|
||||
- name: SMTP_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: alma-assignments-reporter-smtp
|
||||
key: username
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: alma-assignments-reporter-smtp
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: alma-creds
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: alma-creds
|
||||
secret:
|
||||
secretName: alma-assignments-reporter-alma
|
||||
image: example.invalid/alma-assignments-reporter:main
|
||||
imagePullPolicy: Always
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: alma-assignments-reporter
|
||||
|
||||
@@ -147,7 +147,7 @@ func loadConfig() (Config, error) {
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("parse ALMA_UPCOMING_DAYS: %w", err)
|
||||
}
|
||||
almaCredsFile, err := readValue("ALMA_CREDS_FILE", "/config/alma.creds", true)
|
||||
almaCredsFile, err := readValue("ALMA_CREDS_FILE", "/config/alma.creds", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
@@ -269,6 +269,20 @@ func splitCSV(value string) []string {
|
||||
}
|
||||
|
||||
func loadAlmaCreds(filePath string) (map[string]string, error) {
|
||||
if envUser, _ := readValue("ALMA_USERNAME", "", false); envUser != "" {
|
||||
if envPass, _ := readValue("ALMA_PASSWORD", "", false); envPass != "" {
|
||||
return map[string]string{
|
||||
"username": envUser,
|
||||
"password": envPass,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("ALMA_PASSWORD is required when ALMA_USERNAME is set")
|
||||
}
|
||||
|
||||
if filePath == "" {
|
||||
return nil, errors.New("missing Alma credentials: set ALMA_USERNAME/ALMA_PASSWORD or ALMA_CREDS_FILE")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read alma creds: %w", err)
|
||||
@@ -291,13 +305,6 @@ func loadAlmaCreds(filePath string) (map[string]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if envUser, _ := readValue("ALMA_USERNAME", "", false); envUser != "" {
|
||||
values["username"] = envUser
|
||||
}
|
||||
if envPass, _ := readValue("ALMA_PASSWORD", "", false); envPass != "" {
|
||||
values["password"] = envPass
|
||||
}
|
||||
|
||||
if values["username"] == "" || values["password"] == "" {
|
||||
return nil, errors.New("alma creds file must contain username and password")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -93,6 +94,44 @@ func TestExtractLikeLiveMissingTable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAlmaCredsFromEnv(t *testing.T) {
|
||||
t.Setenv("ALMA_USERNAME", "student")
|
||||
t.Setenv("ALMA_PASSWORD", "secret")
|
||||
|
||||
got, err := loadAlmaCreds("")
|
||||
if err != nil {
|
||||
t.Fatalf("loadAlmaCreds returned error: %v", err)
|
||||
}
|
||||
if got["username"] != "student" || got["password"] != "secret" {
|
||||
t.Fatalf("unexpected creds: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAlmaCredsRequiresPasswordWithEnvUsername(t *testing.T) {
|
||||
t.Setenv("ALMA_USERNAME", "student")
|
||||
t.Setenv("ALMA_PASSWORD", "")
|
||||
|
||||
if _, err := loadAlmaCreds(""); err == nil {
|
||||
t.Fatal("expected error when ALMA_PASSWORD is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAlmaCredsFromFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := dir + "/alma.creds"
|
||||
if err := os.WriteFile(path, []byte("username: student\npassword: secret\n"), 0o600); err != nil {
|
||||
t.Fatalf("write creds file: %v", err)
|
||||
}
|
||||
|
||||
got, err := loadAlmaCreds(path)
|
||||
if err != nil {
|
||||
t.Fatalf("loadAlmaCreds returned error: %v", err)
|
||||
}
|
||||
if got["username"] != "student" || got["password"] != "secret" {
|
||||
t.Fatalf("unexpected creds: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseHTML(text string) *html.Node {
|
||||
doc, err := html.Parse(strings.NewReader(text))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user