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
62 changes: 61 additions & 1 deletion pkg/asset/config/appliance_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ pullSecret: pull-secret
# [Optional]
# useBinary: %t

# Path to pre-mirrored images from oc-mirror workspace.
# When provided, skips image mirroring and uses the pre-mirrored registry data.
# The path should point to an oc-mirror workspace directory containing a 'data' subdirectory.
# [Optional]
# mirrorPath: /path/to/mirror/workspace
Comment thread
rwsu marked this conversation as resolved.

# Enable all default CatalogSources (on openshift-marketplace namespace).
# Should be disabled for disconnected environments.
# Default: false
Expand Down Expand Up @@ -364,6 +370,7 @@ func (a *ApplianceConfig) GetRelease() (string, string, error) {
cmd := fmt.Sprintf(templateGetVersion, releaseImage)
releaseVersion, err = executer.NewExecuter().Execute(cmd)
if err != nil {
logrus.Debugf("Error executing command: %s, error: %v", cmd, err)
return "", "", nil
}
releaseVersion = strings.Trim(releaseVersion, "'")
Expand All @@ -378,7 +385,7 @@ func (a *ApplianceConfig) GetRelease() (string, string, error) {
return "", "", nil
}
releaseDigest = strings.Trim(releaseDigest, "'")
releaseImage = fmt.Sprintf("%s@%s", strings.Split(releaseImage, ":")[0], releaseDigest)
releaseImage = appendDigest(releaseImage, releaseDigest)
}
logrus.Debugf("Release image: %s", releaseImage)
}
Expand All @@ -390,6 +397,19 @@ func (a *ApplianceConfig) GetRelease() (string, string, error) {
return releaseImage, releaseVersion, nil
}

// appendDigest appends a digest to an image reference, stripping any existing
// tag first to avoid producing a "tag@digest" reference that fails image
// validation. For example, "registry.example.com/img:tag" becomes
// "registry.example.com/img@sha256:abc123".
// LastIndex is used to locate the tag colon so that a port in the registry
// host (e.g. "registry.example.com:5000/img:tag") is preserved correctly.
func appendDigest(image, digest string) string {
if idx := strings.LastIndex(image, ":"); idx > strings.LastIndex(image, "/") {
image = image[:idx]
}
return fmt.Sprintf("%s@%s", image, digest)
}

func (a *ApplianceConfig) validateConfig(f asset.FileFetcher) field.ErrorList {
allErrs := field.ErrorList{}

Expand Down Expand Up @@ -430,6 +450,11 @@ func (a *ApplianceConfig) validateConfig(f asset.FileFetcher) field.ErrorList {
}
}

// Validate mirrorPath
if err := a.validateMirrorPath(); err != nil {
allErrs = append(allErrs, err...)
}

return allErrs
}

Expand Down Expand Up @@ -553,6 +578,41 @@ func (a *ApplianceConfig) validatePinnedImageSet() error {
return nil
}

func (a *ApplianceConfig) validateMirrorPath() field.ErrorList {
allErrs := field.ErrorList{}

if a.Config.MirrorPath != nil {
mirrorPath := swag.StringValue(a.Config.MirrorPath)
if mirrorPath != "" {
// Validate mirror path exists and is a directory
info, err := os.Stat(mirrorPath)
if err != nil {
if os.IsNotExist(err) {
allErrs = append(allErrs, field.Invalid(field.NewPath("mirrorPath"),
mirrorPath, "mirror path does not exist"))
} else {
allErrs = append(allErrs, field.Invalid(field.NewPath("mirrorPath"),
mirrorPath, fmt.Sprintf("failed to access mirror path: %v", err)))
}
} else if !info.IsDir() {
allErrs = append(allErrs, field.Invalid(field.NewPath("mirrorPath"),
mirrorPath, "mirror path must be a directory"))
} else {
// Validate data subdirectory exists
dataDir := filepath.Join(mirrorPath, "data")
if _, err := os.Stat(dataDir); err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("mirrorPath"),
mirrorPath, "mirror path must contain a 'data' subdirectory (expected oc-mirror workspace structure)"))
}
}

logrus.Infof("Using pre-mirrored images from: %s", mirrorPath)
}
}

return allErrs
}

func (a *ApplianceConfig) storePullSecret() error {
// Get home dir (~)
homeDir, err := os.UserHomeDir()
Expand Down
37 changes: 37 additions & 0 deletions pkg/asset/config/appliance_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package config

import (
"testing"

. "github.com/onsi/ginkgo/v2/dsl/core"
. "github.com/onsi/gomega"
)

func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}

var _ = Describe("appendDigest", func() {
const digest = "sha256:abc123"

It("appends digest to image with no tag", func() {
Expect(appendDigest("registry.example.com/img", digest)).
To(Equal("registry.example.com/img@sha256:abc123"))
})

It("strips tag before appending digest", func() {
Expect(appendDigest("registry.example.com/img:tag", digest)).
To(Equal("registry.example.com/img@sha256:abc123"))
})

It("handles registry with port and no tag", func() {
Expect(appendDigest("registry.example.com:5000/img", digest)).
To(Equal("registry.example.com:5000/img@sha256:abc123"))
})

It("strips tag from image with registry port", func() {
Expect(appendDigest("registry.example.com:5000/img:tag", digest)).
To(Equal("registry.example.com:5000/img@sha256:abc123"))
})
})
49 changes: 42 additions & 7 deletions pkg/asset/data/data_iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/go-openapi/swag"
"github.com/openshift/appliance/pkg/asset/config"
"github.com/openshift/appliance/pkg/consts"
"github.com/openshift/appliance/pkg/executer"
"github.com/openshift/appliance/pkg/genisoimage"
"github.com/openshift/appliance/pkg/log"
"github.com/openshift/appliance/pkg/registry"
Expand Down Expand Up @@ -58,7 +59,10 @@ func (a *DataISO) Generate(dependencies asset.Parents) error {
}
r := release.NewRelease(releaseConfig)

dataDirPath := filepath.Join(envConfig.TempDir, dataDir)
dataDirPath, err := filepath.Abs(filepath.Join(envConfig.TempDir, dataDir))
if err != nil {
return err
}
if err := os.MkdirAll(dataDirPath, os.ModePerm); err != nil {
logrus.Errorf("Failed to create dir: %s", dataDirPath)
return err
Expand Down Expand Up @@ -93,14 +97,20 @@ func (a *DataISO) Generate(dependencies asset.Parents) error {
applianceConfig.Config.OcpRelease.Version),
envConfig,
)
registryDir, err := registry.GetRegistryDataPath(envConfig.TempDir, dataDir)
if err != nil {
return log.StopSpinner(spinner, err)
spinner.DirToMonitor = dataDirPath

// When mirror-path is provided, pre-populate the registry data directory before
// starting the registry so that bundle.Push() adds release-bundles on top of the
// mirrored data rather than overwriting it afterwards.
if applianceConfig.Config.MirrorPath != nil && swag.StringValue(applianceConfig.Config.MirrorPath) != "" {
if err := copyMirrorRegistryData(swag.StringValue(applianceConfig.Config.MirrorPath), dataDirPath); err != nil {
return log.StopSpinner(spinner, err)
}
}
spinner.DirToMonitor = registryDir

releaseImageRegistry := registry.NewRegistry(
registry.RegistryConfig{
DataDirPath: registryDir,
DataDirPath: dataDirPath,
URI: registryUri,
Port: swag.IntValue(applianceConfig.Config.ImageRegistry.Port),
UseBinary: swag.BoolValue(applianceConfig.Config.ImageRegistry.UseBinary),
Expand Down Expand Up @@ -138,12 +148,37 @@ func (a *DataISO) Generate(dependencies asset.Parents) error {
)
spinner.FileToMonitor = dataIsoName
imageGen := genisoimage.NewGenIsoImage(nil)
if err = imageGen.GenerateImage(envConfig.CacheDir, dataIsoName, filepath.Join(envConfig.TempDir, dataDir), dataVolumeName); err != nil {
if err = imageGen.GenerateImage(envConfig.CacheDir, dataIsoName, dataDirPath, dataVolumeName); err != nil {
return log.StopSpinner(spinner, err)
}
return log.StopSpinner(spinner, a.updateAsset(envConfig))
}

// copyMirrorRegistryData copies the Docker registry data from a mirror-path
// workspace into the temp data directory so it's available for ISO generation.
func copyMirrorRegistryData(mirrorPath, registryDataSourcePath string) error {
dockerSrcPath := filepath.Join(mirrorPath, dataDir, "docker")
dockerDstPath := filepath.Join(registryDataSourcePath, "docker")

logrus.Infof("Copying Docker registry data from %s to %s", dockerSrcPath, dockerDstPath)

if _, err := os.Stat(dockerSrcPath); err != nil {
return fmt.Errorf("docker registry data not found at %s (mirror-path may be invalid): %w", dockerSrcPath, err)
}

if err := os.MkdirAll(registryDataSourcePath, os.ModePerm); err != nil {
return fmt.Errorf("failed to create directory for Docker registry data: %w", err)
}

// Note: paths are program-generated from validated inputs
if _, err := executer.NewExecuter().Execute(fmt.Sprintf("cp -r %s %s", dockerSrcPath, dockerDstPath)); err != nil {
return fmt.Errorf("failed to copy Docker registry data from %s to %s: %w", dockerSrcPath, dockerDstPath, err)
}

logrus.Infof("Successfully copied Docker registry data")
return nil
}

// Name returns the human-friendly name of the asset.
func (a *DataISO) Name() string {
return "Data ISO"
Expand Down
Loading