Skip to content
Open
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
48 changes: 44 additions & 4 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ var (

envConfig config.EnvConfig
deployConfig *config.DeployConfig

// assetStoreInstance caches the asset store to ensure the same instance
// is reused throughout command execution. This prevents EnvConfig from
// being regenerated with default values when setupApplianceConfig() fetches
// ApplianceConfig, which would overwrite runtime flags like IsLiveISO.
assetStoreInstance asset.Store
)

func NewBuildCmd() *cobra.Command {
Expand Down Expand Up @@ -94,6 +100,10 @@ func runBuild(cmd *cobra.Command, args []string) {
cleanup := log.SetupFileHook(rootOpts.dir)
defer cleanup()

// Load ApplianceConfig and setup cleanup of pull secret temp file
_, cleanupPullSecret := setupApplianceConfig(cmd)
defer cleanupPullSecret()

// Load ApplianceDiskImage asset to check whether a clean is required
applianceDiskImage := appliance.ApplianceDiskImage{}
if asset, err := getAssetStore().Load(&applianceDiskImage); err == nil && asset != nil {
Expand Down Expand Up @@ -135,6 +145,10 @@ func runBuildISO(cmd *cobra.Command, args []string) {
cleanup := log.SetupFileHook(rootOpts.dir)
defer cleanup()

// Load ApplianceConfig and setup cleanup of pull secret temp file
_, cleanupPullSecret := setupApplianceConfig(cmd)
defer cleanupPullSecret()

// Generate DeployConfig asset
if err := getAssetStore().Fetch(cmd.Context(), deployConfig); err != nil {
logrus.Fatal(err)
Expand All @@ -160,6 +174,10 @@ func runBuildUpgradeISO(cmd *cobra.Command, args []string) {
cleanup := log.SetupFileHook(rootOpts.dir)
defer cleanup()

// Load ApplianceConfig and setup cleanup of pull secret temp file
_, cleanupPullSecret := setupApplianceConfig(cmd)
defer cleanupPullSecret()

// Generate UpgradeConfig asset
if err := getAssetStore().Fetch(cmd.Context(), deployConfig); err != nil {
logrus.Fatal(err)
Expand Down Expand Up @@ -190,6 +208,10 @@ func runBuildLiveISO(cmd *cobra.Command, args []string) {
cleanup := log.SetupFileHook(rootOpts.dir)
defer cleanup()

// Load ApplianceConfig and setup cleanup of pull secret temp file
_, cleanupPullSecret := setupApplianceConfig(cmd)
defer cleanupPullSecret()

// Load ApplianceLiveISO asset to check whether a clean is required
applianceLiveISO := appliance.ApplianceLiveISO{}
if asset, err := getAssetStore().Load(&applianceLiveISO); err == nil && asset != nil {
Expand Down Expand Up @@ -247,9 +269,27 @@ func preRunBuildLiveISO(cmd *cobra.Command, args []string) {
}

func getAssetStore() asset.Store {
assetStore, err := assetstore.NewStore(rootOpts.dir)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create asset store"))
if assetStoreInstance == nil {
assetStore, err := assetstore.NewStore(rootOpts.dir)
if err != nil {
logrus.Fatal(errors.Wrap(err, "failed to create asset store"))
}
assetStoreInstance = assetStore
}
return assetStore
return assetStoreInstance
}

func setupApplianceConfig(cmd *cobra.Command) (*config.ApplianceConfig, func()) {
applianceConfig := &config.ApplianceConfig{}
if err := getAssetStore().Fetch(cmd.Context(), applianceConfig); err != nil {
logrus.Fatal(errors.Wrapf(err, "failed to fetch %s", applianceConfig.Name()))
}

cleanup := func() {
if err := applianceConfig.CleanupPullSecret(); err != nil {
logrus.Warnf("Failed to cleanup pull secret: %v", err)
}
}

return applianceConfig, cleanup
}
116 changes: 97 additions & 19 deletions pkg/asset/config/appliance_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,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}}'"
templateGetDigest = "oc adm release info --registry-config %s %s -o template --template '{{.digest}}'"
)

var (
Expand All @@ -59,9 +59,11 @@ var (

// ApplianceConfig reads the appliance-config.yaml file.
type ApplianceConfig struct {
File *asset.File
Config *types.ApplianceConfig
Template string
File *asset.File
Config *types.ApplianceConfig
Template string
pullSecretPath string
pullSecretFile *os.File
}

var _ asset.WritableAsset = (*ApplianceConfig)(nil)
Expand Down Expand Up @@ -154,6 +156,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

# Enable all default CatalogSources (on openshift-marketplace namespace).
# Should be disabled for disconnected environments.
# Default: false
Expand Down Expand Up @@ -362,24 +370,25 @@ func (a *ApplianceConfig) GetRelease() (string, string, error) {
releaseImage = swag.StringValue(a.Config.OcpRelease.URL)

// Get version
cmd := fmt.Sprintf(templateGetVersion, releaseImage)
cmd := fmt.Sprintf(templateGetVersion, a.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, "'")
logrus.Debugf("Release version: %s", releaseVersion)
logrus.Debugf("Got release version: '%s'", releaseVersion)

// Get image
if !strings.Contains(releaseImage, "@") {
var releaseDigest string
cmd := fmt.Sprintf(templateGetDigest, releaseImage)
cmd := fmt.Sprintf(templateGetDigest, a.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 = fmt.Sprintf("%s@%s", releaseImage, releaseDigest)
}
logrus.Debugf("Release image: %s", releaseImage)
}
Expand Down Expand Up @@ -431,6 +440,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 @@ -554,23 +568,87 @@ 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()
// Create temporary file for pull secret
tmpFile, err := os.CreateTemp("", "appliance-pull-secret-*.json")
if err != nil {
return errors.Wrapf(err, "failed to get home directory")
return errors.Wrap(err, "failed to create temporary file for pull secret")
}

// Create sub dirs
configPath := filepath.Join(homeDir, ".docker", "config.json")
if err = os.MkdirAll(filepath.Dir(configPath), os.ModePerm); err != nil {
return err
// Write pull secret to temp file
if _, err = tmpFile.WriteString(a.Config.PullSecret); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
return errors.Wrap(err, "failed to write pull secret to temporary file")
}

// Write pull secret
if err = os.WriteFile(configPath, []byte(a.Config.PullSecret), os.ModePerm); err != nil {
return errors.Wrap(err, "failed to write file")
if err = tmpFile.Close(); err != nil {
os.Remove(tmpFile.Name())
return errors.Wrap(err, "failed to close temporary pull secret file")
}

a.pullSecretPath = tmpFile.Name()
logrus.Debugf("Pull secret successfully written to temporary file: %s", a.pullSecretPath)

return nil
}

// GetPullSecretPath returns the path to the temporary pull secret file
// If the path is empty (e.g., after loading from state), it recreates the temp file
func (a *ApplianceConfig) GetPullSecretPath() string {
if a.pullSecretPath == "" && a.Config != nil {
logrus.Debugf("Pull secret path empty, recreating temporary file")
if err := a.storePullSecret(); err != nil {
logrus.Warnf("Failed to recreate pull secret temp file: %v", err)
return ""
}
}
return a.pullSecretPath
}

// CleanupPullSecret removes the temporary pull secret file
func (a *ApplianceConfig) CleanupPullSecret() error {
if a.pullSecretPath != "" {
logrus.Debugf("Cleaning up temporary pull secret file: %s", a.pullSecretPath)
if err := os.Remove(a.pullSecretPath); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "failed to remove temporary pull secret file: %s", a.pullSecretPath)
}
a.pullSecretPath = ""
}
return nil
}
2 changes: 2 additions & 0 deletions pkg/asset/config/env_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ func (e *EnvConfig) Generate(_ context.Context, dependencies asset.Parents) erro
return err
}

logrus.Debugf("EnvConfig.Generate() called with AssetsDir='%s', IsLiveISO=%v, DebugBaseIgnition=%v", e.AssetsDir, e.IsLiveISO, e.DebugBaseIgnition)

// Cache dir in 'version-arch' format
cacheDirPattern := fmt.Sprintf("%s-%s",
applianceConfig.Config.OcpRelease.Version, applianceConfig.GetCpuArchitecture())
Expand Down
35 changes: 34 additions & 1 deletion pkg/asset/data/data_iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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 @@ -123,7 +124,39 @@ func (a *DataISO) Generate(_ context.Context, 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
// 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) != "" {
mirrorDataPath := filepath.Join(swag.StringValue(applianceConfig.Config.MirrorPath), dataDir)
dockerSrcPath := filepath.Join(mirrorDataPath, "docker")
dockerDstPath := filepath.Join(registryDataSourcePath, "docker")

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

// Validate source directory exists
if _, err := os.Stat(dockerSrcPath); err != nil {
return log.StopSpinner(spinner, fmt.Errorf("Docker registry data not found at %s (mirror-path may be invalid): %w", dockerSrcPath, err))
}

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

// Copy directory recursively using cp command
// Note: Paths are safe here as they're program-generated from validated inputs
cpCmd := fmt.Sprintf("cp -r %s %s", dockerSrcPath, dockerDstPath)
exec := executer.NewExecuter()
if _, err := exec.Execute(cpCmd); err != nil {
return log.StopSpinner(spinner, fmt.Errorf("failed to copy Docker registry data from %s to %s: %w", dockerSrcPath, dockerDstPath, err))
}

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

if err = imageGen.GenerateImage(envConfig.CacheDir, dataIsoName, registryDataSourcePath, dataVolumeName); err != nil {
return log.StopSpinner(spinner, err)
}
return log.StopSpinner(spinner, a.updateAsset(envConfig))
Expand Down
3 changes: 2 additions & 1 deletion pkg/asset/deploy/deploy_iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ func (i *DeployISO) buildDeploymentIso(envConfig *config.EnvConfig, applianceCon
envConfig,
)
applianceTarFile := filepath.Join(deployDir, consts.ApplianceImageTar)
authFile := applianceConfig.GetPullSecretPath()
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
1 change: 1 addition & 0 deletions pkg/asset/ignition/bootstrap_ignition.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func (i *BootstrapIgnition) Generate(_ context.Context, dependencies asset.Paren
coreosImagePath := envConfig.FindInCache(coreosImagePattern)

// Add bootstrap scripts to ignition
logrus.Debugf("BootstrapIgnition rendering templates with IsLiveISO=%v", envConfig.IsLiveISO)
templateData := templates.GetBootstrapIgnitionTemplateData(
envConfig.IsLiveISO,
swag.BoolValue(applianceConfig.Config.EnableInteractiveFlow),
Expand Down
4 changes: 3 additions & 1 deletion pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,12 @@ 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 := applianceConfig.GetPullSecretPath()
if err := skopeo.NewSkopeo(nil).CopyToFile(
sourceRegistryUri,
consts.RegistryImage,
fileInCachePath); err != nil {
fileInCachePath,
authFile); err != nil {
return "", err
}
}
Expand Down
Loading