For AI Agents: This file contains critical context for understanding and working with the jobset-operator codebase. Read this fully before making changes. Pay special attention to "Never Do" and "Common Agent Mistakes" sections.
OpenShift operator that manages the deployment and lifecycle of the JobSet controller for distributed ML training and HPC workloads.
# Build
make build # Build operator binary
make lint # Run linting
# Test
make test-unit # Run unit tests
make test-e2e # Run e2e tests (requires OPERATOR_IMAGE and RELATED_IMAGE_OPERAND_IMAGE)
make test-e2e-operand # Run operand e2e tests (JobSet controller tests)
# Code Generation
make generate # Regenerate all code
make verify-codegen # Verify codegen is up to date
# Deploy
oc apply -f deploy/ --server-side # Deploy to cluster
oc get jobsetoperator cluster -o yaml # Check statusNote: This lists the most commonly used commands. Consult the Makefile for additional build, test, and verification targets.
- ARCHITECTURE.md - System design, components, reconciliation flow
- CONTRIBUTING.md - Development workflow, testing, debugging
- README.md - Quick start guide
Before making changes:
- Read "Overview" to understand what this operator does
- Check "Never Do" and "Common Agent Mistakes" to avoid critical errors
- Review "Architecture Patterns" for code structure and library-go patterns
- Check "Quick Reference" for essential commands
When stuck:
- Check "Common Workflows" for your specific task
- Review "Code Organization" to find relevant code
- Check "What to Ask First" to see if you need human input
- Review "Architecture & Design Documentation" links for design context
When implementing:
- Follow "Controller Pattern" for adding/modifying controllers
- Use "What to Always Do" as a checklist
- Run commands from "Quick Reference" or Makefile to verify changes
- Add tests following patterns in "Testing" section
-
ARCHITECTURE.md - Comprehensive system architecture documentation covering:
- System architecture diagrams and data flow
- Component architecture (operator controllers, operand, cert-manager integration)
- Deployment topology and resource ownership
- Reconciliation flow and controller patterns
- Certificate management lifecycle
- Design decisions and their rationales
-
CONTRIBUTING.md - Development workflow, PR process, and code conventions
-
README.md - Quick start: building, deploying, and running tests
-
API Documentation: The
JobSetOperatorCRD is defined inpkg/apis/openshiftoperator/v1/types_jobsetoperator.gowith kubebuilder markers for validation -
Operand Repository: openshift/kubernetes-sigs-jobset - OpenShift fork of the upstream JobSet controller
-
Enhancement Proposals: See OpenShift enhancements repo for architectural decisions affecting JobSet integration into OpenShift
-
Developer Guide: OpenShift Operator Development covers build/test/deploy patterns shared across OpenShift operators
- Overview
- Architecture Patterns
- Code Organization
- Common Workflows
- Development Guidelines
- What to Always Do
- What to Ask First
- What to Never Do
The JobSet Operator is an OpenShift operator that deploys and manages the JobSet controller in OpenShift clusters. JobSet is a Kubernetes-native API for managing groups of Jobs as a unit, designed for distributed ML training and HPC workloads.
Purpose: Enable multi-job workloads with coordinated lifecycle management, networking, and fault recovery.
Key Capabilities:
- Manages multiple Jobs as a single coordinated unit
- Provides stable pod hostnames via IndexedJobs and headless services
- Intelligent failure recovery (recreates affected Jobs, not everything)
- Reduces scheduler pressure during recovery at scale
- Supports multi-role workloads (leader-worker, parameter server-worker patterns)
This operator follows the OpenShift library-go operator pattern:
// Standard imports for library-go operators
import (
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
"github.com/openshift/library-go/pkg/operator/resource/resourceread"
"github.com/openshift/library-go/pkg/operator/v1helpers"
)Key Characteristics:
- Factory-based Controllers: Use
factory.New()pattern from library-go - Informer-driven: React to cluster changes via Kubernetes informers
- Event Recording: Use
events.Recorderfor all state changes - Singleton Operator CR: The
JobSetOperatorCR must be namedcluster - Status Conditions: Track
Available,Degraded,Progressingconditions
The operator has two main controllers:
Location: pkg/operator/starter.go:75-142
Manages static resources that don't change based on cluster state:
- ServiceAccounts
- ClusterRoles/ClusterRoleBindings
- Roles/RoleBindings
- Services
Pattern:
staticResourceController := staticresourcecontroller.NewStaticResourceController(
"JobSetOperatorStaticResources",
func(name string) ([]byte, error) {
bytes, err := bindata.Asset(name)
// Apply transformations (namespace, ownerRefs)
return out, nil
},
[]string{"assets/path/to/resource.yaml", ...},
clientHolder,
operatorClient,
eventRecorder,
)Location: pkg/operator/target_config_reconciler.go
Manages dynamic resources and the operand deployment:
- cert-manager resources (Issuer, Certificates)
- Webhook configurations (ValidatingWebhook, MutatingWebhook)
- CRDs (with version-specific fixes)
- Prometheus monitoring (ServiceMonitor, Role, RoleBinding)
- ConfigMap and Deployment
Reconciliation Pattern:
func (t *TargetConfigReconciler) sync(ctx context.Context, syncCtx factory.SyncContext) error {
// 1. Check management state
if spec.ManagementState != operatorv1.Managed {
return nil
}
// 2. Update Available condition
v1helpers.UpdateStatus(ctx, t.jobSetOperatorClient,
v1helpers.UpdateConditionFn(condition))
// 3. Manage resources in dependency order
_, _, err = t.manageWebhookService(ctx, ownerReference)
_, _, err = t.manageCertManagerIssuer(ctx, ownerReference)
// ... more resources
// 4. Update final status
return err
}Always use library-go resource apply functions:
// For core Kubernetes resources
resourceapply.ApplyService(ctx, kubeClient.CoreV1(), eventRecorder, service)
resourceapply.ApplyDeployment(ctx, kubeClient.AppsV1(), eventRecorder, deployment, expectedGeneration)
resourceapply.ApplyConfigMap(ctx, kubeClient.CoreV1(), eventRecorder, configMap)
// For CRDs
resourceapply.ApplyCustomResourceDefinitionV1(ctx, apiextensionsClient, eventRecorder, crd)
// For webhooks
resourceapply.ApplyValidatingWebhookConfigurationImproved(ctx, kubeClient, eventRecorder, webhook, cache)
// For unstructured (cert-manager resources)
resourceapply.ApplyUnstructuredResourceImproved(ctx, dynamicClient, eventRecorder, obj, cache, gvr, nil, nil)Benefits: Automatic diffing, event recording, and idempotent operations.
jobset-operator/
├── cmd/jobset-operator/ # Entrypoint
│ └── main.go # Calls pkg/cmd/operator/cmd.go
├── pkg/
│ ├── apis/openshiftoperator/v1/ # API types
│ │ └── types_jobsetoperator.go # JobSetOperator CRD definition
│ ├── cmd/operator/ # CLI command setup
│ │ └── cmd.go # Cobra command configuration
│ ├── operator/ # Core operator logic
│ │ ├── starter.go # Controller initialization
│ │ ├── target_config_reconciler.go # Main reconciliation logic
│ │ ├── resource.go # Resource helper functions
│ │ └── operatorclient/ # Typed client wrappers
│ ├── generated/ # Generated code (don't edit manually)
│ │ ├── clientset/ # Kubernetes client
│ │ ├── informers/ # Informers for cache
│ │ └── applyconfiguration/ # Server-side apply configs
│ └── version/ # Version info
├── bindata/ # Embedded YAML assets
│ └── assets/
│ ├── jobset-controller-generated/ # Upstream JobSet manifests
│ ├── jobset-controller-config/ # Controller config
│ └── jobset-controller/ # ConfigMap template
├── deploy/ # Deployment manifests
├── test/e2e/ # End-to-end tests
└── hack/ # Build and code generation scripts
File: pkg/apis/openshiftoperator/v1/types_jobsetoperator.go
// JobSetOperator is a singleton - MUST be named "cluster"
// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="JobSetOperator is a singleton, .metadata.name must be 'cluster'"
type JobSetOperator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec JobSetOperatorSpec `json:"spec"`
Status JobSetOperatorStatus `json:"status"`
}
type JobSetOperatorSpec struct {
operatorv1.OperatorSpec `json:",inline"`
// Inherits: ManagementState, LogLevel, OperatorLogLevel
}Management States:
Managed(default): Operator reconciles resourcesUnmanaged: Operator doesn't reconcile, allows manual changesRemoved: Operator doesn't reconcile (kept for compatibility)
File: pkg/operator/target_config_reconciler.go:449-491
The deployment is managed from embedded assets at:
bindata/assets/jobset-controller-generated/apps_v1_deployment_jobset-controller-manager.yaml
Image Substitution:
images := map[string]string{
"${CONTROLLER_IMAGE}:latest": t.targetImagePullSpec,
}The OPERAND_IMAGE env var is passed at operator startup and injected into the deployment.
Log Level Mapping:
switch jobSetOperator.Spec.LogLevel {
case operatorv1.Normal:
logLevel = "info"
case operatorv1.Debug, operatorv1.Trace, operatorv1.TraceAll:
logLevel = "debug"
default:
logLevel = "info"
}-
Update operand-git-ref file:
echo "v0.11.0" > operand-git-ref
-
Regenerate manifests:
hack/update-jobset-controller-manifests.sh
This script:
- Clones the operand repo at the specified ref
- Extracts manifests via
make manifests - Copies them to
bindata/assets/jobset-controller-generated/
-
Test locally (see CONTRIBUTING.md)
-
Add asset to bindata (if new upstream resource):
# Assets are synced from upstream via: hack/update-jobset-controller-manifests.sh -
For static resources (ServiceAccounts, ClusterRoles, RoleBindings, etc. that don't depend on runtime state): Add the asset path to the
[]stringarray (3rd parameter) in thestaticresourcecontroller.NewStaticResourceController()call inpkg/operator/starter.go(within theRunOperatorfunction). For dynamic resources (that depend on runtime state like certs, webhooks), create a manager function intarget_config_reconciler.go:func (t *TargetConfigReconciler) manageMyResource(ctx context.Context, ownerReference metav1.OwnerReference) (*corev1.MyResource, bool, error) { required := resourceread.ReadMyResourceV1OrDie( bindata.MustAsset("assets/path/to/resource.yaml")) required.Namespace = t.operatorNamespace required.OwnerReferences = []metav1.OwnerReference{ownerReference} // Apply transformations return resourceapply.ApplyMyResource(ctx, t.kubeClient, t.eventRecorder, required) }
-
Call in sync() in dependency order:
func (t *TargetConfigReconciler) sync(ctx context.Context, syncCtx factory.SyncContext) error { // ... existing code _, _, err = t.manageMyResource(ctx, ownerReference) if err != nil { return err } // ... rest of sync }
-
Add informer if needed in
NewTargetConfigReconciler:return factory.New().WithInformers( // ... existing informers kubeInformersForNamespaces.InformersFor(operatorNamespace).Core().V1().MyResources().Informer(), )
Important: Before modifying the operator API, ensure there is a corresponding enhancement proposal in openshift/enhancements or open one. API changes require design review and approval.
-
Edit the API type:
pkg/apis/openshiftoperator/v1/types_jobsetoperator.go -
Regenerate CRD:
make regen-crd
This generates:
manifests/jobset-operator.crd.yamldeploy/00_jobset-operator.crd.yaml
-
Regenerate clients:
make generate-clients
Updates:
pkg/generated/clientset/pkg/generated/informers/pkg/generated/applyconfiguration/
-
Update reconciler logic to handle new fields
-
Verify:
make verify-codegen
The operator uses cert-manager for TLS certificates:
Resources Created:
- Issuer:
jobset-selfsigned-issuer(self-signed) - Certificates:
jobset-serving-cert→ secretwebhook-server-certjobset-metrics-cert→ secretmetrics-server-cert
Pattern for Unstructured Resources:
gvr := schema.GroupVersionResource{
Group: "cert-manager.io",
Version: "v1",
Resource: "certificates",
}
// Use ReadGenericWithUnstructured unless a type-specific function exists in resourceread
cert, err := resourceread.ReadGenericWithUnstructured(
bindata.MustAsset("assets/..."))
certAsUnstructured, ok := cert.(*unstructured.Unstructured)
// Set namespace and ownerRefs
certAsUnstructured.SetNamespace(t.operatorNamespace)
certAsUnstructured.SetOwnerReferences([]metav1.OwnerReference{ownerReference})
return resourceapply.ApplyUnstructuredResourceImproved(
ctx, t.dynamicClient, t.eventRecorder, certAsUnstructured,
t.resourceCache, gvr, nil, nil)Wait for Secrets:
secret, _, err := t.checkSecretReady(WebhookCertificateSecretName)
if err != nil {
return err // Requeue until cert-manager creates the secret
}Inject CA into Webhooks/CRDs:
func injectCertManagerCA(obj metav1.Object, namespace string) error {
annotations := obj.GetAnnotations()
injectAnnotation := annotations[CertManagerInjectCaAnnotation]
injectAnnotation = strings.Replace(injectAnnotation,
"$(CERTIFICATE_NAMESPACE)", namespace, 1)
annotations[CertManagerInjectCaAnnotation] = injectAnnotation
obj.SetAnnotations(annotations)
return nil
}// ✅ GOOD: Use library-go resource apply
resourceapply.ApplyDeployment(ctx, kubeClient, eventRecorder, deployment, expectedGeneration)
// ❌ BAD: Direct client calls
_, err := kubeClient.AppsV1().Deployments(ns).Update(ctx, deployment, metav1.UpdateOptions{})// ✅ GOOD: Events are automatically recorded by resourceapply functions
resourceapply.ApplyService(ctx, kubeClient.CoreV1(), t.eventRecorder, service)
// For custom logic, record explicitly:
t.eventRecorder.Event("ServiceCreated", "Created webhook service")ownerReference := metav1.OwnerReference{
APIVersion: "operator.openshift.io/v1",
Kind: "JobSetOperator",
Name: jobSetOperator.Name,
UID: jobSetOperator.UID,
}
resource.OwnerReferences = []metav1.OwnerReference{ownerReference}Why: Ensures cascading deletion when the operator CR is deleted.
_, _, err = v1helpers.UpdateStatus(ctx, t.jobSetOperatorClient,
v1helpers.UpdateConditionFn(operatorv1.OperatorCondition{
Type: operatorv1.OperatorStatusTypeAvailable,
Status: operatorv1.ConditionTrue,
Reason: "AsExpected",
Message: "All operand pods are running",
}))Required Conditions:
Available: Operand is running and readyDegraded: Operator encountered an errorProgressing: Rollout in progress
spec, _, _, err := t.jobSetOperatorClient.GetOperatorState()
if spec.ManagementState != operatorv1.Managed {
return nil // Don't reconcile
}Check for cert-manager before creating resources:
found, err := isResourceRegistered(t.discoveryClient, schema.GroupVersionKind{
Group: "cert-manager.io",
Version: "v1",
Kind: "Issuer",
})
if !found {
return fmt.Errorf("please make sure that cert-manager is installed")
}// For namespaced resources
resource.Namespace = t.operatorNamespace
// For role bindings
for i := range roleBinding.Subjects {
roleBinding.Subjects[i].Namespace = t.operatorNamespace
}func customCRDfixes(crd *apiextensionsv1.CustomResourceDefinition, serverVersion *version.Version) {
if serverVersion.Major() == 1 && serverVersion.Minor() <= 31 {
// Apply workaround for k8s 1.31 and earlier
}
}Ask: "Should this be in the operator spec or a separate CR?"
- Operator spec: Configuration affecting the operator itself (log level, management state)
- Operand configuration: Usually passed via ConfigMap or separate CR
Ask: "What are the dependencies?"
Current order in sync():
- WebhookService (needed for cert DNS names)
- cert-manager Issuer
- cert-manager Certificates
- Wait for Secrets (certs must be ready)
- Webhooks (need CA from certs)
- CRD (needs CA from certs)
- Prometheus resources
- ConfigMap
- Deployment (needs all above)
Ask: "Should this be upstreamed to kubernetes-sigs/jobset?"
- Asset files in
bindata/assets/jobset-controller-generated/come from upstream and must not be manually edited - Any manual modifications will be overwritten by
hack/update-jobset-controller-manifests.sh - Transformations should happen in the reconciler code, not in asset files
Ask: "Is this change compatible with existing clusters?"
- Default namespace:
openshift-jobset-operator - Changing requires migration strategy
- See
starter.go:37-40for namespace fallback logic
Ask: "Is this in library-go or do we need to vendor?"
- Prefer library-go patterns over custom implementations
- Check
go.modfor existing dependencies - Large dependencies should be justified
Files with // Code generated headers are auto-generated:
pkg/generated/
pkg/apis/*/zz_generated.*.go
bindata/assets.go
bindata/assets/jobset-controller-generated/
Instead: Run the appropriate generator:
make generate-clients # For clientset/informers
make regen-crd # For CRD
make generate # For all codegen
hack/update-jobset-controller-manifests.sh # For upstream assets// ❌ BAD: Resource won't be cleaned up
resource.OwnerReferences = nil
// ✅ GOOD: Always set owner reference
resource.OwnerReferences = []metav1.OwnerReference{ownerReference}// ❌ BAD: Hard-coded namespace
namespace := "openshift-jobset-operator"
// ✅ GOOD: Use the configured namespace
namespace := t.operatorNamespaceException: openshift-monitoring for Prometheus integration (external namespace).
// ❌ BAD: Blocking wait
for !isCertReady() {
time.Sleep(1 * time.Second)
}
// ✅ GOOD: Return error and let informer re-trigger
secret, _, err := t.checkSecretReady(secretName)
if err != nil {
return err // Controller will retry
}// ❌ BAD: Directly editing JobSet controller config
configMap.Data["config.yaml"] = customConfig
// ✅ GOOD: Use the upstream config and let users customize via JobSetOperator spec
// Then transform in the reconciler if neededThe operand (JobSet controller) configuration comes from upstream and should only be modified if:
- Required for OpenShift integration
- Bug fix until upstream release
- Security requirement
// ❌ BAD: Ignoring errors
_, _, _ = t.manageWebhookService(ctx, ownerReference)
// ✅ GOOD: Propagate errors
_, _, err := t.manageWebhookService(ctx, ownerReference)
if err != nil {
return err
}// ❌ BAD: Create without checking existence
_, err := kubeClient.CoreV1().Services(ns).Create(ctx, service, metav1.CreateOptions{})
// ✅ GOOD: Use Apply (idempotent)
resourceapply.ApplyService(ctx, kubeClient.CoreV1(), eventRecorder, service)Degraded Condition: Automatically handled by library-go's WithSyncDegradedOnError() pattern:
// Controller setup (in NewTargetConfigReconciler)
return factory.New().
WithInformers(...).
WithSyncDegradedOnError(jobSetOperatorClient). // Automatically sets Degraded=True on error
WithSync(t.sync).
ToController("TargetConfigController", eventRecorder)
// In sync function - just return errors
func (t *TargetConfigReconciler) sync(ctx context.Context, syncCtx factory.SyncContext) error {
if err != nil {
return err // Degraded=True set automatically
}
}Available Condition: Must be explicitly updated:
// ✅ GOOD: Always update Available condition
_, _, err := v1helpers.UpdateStatus(ctx, t.jobSetOperatorClient,
v1helpers.UpdateConditionFn(constructAvailableCondition(err, deployment)))For custom Degraded reasons: Explicitly set before returning:
// Only needed when you want a specific Reason/Message
_, _, err = v1helpers.UpdateStatus(ctx, client, v1helpers.UpdateConditionFn(
operatorv1.OperatorCondition{
Type: operatorv1.OperatorStatusTypeDegraded,
Status: operatorv1.ConditionTrue,
Reason: "MissingDependency",
Message: "cert-manager must be installed",
}))
return errThe API enforces a singleton pattern:
// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="JobSetOperator is a singleton, .metadata.name must be 'cluster'"Only one JobSetOperator CR named cluster should exist.
// ✅ GOOD: Always check first
found, err := isResourceRegistered(t.discoveryClient, ...)
if !found {
return fmt.Errorf("cert-manager must be installed")
}cert-manager is a prerequisite but not bundled with the operator.
These are specific mistakes AI agents frequently make in this codebase:
-
Don't suggest
client.Get()orclient.List()in sync functions - Use informers and listers instead. Direct API calls in controller sync loops cause performance issues and are against OpenShift library-go patterns. Always use the cached listers from informers. -
Don't propose adding controllers without mentioning
pkg/operator/starter.go- All controllers must be registered in the starter'sRunOperator()function, or they won't run. Simply creating a controller file isn't enough. -
Don't generate code changes without verifying - Always run
make verify,make verify-codegen, andmake verify-controller-manifestsbefore suggesting code is complete. CI will fail if verification doesn't pass locally first. -
Don't recommend modifying YAML in
bindata/assets/jobset-controller-generated/without explaining the regeneration process - These files come from upstream and are overwritten byhack/update-jobset-controller-manifests.sh. Explain that changes should be made in the reconciler logic or upstream, not directly in these assets. -
Don't propose changes to RBAC without updating
deploy/manifests - Operator permissions must be declared in deploy manifest files. Code changes alone won't grant new permissions. -
Don't use table-driven tests without the
t.Run()pattern - All tests should use subtests for better failure isolation and reporting:for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test implementation }) }
-
Don't assume single namespace - This operator manages resources across multiple namespaces (
openshift-jobset-operatorfor operator, same namespace for operand). Be explicit about which namespace resources belong to. -
Don't suggest using
resourcemerge.SetDeploymentGeneration()without updating status - Generation tracking must be paired with status updates:v1helpers.UpdateStatus(ctx, client, func(status *operatorv1.OperatorStatus) error { resourcemerge.SetDeploymentGeneration(&status.Generations, deployment) return nil })
-
Don't recommend cert-manager changes without security review - Certificate management is security-critical. Flag any cert-manager modifications for human security review.
-
Don't ignore the singleton pattern - The
JobSetOperatorCR must be named "cluster". Don't suggest creating CRs with other names or supporting multiple instances.
- Go 1.26+ - Check
go.modfor the exact version required - Kubernetes client-go - Standard Kubernetes client library
- OpenShift library-go - Controller factory, resourceapply, v1helpers, operators
- OpenShift api -
operator.openshift.io/v1for JobSetOperator CRD - cert-manager.io/v1 - Certificate management (runtime dependency, not vendored)
- k8s.io/apiextensions-apiserver - CRD management
- k8s.io/klog/v2 - Structured logging
- github.com/onsi/ginkgo/v2 - BDD-style testing framework
- github.com/onsi/gomega - Matcher library for tests
Key Patterns:
- library-go factory pattern - All controllers use
factory.New() - Informer-based - No direct API calls in sync loops
- Declarative apply - Use
resourceapply.*functions - Operator lifecycle - Follows OpenShift operator conventions
The JobSet Operator manages resources in a single primary namespace:
openshift-jobset-operator- Operator and operand both run here- Operator deployment:
jobset-operator - Operand deployment:
jobset-controller-manager - Certificates, secrets, services, RBAC resources
- JobSetOperator CR (cluster-scoped, but operator watches from this namespace)
- Operator deployment:
Cluster-Scoped Resources:
JobSetOperatorCR (name must be "cluster")jobsets.jobset.x-k8s.ioCRD- ClusterRoles and ClusterRoleBindings for operator and operand
- ValidatingWebhookConfiguration and MutatingWebhookConfiguration
Cross-Namespace Access:
openshift-monitoring- Prometheus scrapes metrics from operator namespace- Role and RoleBinding grant Prometheus ServiceAccount access
- ServiceMonitor configures scraping with mTLS
Important: Unlike multi-namespace operators (e.g., authentication-operator), JobSet keeps everything in one namespace for simplicity. The operand manages JobSets cluster-wide from this single namespace.
- cert-manager Dependency - This operator requires cert-manager for TLS certificates. The operator checks for cert-manager at startup and enters Degraded state if missing.
- Webhook Security - ValidatingWebhook and MutatingWebhook use TLS with certificates from cert-manager. CA bundles are automatically injected via annotations.
- Metrics Security - Metrics endpoint uses mTLS with certificates from cert-manager. Prometheus must present valid client certificates.
- RBAC Principle of Least Privilege - Operator has minimal permissions needed. Operand has permissions to manage Jobs, Services, and related resources cluster-wide.
- No Secrets in Logs - Operator does not log certificate contents or sensitive configuration.
- Owner References for Cleanup - All created resources have owner references to ensure proper cascading deletion and prevent orphaned resources.
Security-Critical Code:
pkg/operator/target_config_reconciler.go- Certificate and webhook managementdeploy/*.yaml- RBAC definitionsbindata/assets/jobset-controller-generated/*webhook*- Webhook configurations
Never:
- Skip certificate validation
- Log sensitive data (certs, tokens, secrets)
- Grant more permissions than needed
- Disable webhook verification
Location: pkg/ (co-located with code)
Pattern: Not extensively used in this operator (relies on e2e).
Location: test/e2e/e2e_test.go
Test Scenarios:
- Condition Verification: Checks Available/Degraded conditions
- Pod Recovery: Deletes operand pod, verifies recreation
- Management State Unmanaged: Allows manual scaling
- Management State Removed: Allows manual scaling (compatibility test)
Running:
export OPERATOR_IMAGE=quay.io/user/jobset-operator:tag
export RELATED_IMAGE_OPERAND_IMAGE=quay.io/user/jobset:tag
make test-e2e # Operator e2e tests
make test-e2e-operand # Operand e2e tests (JobSet controller)The operand (JobSet controller) has its own test suite in the upstream repo.
Running Operand Tests:
make test-e2e-operandThis runs the upstream JobSet test suite against the deployed controller.
// Namespaces
operatorNamespace = "openshift-jobset-operator"
OpenshiftMonitoringNamespace = "openshift-monitoring"
// Secrets
WebhookCertificateSecretName = "webhook-server-cert"
MetricsCertificateSecretName = "metrics-server-cert"
WebhookCertificateName = "jobset-serving-cert"
// Labels
operandLabel = "control-plane=controller-manager"
// Names
OperandName = "jobset-controller-manager"
OperatorConfigName = "cluster"
// Annotations
CertManagerInjectCaAnnotation = "cert-manager.io/inject-ca-from"Current Versions (from README.md):
- Operator 1.0.0: JobSet 0.11.0, OCP 4.18-4.23, 5.0, k8s 1.36, Go 1.26
Tracking:
operand-git-reffile: Upstream JobSet version/commitOS_GIT_VERSION: Set by OpenShift ART build system
- Slack: #forum-ocp-workloads (OpenShift internal)
- Component: jobset-operator (see OWNERS file)
- Approvers: control-plane-approvers
Document Version: 1.0
Last Updated: 2026-06-23
Maintenance: Update this document when making significant architectural changes, adding new workflows, or when patterns change. Include documentation updates in the same PR as code changes.