Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions api/v1alpha1/imagebuild_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func IsTerminalBuildPhase(phase string) bool {

// ImageBuildSpec defines the desired state of ImageBuild
// +kubebuilder:printcolumn:name="StorageClass",type=string,JSONPath=`.spec.storageClass`
// +kubebuilder:validation:XValidation:rule="!has(self.reproducible) || !self.reproducible || self.secureBuild",message="reproducible builds require secureBuild to be true"
type ImageBuildSpec struct {
// ─── Common fields ───

Expand Down Expand Up @@ -102,6 +103,18 @@ type ImageBuildSpec struct {
// +optional
TaskBundleRef string `json:"taskBundleRef,omitempty"`

// Reproducible enables full build provenance: saves RPMs, AIB manifest,
// and task bundle ref as OCI referrers for future reproduction.
// Requires SecureBuild to be true for task bundle pinning.
// +optional
Reproducible bool `json:"reproducible,omitempty"`
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// RestoreSourcesRef is the OCI image reference from a prior reproducible build.
// The build pod will pull the sources archive (OCI referrer) attached to this
// image and pre-populate the osbuild store, ensuring identical RPM inputs.
// +optional
RestoreSourcesRef string `json:"restoreSourcesRef,omitempty"`

// TTL is the time-to-live for this build. After this duration past its
// completion, the build transitions to the Expired phase and its resources
// (PipelineRuns, TaskRuns, PVCs, registry images) are cleaned up.
Expand Down
5 changes: 3 additions & 2 deletions api/v1alpha1/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
LabelWorkspaceName = "automotive.sdv.cloud.redhat.com/workspace-name"
LabelOwner = "automotive.sdv.cloud.redhat.com/owner"

AnnotationTraceID = "automotive.sdv.cloud.redhat.com/trace-id"
AnnotationRequestedBy = "automotive.sdv.cloud.redhat.com/requested-by"
AnnotationTraceID = "automotive.sdv.cloud.redhat.com/trace-id"
AnnotationRequestedBy = "automotive.sdv.cloud.redhat.com/requested-by"
AnnotationTaskBundleRef = "automotive.sdv.cloud.redhat.com/task-bundle-ref"
)
34 changes: 32 additions & 2 deletions cmd/caib/buildcmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ type Options struct {
InternalRegistryImageName *string
InternalRegistryTag *string

SecureBuild *bool
TTL *string
SecureBuild *bool
Reproducible *bool
TaskBundleRef *string
RestoreSourcesRef *string
TTL *string

InsecureSkipTLS *bool

Expand Down Expand Up @@ -143,6 +146,10 @@ func (h *Handler) validateBootcBuildFlags() error {
}
}

if err := h.validateReproducibleFlags(); err != nil {
return err
}

if *h.opts.ContainerPush == "" && !*h.opts.BuildDiskImage && !*h.opts.UseInternalRegistry {
return fmt.Errorf(
"--push is required when not building a disk image " +
Expand All @@ -153,6 +160,16 @@ func (h *Handler) validateBootcBuildFlags() error {
return nil
}

func (h *Handler) validateReproducibleFlags() error {
if err := common.ValidateReproducibleRequiresSecure(*h.opts.Reproducible, *h.opts.SecureBuild); err != nil {
return err
}
if *h.opts.Reproducible && *h.opts.UseInternalRegistry {
return fmt.Errorf("--reproducible cannot be used with --internal-registry (internal registry does not support OCI referrers)")
}
return nil
}

// applyRegistryCredentialsToRequest sets registry credentials on the build request.
// When --internal-registry is combined with --push, both are configured so the
// container is pushed externally while the disk image uses the internal registry.
Expand Down Expand Up @@ -474,6 +491,9 @@ func (h *Handler) RunBuild(cmd *cobra.Command, args []string) {
BuilderImage: *h.opts.BuilderImage,
RebuildBuilder: *h.opts.RebuildBuilder,
SecureBuild: *h.opts.SecureBuild,
Reproducible: *h.opts.Reproducible,
TaskBundleRef: *h.opts.TaskBundleRef,
RestoreSourcesRef: *h.opts.RestoreSourcesRef,
TTL: *h.opts.TTL,
}

Expand Down Expand Up @@ -587,6 +607,8 @@ func (h *Handler) RunDisk(cmd *cobra.Command, args []string) {
Compression: buildapitypes.Compression(*h.opts.CompressionAlgo),
ExportOCI: *h.opts.ExportOCI,
SecureBuild: *h.opts.SecureBuild,
TaskBundleRef: *h.opts.TaskBundleRef,
RestoreSourcesRef: *h.opts.RestoreSourcesRef,
TTL: *h.opts.TTL,
}

Expand Down Expand Up @@ -643,6 +665,11 @@ func (h *Handler) RunBuildDev(cmd *cobra.Command, args []string) {
return
}

if err := h.validateReproducibleFlags(); err != nil {
h.handleError(err)
return
}

if *h.opts.UseInternalRegistry {
if *h.opts.ExportOCI != "" {
h.handleError(fmt.Errorf("--internal-registry cannot be used with --push"))
Expand Down Expand Up @@ -723,6 +750,9 @@ func (h *Handler) RunBuildDev(cmd *cobra.Command, args []string) {
Compression: buildapitypes.Compression(*h.opts.CompressionAlgo),
ExportOCI: *h.opts.ExportOCI,
SecureBuild: *h.opts.SecureBuild,
Reproducible: *h.opts.Reproducible,
TaskBundleRef: *h.opts.TaskBundleRef,
RestoreSourcesRef: *h.opts.RestoreSourcesRef,
TTL: *h.opts.TTL,
}

Expand Down
6 changes: 6 additions & 0 deletions cmd/caib/buildcmd/build_disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func newTestDiskOpts() Options {
internalRegImageName string
internalRegTag string
secureBuild bool
reproducible bool
taskBundleRef string
restoreSourcesRef string
buildTTL string
insecureSkipTLS bool
)
Expand Down Expand Up @@ -86,6 +89,9 @@ func newTestDiskOpts() Options {
InternalRegistryImageName: &internalRegImageName,
InternalRegistryTag: &internalRegTag,
SecureBuild: &secureBuild,
Reproducible: &reproducible,
TaskBundleRef: &taskBundleRef,
RestoreSourcesRef: &restoreSourcesRef,
TTL: &buildTTL,
InsecureSkipTLS: &insecureSkipTLS,
}
Expand Down
9 changes: 9 additions & 0 deletions cmd/caib/common/build_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ func ValidateBuildName(name string) error {
return nil
}

// ValidateReproducibleRequiresSecure returns an error when reproducible builds
// are requested without secure build mode.
func ValidateReproducibleRequiresSecure(reproducible, secureBuild bool) error {
if reproducible && !secureBuild {
return fmt.Errorf("--reproducible requires --secure for task bundle pinning")
}
Comment on lines +51 to +54

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the error describe the actual invariant.

This helper rejects every reproducible build without secure mode, but the current text makes it sound like only task-bundle pinning is blocked. A shorter message like --reproducible requires --secure is less misleading for CLI users.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/caib/common/build_validation.go` around lines 51 - 54, Update the error
returned by ValidateReproducibleRequiresSecure to accurately state the
invariant: when reproducible is true and secureBuild is false return an error
with the message "--reproducible requires --secure" (replace the current longer
message about task bundle pinning). Locate the function
ValidateReproducibleRequiresSecure and change the fmt.Errorf call to use the
shorter, precise message.

return nil
}

// ValidateManifestSuffix validates the manifest file extension.
func ValidateManifestSuffix(filename string) error {
for _, suffix := range validManifestSuffix {
Expand Down
5 changes: 4 additions & 1 deletion cmd/caib/common/oci_artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
)

// PullOCIArtifact pulls and extracts an OCI artifact to local destination.
func PullOCIArtifact(ociRef, destPath, username, password string, insecureSkipTLS bool) error {
func PullOCIArtifact(ociRef, destPath, username, password string, insecureSkipTLS bool, authFilePaths ...string) error {
Comment thread
bennyz marked this conversation as resolved.
fmt.Printf("Pulling OCI artifact %s to %s\n", ociRef, destPath)

destDir := filepath.Dir(destPath)
Expand All @@ -30,6 +30,9 @@ func PullOCIArtifact(ociRef, destPath, username, password string, insecureSkipTL

ctx := context.Background()
systemCtx := &types.SystemContext{}
if len(authFilePaths) > 0 && authFilePaths[0] != "" {
systemCtx.AuthFilePath = authFilePaths[0]
}
if username != "" && password != "" {
fmt.Printf("Using provided username/password credentials\n")
systemCtx.DockerAuthConfig = &types.DockerAuthConfig{
Expand Down
49 changes: 47 additions & 2 deletions cmd/caib/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Options struct {
RunToken func(*cobra.Command, []string)
RunDelete func(*cobra.Command, []string)
RunCancel func(*cobra.Command, []string)
RunInspect func(*cobra.Command, []string)

GetDefaultArch func() string

Expand Down Expand Up @@ -70,8 +71,11 @@ type Options struct {
InternalRegistryImageName *string
InternalRegistryTag *string

SecureBuild *bool
TTL *string
SecureBuild *bool
Reproducible *bool
TaskBundleRef *string
RestoreSourcesRef *string
TTL *string

SealedBuilderImage *string
SealedArchitecture *string
Expand Down Expand Up @@ -164,6 +168,10 @@ func NewImageCmd(opts Options) *cobra.Command {
// Secure build
buildCmd.Flags().BoolVar(opts.SecureBuild, "secure", false, "resolve tasks from signed Tekton Bundle (requires OperatorConfig taskBundleRef)")
buildCmd.Flags().StringVar(opts.TTL, "ttl", "", "time-to-live for the build (e.g. 24h, 72h, 168h); empty=server default, 0=no expiry")
// Reproducible build
buildCmd.Flags().BoolVar(opts.Reproducible, "reproducible", false, "save RPMs, manifest, and task bundle for future reproduction (requires --secure)")
buildCmd.Flags().StringVar(opts.TaskBundleRef, "task-bundle-ref", "", "digest-pinned Tekton bundle ref for reproducible rebuild (e.g. quay.io/org/tasks@sha256:abc...)")
buildCmd.Flags().StringVar(opts.RestoreSourcesRef, "restore-sources", "", "OCI image ref from prior build — restores archived sources for exact reproducible rebuild")
// 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)")
Expand Down Expand Up @@ -222,6 +230,7 @@ func NewImageCmd(opts Options) *cobra.Command {
// Secure build
diskCmd.Flags().BoolVar(opts.SecureBuild, "secure", false, "resolve tasks from signed Tekton Bundle (requires OperatorConfig taskBundleRef)")
diskCmd.Flags().StringVar(opts.TTL, "ttl", "", "time-to-live for the build (e.g. 24h, 72h, 168h); empty=server default, 0=no expiry")
diskCmd.Flags().StringVar(opts.TaskBundleRef, "task-bundle-ref", "", "digest-pinned Tekton bundle ref for reproducible rebuild (e.g. quay.io/org/tasks@sha256:abc...)")
// 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)")
Expand Down Expand Up @@ -268,6 +277,10 @@ func NewImageCmd(opts Options) *cobra.Command {
// Secure build
buildDevCmd.Flags().BoolVar(opts.SecureBuild, "secure", false, "resolve tasks from signed Tekton Bundle (requires OperatorConfig taskBundleRef)")
buildDevCmd.Flags().StringVar(opts.TTL, "ttl", "", "time-to-live for the build (e.g. 24h, 72h, 168h); empty=server default, 0=no expiry")
// Reproducible build
buildDevCmd.Flags().BoolVar(opts.Reproducible, "reproducible", false, "save RPMs, manifest, and task bundle for future reproduction (requires --secure)")
buildDevCmd.Flags().StringVar(opts.TaskBundleRef, "task-bundle-ref", "", "digest-pinned Tekton bundle ref for reproducible rebuild (e.g. quay.io/org/tasks@sha256:abc...)")
buildDevCmd.Flags().StringVar(opts.RestoreSourcesRef, "restore-sources", "", "OCI image ref from prior build — restores archived sources for exact reproducible rebuild")
// 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)")
Expand Down Expand Up @@ -313,6 +326,15 @@ func NewImageCmd(opts Options) *cobra.Command {
)
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")
inspectCmd := newInspectCmd(opts)
inspectCmd.Flags().StringVar(
opts.RegistryAuthFile,
"registry-auth-file",
"",
"path to Docker/Podman auth file for registry authentication",
)
inspectCmd.Flags().StringVarP(opts.OutputDir, "output-dir", "o", "", "download referrer artifacts (manifest, RPMs) to this directory")

// Sealed operation shared flags
addSealedFlags(prepareResealCmd, opts, defaultServer)
addSealedFlags(resealCmd, opts, defaultServer)
Expand All @@ -332,6 +354,7 @@ func NewImageCmd(opts Options) *cobra.Command {
deleteCmd,
cancelCmd,
flashCmd,
inspectCmd,
prepareResealCmd,
resealCmd,
extractForSigningCmd,
Expand Down Expand Up @@ -559,6 +582,28 @@ Examples:
}
}

func newInspectCmd(opts Options) *cobra.Command {
return &cobra.Command{
Use: "inspect <oci-registry-reference>",
Short: "Show build provenance and reproducibility info for an OCI artifact",
Long: `Inspect reads OCI manifest annotations and referrer artifacts to display
build provenance information: distro, target, architecture, builder versions,
and the exact command to reproduce the build.

If --output-dir is given, referrer artifacts (AIB manifest, RPM archive,
osbuild manifest) are downloaded to the specified directory.

Examples:
# Show build provenance
caib image inspect quay.io/org/my-os:v1

# Show provenance and download artifacts for reproduction
caib image inspect quay.io/org/my-os:v1 -o ./rebuild/`,
Args: cobra.ExactArgs(1),
Run: opts.RunInspect,
}
}

func newPrepareResealCmd(opts Options) *cobra.Command {
return &cobra.Command{
Use: "prepare-reseal [source-container] [output-container]",
Expand Down
Loading
Loading