Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
83 changes: 78 additions & 5 deletions pkg/asset/config/appliance_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ const (
PodmanPull = "podman pull %s"

// Release
templateGetVersion = "oc adm release info %s -o template --template '{{.metadata.version}}'"
templateGetDigest = "oc adm release info %s -o template --template '{{.digest}}'"
templateGetVersion = "oc adm release info --registry-config %s %s -o template --template '{{.metadata.version}}'"
Comment thread
rwsu marked this conversation as resolved.
Outdated
templateGetDigest = "oc adm release info --registry-config %s %s -o template --template '{{.digest}}'"
)

var (
Expand Down 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 @@ -361,9 +367,14 @@ func (a *ApplianceConfig) GetRelease() (string, string, error) {
releaseImage = swag.StringValue(a.Config.OcpRelease.URL)

// Get version
cmd := fmt.Sprintf(templateGetVersion, releaseImage)
pullSecretPath, err := GetPullSecretPath()
if err != nil {
return "", "", err
}
cmd := fmt.Sprintf(templateGetVersion, pullSecretPath, 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 @@ -372,13 +383,13 @@ func (a *ApplianceConfig) GetRelease() (string, string, error) {
// Get image
if !strings.Contains(releaseImage, "@") {
var releaseDigest string
cmd := fmt.Sprintf(templateGetDigest, releaseImage)
cmd := fmt.Sprintf(templateGetDigest, pullSecretPath, releaseImage)
releaseDigest, err = executer.NewExecuter().Execute(cmd)
if err != nil {
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 +401,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 +454,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 +582,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 All @@ -573,3 +637,12 @@ func (a *ApplianceConfig) storePullSecret() error {

return nil
}

// GetPullSecretPath returns the path to the pull secret file (~/.docker/config.json)
func GetPullSecretPath() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", errors.Wrapf(err, "failed to get home directory")
}
return filepath.Join(homeDir, ".docker", "config.json"), nil
}
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"))
})
})
38 changes: 37 additions & 1 deletion 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 @@ -138,12 +139,47 @@ 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 {

// When mirror-path is provided, copy the Docker registry data from mirror-path/data
Comment thread
rwsu marked this conversation as resolved.
Outdated
// to temp/data so it's in the same location as the registry container image (images/registry/registry.tar)
registryDataSourcePath := filepath.Join(envConfig.TempDir, dataDir)
if applianceConfig.Config.MirrorPath != nil && swag.StringValue(applianceConfig.Config.MirrorPath) != "" {
if err := copyMirrorRegistryData(swag.StringValue(applianceConfig.Config.MirrorPath), registryDataSourcePath); err != nil {
return log.StopSpinner(spinner, err)
}
}

if err = imageGen.GenerateImage(envConfig.CacheDir, dataIsoName, registryDataSourcePath, 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
6 changes: 5 additions & 1 deletion pkg/asset/deploy/deploy_iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,12 @@ func (i *DeployISO) buildDeploymentIso(envConfig *config.EnvConfig, applianceCon
envConfig,
)
applianceTarFile := filepath.Join(deployDir, consts.ApplianceImageTar)
authFile, err := config.GetPullSecretPath()
if err != nil {
return err
}
if err = skopeo.NewSkopeo(nil).CopyToFile(
consts.ApplianceImage, consts.ApplianceImageName, applianceTarFile); err != nil {
consts.ApplianceImage, consts.ApplianceImageName, applianceTarFile, authFile); err != nil {
return err
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,15 @@ func CopyRegistryImageIfNeeded(envConfig *config.EnvConfig, applianceConfig *con
// Pull the source registry image (docker-registry from OCP release or from appliance config)
// and copy it to dir format to preserve digests
logrus.Infof("Copying registry image from %s to %s", sourceRegistryUri, consts.RegistryImage)
authFile, err := config.GetPullSecretPath()
if err != nil {
return "", err
}
if err := skopeo.NewSkopeo(nil).CopyToFile(
sourceRegistryUri,
consts.RegistryImage,
fileInCachePath); err != nil {
fileInCachePath,
authFile); err != nil {
return "", err
}
}
Expand Down
Loading