From bf1c4225c5464a5947f7fa5ba6af48083564db02 Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Tue, 10 Mar 2026 17:33:47 +0200 Subject: [PATCH] add --flash-cmd to override OperatorConfig Signed-off-by: Benny Zlotnik Assisted-by: claude-sonnet-4.6 --- api/v1alpha1/imagebuild_types.go | 12 ++++++++++++ cmd/caib/buildcmd/build.go | 4 ++++ cmd/caib/flashcmd/flash.go | 2 ++ cmd/caib/image/image.go | 5 +++++ cmd/caib/main.go | 1 + cmd/caib/runtime_wiring.go | 5 +++++ .../automotive.sdv.cloud.redhat.com_imagebuilds.yaml | 4 ++++ internal/buildapi/server.go | 6 +++++- internal/buildapi/types.go | 1 + internal/controller/imagebuild/controller.go | 8 ++++++++ 10 files changed, 47 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/imagebuild_types.go b/api/v1alpha1/imagebuild_types.go index 93bb45a9..f4893923 100644 --- a/api/v1alpha1/imagebuild_types.go +++ b/api/v1alpha1/imagebuild_types.go @@ -66,6 +66,10 @@ type FlashSpec struct { // LeaseDuration is the duration for the device lease in HH:MM:SS format // +kubebuilder:default="03:00:00" LeaseDuration string `json:"leaseDuration,omitempty"` + + // FlashCmd overrides the flash command from OperatorConfig target mappings + // +optional + FlashCmd string `json:"flashCmd,omitempty"` } // AIBSpec defines the automotive-image-builder configuration @@ -425,6 +429,14 @@ func (s *ImageBuildSpec) GetRebuildBuilder() bool { return false } +// GetFlashCmd returns the user-specified flash command override, or empty string +func (s *ImageBuildSpec) GetFlashCmd() string { + if s.Flash != nil { + return s.Flash.FlashCmd + } + return "" +} + // GetFlashLeaseDuration returns the flash lease duration, or default func (s *ImageBuildSpec) GetFlashLeaseDuration() string { if s.Flash != nil && s.Flash.LeaseDuration != "" { diff --git a/cmd/caib/buildcmd/build.go b/cmd/caib/buildcmd/build.go index d520cddb..d909bafb 100644 --- a/cmd/caib/buildcmd/build.go +++ b/cmd/caib/buildcmd/build.go @@ -62,6 +62,7 @@ type Options struct { FlashAfterBuild *bool JumpstarterClient *string LeaseDuration *string + FlashCmd *string UseInternalRegistry *bool InternalRegistryImageName *string @@ -408,6 +409,7 @@ func (h *Handler) RunBuild(cmd *cobra.Command, args []string) { req.FlashEnabled = true req.FlashClientConfig = base64.StdEncoding.EncodeToString(clientConfigBytes) req.FlashLeaseDuration = *h.opts.LeaseDuration + req.FlashCmd = *h.opts.FlashCmd } resp, err := api.CreateBuild(ctx, req) @@ -524,6 +526,7 @@ func (h *Handler) RunDisk(cmd *cobra.Command, args []string) { req.FlashEnabled = true req.FlashClientConfig = base64.StdEncoding.EncodeToString(clientConfigBytes) req.FlashLeaseDuration = *h.opts.LeaseDuration + req.FlashCmd = *h.opts.FlashCmd } resp, err := api.CreateBuild(ctx, req) @@ -658,6 +661,7 @@ func (h *Handler) RunBuildDev(cmd *cobra.Command, args []string) { req.FlashEnabled = true req.FlashClientConfig = base64.StdEncoding.EncodeToString(clientConfigBytes) req.FlashLeaseDuration = *h.opts.LeaseDuration + req.FlashCmd = *h.opts.FlashCmd } resp, err := api.CreateBuild(ctx, req) diff --git a/cmd/caib/flashcmd/flash.go b/cmd/caib/flashcmd/flash.go index 4b27b9d8..13b159a4 100644 --- a/cmd/caib/flashcmd/flash.go +++ b/cmd/caib/flashcmd/flash.go @@ -37,6 +37,7 @@ type Options struct { Target *string ExporterSelector *string LeaseDuration *string + FlashCmd *string WaitForBuild *bool FollowLogs *bool InsecureSkipTLS *bool @@ -116,6 +117,7 @@ func (h *Handler) RunFlash(cmd *cobra.Command, args []string) { ExporterSelector: *h.opts.ExporterSelector, ClientConfig: clientConfigB64, LeaseDuration: *h.opts.LeaseDuration, + FlashCmd: *h.opts.FlashCmd, } resp, err := api.CreateFlash(ctx, req) diff --git a/cmd/caib/image/image.go b/cmd/caib/image/image.go index 8249d0b3..a1faeee4 100644 --- a/cmd/caib/image/image.go +++ b/cmd/caib/image/image.go @@ -57,6 +57,7 @@ type Options struct { FlashName *string ExporterSelector *string LeaseDuration *string + FlashCmd *string UseInternalRegistry *bool InternalRegistryImageName *string @@ -140,6 +141,7 @@ func NewImageCmd(opts Options) *cobra.Command { buildCmd.Flags().BoolVar(opts.FlashAfterBuild, "flash", false, "flash the image to device after build completes") buildCmd.Flags().StringVar(opts.JumpstarterClient, "client", "", "path to Jumpstarter client config file (required for --flash)") buildCmd.Flags().StringVar(opts.LeaseDuration, "lease", "03:00:00", "device lease duration for flash (HH:MM:SS)") + buildCmd.Flags().StringVar(opts.FlashCmd, "flash-cmd", "", "override flash command (default: from OperatorConfig target mapping)") // Internal registry options buildCmd.Flags().BoolVar(opts.UseInternalRegistry, "internal-registry", false, "push to OpenShift internal registry") buildCmd.Flags().StringVar(opts.InternalRegistryImageName, "image-name", "", "override image name for internal registry (default: build name)") @@ -195,6 +197,7 @@ func NewImageCmd(opts Options) *cobra.Command { diskCmd.Flags().BoolVar(opts.FlashAfterBuild, "flash", false, "flash the image to device after build completes") diskCmd.Flags().StringVar(opts.JumpstarterClient, "client", "", "path to Jumpstarter client config file (required for --flash)") diskCmd.Flags().StringVar(opts.LeaseDuration, "lease", "03:00:00", "device lease duration for flash (HH:MM:SS)") + diskCmd.Flags().StringVar(opts.FlashCmd, "flash-cmd", "", "override flash command (default: from OperatorConfig target mapping)") // Internal registry options diskCmd.Flags().BoolVar(opts.UseInternalRegistry, "internal-registry", false, "push to OpenShift internal registry") diskCmd.Flags().StringVar(opts.InternalRegistryImageName, "image-name", "", "override image name for internal registry (default: build name)") @@ -232,6 +235,7 @@ func NewImageCmd(opts Options) *cobra.Command { buildDevCmd.Flags().BoolVar(opts.FlashAfterBuild, "flash", false, "flash the image to device after build completes") buildDevCmd.Flags().StringVar(opts.JumpstarterClient, "client", "", "path to Jumpstarter client config file (required for --flash)") buildDevCmd.Flags().StringVar(opts.LeaseDuration, "lease", "03:00:00", "device lease duration for flash (HH:MM:SS)") + buildDevCmd.Flags().StringVar(opts.FlashCmd, "flash-cmd", "", "override flash command (default: from OperatorConfig target mapping)") // Internal registry options buildDevCmd.Flags().BoolVar(opts.UseInternalRegistry, "internal-registry", false, "push to OpenShift internal registry") buildDevCmd.Flags().StringVar(opts.InternalRegistryImageName, "image-name", "", "override image name for internal registry (default: build name)") @@ -255,6 +259,7 @@ func NewImageCmd(opts Options) *cobra.Command { flashCmd.Flags().StringVarP(opts.Target, "target", "t", "", "target platform for exporter lookup") flashCmd.Flags().StringVar(opts.ExporterSelector, "exporter", "", "direct exporter selector (alternative to --target)") flashCmd.Flags().StringVar(opts.LeaseDuration, "lease", "03:00:00", "device lease duration (HH:MM:SS)") + flashCmd.Flags().StringVar(opts.FlashCmd, "flash-cmd", "", "override flash command (default: from OperatorConfig target mapping)") flashCmd.Flags().BoolVarP(opts.FollowLogs, "follow", "f", false, "follow flash logs (shows full log output instead of progress bar)") flashCmd.Flags().BoolVarP(opts.WaitForBuild, "wait", "w", true, "wait for flash to complete") _ = flashCmd.MarkFlagRequired("client") diff --git a/cmd/caib/main.go b/cmd/caib/main.go index 9bdd616d..77dbf425 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -49,6 +49,7 @@ var ( flashName string exporterSelector string leaseDuration string + flashCmdOverride string // Internal registry options useInternalRegistry bool diff --git a/cmd/caib/runtime_wiring.go b/cmd/caib/runtime_wiring.go index 8b58ca13..b0eda719 100644 --- a/cmd/caib/runtime_wiring.go +++ b/cmd/caib/runtime_wiring.go @@ -44,6 +44,7 @@ type runtimeState struct { FlashName *string ExporterSelector *string LeaseDuration *string + FlashCmd *string UseInternalRegistry *bool InternalRegistryImageName *string @@ -98,6 +99,7 @@ func newRuntimeState() runtimeState { FlashName: &flashName, ExporterSelector: &exporterSelector, LeaseDuration: &leaseDuration, + FlashCmd: &flashCmdOverride, UseInternalRegistry: &useInternalRegistry, InternalRegistryImageName: &internalRegistryImageName, @@ -157,6 +159,7 @@ func (s runtimeState) newHandlers() handlerSet { FlashAfterBuild: s.FlashAfterBuild, JumpstarterClient: s.JumpstarterClient, LeaseDuration: s.LeaseDuration, + FlashCmd: s.FlashCmd, UseInternalRegistry: s.UseInternalRegistry, InternalRegistryImageName: s.InternalRegistryImageName, InternalRegistryTag: s.InternalRegistryTag, @@ -185,6 +188,7 @@ func (s runtimeState) newHandlers() handlerSet { Target: s.Target, ExporterSelector: s.ExporterSelector, LeaseDuration: s.LeaseDuration, + FlashCmd: s.FlashCmd, WaitForBuild: s.WaitForBuild, FollowLogs: s.FollowLogs, InsecureSkipTLS: s.InsecureSkipTLS, @@ -261,6 +265,7 @@ func (s runtimeState) imageOptions(h handlerSet) image.Options { FlashName: s.FlashName, ExporterSelector: s.ExporterSelector, LeaseDuration: s.LeaseDuration, + FlashCmd: s.FlashCmd, UseInternalRegistry: s.UseInternalRegistry, InternalRegistryImageName: s.InternalRegistryImageName, diff --git a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml index af32c17e..7abcf193 100644 --- a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml +++ b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml @@ -159,6 +159,10 @@ spec: The secret should have a key "client.yaml" with the config contents If set, flash is enabled automatically type: string + flashCmd: + description: FlashCmd overrides the flash command from OperatorConfig + target mappings + type: string leaseDuration: default: "03:00:00" description: LeaseDuration is the duration for the device lease diff --git a/internal/buildapi/server.go b/internal/buildapi/server.go index e1a86fd6..b3f2eaf0 100644 --- a/internal/buildapi/server.go +++ b/internal/buildapi/server.go @@ -1590,6 +1590,7 @@ func (a *APIServer) createBuild(c *gin.Context) { flashSpec = &automotivev1alpha1.FlashSpec{ ClientConfigSecretRef: flashSecretName, LeaseDuration: req.FlashLeaseDuration, + FlashCmd: req.FlashCmd, } } @@ -1769,7 +1770,10 @@ func (a *APIServer) getBuild(c *gin.Context, name string) { if operatorConfig.Spec.Jumpstarter != nil { if mapping, ok := operatorConfig.Spec.Jumpstarter.TargetMappings[build.Spec.GetTarget()]; ok { jumpstarterInfo.ExporterSelector = mapping.Selector - flashCmd := mapping.FlashCmd + flashCmd := build.Spec.GetFlashCmd() + if flashCmd == "" { + flashCmd = mapping.FlashCmd + } // Replace placeholders in flash command using translated URLs if flashCmd != "" { imageURI := diskImage diff --git a/internal/buildapi/types.go b/internal/buildapi/types.go index 897c544f..3fbd0394 100644 --- a/internal/buildapi/types.go +++ b/internal/buildapi/types.go @@ -142,6 +142,7 @@ type BuildRequest struct { FlashEnabled bool `json:"flashEnabled,omitempty"` // Enable flashing after build FlashClientConfig string `json:"flashClientConfig,omitempty"` // Base64-encoded Jumpstarter client config FlashLeaseDuration string `json:"flashLeaseDuration,omitempty"` // Lease duration in HH:MM:SS format + FlashCmd string `json:"flashCmd,omitempty"` // Override flash command from OperatorConfig } // RegistryCredentials contains authentication details for container registries. diff --git a/internal/controller/imagebuild/controller.go b/internal/controller/imagebuild/controller.go index 75c4a013..0892823b 100644 --- a/internal/controller/imagebuild/controller.go +++ b/internal/controller/imagebuild/controller.go @@ -569,6 +569,10 @@ func (r *ImageBuildReconciler) createBuildTaskRun( flashCmd = mapping.FlashCmd } } + // User-specified flash command overrides OperatorConfig + if userCmd := imageBuild.Spec.GetFlashCmd(); userCmd != "" { + flashCmd = userCmd + } if flashExporterSelector == "" { return fmt.Errorf("flash enabled but no Jumpstarter target mapping found for target %q; "+ "configure OperatorConfig.spec.jumpstarter.targetMappings[%q] with selector and flashCmd", target, target) @@ -1282,6 +1286,10 @@ func (r *ImageBuildReconciler) createFlashTaskRun( flashCmd = mapping.FlashCmd } } + // User-specified flash command overrides OperatorConfig + if userCmd := imageBuild.Spec.GetFlashCmd(); userCmd != "" { + flashCmd = userCmd + } if exporterSelector == "" { return fmt.Errorf("no Jumpstarter exporter mapping found for target %q in OperatorConfig", target)