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
13 changes: 13 additions & 0 deletions bundle/Dockerfile.bundle
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
# This Dockerfile is used to build the release bundle image.
FROM scratch

COPY imageset.yaml /manifests/imageset.yaml
COPY mapping.txt /mirror/mapping.txt

LABEL com.redhat.component="openshift-appliance-release-bundle-container" \
name="openshift-appliance-release-bundle" \
summary="A release bundle for OpenShift Appliance" \
description="A release bundle for OpenShift Appliance" \
io.k8s.display-name="openshift-appliance-release-bundle" \
io.k8s.description="A release bundle for OpenShift Appliance" \
io.openshift.tags="openshift,appliance,installer,agent" \
vendor="Red Hat, Inc." \
url="https://github.com/openshift/appliance"
17 changes: 17 additions & 0 deletions pkg/asset/data/data_iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/openshift/appliance/pkg/registry"
"github.com/openshift/appliance/pkg/release"
"github.com/openshift/appliance/pkg/releasebundle"
"github.com/openshift/appliance/pkg/templates"
"github.com/openshift/installer/pkg/asset"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -114,10 +115,26 @@ func (a *DataISO) Generate(dependencies asset.Parents) error {
return log.StopSpinner(spinner, err)
}

imageSetPath := templates.GetFilePathByTemplate(consts.ImageSetTemplateFile, envConfig.TempDir)
mappingBytes, err := release.FindMappingFileInMirrorWorkspace(filepath.Join(envConfig.TempDir, "oc-mirror"))
if err != nil {
return log.StopSpinner(spinner, err)
}
if len(mappingBytes) == 0 {
// Real oc-mirror runs often omit mapping.txt in the workspace; dry-run generates it (see GetMappingFile).
logrus.Debug("mapping.txt not found under oc-mirror workspace; running oc mirror dry-run to produce it for the release bundle")
mappingBytes, err = r.GetMappingFile()
if err != nil {
return log.StopSpinner(spinner, fmt.Errorf("generate mapping.txt for release bundle: %w", err))
}
}

// Build and push release bundle image
bundle := releasebundle.NewBundle(releasebundle.BundleConfig{
Port: swag.IntValue(applianceConfig.Config.ImageRegistry.Port),
ReleaseVersion: releaseVersion,
ImageSetPath: imageSetPath,
MappingBytes: mappingBytes,
})
if err = bundle.Push(); err != nil {
return log.StopSpinner(spinner, err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ func CopyRegistryImageIfNeeded(envConfig *config.EnvConfig, applianceConfig *con
} else {
// 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)
logrus.Debugf("Copying registry image from %s to %s", sourceRegistryUri, consts.RegistryImage)
if err := skopeo.NewSkopeo(nil).CopyToFile(
sourceRegistryUri,
consts.RegistryImage,
Expand Down
44 changes: 43 additions & 1 deletion pkg/release/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package release

import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -183,7 +184,7 @@ func (r *release) copyOutputYamls(ocMirrorDir string, enableInteractiveFlow *boo
if err != nil {
return err
}

// Iterate over all yaml files and replace the localhost with the internal registry URI
for _, yamlPath := range yamlPaths {
logrus.Debugf("Copying ymals from oc-mirror output: %s", yamlPath)
Expand Down Expand Up @@ -281,3 +282,44 @@ func (r *release) GetMappingFile() ([]byte, error) {

return r.OSInterface.ReadFile(mappingFilePath)
}

// FindMappingFileInMirrorWorkspace returns the contents of the first mapping.txt found under root
// (typically envConfig.TempDir/oc-mirror after a real oc mirror run). If root is missing or no
// mapping file exists, it returns (nil, nil).
func FindMappingFileInMirrorWorkspace(root string) ([]byte, error) {
info, err := os.Stat(root)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
if !info.IsDir() {
return nil, nil
}
// Same layout as dry-run output (see GetMappingFile); real mirror may or may not write this path.
prio := filepath.Join(root, "working-dir", "dry-run", consts.OcMirrorMappingFileName)
if data, err := os.ReadFile(prio); err == nil {
return data, nil
} else if !os.IsNotExist(err) {
return nil, err
}
var found string
err = filepath.WalkDir(root, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if !d.IsDir() && d.Name() == consts.OcMirrorMappingFileName {
found = p
return fs.SkipAll
}
return nil
})
if err != nil {
return nil, err
}
if found == "" {
return nil, nil
}
return os.ReadFile(found)
}
61 changes: 61 additions & 0 deletions pkg/release/release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,67 @@ var _ = Describe("Test Release", func() {
})
})

func TestFindMappingFileInMirrorWorkspace(t *testing.T) {
t.Run("finds nested mapping.txt", func(t *testing.T) {
dir := t.TempDir()
sub := filepath.Join(dir, "a", "b")
if err := os.MkdirAll(sub, 0o755); err != nil {
t.Fatal(err)
}
want := "x=y\n"
if err := os.WriteFile(filepath.Join(sub, "mapping.txt"), []byte(want), 0o644); err != nil {
t.Fatal(err)
}
b, err := FindMappingFileInMirrorWorkspace(dir)
if err != nil {
t.Fatal(err)
}
if string(b) != want {
t.Fatalf("got %q want %q", b, want)
}
})
t.Run("missing root returns nil", func(t *testing.T) {
b, err := FindMappingFileInMirrorWorkspace(filepath.Join(t.TempDir(), "nope"))
if err != nil {
t.Fatal(err)
}
if b != nil {
t.Fatal("expected nil bytes")
}
})
t.Run("empty tree returns nil", func(t *testing.T) {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "x"), 0o755); err != nil {
t.Fatal(err)
}
b, err := FindMappingFileInMirrorWorkspace(dir)
if err != nil {
t.Fatal(err)
}
if b != nil {
t.Fatal("expected nil bytes")
}
})
t.Run("finds working-dir/dry-run/mapping.txt", func(t *testing.T) {
dir := t.TempDir()
sub := filepath.Join(dir, "working-dir", "dry-run")
if err := os.MkdirAll(sub, 0o755); err != nil {
t.Fatal(err)
}
want := "registry/a=b\n"
if err := os.WriteFile(filepath.Join(sub, "mapping.txt"), []byte(want), 0o644); err != nil {
t.Fatal(err)
}
b, err := FindMappingFileInMirrorWorkspace(dir)
if err != nil {
t.Fatal(err)
}
if string(b) != want {
t.Fatalf("got %q want %q", b, want)
}
})
}

func TestRelease(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "release_test")
Expand Down
80 changes: 73 additions & 7 deletions pkg/releasebundle/releasebundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/openshift/appliance/pkg/executer"
"github.com/pkg/errors"
)

const (
bundleBuildCmd = "podman build -f %s -t %s %s"
bundlePushCmd = "podman push --tls-verify=false %s"
)
const bundlePushCmd = "podman push --tls-verify=false %s"

type BundleConfig struct {
Executer executer.Executer
Port int
ReleaseVersion string
// ImageSetPath is the absolute path to the rendered imageset.yaml used for oc mirror.
ImageSetPath string
// MappingBytes is oc-mirror mapping.txt content (may be nil if not produced).
MappingBytes []byte
}

type Bundle struct {
Expand All @@ -32,14 +34,51 @@ func NewBundle(config BundleConfig) *Bundle {
}

func (b *Bundle) Push() error {
dockerfilePath, ctx, err := resolveDockerfile()
if b.ImageSetPath == "" {
return errors.New("bundle: ImageSetPath is required")
}
dockerfileSrc, err := readBundleDockerfile()
if err != nil {
return err
}

stagedir, err := os.MkdirTemp("", "appliance-release-bundle-*")
if err != nil {
return errors.Wrap(err, "create bundle staging dir")
}
defer os.RemoveAll(stagedir)

dockerfileDest := filepath.Join(stagedir, "Dockerfile.bundle")
if err := os.WriteFile(dockerfileDest, dockerfileSrc, 0o644); err != nil {
return errors.Wrap(err, "write staged Dockerfile.bundle")
}

imageSetSrc, err := os.ReadFile(b.ImageSetPath)
if err != nil {
return errors.Wrap(err, "read imageset for bundle")
}
if err := os.WriteFile(filepath.Join(stagedir, "imageset.yaml"), imageSetSrc, 0o644); err != nil {
return errors.Wrap(err, "write staged imageset.yaml")
}

mapping := b.MappingBytes
if len(mapping) == 0 {
mapping = []byte("# mapping.txt was not found under the oc-mirror workspace\n")
}
if err := os.WriteFile(filepath.Join(stagedir, "mapping.txt"), mapping, 0o644); err != nil {
return errors.Wrap(err, "write staged mapping.txt")
}

tag := Tag(b.ReleaseVersion)
imageRef := registryImageRef(b.Port, tag)
buildCmd := fmt.Sprintf(bundleBuildCmd, dockerfilePath, imageRef, ctx)
bundleVer := b.ReleaseVersion
if bundleVer == "" {
bundleVer = "unknown"
}
buildCmd := fmt.Sprintf(
"podman build --build-arg BUNDLE_VERSION=%s --build-arg BUNDLE_RELEASE=1 -f %s -t %s %s",
strconv.Quote(bundleVer), dockerfileDest, imageRef, stagedir,
)
if _, err := b.Executer.Execute(buildCmd); err != nil {
return errors.Wrap(err, "build release bundle image")
}
Expand All @@ -52,13 +91,40 @@ func (b *Bundle) Push() error {
return nil
}

func readBundleDockerfile() ([]byte, error) {
path, err := bundleDockerfileAbsPath()
if err != nil {
return nil, err
}
data, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrap(err, "read bundle Dockerfile.bundle")
}
return data, nil
}

func bundleDockerfileAbsPath() (string, error) {
path, _, err := resolveDockerfile()
if err != nil {
return "", err
}
if filepath.IsAbs(path) {
return path, nil
}
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, path), nil
}

// registryImageRef is the image reference used for podman build/push against the local registry
// during appliance data ISO generation (must stay aligned with oc mirror localhost layout).
func registryImageRef(port int, tag string) string {
return fmt.Sprintf("127.0.0.1:%d/%s:%s", port, ImageRepository, tag)
}

// resolveDockerfile returns paths for podman build: Dockerfile path and build context directory.
// resolveDockerfile returns paths for locating Dockerfile.bundle (path may be relative to cwd).
func resolveDockerfile() (dockerfilePath, contextDir string, err error) {
candidates := []struct {
dockerfile string
Expand Down
Loading