mirror of
https://github.com/lukaszraczylo/jobs-manager-operator.git
synced 2026-06-05 22:33:44 +00:00
2b36071647
* Multiple fixes - add goreleaser to the build / release process - add kubectl plugin for job graphs visualization - add installation scripts - update dependencies * Update the release & CRD content. * Next set of improvements. Code Quality - Label constants: Added LabelWorkflowName, LabelGroupName, LabelJobName, LabelJobID in controllers/definitions.go - Removed commented debug code: Cleaned up dead code from multiple files - Removed unused dependencyTree field: Cleaned connPackage struct - Fixed snake_case variables: Changed to camelCase (runGroup, groupDep, runJob, jobDep, k8sJob) Kubernetes Best Practices - Finalizers: Implemented handleDeletion() and deleteChildJobs() for proper cleanup - Status enum validation: Added +kubebuilder:validation:Enum=pending;running;succeeded;failed;aborted - ImagePullPolicy default: Created getImagePullPolicy() helper that defaults to IfNotPresent - Resource limits support: Added Resources *corev1.ResourceRequirements to ManagedJobParameters Observability - Prometheus metrics: Created controllers/metrics.go with counters (jobs created/succeeded/failed), histogram (reconciliation duration), and gauge (active jobs) - Structured logging: Added logger field to connPackage, used context-based logging throughout Configuration - Leader election ID: Made configurable via --leader-election-id flag - Development mode: Made configurable via --dev-mode flag and LOG_LEVEL env var Performance - Dependency lookup optimization: Changed from O(n*m) to O(1) using lookup maps (jobDepMap, groupDepMap) - Reconciliation backoff: Added RequeueAfter: 30*time.Second when workflow is running Documentation & Testing - Godoc documentation: Added comprehensive comments to API types and controller - Unit tests: Added helpers_test.go with tests for all helper functions - Integration tests: Added managedjob_controller_test.go with Ginkgo/Gomega tests * Add the helm chart release. * Add reasonable test coverage.
136 lines
3.5 KiB
Go
136 lines
3.5 KiB
Go
package visualization
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
|
|
|
jobsmanagerv1beta1 "raczylo.com/jobs-manager-operator/api/v1beta1"
|
|
)
|
|
|
|
// Client wraps the Kubernetes client for ManagedJob operations
|
|
type Client struct {
|
|
client client.Client
|
|
}
|
|
|
|
// NewClient creates a new Client for ManagedJob operations
|
|
func NewClient() (*Client, error) {
|
|
cfg, err := config.GetConfig()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
|
|
}
|
|
|
|
scheme := runtime.NewScheme()
|
|
if err := clientgoscheme.AddToScheme(scheme); err != nil {
|
|
return nil, fmt.Errorf("failed to add client-go scheme: %w", err)
|
|
}
|
|
if err := jobsmanagerv1beta1.AddToScheme(scheme); err != nil {
|
|
return nil, fmt.Errorf("failed to add jobsmanager scheme: %w", err)
|
|
}
|
|
|
|
cl, err := client.New(cfg, client.Options{Scheme: scheme})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create client: %w", err)
|
|
}
|
|
|
|
return &Client{client: cl}, nil
|
|
}
|
|
|
|
// GetManagedJob retrieves a ManagedJob by name and namespace
|
|
func (c *Client) GetManagedJob(ctx context.Context, name, namespace string) (*jobsmanagerv1beta1.ManagedJob, error) {
|
|
mj := &jobsmanagerv1beta1.ManagedJob{}
|
|
err := c.client.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, mj)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get ManagedJob %s/%s: %w", namespace, name, err)
|
|
}
|
|
return mj, nil
|
|
}
|
|
|
|
// ListManagedJobs lists all ManagedJobs in a namespace
|
|
func (c *Client) ListManagedJobs(ctx context.Context, namespace string) (*jobsmanagerv1beta1.ManagedJobList, error) {
|
|
mjList := &jobsmanagerv1beta1.ManagedJobList{}
|
|
opts := []client.ListOption{}
|
|
if namespace != "" {
|
|
opts = append(opts, client.InNamespace(namespace))
|
|
}
|
|
err := c.client.List(ctx, mjList, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list ManagedJobs: %w", err)
|
|
}
|
|
return mjList, nil
|
|
}
|
|
|
|
// BuildTree builds a StatusTree from a ManagedJob
|
|
func BuildTree(mj *jobsmanagerv1beta1.ManagedJob) *StatusTree {
|
|
root := NewStatusTreeWithStatus(mj.Name, mj.Status)
|
|
|
|
for _, group := range mj.Spec.Groups {
|
|
groupNode := root.AddWithStatus(group.Name, group.Status)
|
|
|
|
// Add group dependencies
|
|
for _, dep := range group.Dependencies {
|
|
groupNode.Add(RenderDependency(dep.Name, true))
|
|
}
|
|
|
|
// Add jobs
|
|
for _, job := range group.Jobs {
|
|
jobNode := groupNode.AddWithStatus(job.Name, job.Status)
|
|
|
|
// Add job dependencies
|
|
for _, dep := range job.Dependencies {
|
|
jobNode.Add(RenderDependency(dep.Name, false))
|
|
}
|
|
}
|
|
}
|
|
|
|
return root
|
|
}
|
|
|
|
// GetStatusSummary returns a summary of job statuses
|
|
type StatusSummary struct {
|
|
Name string
|
|
Namespace string
|
|
Status string
|
|
Groups int
|
|
Jobs int
|
|
Pending int
|
|
Running int
|
|
Succeeded int
|
|
Failed int
|
|
Aborted int
|
|
}
|
|
|
|
// GetStatusSummary builds a summary of the ManagedJob status
|
|
func GetStatusSummary(mj *jobsmanagerv1beta1.ManagedJob) StatusSummary {
|
|
summary := StatusSummary{
|
|
Name: mj.Name,
|
|
Namespace: mj.Namespace,
|
|
Status: mj.Status,
|
|
Groups: len(mj.Spec.Groups),
|
|
}
|
|
|
|
for _, group := range mj.Spec.Groups {
|
|
for _, job := range group.Jobs {
|
|
summary.Jobs++
|
|
switch job.Status {
|
|
case StatusPending:
|
|
summary.Pending++
|
|
case StatusRunning:
|
|
summary.Running++
|
|
case StatusSucceeded:
|
|
summary.Succeeded++
|
|
case StatusFailed:
|
|
summary.Failed++
|
|
case StatusAborted:
|
|
summary.Aborted++
|
|
}
|
|
}
|
|
}
|
|
|
|
return summary
|
|
}
|