diff --git a/cmd/caib/README.md b/cmd/caib/README.md index f9c2cdfe..841316cb 100644 --- a/cmd/caib/README.md +++ b/cmd/caib/README.md @@ -97,8 +97,6 @@ bin/caib build [flags] | `--aib-image` | `quay.io/.../automotive-image-builder:latest` | AIB container image | | `--storage-class` | | Storage class for build workspace PVC | | `-D`, `--define` | | Custom definition `KEY=VALUE` (repeatable) | -| `--registry-username` | `$REGISTRY_USERNAME` | Registry username for push operations | -| `--registry-password` | `$REGISTRY_PASSWORD` | Registry password (or use docker/podman auth) | | `--timeout` | `60` | Timeout in minutes | | `-w`, `--wait` | `false` | Wait for build to complete | | `-f`, `--follow` | `false` | Follow build logs | @@ -153,8 +151,6 @@ bin/caib disk [flags] | `-a`, `--arch` | (current system) | Architecture (`amd64`, `arm64`) | | `--aib-image` | `quay.io/.../automotive-image-builder:latest` | AIB container image | | `--storage-class` | | Kubernetes storage class | -| `--registry-username` | `$REGISTRY_USERNAME` | Registry username | -| `--registry-password` | `$REGISTRY_PASSWORD` | Registry password | | `--timeout` | `60` | Timeout in minutes | | `-w`, `--wait` | `false` | Wait for build to complete | | `-f`, `--follow` | `false` | Follow build logs | @@ -203,8 +199,6 @@ bin/caib build-dev [flags] | `--aib-image` | `quay.io/.../automotive-image-builder:latest` | AIB container image | | `--storage-class` | | Storage class for build workspace PVC | | `-D`, `--define` | | Custom definition `KEY=VALUE` (repeatable) | -| `--registry-username` | `$REGISTRY_USERNAME` | Registry username | -| `--registry-password` | `$REGISTRY_PASSWORD` | Registry password | | `--timeout` | `60` | Timeout in minutes | | `-w`, `--wait` | `false` | Wait for build to complete | | `-f`, `--follow` | `false` | Follow build logs | @@ -220,14 +214,12 @@ bin/caib build-dev my-manifest.aib.yml \ -o ./disk.qcow2 \ --follow -# Build and push to OCI registry +# Build and push to OCI registry (requires REGISTRY_USERNAME/REGISTRY_PASSWORD env vars) bin/caib build-dev my-manifest.aib.yml \ --arch arm64 \ --mode image \ --format qcow2 \ --push quay.io/myorg/disk-image:v1.0 \ - --registry-username myuser \ - --registry-password mypass \ --follow ``` @@ -280,9 +272,8 @@ The CLI automatically detects authentication in this order: For registry authentication (`--push`, `--push-disk`): -1. `--registry-username` / `--registry-password` flags -2. `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` environment variables -3. Docker/Podman auth files (`~/.docker/config.json`, `~/.config/containers/auth.json`) +1. `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` environment variables +2. Docker/Podman auth files (`~/.docker/config.json`, `~/.config/containers/auth.json`) ## Manifest File References @@ -328,7 +319,7 @@ Supported locations: | HTTP 503/504 during log follow | Build pod starting | CLI retries automatically | | Build fails after upload | PVC transition timing | Increase `--timeout`, check operator logs | | "no bearer token found" | Not logged in | Run `oc login` or set `CAIB_TOKEN` | -| Registry auth failure | Missing credentials | Set `--registry-username/password` or login via `podman login` | +| Registry auth failure | Missing credentials | Set `REGISTRY_USERNAME/REGISTRY_PASSWORD` env vars or login via `podman login` | ## Version diff --git a/cmd/caib/main.go b/cmd/caib/main.go index baf77794..117efd94 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -70,8 +70,6 @@ var ( compressArtifacts bool compressionAlgo string authToken string - registryUsername string - registryPassword string containerPush string buildDiskImage bool @@ -97,15 +95,11 @@ func createBuildAPIClient(serverURL string, authToken *string) (*buildapiclient. return buildapiclient.New(serverURL, opts...) } -// extractRegistryCredentials extracts registry URL and ensures credentials are loaded from env vars -func extractRegistryCredentials(primaryRef, secondaryRef string, username, password *string) string { - // Try environment variables if command line flags are empty - if *username == "" { - *username = os.Getenv("REGISTRY_USERNAME") - } - if *password == "" { - *password = os.Getenv("REGISTRY_PASSWORD") - } +// extractRegistryCredentials extracts registry URL and returns registry URL and credentials from env vars +func extractRegistryCredentials(primaryRef, secondaryRef string) (string, string, string) { + // Get credentials from environment variables only + username := os.Getenv("REGISTRY_USERNAME") + password := os.Getenv("REGISTRY_PASSWORD") // Determine the reference to use for URL extraction ref := primaryRef @@ -115,21 +109,39 @@ func extractRegistryCredentials(primaryRef, secondaryRef string, username, passw // If no reference, return empty if ref == "" { - return "" + return "", username, password } // Warn if credentials missing (will fall back to Docker/Podman auth files) - if *username == "" || *password == "" { - fmt.Println("Warning: No registry credentials provided via flags or environment variables.") + if username == "" || password == "" { + fmt.Println("Warning: No registry credentials provided via environment variables.") fmt.Println("Will attempt to use Docker/Podman auth files as fallback.") } // Extract registry URL from reference parts := strings.SplitN(ref, "/", 2) - if len(parts) > 0 && strings.Contains(parts[0], ".") { - return parts[0] + if len(parts) > 1 && (strings.Contains(parts[0], ".") || strings.Contains(parts[0], ":") || parts[0] == "localhost") { + return parts[0], username, password + } + return "docker.io", username, password +} + +// validateRegistryCredentials validates registry credentials and returns an error for partial credentials +func validateRegistryCredentials(registryURL, username, password string) error { + // If no registry URL, no credentials needed + if registryURL == "" { + return nil } - return "docker.io" + + // Both username and password must be provided together, or neither + if (username == "") != (password == "") { + if username == "" { + return fmt.Errorf("REGISTRY_PASSWORD is set but REGISTRY_USERNAME is missing") + } + return fmt.Errorf("REGISTRY_USERNAME is set but REGISTRY_PASSWORD is missing") + } + + return nil } func main() { @@ -253,8 +265,6 @@ Examples: ) buildCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") buildCmd.Flags().StringVar(&exportOCI, "push-disk", "", "push disk image as OCI artifact to registry") - buildCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") - buildCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password (or REGISTRY_PASSWORD env)") buildCmd.Flags().StringVar( &automotiveImageBuilder, "aib-image", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "AIB container image", @@ -302,8 +312,6 @@ Examples: ) diskCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") diskCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") - diskCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") - diskCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password (or REGISTRY_PASSWORD env)") diskCmd.Flags().StringVarP(&distro, "distro", "d", "autosd", "distribution") diskCmd.Flags().StringVarP(&target, "target", "t", "qemu", "target platform") diskCmd.Flags().StringVarP(&architecture, "arch", "a", getDefaultArch(), "architecture (amd64, arm64)") @@ -328,12 +336,6 @@ Examples: buildDevCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download artifact to file") buildDevCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") buildDevCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") - buildDevCmd.Flags().StringVar( - ®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)", - ) - buildDevCmd.Flags().StringVar( - ®istryPassword, "registry-password", "", "registry password (or REGISTRY_PASSWORD env)", - ) buildDevCmd.Flags().StringVar( &automotiveImageBuilder, "aib-image", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "AIB container image", @@ -402,7 +404,12 @@ func runBuild(_ *cobra.Command, args []string) { } // Extract registry URL and credentials - effectiveRegistryURL := extractRegistryCredentials(containerPush, exportOCI, ®istryUsername, ®istryPassword) + effectiveRegistryURL, registryUsername, registryPassword := extractRegistryCredentials(containerPush, exportOCI) + + // Validate credentials (error on partial credentials) + if err := validateRegistryCredentials(effectiveRegistryURL, registryUsername, registryPassword); err != nil { + handleError(err) + } req := buildapitypes.BuildRequest{ Name: buildName, @@ -424,7 +431,7 @@ func runBuild(_ *cobra.Command, args []string) { ServeArtifact: outputDir != "" && exportOCI == "", } - if effectiveRegistryURL != "" { + if effectiveRegistryURL != "" && registryUsername != "" && registryPassword != "" { req.RegistryCredentials = &buildapitypes.RegistryCredentials{ Enabled: true, AuthType: "username-password", @@ -503,7 +510,12 @@ func runDisk(_ *cobra.Command, args []string) { } // Extract registry URL and credentials - effectiveRegistryURL := extractRegistryCredentials(containerRef, exportOCI, ®istryUsername, ®istryPassword) + effectiveRegistryURL, registryUsername, registryPassword := extractRegistryCredentials(containerRef, exportOCI) + + // Validate credentials (error on partial credentials) + if err := validateRegistryCredentials(effectiveRegistryURL, registryUsername, registryPassword); err != nil { + handleError(err) + } req := buildapitypes.BuildRequest{ Name: buildName, @@ -767,7 +779,12 @@ func runBuildDev(_ *cobra.Command, args []string) { } // Extract registry URL and credentials - effectiveRegistryURL := extractRegistryCredentials("", exportOCI, ®istryUsername, ®istryPassword) + effectiveRegistryURL, registryUsername, registryPassword := extractRegistryCredentials("", exportOCI) + + // Validate credentials (error on partial credentials) + if err := validateRegistryCredentials(effectiveRegistryURL, registryUsername, registryPassword); err != nil { + handleError(err) + } req := buildapitypes.BuildRequest{ Name: buildName, @@ -786,7 +803,7 @@ func runBuildDev(_ *cobra.Command, args []string) { ExportOCI: exportOCI, } - if effectiveRegistryURL != "" { + if effectiveRegistryURL != "" && registryUsername != "" && registryPassword != "" { req.RegistryCredentials = &buildapitypes.RegistryCredentials{ Enabled: true, AuthType: "username-password",