A cloud-agnostic, reusable Kubernetes sidecar container that monitors adapter operation results and updates Kubernetes Job status.
The status reporter is a production-ready Kubernetes sidecar container that works with any adapter container (validation, DNS, pull secret, etc.) that follows the defined result contract. It provides robust monitoring and status reporting capabilities for Kubernetes Jobs.
Key Features:
- Monitors adapter container execution via file polling and container state watching
- Handles various failure scenarios (OOMKilled, crashes, timeouts, invalid results)
- Updates Kubernetes Job status with detailed condition information
- Zero-dependency on adapter implementation - uses simple JSON contract
- Cloud-agnostic design - works with any Kubernetes environment
The status reporter works with any adapter container that follows this simple JSON contract:
-
Result File Requirements:
- Location: Write results to the result file (configurable via
RESULTS_PATHenv var) - Format: Valid JSON file (max size: 1MB)
- Timing: Must be written before the adapter container exits or within the configured timeout
- Location: Write results to the result file (configurable via
-
JSON Schema:
{ "status": "success", // Required: "success" or "failure" "reason": "AllChecksPassed", // Required: Machine-readable identifier (max 128 chars) "message": "All validation checks passed successfully", // Required: Human-readable description (max 1024 chars) "details": { // Optional: Adapter-specific data (any valid JSON), this information will not be reflected in k8s Job Status "checks_run": 5, "duration_ms": 1234 } } -
Field Validation:
status: Must be exactly"success"or"failure"(case-sensitive)reason: Trimmed and truncated to 128 characters. Defaults to"NoReasonProvided"if empty/missingmessage: Trimmed and truncated to 1024 characters. Defaults to"No message provided"if empty/missingdetails: Optional JSON object containing any adapter-specific information
-
Examples:
Success result:
Adapter writes to the result file:
{ "status": "success", "reason": "ValidationPassed", "message": "GCP environment validated successfully" }Resulting Kubernetes Job status:
status: conditions: - type: Available status: "True" reason: ValidationPassed message: GCP environment validated successfully lastTransitionTime: "2024-01-15T10:30:00Z"
Failure result with details:
Adapter writes to the result file:
{ "status": "failure", "reason": "MissingPermissions", "message": "Service account lacks required IAM permissions", "details": { "missing_permissions": ["compute.instances.list", "iam.serviceAccounts.get"], "service_account": "my-sa@project.iam.gserviceaccount.com" } }Resulting Kubernetes Job status:
status: conditions: - type: Available status: "False" reason: MissingPermissions message: Service account lacks required IAM permissions lastTransitionTime: "2024-01-15T10:30:00Z"
Timeout scenario:
If adapter doesn't write result file within timeout, Job status will be:
status: conditions: - type: Available status: "False" reason: AdapterTimeout message: "Adapter did not produce results within 5m0s" lastTransitionTime: "2024-01-15T10:30:00Z"
Container crash scenario:
If adapter container exits with non-zero code, Job status will be:
status: conditions: - type: Available status: "False" reason: AdapterExitedWithError message: "Adapter container exited with code 1: Error" lastTransitionTime: "2024-01-15T10:30:00Z"
OOMKilled scenario:
If adapter container is killed due to memory limits:
status: conditions: - type: Available status: "False" reason: AdapterOOMKilled message: "Adapter container was killed due to out of memory (OOMKilled)" lastTransitionTime: "2024-01-15T10:30:00Z"
Invalid result format:
If adapter writes invalid JSON or schema:
status: conditions: - type: Available status: "False" reason: InvalidResultFormat message: "Failed to parse adapter result: status: must be either 'success' or 'failure'" lastTransitionTime: "2024-01-15T10:30:00Z"
-
Shared Volume Configuration:
Both adapter and status reporter containers must share a volume mounted at
/results:volumes: - name: results emptyDir: {} containers: - name: adapter volumeMounts: - name: results mountPath: /results - name: status-reporter volumeMounts: - name: results mountPath: /results
The status reporter is configured exclusively through environment variables. Below is a complete reference of all supported configuration options.
| Environment Variable | Type | Required | Default | Description |
|---|---|---|---|---|
JOB_NAME |
string | Yes | - | Name of the Kubernetes Job to update |
JOB_NAMESPACE |
string | Yes | - | Namespace of the Kubernetes Job |
POD_NAME |
string | Yes | - | Name of the current Pod (typically injected via downward API) |
RESULTS_PATH |
string | No | /results/adapter-result.json |
Absolute path to the adapter result file (must be a file, not a directory) |
POLL_INTERVAL_SECONDS |
integer | No | 2 |
Interval in seconds between result file checks (must be positive and less than MAX_WAIT_TIME_SECONDS) |
MAX_WAIT_TIME_SECONDS |
integer | No | 300 |
Maximum time in seconds to wait for adapter results before timing out (must be positive) |
CONDITION_TYPE |
string | No | Available |
Kubernetes condition type to set on the Job status |
LOG_LEVEL |
string | No | info |
Logging verbosity level |
ADAPTER_CONTAINER_NAME |
string | No | "" (auto-detect) |
Name of the adapter container to monitor; if empty, automatically detects the first non-reporter container in the Pod |
Here's a complete example showing how to configure the status reporter.
---
# ServiceAccount for the validator job
apiVersion: v1
kind: ServiceAccount
metadata:
name: status-reporter-sa
namespace: <namespace>
---
# Role with necessary permissions for status reporter
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: status-reporter
namespace: <namespace>
rules:
# Permission to get and update job status
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get"]
- apiGroups: ["batch"]
resources: ["jobs/status"]
verbs: ["get", "update", "patch"]
# Permission to get pod status
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# RoleBinding to grant permissions to the service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: status-reporter
namespace: <namespace>
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: status-reporter
subjects:
- kind: ServiceAccount
name: status-reporter-sa
namespace: <namespace>sed 's/<namespace>/your-namespace/g' rbac.yaml | kubectl apply -f -apiVersion: batch/v1
kind: Job
metadata:
name: my-adapter-job
namespace: <namespace>
spec:
backoffLimit: 0
activeDeadlineSeconds: 310 # 300 adapter timeout + 10 second buffer
template:
spec:
serviceAccountName: status-reporter-sa
volumes:
- name: results
emptyDir: {}
containers:
# Replace with real adapter information
- name: my-adapter
image: busybox:1.36
imagePullPolicy: IfNotPresent
# Adapter writes results to shared volume
volumeMounts:
- name: results
mountPath: /results
# Simulate adapter work and write result file
command:
- /bin/sh
- -c
- |
echo "Simulating adapter validation work..."
sleep 3
# Write adapter result in the expected JSON format
# Success example:
cat > $RESULTS_PATH <<'EOF'
{
"status": "success",
"reason": "GCPValidationPassed",
"message": "All GCP infrastructure validations completed successfully",
"details": {
"validations_run": 5,
"validations_passed": 5,
"checks": [
"VPC configuration validated",
"IAM permissions verified",
"DNS settings confirmed",
"Network policies applied",
"Resource quotas checked"
],
"duration_seconds": 3,
"gcp_project": "example-project-123"
}
}
EOF
echo "Adapter result written to $RESULTS_PATH"
cat $RESULTS_PATH
sleep 5
env:
- name: RESULTS_PATH
value: "/results/adapter-result.json"
- name: status-reporter
image: <status-reporter-image>
env:
# Required configuration
- name: JOB_NAME
value: my-adapter-job
- name: JOB_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# Optional configuration (shown with non-default values)
- name: RESULTS_PATH
value: "/results/adapter-result.json"
- name: POLL_INTERVAL_SECONDS
value: "5"
- name: MAX_WAIT_TIME_SECONDS
value: "300" # 5 minutes
- name: CONDITION_TYPE
value: "Available"
- name: LOG_LEVEL
value: "info"
- name: ADAPTER_CONTAINER_NAME
value: "my-adapter"
volumeMounts:
- name: results
mountPath: /results
restartPolicy: Neversed -e 's|<namespace>|your-namespace|g' \
-e 's|<status-reporter-image>|quay.io/rh-ee-dawang/status-reporter:dev-04e8d0a|g' \
job.yaml | kubectl apply -f -status-reporter/
├── cmd/reporter/ # Main entry point
├── pkg/ # Core packages (reporter, k8s, result parser)
├── Dockerfile # Container image definition
├── Makefile # Build, test, and image targets
└── README.md # This file
The status reporter is production-ready and can be used with any adapter container.
$ make
Available targets:
binary Build binary
clean Clean build artifacts and test coverage files
fmt Format code with gofmt and goimports
help Display this help message
image-dev Build and push to personal Quay registry (requires QUAY_USER)
image-push Build and push container image to registry
image Build container image with Docker or Podman
lint Run golangci-lint
mod-tidy Tidy Go module dependencies
test-coverage-html Generate HTML coverage report
test-coverage Run unit tests with coverage report
test Run unit tests with race detection
verify Run all verification checks (lint + test)See LICENSE file for details.
For questions or issues, please open a GitHub issue in this repository.