Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apiVersion: runwhen.com/v1
kind: GenerationRules
spec:
platform: kubernetes
generationRules:
- resourceTypes:
- service
matchRules:
# Airflow Helm charts expose the web UI on port 8080. Matching on 8080
# avoids unrelated Services that include "airflow" in the release name
# but use other ports (Redis, Postgres, etc.).
- type: and
matches:
- type: pattern
pattern: "airflow"
properties: [name]
mode: substring
- type: pattern
pattern: "^8080$"
properties: [spec/ports/port]
mode: exact
slxs:
- baseName: airflow-http-hc
qualifiers: ["resource", "namespace", "cluster"]
baseTemplateName: k8s-airflow-http-health
levelOfDetail: basic
outputItems:
- type: slx
- type: sli
- type: runbook
templateName: k8s-airflow-http-health-taskset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
apiVersion: runwhen.com/v1
kind: ServiceLevelIndicator
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
runwhen.com/sli: "true"
spec:
displayUnitsLong: OK
displayUnitsShort: ok
locations:
- {{default_location}}
description: Aggregates Airflow webserver /health, API reachability, and Kubernetes Service presence for {{match_resource.resource.metadata.name}}.
codeBundle:
{% if repo_url %}
repoUrl: {{repo_url}}
{% else %}
repoUrl: https://github.com/runwhen-contrib/rw-cli-codecollection.git
{% endif %}
{% if ref %}
ref: {{ref}}
{% else %}
ref: main
{% endif %}
pathToRobot: codebundles/k8s-airflow-http-health/sli.robot
intervalStrategy: intermezzo
intervalSeconds: 300
configProvided:
- name: CONTEXT
value: "{{context}}"
- name: NAMESPACE
value: "{{match_resource.resource.metadata.namespace}}"
- name: AIRFLOW_WEBSERVER_SERVICE_NAME
value: "{{match_resource.resource.metadata.name}}"
- name: PROXY_BASE_URL
value: ""
- name: AIRFLOW_HTTP_PORT
value: "8080"
- name: KUBERNETES_DISTRIBUTION_BINARY
value: "{{custom.kubernetes_distribution_binary | default('kubectl')}}"
secretsProvided:
{% if wb_version %}
{% include "kubernetes-auth.yaml" ignore missing %}
{% else %}
- name: kubeconfig
workspaceKey: {{custom.kubeconfig_secret_name | default("kubeconfig")}}
{% endif %}
alertConfig:
tasks:
persona: eager-edgar
sessionTTL: 10m
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: runwhen.com/v1
kind: ServiceLevelX
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
imageURL: https://storage.googleapis.com/runwhen-nonprod-shared-images/icons/kubernetes/resources/labeled/svc.svg
alias: Airflow HTTP Health for {{match_resource.resource.metadata.name}}
asMeasuredBy: Airflow /health JSON, REST API reachability, and Kubernetes Service/Endpoints presence.
configProvided:
- name: AIRFLOW_WEBSERVER_SERVICE_NAME
value: {{match_resource.resource.metadata.name}}
owners:
- {{workspace.owner_email}}
statement: The Airflow webserver Service should serve healthy HTTP endpoints and have backing Endpoints.
additionalContext:
{% include "kubernetes-hierarchy.yaml" ignore missing %}
qualified_name: "{{ match_resource.qualified_name }}"
tags:
{% include "kubernetes-tags.yaml" ignore missing %}
- name: access
value: read-only
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
apiVersion: runwhen.com/v1
kind: Runbook
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
location: {{default_location}}
description: Airflow webserver HTTP/API health and Kubernetes Service correlation for {{match_resource.resource.metadata.name}}.
codeBundle:
{% if repo_url %}
repoUrl: {{repo_url}}
{% else %}
repoUrl: https://github.com/runwhen-contrib/rw-cli-codecollection.git
{% endif %}
{% if ref %}
ref: {{ref}}
{% else %}
ref: main
{% endif %}
pathToRobot: codebundles/k8s-airflow-http-health/runbook.robot
configProvided:
- name: CONTEXT
value: "{{context}}"
- name: NAMESPACE
value: "{{match_resource.resource.metadata.namespace}}"
- name: AIRFLOW_WEBSERVER_SERVICE_NAME
value: "{{match_resource.resource.metadata.name}}"
- name: PROXY_BASE_URL
value: ""
- name: AIRFLOW_HTTP_PORT
value: "8080"
- name: AIRFLOW_SCHEDULER_SERVICE_NAME
value: ""
- name: AIRFLOW_TRIGGERER_SERVICE_NAME
value: ""
- name: KUBERNETES_DISTRIBUTION_BINARY
value: "{{custom.kubernetes_distribution_binary | default('kubectl')}}"
secretsProvided:
{% if wb_version %}
{% include "kubernetes-auth.yaml" ignore missing %}
{% else %}
- name: kubeconfig
workspaceKey: {{custom.kubeconfig_secret_name | default("kubeconfig")}}
- name: airflow_api_credentials
workspaceKey: {{custom.airflow_api_credentials_secret_name | default("airflow_api_credentials")}}
{% endif %}
167 changes: 167 additions & 0 deletions codebundles/k8s-airflow-http-health/.test/Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
version: "3"

tasks:
default:
desc: "Run/refresh config and RunWhen Local discovery"
cmds:
- task: check-unpushed-commits
- task: generate-rwl-config
- task: run-rwl-discovery

clean:
desc: "Run cleanup tasks"
cmds:
- task: remove-kubernetes-objects
- task: delete-slxs
- task: clean-rwl-discovery

build-infra:
desc: "Build test infrastructure"
cmds:
- task: create-kubernetes-objects

create-kubernetes-objects:
desc: "Apply manifests from kubernetes directory using kubectl"
cmds:
- kubectl apply -f kubernetes/
silent: true

remove-kubernetes-objects:
desc: "Delete kubernetes objects"
cmds:
- kubectl delete -f kubernetes/ --ignore-not-found=true
silent: true

check-unpushed-commits:
desc: Check if outstanding commits or file updates need to be pushed before testing.
vars:
BASE_DIR: "../"
cmds:
- |
echo "Checking for uncommitted changes in $BASE_DIR and $BASE_DIR.runwhen, excluding '.test'..."
UNCOMMITTED_FILES=$(git diff --name-only HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true)
if [ -n "$UNCOMMITTED_FILES" ]; then
echo "Uncommitted changes found:"
echo "$UNCOMMITTED_FILES"
exit 1
fi
- |
git fetch origin
UNPUSHED_FILES=$(git diff --name-only origin/$(git rev-parse --abbrev-ref HEAD) HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true)
if [ -n "$UNPUSHED_FILES" ]; then
echo "Unpushed commits found:"
echo "$UNPUSHED_FILES"
exit 1
fi
silent: true

generate-rwl-config:
desc: "Generate RunWhen Local configuration (workspaceInfo.yaml)"
env:
RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}'
cmds:
- |
repo_url=$(git config --get remote.origin.url)
branch_name=$(git rev-parse --abbrev-ref HEAD)
codebundle=$(basename "$(dirname "$PWD")")
namespace=$(yq e 'select(.kind == "Namespace") | .metadata.name' kubernetes/manifest.yaml -N)
if [ -z "$namespace" ]; then
echo "Could not determine namespace from kubernetes/manifest.yaml"
exit 1
fi

cat <<EOF > workspaceInfo.yaml
workspaceName: "$RW_WORKSPACE"
workspaceOwnerEmail: authors@runwhen.com
defaultLocation: location-01
defaultLOD: none
cloudConfig:
kubernetes:
kubeconfigFile: /shared/kubeconfig.secret
namespaceLODs:
$namespace: detailed
namespaces:
- $namespace
codeCollections:
- repoURL: "$repo_url"
branch: "$branch_name"
codeBundles: ["$codebundle"]
custom:
kubeconfig_secret_name: "kubeconfig"
kubernetes_distribution_binary: kubectl
airflow_api_credentials_secret_name: "airflow_api_credentials"
EOF
echo "Generated workspaceInfo.yaml (namespace=$namespace, codebundle=$codebundle)"
silent: true

run-rwl-discovery:
desc: "Run RunWhen Local Discovery on test infrastructure"
cmds:
- |
CONTAINER_NAME="RunWhenLocal"

if [ ! -f "kubeconfig.secret" ]; then
echo "Missing kubeconfig.secret in $(pwd)."
echo "Drop a kubeconfig file named 'kubeconfig.secret' into this directory (pointing at the test cluster) and re-run."
exit 1
fi

if docker ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then
echo "Stopping and removing existing container $CONTAINER_NAME..."
docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME
elif docker ps -a -q --filter "name=$CONTAINER_NAME" | grep -q .; then
echo "Removing existing stopped container $CONTAINER_NAME..."
docker rm $CONTAINER_NAME
else
echo "No existing container named $CONTAINER_NAME found."
fi

echo "Cleaning up output directory..."
sudo rm -rf output || { echo "Failed to remove output directory"; exit 1; }
mkdir -p output && chmod 777 output || { echo "Failed to set permissions on output directory"; exit 1; }

echo "Starting new container $CONTAINER_NAME..."
docker run --name $CONTAINER_NAME -p 8081:8081 -v "$(pwd)":/shared -d ghcr.io/runwhen-contrib/runwhen-local:latest || {
echo "Failed to start container"; exit 1;
}

echo "Running workspace builder script in container..."
docker exec -w /workspace-builder $CONTAINER_NAME ./run.sh $1 --verbose || {
echo "Error executing workspace builder script in container"; exit 1;
}

echo "Review generated config files under output/workspaces/"
silent: true

validate-generation-rules:
desc: "Validate YAML files in .runwhen/generation-rules"
cmds:
- |
for cmd in curl yq ajv; do
command -v $cmd >/dev/null || { echo "Missing $cmd"; exit 1; }
done
temp_dir=$(mktemp -d)
curl -s -o "$temp_dir/generation-rule-schema.json" \
https://raw.githubusercontent.com/runwhen-contrib/runwhen-local/refs/heads/main/src/generation-rule-schema.json
for yaml_file in ../.runwhen/generation-rules/*.yaml; do
json_file="$temp_dir/$(basename "${yaml_file%.*}.json")"
yq -o=json "$yaml_file" > "$json_file"
ajv validate -s "$temp_dir/generation-rule-schema.json" -d "$json_file" --spec=draft2020 --strict=false
done
rm -rf "$temp_dir"
silent: true

delete-slxs:
desc: "No-op placeholder (optional platform upload flow)"
cmds:
- |
echo "Optional - configure RW_WORKSPACE, RW_API, RW_PAT for platform upload flows."
silent: true

clean-rwl-discovery:
desc: "Clean RunWhen Local discovery output"
cmds:
- |
sudo rm -rf output || rm -rf output
rm -f workspaceInfo.yaml
silent: true
Loading