Skip to content

Commit 106eb1c

Browse files
committed
Fix: farm build can't handle registry/path:tag
Fixes: #25039 Contrary to its specified behaviour (https://docs.podman.io/en/stable/markdown/podman-farm-build.1.html), `podman farm build` *cannot* handle the more extended form of `--tag` that might be passed to it. Whilst `--tag`s of the form `registry/repository` are handled ok, instances of the form `registry/repository:tag` cause the farm to fail when attempting to push the images it has built to the registry. This commit addresses that issue. The primary cause (in list_builder.go) is the indiscriminate appending of the `UnknownDigestSuffix` to ALL forms of the supplied `--tag` prior to pushing its associated images to the registry. This suffix exists specifically to allow images to be pushed without either a digest or :tag, so attaching it to an image actually bearing a :tag is a logical contradiction, and the construct subsequently gets caught and kicked out by `docker_transport.go`. This solution: * amends list_builder.go as follows: - the listLocal struct is refactored so that the provided `--tag` is held as a valid `v5/docker/reference.Named` rather than as a plain string. The `--tag` string is then made available to the program via two new member functions. It is made available in two forms: a longer reference format (`registry/repository[:tag]`) and a shorter name format (`registry/repository`). Some parts of the existing processing require the long `reference` form, and others the short `name` form. The remainder of code has thus been amended to use the contextually appropriate form; - newManifestListBuilder() is amended so that it throws an errors if the `--tag` it is to be build upon can't be transformed to a valid Reference.Named. * amends farm.go to handle any new error thrown by the above; * amends farm_build_test.go to provide additional test cases to exercise the above. Signed-off-by: Kevin Crocombe <kevin.crocombe@pegortech.co.uk>
1 parent f00dc6b commit 106eb1c

File tree

3 files changed

+76
-34
lines changed

3 files changed

+76
-34
lines changed

pkg/farm/farm.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ func (f *Farm) Schedule(ctx context.Context, platforms []string) (Schedule, erro
187187
emulated[e] = name
188188
}
189189
}
190+
190191
return nil
191192
})
192193
}
@@ -271,7 +272,11 @@ func (f *Farm) Build(ctx context.Context, schedule Schedule, options entities.Bu
271272
authfile: options.Authfile,
272273
skipTLSVerify: options.SkipTLSVerify,
273274
}
274-
manifestListBuilder := newManifestListBuilder(reference, f.localEngine, listBuilderOptions)
275+
// Bug #25039: manifestListBuilder now returns an error should a dodgy reference be provided.
276+
manifestListBuilder, err := newManifestListBuilder(reference, f.localEngine, listBuilderOptions)
277+
if err != nil {
278+
return fmt.Errorf("failed to create manifest list %q: %w", reference, err)
279+
}
275280

276281
// Start builds in parallel and wait for them all to finish.
277282
var (

pkg/farm/list_builder.go

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/hashicorp/go-multierror"
1111
"github.com/sirupsen/logrus"
1212
"go.podman.io/image/v5/docker"
13+
"go.podman.io/image/v5/docker/reference"
1314
"go.podman.io/image/v5/types"
1415
)
1516

@@ -20,20 +21,50 @@ type listBuilderOptions struct {
2021
skipTLSVerify *bool
2122
}
2223

24+
// Bug #25039: listLocal now holds the reference of the manifest to be built
25+
// as a referenceNamed, rather than as a plain string. It is now made available in two
26+
// string formats via two member functions (listName() and listReference())
27+
//
28+
// reference := name [ ":" tag ] [ "@" digest ]
29+
// name := [domain '/'] path-component ['/' path-component]*
30+
//
31+
// Prior to this fix, the program was incorrectly using the fuller `reference` format
32+
// in places that actually required the shorter `name` format, and failing as a consequence.
33+
// The program only worked when the reference was supplied untagged (i.e some.domain/name).
34+
// In such situations, `name` and `reference` formats would be identical.
35+
//
36+
// In the body of the code, listName has been refactored to listReference() where that is
37+
// appropriate form to use in that context.
2338
type listLocal struct {
24-
listName string
39+
listRef reference.Named
2540
localEngine entities.ImageEngine
2641
options listBuilderOptions
2742
}
2843

29-
// newManifestListBuilder returns a manifest list builder which saves a
30-
// manifest list and images to local storage.
31-
func newManifestListBuilder(listName string, localEngine entities.ImageEngine, options listBuilderOptions) *listLocal {
44+
func (l *listLocal) listName() string {
45+
// i.e. domain/path-component
46+
return l.listRef.Name()
47+
}
48+
49+
func (l *listLocal) listReference() string {
50+
// i.e. domain/path-component:tag
51+
return l.listRef.String()
52+
}
53+
54+
// Bug #25039: manifestListBuilder amended to address problems arising from the
55+
// provision of an incorrectly specified reference. If the provided reference
56+
// will not parse correctly, the function now throws an error.
57+
func newManifestListBuilder(listName string, localEngine entities.ImageEngine, options listBuilderOptions) (*listLocal, error) {
58+
ref, err := reference.ParseNamed(listName)
59+
60+
if err != nil {
61+
return nil, fmt.Errorf("could not parse reference %q: %w", listName, err)
62+
}
3263
return &listLocal{
33-
listName: listName,
64+
listRef: ref,
3465
options: options,
3566
localEngine: localEngine,
36-
}
67+
}, err
3768
}
3869

3970
// Build retrieves images from the build reports and assembles them into a
@@ -45,15 +76,17 @@ func (l *listLocal) build(ctx context.Context, images map[entities.BuildReport]e
4576
skipTLSVerify = types.NewOptionalBool(*l.options.skipTLSVerify)
4677
}
4778

48-
exists, err := l.localEngine.ManifestExists(ctx, l.listName)
79+
exists, err := l.localEngine.ManifestExists(ctx, l.listReference())
4980
if err != nil {
5081
return "", err
5182
}
5283
// Create list if it doesn't exist
84+
//
85+
// Bug #25039: we can safely use the longer form, (i.e. potentially tagged), listReference() here. (previously listName)
5386
if !exists.Value {
54-
_, err = l.localEngine.ManifestCreate(ctx, l.listName, []string{}, entities.ManifestCreateOptions{SkipTLSVerify: skipTLSVerify})
87+
_, err = l.localEngine.ManifestCreate(ctx, l.listReference(), []string{}, entities.ManifestCreateOptions{SkipTLSVerify: skipTLSVerify})
5588
if err != nil {
56-
return "", fmt.Errorf("creating manifest list %q: %w", l.listName, err)
89+
return "", fmt.Errorf("creating manifest list %q: %w", l.listReference(), err)
5790
}
5891
}
5992

@@ -67,14 +100,17 @@ func (l *listLocal) build(ctx context.Context, images map[entities.BuildReport]e
67100
pushGroup.Go(func() error {
68101
logrus.Infof("pushing image %s", image.ID)
69102
defer logrus.Infof("pushed image %s", image.ID)
70-
// Push the image to the registry
71-
report, err := engine.Push(ctx, image.ID, l.listName+docker.UnknownDigestSuffix, entities.ImagePushOptions{Authfile: l.options.authfile, Quiet: false, SkipTLSVerify: skipTLSVerify})
103+
104+
// Bug #25039: prior to fix, would crash here owing to the UnknownDigestSuffix being inappropriately applied to
105+
// the longer `reference` format . We can only use the shorter `name` format here.
106+
report, err := engine.Push(ctx, image.ID, l.listName()+docker.UnknownDigestSuffix, entities.ImagePushOptions{Authfile: l.options.authfile, Quiet: false, SkipTLSVerify: skipTLSVerify})
72107
if err != nil {
73108
return fmt.Errorf("pushing image %q to registry: %w", image, err)
74109
}
75110
refsMutex.Lock()
76111
defer refsMutex.Unlock()
77-
refs = append(refs, "docker://"+l.listName+"@"+report.ManifestDigest)
112+
113+
refs = append(refs, "docker://"+l.listName()+"@"+report.ManifestDigest)
78114
return nil
79115
})
80116
}
@@ -108,18 +144,18 @@ func (l *listLocal) build(ctx context.Context, images map[entities.BuildReport]e
108144

109145
// Clear the list in the event it already existed
110146
if exists.Value {
111-
_, err = l.localEngine.ManifestListClear(ctx, l.listName)
147+
_, err = l.localEngine.ManifestListClear(ctx, l.listReference())
112148
if err != nil {
113-
return "", fmt.Errorf("error clearing list %q", l.listName)
149+
return "", fmt.Errorf("error clearing list %q", l.listReference())
114150
}
115151
}
116152

117153
// Add the images to the list
118-
listID, err := l.localEngine.ManifestAdd(ctx, l.listName, refs, entities.ManifestAddOptions{Authfile: l.options.authfile, SkipTLSVerify: skipTLSVerify})
154+
listID, err := l.localEngine.ManifestAdd(ctx, l.listReference(), refs, entities.ManifestAddOptions{Authfile: l.options.authfile, SkipTLSVerify: skipTLSVerify})
119155
if err != nil {
120156
return "", fmt.Errorf("adding images %q to list: %w", refs, err)
121157
}
122-
_, err = l.localEngine.ManifestPush(ctx, l.listName, l.listName, entities.ImagePushOptions{Authfile: l.options.authfile, SkipTLSVerify: skipTLSVerify})
158+
_, err = l.localEngine.ManifestPush(ctx, l.listReference(), l.listReference(), entities.ImagePushOptions{Authfile: l.options.authfile, SkipTLSVerify: skipTLSVerify})
123159
if err != nil {
124160
return "", err
125161
}
@@ -131,5 +167,5 @@ func (l *listLocal) build(ctx context.Context, images map[entities.BuildReport]e
131167
}
132168
}
133169

134-
return l.listName, nil
170+
return l.listReference(), nil
135171
}

test/e2e/farm_build_test.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ var _ = Context("Testing farm build functionality :", Ordered, func() {
141141
const OFFLINE_URL = "OFFLINE_URL"
142142
const GOOD_SHORT_TAG = "GOOD_SHORT_TAG"
143143
const GOOD_LONG_TAG = "GOOD_LONG_TAG"
144+
const GOOD_VERY_LONG_TAG = "GOOD_VERY_LONG_TAG"
144145

145146
type testImageDescriptor struct {
146147
image string
@@ -400,15 +401,6 @@ var _ = Context("Testing farm build functionality :", Ordered, func() {
400401
hostArch = strings.Trim(hostArch, "\\\"/")
401402

402403
// emuInfo = podmanStaticLocal.PodmanExitCleanly("info", "--format", "{{json .Host.EmulatedArchitectures}}").OutputToString()
403-
404-
// fmt.Printf("socket = %s\n", podmanStaticTest.RemoteSocket)
405-
// fmt.Printf("url = %s\n", proxyConnectionURL)
406-
// fmt.Printf("offlineUrl = %s\n", offlineConnectionURL)
407-
// fmt.Printf("host architecture = %s\n", hostArch)
408-
// fmt.Printf("host emulation capability = %s\n", emuInfo)
409-
// fmt.Printf("PODMAN_CONNECTIONS_CONF = %s\n", connectionsConf)
410-
// fmt.Printf("CONTAINERS_CONF = %s\n", containersConf)
411-
412404
})
413405
/*##########################################################################################################*/
414406

@@ -962,6 +954,10 @@ var _ = Context("Testing farm build functionality :", Ordered, func() {
962954
scenario.tag = goodTagBase + strings.ToLower(RandomString(10)) + ":tag"
963955
}
964956

957+
if scenario.tag == GOOD_VERY_LONG_TAG {
958+
scenario.tag = goodTagBase + strings.ToLower(RandomString(10)) + "/path/path/path:tag"
959+
}
960+
965961
return scenario
966962
}
967963
/*##########################################################################################################*/
@@ -1216,14 +1212,19 @@ var _ = Context("Testing farm build functionality :", Ordered, func() {
12161212
},
12171213
),
12181214
//
1219-
// NB: THIS TEST SHOULD WORK, BUT FAILS IN THE CURRENT PRODUCTION CODE
1215+
Entry("proxyFarm build with full reference form of tag (registry:port/path:tag)",
1216+
withTestScenarioOf{farm: "proxyFarm", params: "", image: standardTestImage, tag: GOOD_LONG_TAG},
1217+
expectBuildsOf{
1218+
build{arch: HOST_ARCH, expectedTobeBuiltOn: LOCAL_HOST, usingEmulation: false, withCleanup: false},
1219+
},
1220+
),
12201221
//
1221-
// Entry("proxyFarm build with full reference form of tag",
1222-
// withTestScenarioOf{farm: "proxyFarm", params: "", image: standardTestImage, tag: GOOD_LONG_TAG},
1223-
// expectBuildsOf{
1224-
// build{arch: HOST_ARCH, expectedTobeBuiltOn: LOCAL_HOST, usingEmulation: false, withCleanup: false},
1225-
// },
1226-
// ),
1222+
Entry("proxyFarm build with even fuller reference form of tag (registry:port/path/path/path:tag)",
1223+
withTestScenarioOf{farm: "proxyFarm", params: "", image: standardTestImage, tag: GOOD_VERY_LONG_TAG},
1224+
expectBuildsOf{
1225+
build{arch: HOST_ARCH, expectedTobeBuiltOn: LOCAL_HOST, usingEmulation: false, withCleanup: false},
1226+
},
1227+
),
12271228
//
12281229
Entry("proxyFarm build with --local=true",
12291230
withTestScenarioOf{farm: "proxyFarm", params: "--local=true", image: standardTestImage, tag: GOOD_SHORT_TAG},

0 commit comments

Comments
 (0)