Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
452eedd6d6 | ||
|
|
1aa22cf871 | ||
|
|
f33a956494 | ||
|
|
c4403f297a | ||
|
|
6f67b8012f | ||
|
|
300bc3c61c | ||
|
|
7e92eb651c | ||
|
|
b9f832858a |
@@ -19,6 +19,16 @@ jobs:
|
|||||||
token: ${{ secrets.GITEA_TOKEN }}
|
token: ${{ secrets.GITEA_TOKEN }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
|
- name: Check release credentials before changing the version
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
|
||||||
|
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test -n "$HARBOR_USERNAME" || { echo 'Missing HARBOR_USERNAME action secret'; exit 1; }
|
||||||
|
test -n "$HARBOR_PASSWORD" || { echo 'Missing HARBOR_PASSWORD action secret'; exit 1; }
|
||||||
|
|
||||||
- name: Test and lint in the shared builder image
|
- name: Test and lint in the shared builder image
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -90,9 +100,12 @@ jobs:
|
|||||||
install "$RUNNER_TEMP/oras/oras" "$RUNNER_TEMP/oras-bin"
|
install "$RUNNER_TEMP/oras/oras" "$RUNNER_TEMP/oras-bin"
|
||||||
printf '%s' "$HARBOR_PASSWORD" | "$RUNNER_TEMP/oras-bin" login harbor.k3s.k0.nu --username "$HARBOR_USERNAME" --password-stdin
|
printf '%s' "$HARBOR_PASSWORD" | "$RUNNER_TEMP/oras-bin" login harbor.k3s.k0.nu --username "$HARBOR_USERNAME" --password-stdin
|
||||||
unset HARBOR_PASSWORD
|
unset HARBOR_PASSWORD
|
||||||
|
(
|
||||||
|
cd "$package_dir"
|
||||||
"$RUNNER_TEMP/oras-bin" push --artifact-type application/vnd.ai-spel.kubernetes-manifest.v1 \
|
"$RUNNER_TEMP/oras-bin" push --artifact-type application/vnd.ai-spel.kubernetes-manifest.v1 \
|
||||||
"harbor.k3s.k0.nu/ai-spel/rule-engine-manifest:$version" \
|
"harbor.k3s.k0.nu/ai-spel/rule-engine-manifest:$version" \
|
||||||
"$package_dir/manifest.yaml:application/yaml"
|
"manifest.yaml:application/yaml"
|
||||||
|
)
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Deploy rule engine through Argo CD
|
- name: Deploy rule engine through Argo CD
|
||||||
@@ -115,8 +128,11 @@ jobs:
|
|||||||
echo 'NATS is not installed yet. The backend release pipeline bootstraps NATS and this Argo Application.'
|
echo 'NATS is not installed yet. The backend release pipeline bootstraps NATS and this Argo Application.'
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
if ! kubectl -n argocd get application ai-spel-rule-engine >/dev/null 2>&1; then
|
||||||
|
echo 'The backend release pipeline owns first-time Argo Application creation; skipping this worker-only rollout.'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
kubectl -n ai-spel rollout status statefulset/ai-spel-nats --timeout=10m
|
kubectl -n ai-spel rollout status statefulset/ai-spel-nats --timeout=10m
|
||||||
kubectl -n argocd apply -f deploy/argocd/application.yaml
|
|
||||||
kubectl -n argocd patch application ai-spel-rule-engine --type merge -p "{\"spec\":{\"source\":{\"targetRevision\":\"$version\"}}}"
|
kubectl -n argocd patch application ai-spel-rule-engine --type merge -p "{\"spec\":{\"source\":{\"targetRevision\":\"$version\"}}}"
|
||||||
deadline=$((SECONDS + 600))
|
deadline=$((SECONDS + 600))
|
||||||
while (( SECONDS < deadline )); do
|
while (( SECONDS < deadline )); do
|
||||||
|
|||||||
Generated
+1
-1
@@ -13,7 +13,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ai-spel-rule-engine"
|
name = "ai-spel-rule-engine"
|
||||||
version = "0.1.1"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-nats",
|
"async-nats",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ members = ["crates/rule-engine"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.2"
|
version = "0.1.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ remains responsible for validating and committing effects.
|
|||||||
## Contract
|
## Contract
|
||||||
|
|
||||||
Requests use NATS subject `ai-spel.rules.evaluate.v1` and contract version 1.
|
Requests use NATS subject `ai-spel.rules.evaluate.v1` and contract version 1.
|
||||||
The canonical JSON Schema is `contracts/rule-evaluation-v1.schema.json`.
|
The canonical JSON Schema is `contracts/rule-evaluation-v1.schema.json`; its
|
||||||
|
versioned `$id` is updated by the release version-bump workflow.
|
||||||
Responses echo the correlation ID and have status `evaluated` or `rejected`.
|
Responses echo the correlation ID and have status `evaluated` or `rejected`.
|
||||||
|
|
||||||
The NATS user may subscribe only to the evaluation subject and publish only to
|
The NATS user may subscribe only to the evaluation subject and publish only to
|
||||||
reply inboxes. The service receives no credentials other than its own NATS
|
reply inboxes. The service receives no credentials other than its own NATS
|
||||||
identity and has no configured database or AI-provider secrets.
|
identity and has no configured database or AI-provider secrets. Its chart
|
||||||
|
limits pod egress to NATS and cluster DNS; Luau evaluation has no external
|
||||||
|
network path.
|
||||||
|
|
||||||
## Local commands
|
## Local commands
|
||||||
|
|
||||||
@@ -21,3 +24,13 @@ For source development, the Rust commands are `cargo fmt --all -- --check`,
|
|||||||
`cargo test --workspace`, and `cargo clippy --workspace --all-targets -- -D warnings`.
|
`cargo test --workspace`, and `cargo clippy --workspace --all-targets -- -D warnings`.
|
||||||
Production builds, tests, image builds, and executable verification for this
|
Production builds, tests, image builds, and executable verification for this
|
||||||
project are run by Gitea Actions on the existing `large` runner and Kubernetes.
|
project are run by Gitea Actions on the existing `large` runner and Kubernetes.
|
||||||
|
|
||||||
|
## Release deployment boundary
|
||||||
|
|
||||||
|
The API repository owns first-time NATS and Argo Application provisioning. The
|
||||||
|
worker release pipeline publishes this repository's versioned image and chart,
|
||||||
|
then updates the existing `ai-spel-rule-engine` Argo Application. It uses the
|
||||||
|
`AI_SPEL_TEST_KUBECONFIG` repository action secret, scoped to patch that one
|
||||||
|
Argo Application and read rollout health in namespace `ai-spel`. The one-time
|
||||||
|
service-account roles are described in `deploy/ci/namespace-scoped-rbac.yaml`;
|
||||||
|
they do not grant database, secret, workload-write, or cluster-wide access.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://gitea.k3s.k0.nu/ai-spel/ai-spel-rule-engine/raw/tag/v0.1.0/contracts/rule-evaluation-v1.schema.json",
|
"$id": "https://gitea.k3s.k0.nu/ai-spel/ai-spel-rule-engine/raw/tag/v0.1.6/contracts/rule-evaluation-v1.schema.json",
|
||||||
"title": "AI Spel Luau rule evaluation contract v1",
|
"title": "AI Spel Luau rule evaluation contract v1",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ spec:
|
|||||||
source:
|
source:
|
||||||
repoURL: harbor.k3s.k0.nu/ai-spel
|
repoURL: harbor.k3s.k0.nu/ai-spel
|
||||||
chart: ai-spel-rule-engine
|
chart: ai-spel-rule-engine
|
||||||
targetRevision: "0.1.2"
|
targetRevision: "0.1.6"
|
||||||
helm:
|
helm:
|
||||||
releaseName: ai-spel-rule-engine
|
releaseName: ai-spel-rule-engine
|
||||||
destination:
|
destination:
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: argocd
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: ai-spel-rule-engine-deployer-token
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/service-account.name: ai-spel-rule-engine-deployer
|
||||||
|
type: kubernetes.io/service-account-token
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: argocd
|
||||||
|
rules:
|
||||||
|
- apiGroups: [argoproj.io]
|
||||||
|
resources: [applications]
|
||||||
|
resourceNames: [ai-spel-rule-engine]
|
||||||
|
verbs: [get, patch]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: argocd
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: argocd
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: ai-spel
|
||||||
|
rules:
|
||||||
|
- apiGroups: [apps]
|
||||||
|
resources: [deployments, replicasets, statefulsets]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [pods, events]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: ai-spel
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
|
namespace: argocd
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: ai-spel-rule-engine-deployer
|
||||||
@@ -2,5 +2,5 @@ apiVersion: v2
|
|||||||
name: ai-spel-rule-engine
|
name: ai-spel-rule-engine
|
||||||
description: Isolated, sandboxed Luau proposal evaluator
|
description: Isolated, sandboxed Luau proposal evaluator
|
||||||
type: application
|
type: application
|
||||||
version: 0.1.2
|
version: 0.1.6
|
||||||
appVersion: "0.1.2"
|
appVersion: "0.1.6"
|
||||||
@@ -26,6 +26,27 @@ spec:
|
|||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: {{ . | quote }}
|
- name: {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .Values.pod.tolerations }}
|
||||||
|
tolerations: {{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: {{ .Chart.Name }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
topologySpreadConstraints:
|
||||||
|
- maxSkew: 1
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
whenUnsatisfiable: ScheduleAnyway
|
||||||
|
labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: {{ .Chart.Name }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
containers:
|
containers:
|
||||||
- name: rule-engine
|
- name: rule-engine
|
||||||
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
|
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ spec:
|
|||||||
- to:
|
- to:
|
||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: ai-spel-nats
|
||||||
app.kubernetes.io/instance: ai-spel-nats
|
app.kubernetes.io/instance: ai-spel-nats
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
@@ -20,9 +21,6 @@ spec:
|
|||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
kubernetes.io/metadata.name: kube-system
|
kubernetes.io/metadata.name: kube-system
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
k8s-app: kube-dns
|
|
||||||
ports:
|
ports:
|
||||||
- protocol: UDP
|
- protocol: UDP
|
||||||
port: 53
|
port: 53
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ deployment:
|
|||||||
maxSurge: 1
|
maxSurge: 1
|
||||||
minReadySeconds: 5
|
minReadySeconds: 5
|
||||||
progressDeadlineSeconds: 600
|
progressDeadlineSeconds: 600
|
||||||
|
pod:
|
||||||
|
tolerations:
|
||||||
|
- key: workload-tier
|
||||||
|
operator: Equal
|
||||||
|
value: small
|
||||||
|
effect: NoSchedule
|
||||||
service:
|
service:
|
||||||
port: 8080
|
port: 8080
|
||||||
nats:
|
nats:
|
||||||
|
|||||||
@@ -9,9 +9,22 @@ match = re.search(r'(?ms)(^\[workspace\.package\]\s+.*?^version\s*=\s*")(\d+)\.(
|
|||||||
if not match:
|
if not match:
|
||||||
raise SystemExit("Missing [workspace.package].version")
|
raise SystemExit("Missing [workspace.package].version")
|
||||||
major, minor, patch = map(int, match.group(2, 3, 4))
|
major, minor, patch = map(int, match.group(2, 3, 4))
|
||||||
|
current_version = f"{major}.{minor}.{patch}"
|
||||||
version = f"{major}.{minor}.{patch + 1}"
|
version = f"{major}.{minor}.{patch + 1}"
|
||||||
cargo_path.write_text(cargo[:match.start(2)] + version + cargo[match.end(4):])
|
cargo_path.write_text(cargo[:match.start(2)] + version + cargo[match.end(4):])
|
||||||
|
|
||||||
|
lock_path = Path("Cargo.lock")
|
||||||
|
lock = lock_path.read_text()
|
||||||
|
lock_pattern = rf'(?ms)^\[\[package\]\]\nname\s*=\s*"ai-spel-rule-engine"\nversion\s*=\s*"{re.escape(current_version)}"'
|
||||||
|
if not re.search(lock_pattern, lock):
|
||||||
|
raise SystemExit(f"Missing ai-spel-rule-engine {current_version} package in {lock_path}")
|
||||||
|
lock_path.write_text(re.sub(
|
||||||
|
lock_pattern,
|
||||||
|
f'[[package]]\nname = "ai-spel-rule-engine"\nversion = "{version}"',
|
||||||
|
lock,
|
||||||
|
count=1,
|
||||||
|
))
|
||||||
|
|
||||||
for path, pattern in (
|
for path, pattern in (
|
||||||
(Path("deploy/helm/ai-spel-rule-engine/Chart.yaml"), r'(?m)^version:\s*\d+\.\d+\.\d+\s*$'),
|
(Path("deploy/helm/ai-spel-rule-engine/Chart.yaml"), r'(?m)^version:\s*\d+\.\d+\.\d+\s*$'),
|
||||||
(Path("deploy/helm/ai-spel-rule-engine/Chart.yaml"), r'(?m)^appVersion:\s*"\d+\.\d+\.\d+"\s*$'),
|
(Path("deploy/helm/ai-spel-rule-engine/Chart.yaml"), r'(?m)^appVersion:\s*"\d+\.\d+\.\d+"\s*$'),
|
||||||
@@ -24,4 +37,16 @@ for path, pattern in (
|
|||||||
f'appVersion: "{version}"' if pattern.startswith(r'(?m)^appVersion') else f' targetRevision: "{version}"'
|
f'appVersion: "{version}"' if pattern.startswith(r'(?m)^appVersion') else f' targetRevision: "{version}"'
|
||||||
)
|
)
|
||||||
path.write_text(re.sub(pattern, replacement, contents, count=1))
|
path.write_text(re.sub(pattern, replacement, contents, count=1))
|
||||||
|
|
||||||
|
schema_path = Path("contracts/rule-evaluation-v1.schema.json")
|
||||||
|
schema = schema_path.read_text()
|
||||||
|
schema_pattern = r'(?m)^ "\$id": "https://gitea\.k3s\.k0\.nu/ai-spel/ai-spel-rule-engine/raw/tag/v\d+\.\d+\.\d+/contracts/rule-evaluation-v1\.schema\.json",$'
|
||||||
|
if not re.search(schema_pattern, schema):
|
||||||
|
raise SystemExit(f"Missing versioned schema id in {schema_path}")
|
||||||
|
schema_path.write_text(re.sub(
|
||||||
|
schema_pattern,
|
||||||
|
f' "$id": "https://gitea.k3s.k0.nu/ai-spel/ai-spel-rule-engine/raw/tag/v{version}/contracts/rule-evaluation-v1.schema.json",',
|
||||||
|
schema,
|
||||||
|
count=1,
|
||||||
|
))
|
||||||
print(version)
|
print(version)
|
||||||
|
|||||||
Reference in New Issue
Block a user