Skip to content

Commit 8d07e69

Browse files
committed
AGENT-1312: Add manifests and mapping file to the release bundle image
Added ImageSetConfiguration (imageset.yaml) and Mapping file (mapping.txt) to the release bundle (both are generated by oc-mirror). Example of the image content: / ├── manifests/ │ └── imageset.yaml # ImageSetConfiguration (generated by oc-mirror) └── mirror/ └── mapping.txt # Image mapping output (generated by oc-mirror)
1 parent 869d350 commit 8d07e69

7 files changed

Lines changed: 260 additions & 17 deletions

File tree

bundle/Dockerfile.bundle

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
# This Dockerfile is used to build the release bundle image.
22
FROM scratch
3+
4+
COPY imageset.yaml /manifests/imageset.yaml
5+
COPY mapping.txt /mirror/mapping.txt
6+
7+
LABEL com.redhat.component="openshift-appliance-release-bundle-container" \
8+
name="openshift-appliance-release-bundle" \
9+
summary="A release bundle for OpenShift Appliance" \
10+
description="A release bundle for OpenShift Appliance" \
11+
io.k8s.display-name="openshift-appliance-release-bundle" \
12+
io.k8s.description="A release bundle for OpenShift Appliance" \
13+
io.openshift.tags="openshift,appliance,installer,agent" \
14+
vendor="Red Hat, Inc." \
15+
url="https://github.com/openshift/appliance"

pkg/asset/data/data_iso.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/openshift/appliance/pkg/registry"
1414
"github.com/openshift/appliance/pkg/release"
1515
"github.com/openshift/appliance/pkg/releasebundle"
16+
"github.com/openshift/appliance/pkg/templates"
1617
"github.com/openshift/installer/pkg/asset"
1718
"github.com/sirupsen/logrus"
1819
)
@@ -114,10 +115,26 @@ func (a *DataISO) Generate(dependencies asset.Parents) error {
114115
return log.StopSpinner(spinner, err)
115116
}
116117

118+
imageSetPath := templates.GetFilePathByTemplate(consts.ImageSetTemplateFile, envConfig.TempDir)
119+
mappingBytes, err := release.FindMappingFileInMirrorWorkspace(filepath.Join(envConfig.TempDir, "oc-mirror"))
120+
if err != nil {
121+
return log.StopSpinner(spinner, err)
122+
}
123+
if len(mappingBytes) == 0 {
124+
// Real oc-mirror runs often omit mapping.txt in the workspace; dry-run generates it (see GetMappingFile).
125+
logrus.Debug("mapping.txt not found under oc-mirror workspace; running oc mirror dry-run to produce it for the release bundle")
126+
mappingBytes, err = r.GetMappingFile()
127+
if err != nil {
128+
return log.StopSpinner(spinner, fmt.Errorf("generate mapping.txt for release bundle: %w", err))
129+
}
130+
}
131+
117132
// Build and push release bundle image
118133
bundle := releasebundle.NewBundle(releasebundle.BundleConfig{
119134
Port: swag.IntValue(applianceConfig.Config.ImageRegistry.Port),
120135
ReleaseVersion: releaseVersion,
136+
ImageSetPath: imageSetPath,
137+
MappingBytes: mappingBytes,
121138
})
122139
if err = bundle.Push(); err != nil {
123140
return log.StopSpinner(spinner, err)

pkg/registry/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func CopyRegistryImageIfNeeded(envConfig *config.EnvConfig, applianceConfig *con
297297
} else {
298298
// Pull the source registry image (docker-registry from OCP release or from appliance config)
299299
// and copy it to dir format to preserve digests
300-
logrus.Infof("Copying registry image from %s to %s", sourceRegistryUri, consts.RegistryImage)
300+
logrus.Debugf("Copying registry image from %s to %s", sourceRegistryUri, consts.RegistryImage)
301301
if err := skopeo.NewSkopeo(nil).CopyToFile(
302302
sourceRegistryUri,
303303
consts.RegistryImage,

pkg/release/release.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package release
22

33
import (
44
"fmt"
5+
"io/fs"
56
"os"
67
"path"
78
"path/filepath"
@@ -183,7 +184,7 @@ func (r *release) copyOutputYamls(ocMirrorDir string, enableInteractiveFlow *boo
183184
if err != nil {
184185
return err
185186
}
186-
187+
187188
// Iterate over all yaml files and replace the localhost with the internal registry URI
188189
for _, yamlPath := range yamlPaths {
189190
logrus.Debugf("Copying ymals from oc-mirror output: %s", yamlPath)
@@ -281,3 +282,44 @@ func (r *release) GetMappingFile() ([]byte, error) {
281282

282283
return r.OSInterface.ReadFile(mappingFilePath)
283284
}
285+
286+
// FindMappingFileInMirrorWorkspace returns the contents of the first mapping.txt found under root
287+
// (typically envConfig.TempDir/oc-mirror after a real oc mirror run). If root is missing or no
288+
// mapping file exists, it returns (nil, nil).
289+
func FindMappingFileInMirrorWorkspace(root string) ([]byte, error) {
290+
info, err := os.Stat(root)
291+
if err != nil {
292+
if os.IsNotExist(err) {
293+
return nil, nil
294+
}
295+
return nil, err
296+
}
297+
if !info.IsDir() {
298+
return nil, nil
299+
}
300+
// Same layout as dry-run output (see GetMappingFile); real mirror may or may not write this path.
301+
prio := filepath.Join(root, "working-dir", "dry-run", consts.OcMirrorMappingFileName)
302+
if data, err := os.ReadFile(prio); err == nil {
303+
return data, nil
304+
} else if !os.IsNotExist(err) {
305+
return nil, err
306+
}
307+
var found string
308+
err = filepath.WalkDir(root, func(p string, d fs.DirEntry, walkErr error) error {
309+
if walkErr != nil {
310+
return walkErr
311+
}
312+
if !d.IsDir() && d.Name() == consts.OcMirrorMappingFileName {
313+
found = p
314+
return fs.SkipAll
315+
}
316+
return nil
317+
})
318+
if err != nil {
319+
return nil, err
320+
}
321+
if found == "" {
322+
return nil, nil
323+
}
324+
return os.ReadFile(found)
325+
}

pkg/release/release_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,67 @@ var _ = Describe("Test Release", func() {
131131
})
132132
})
133133

134+
func TestFindMappingFileInMirrorWorkspace(t *testing.T) {
135+
t.Run("finds nested mapping.txt", func(t *testing.T) {
136+
dir := t.TempDir()
137+
sub := filepath.Join(dir, "a", "b")
138+
if err := os.MkdirAll(sub, 0o755); err != nil {
139+
t.Fatal(err)
140+
}
141+
want := "x=y\n"
142+
if err := os.WriteFile(filepath.Join(sub, "mapping.txt"), []byte(want), 0o644); err != nil {
143+
t.Fatal(err)
144+
}
145+
b, err := FindMappingFileInMirrorWorkspace(dir)
146+
if err != nil {
147+
t.Fatal(err)
148+
}
149+
if string(b) != want {
150+
t.Fatalf("got %q want %q", b, want)
151+
}
152+
})
153+
t.Run("missing root returns nil", func(t *testing.T) {
154+
b, err := FindMappingFileInMirrorWorkspace(filepath.Join(t.TempDir(), "nope"))
155+
if err != nil {
156+
t.Fatal(err)
157+
}
158+
if b != nil {
159+
t.Fatal("expected nil bytes")
160+
}
161+
})
162+
t.Run("empty tree returns nil", func(t *testing.T) {
163+
dir := t.TempDir()
164+
if err := os.MkdirAll(filepath.Join(dir, "x"), 0o755); err != nil {
165+
t.Fatal(err)
166+
}
167+
b, err := FindMappingFileInMirrorWorkspace(dir)
168+
if err != nil {
169+
t.Fatal(err)
170+
}
171+
if b != nil {
172+
t.Fatal("expected nil bytes")
173+
}
174+
})
175+
t.Run("finds working-dir/dry-run/mapping.txt", func(t *testing.T) {
176+
dir := t.TempDir()
177+
sub := filepath.Join(dir, "working-dir", "dry-run")
178+
if err := os.MkdirAll(sub, 0o755); err != nil {
179+
t.Fatal(err)
180+
}
181+
want := "registry/a=b\n"
182+
if err := os.WriteFile(filepath.Join(sub, "mapping.txt"), []byte(want), 0o644); err != nil {
183+
t.Fatal(err)
184+
}
185+
b, err := FindMappingFileInMirrorWorkspace(dir)
186+
if err != nil {
187+
t.Fatal(err)
188+
}
189+
if string(b) != want {
190+
t.Fatalf("got %q want %q", b, want)
191+
}
192+
})
193+
}
194+
134195
func TestRelease(t *testing.T) {
135196
RegisterFailHandler(Fail)
136197
RunSpecs(t, "release_test")

pkg/releasebundle/releasebundle.go

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"strconv"
78

89
"github.com/openshift/appliance/pkg/executer"
910
"github.com/pkg/errors"
1011
)
1112

12-
const (
13-
bundleBuildCmd = "podman build -f %s -t %s %s"
14-
bundlePushCmd = "podman push --tls-verify=false %s"
15-
)
13+
const bundlePushCmd = "podman push --tls-verify=false %s"
1614

1715
type BundleConfig struct {
1816
Executer executer.Executer
1917
Port int
2018
ReleaseVersion string
19+
// ImageSetPath is the absolute path to the rendered imageset.yaml used for oc mirror.
20+
ImageSetPath string
21+
// MappingBytes is oc-mirror mapping.txt content (may be nil if not produced).
22+
MappingBytes []byte
2123
}
2224

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

3436
func (b *Bundle) Push() error {
35-
dockerfilePath, ctx, err := resolveDockerfile()
37+
if b.ImageSetPath == "" {
38+
return errors.New("bundle: ImageSetPath is required")
39+
}
40+
dockerfileSrc, err := readBundleDockerfile()
3641
if err != nil {
3742
return err
3843
}
3944

45+
stagedir, err := os.MkdirTemp("", "appliance-release-bundle-*")
46+
if err != nil {
47+
return errors.Wrap(err, "create bundle staging dir")
48+
}
49+
defer os.RemoveAll(stagedir)
50+
51+
dockerfileDest := filepath.Join(stagedir, "Dockerfile.bundle")
52+
if err := os.WriteFile(dockerfileDest, dockerfileSrc, 0o644); err != nil {
53+
return errors.Wrap(err, "write staged Dockerfile.bundle")
54+
}
55+
56+
imageSetSrc, err := os.ReadFile(b.ImageSetPath)
57+
if err != nil {
58+
return errors.Wrap(err, "read imageset for bundle")
59+
}
60+
if err := os.WriteFile(filepath.Join(stagedir, "imageset.yaml"), imageSetSrc, 0o644); err != nil {
61+
return errors.Wrap(err, "write staged imageset.yaml")
62+
}
63+
64+
mapping := b.MappingBytes
65+
if len(mapping) == 0 {
66+
mapping = []byte("# mapping.txt was not found under the oc-mirror workspace\n")
67+
}
68+
if err := os.WriteFile(filepath.Join(stagedir, "mapping.txt"), mapping, 0o644); err != nil {
69+
return errors.Wrap(err, "write staged mapping.txt")
70+
}
71+
4072
tag := Tag(b.ReleaseVersion)
4173
imageRef := registryImageRef(b.Port, tag)
42-
buildCmd := fmt.Sprintf(bundleBuildCmd, dockerfilePath, imageRef, ctx)
74+
bundleVer := b.ReleaseVersion
75+
if bundleVer == "" {
76+
bundleVer = "unknown"
77+
}
78+
buildCmd := fmt.Sprintf(
79+
"podman build --build-arg BUNDLE_VERSION=%s --build-arg BUNDLE_RELEASE=1 -f %s -t %s %s",
80+
strconv.Quote(bundleVer), dockerfileDest, imageRef, stagedir,
81+
)
4382
if _, err := b.Executer.Execute(buildCmd); err != nil {
4483
return errors.Wrap(err, "build release bundle image")
4584
}
@@ -52,13 +91,40 @@ func (b *Bundle) Push() error {
5291
return nil
5392
}
5493

94+
func readBundleDockerfile() ([]byte, error) {
95+
path, err := bundleDockerfileAbsPath()
96+
if err != nil {
97+
return nil, err
98+
}
99+
data, err := os.ReadFile(path)
100+
if err != nil {
101+
return nil, errors.Wrap(err, "read bundle Dockerfile.bundle")
102+
}
103+
return data, nil
104+
}
105+
106+
func bundleDockerfileAbsPath() (string, error) {
107+
path, _, err := resolveDockerfile()
108+
if err != nil {
109+
return "", err
110+
}
111+
if filepath.IsAbs(path) {
112+
return path, nil
113+
}
114+
cwd, err := os.Getwd()
115+
if err != nil {
116+
return "", err
117+
}
118+
return filepath.Join(cwd, path), nil
119+
}
120+
55121
// registryImageRef is the image reference used for podman build/push against the local registry
56122
// during appliance data ISO generation (must stay aligned with oc mirror localhost layout).
57123
func registryImageRef(port int, tag string) string {
58124
return fmt.Sprintf("127.0.0.1:%d/%s:%s", port, ImageRepository, tag)
59125
}
60126

61-
// resolveDockerfile returns paths for podman build: Dockerfile path and build context directory.
127+
// resolveDockerfile returns paths for locating Dockerfile.bundle (path may be relative to cwd).
62128
func resolveDockerfile() (dockerfilePath, contextDir string, err error) {
63129
candidates := []struct {
64130
dockerfile string

0 commit comments

Comments
 (0)