package main import ( "context" "errors" "fmt" "log/slog" "os" "os/signal" "syscall" "time" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() if err := run(ctx, os.Args[1:]); err != nil && !errors.Is(err, context.Canceled) { slog.Error("image mirror", "error", err) os.Exit(1) } } func run(ctx context.Context, arguments []string) error { if len(arguments) != 1 { return errors.New("expected one command: run") } if arguments[0] != "run" { return fmt.Errorf("unknown command %q", arguments[0]) } clientConfig, err := rest.InClusterConfig() if err != nil { return fmt.Errorf("load Kubernetes configuration: %w", err) } client, err := dynamic.NewForConfig(clientConfig) if err != nil { return fmt.Errorf("create Kubernetes client: %w", err) } config, err := loadConfig() if err != nil { return err } registry, err := loadRegistry() if err != nil { return err } return (&controller{client: client, registry: registry, config: config}).run(ctx) } func loadConfig() (controllerConfig, error) { namespace := os.Getenv("POD_NAMESPACE") if namespace == "" { return controllerConfig{}, errors.New("POD_NAMESPACE is required") } pollInterval, err := durationEnv("POLL_INTERVAL", 20*time.Second) if err != nil { return controllerConfig{}, err } return controllerConfig{ namespace: namespace, pollInterval: pollInterval, prepareTemplate: valueOrDefault("PREPARE_TEMPLATE", "image-prepare"), promotionTemplate: valueOrDefault("PROMOTION_TEMPLATE", "image-promote"), cleanupTemplate: valueOrDefault("CLEANUP_TEMPLATE", "image-cleanup"), }, nil } func durationEnv(name string, fallback time.Duration) (time.Duration, error) { value := os.Getenv(name) if value == "" { return fallback, nil } duration, err := time.ParseDuration(value) if err != nil || duration <= 0 { return 0, fmt.Errorf("%s must be a positive duration", name) } return duration, nil } func valueOrDefault(name, fallback string) string { if value := os.Getenv(name); value != "" { return value } return fallback }