Skip to content

Commit 0cb1e36

Browse files
authored
fix: remove directory and platform-specific-errors args from multi platform (project-copacetic#1105)
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
1 parent ec81db9 commit 0cb1e36

File tree

9 files changed

+108
-130
lines changed

9 files changed

+108
-130
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,9 @@ jobs:
356356
go test -v ./test/e2e/push --addr="docker://" --copa="$(pwd)/copa"
357357
358358
359-
test-patch-multiarch:
359+
test-patch-multiplatform:
360360
needs: build
361-
name: Test patch with multiarch
361+
name: Test patch with multiplatform
362362
runs-on: ubuntu-latest
363363
timeout-minutes: 30
364364
permissions: read-all
@@ -404,9 +404,9 @@ jobs:
404404
- name: Install required tools
405405
shell: bash
406406
run: .github/workflows/scripts/download-tooling.sh
407-
- name: Install multiarch tooling
407+
- name: Install multiplatform tooling
408408
shell: bash
409-
run: .github/workflows/scripts/download-multiarch-tooling.sh
409+
run: .github/workflows/scripts/download-multiplatform-tooling.sh
410410
- name: Download copa from build artifacts
411411
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
412412
with:

.github/workflows/scripts/download-multiarch-tooling.sh renamed to .github/workflows/scripts/download-multiplatform-tooling.sh

File renamed without changes.

integration/multiarch/fixtures/test-images.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"localImage": "localhost:5000/nginx",
55
"tag": "1.27.0",
66
"distro": "debian",
7-
"description": "Multi-arch nginx 1.27.0, with push",
7+
"description": "Multi-platform nginx 1.27.0, with push",
88
"ignoreErrors": false,
99
"push": true,
1010
"platforms": [
@@ -21,7 +21,7 @@
2121
"localImage": "localhost:5000/nginx",
2222
"tag": "1.27.0",
2323
"distro": "debian",
24-
"description": "Multi-arch nginx 1.27.0, no push",
24+
"description": "Multi-platform nginx 1.27.0, no push",
2525
"ignoreErrors": false,
2626
"push": false,
2727
"platforms": [

integration/multiarch/patch_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestPatch(t *testing.T) {
8585
patchedRef := fmt.Sprintf("%s:%s", img.LocalImage, tagPatched)
8686

8787
t.Log("patching image with multiple architectures")
88-
patchMultiArch(t, ref, tagPatched, reportDir, img.IgnoreErrors, img.Push)
88+
patchMultiPlatform(t, ref, tagPatched, reportDir, img.IgnoreErrors, img.Push)
8989

9090
t.Log("scanning patched image for each platform")
9191
wg = sync.WaitGroup{}
@@ -158,7 +158,7 @@ func (w *addrWrapper) env() []string {
158158
return []string{fmt.Sprintf("DOCKER_HOST=%s", a)}
159159
}
160160

161-
func patchMultiArch(t *testing.T, ref, patchedTag, reportDir string, ignoreErrors, push bool) {
161+
func patchMultiPlatform(t *testing.T, ref, patchedTag, reportDir string, ignoreErrors, push bool) {
162162
var addrFl string
163163
if buildkitAddr != "" {
164164
addrFl = "-a=" + buildkitAddr
@@ -168,13 +168,12 @@ func patchMultiArch(t *testing.T, ref, patchedTag, reportDir string, ignoreError
168168
"patch",
169169
"-i=" + ref,
170170
"-t=" + patchedTag,
171-
"--report-directory=" + reportDir,
171+
"--report=" + reportDir,
172172
"-s=" + scannerPlugin,
173173
"--timeout=30m",
174174
addrFl,
175175
"--ignore-errors=" + strconv.FormatBool(ignoreErrors),
176176
"--debug",
177-
"--platform-specific-errors=fail",
178177
}
179178
if push {
180179
args = append(args, "--push")

pkg/buildkit/buildkit.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func DiscoverPlatformsFromReference(manifestRef string) ([]types.PatchPlatform,
205205
return platforms, nil
206206
}
207207

208-
// return nil if not multi-arch, and handle as normal
208+
// return nil if not multi-platform, and handle as normal
209209
return nil, nil
210210
}
211211

@@ -217,7 +217,7 @@ func DiscoverPlatforms(manifestRef, reportDir, scanner string) ([]types.PatchPla
217217
return nil, err
218218
}
219219
if p == nil {
220-
return nil, errors.New("image is not multi arch")
220+
return nil, errors.New("image is not multi platform")
221221
}
222222
log.WithField("platforms", p).Debug("Discovered platforms from manifest")
223223

pkg/patch/cmd.go

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,18 @@ import (
1616
)
1717

1818
type patchArgs struct {
19-
appImage string
20-
reportFile string
21-
reportDirectory string
22-
patchedTag string
23-
suffix string
24-
workingFolder string
25-
timeout time.Duration
26-
scanner string
27-
ignoreError bool
28-
format string
29-
output string
30-
bkOpts buildkit.Opts
31-
platformSpecificErrors string
32-
push bool
19+
appImage string
20+
reportFile string
21+
patchedTag string
22+
suffix string
23+
workingFolder string
24+
timeout time.Duration
25+
scanner string
26+
ignoreError bool
27+
format string
28+
output string
29+
bkOpts buildkit.Opts
30+
push bool
3331
}
3432

3533
func NewPatchCmd() *cobra.Command {
@@ -49,8 +47,6 @@ func NewPatchCmd() *cobra.Command {
4947
ua.timeout,
5048
ua.appImage,
5149
ua.reportFile,
52-
ua.reportDirectory,
53-
ua.platformSpecificErrors,
5450
ua.patchedTag,
5551
ua.suffix,
5652
ua.workingFolder,
@@ -64,7 +60,7 @@ func NewPatchCmd() *cobra.Command {
6460
}
6561
flags := patchCmd.Flags()
6662
flags.StringVarP(&ua.appImage, "image", "i", "", "Application image name and tag to patch")
67-
flags.StringVarP(&ua.reportFile, "report", "r", "", "Vulnerability report file path")
63+
flags.StringVarP(&ua.reportFile, "report", "r", "", "Vulnerability report file or directory path")
6864
flags.StringVarP(&ua.patchedTag, "tag", "t", "", "Tag for the patched image")
6965
flags.StringVarP(&ua.suffix, "tag-suffix", "", "patched", "Suffix for the patched image (if no explicit --tag provided)")
7066
flags.StringVarP(&ua.workingFolder, "working-folder", "w", "", "Working folder, defaults to system temp folder")
@@ -74,11 +70,9 @@ func NewPatchCmd() *cobra.Command {
7470
flags.StringVarP(&ua.bkOpts.KeyPath, "key", "", "", "Absolute path to buildkit client key")
7571
flags.DurationVar(&ua.timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
7672
flags.StringVarP(&ua.scanner, "scanner", "s", "trivy", "Scanner used to generate the report, defaults to 'trivy'")
77-
flags.BoolVar(&ua.ignoreError, "ignore-errors", false, "Ignore errors and continue patching")
73+
flags.BoolVar(&ua.ignoreError, "ignore-errors", false, "Ignore errors and continue patching (for single-arch: continue with other packages; for multi-platform: continue with other platforms)")
7874
flags.StringVarP(&ua.format, "format", "f", "openvex", "Output format, defaults to 'openvex'")
7975
flags.StringVarP(&ua.output, "output", "o", "", "Output file path")
80-
flags.StringVarP(&ua.reportDirectory, "report-directory", "d", "", "Directory with multi-arch report files")
81-
flags.StringVarP(&ua.platformSpecificErrors, "platform-specific-errors", "", "skip", "Behavior for error in patching any of sub-images for multi-arch patching: 'skip', 'warn', or 'fail'")
8276
flags.BoolVarP(&ua.push, "push", "p", false, "Push patched image to destination registry")
8377

8478
if err := patchCmd.MarkFlagRequired("image"); err != nil {

pkg/patch/patch.go

Lines changed: 48 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ func archTag(base, arch, variant string) string {
6464
return fmt.Sprintf("%s-%s", base, arch)
6565
}
6666

67-
// createMultiArchManifest assembles a multi-arch manifest list and pushes it
67+
// createMultiPlatformManifest assembles a multi-platform manifest list and pushes it
6868
// via Buildx's imagetools helper (equivalent to
6969
// `docker buildx imagetools create --tag … img@sha256:d1 img@sha256:d2 …`).
70-
func createMultiArchManifest(
70+
func createMultiPlatformManifest(
7171
ctx context.Context,
7272
imageName reference.NamedTagged,
7373
items []types.PatchResult,
@@ -95,7 +95,7 @@ func createMultiArchManifest(
9595

9696
err = resolver.Push(ctx, imageName, desc, idxBytes)
9797
if err != nil {
98-
return fmt.Errorf("failed to push multi-arch manifest list: %w", err)
98+
return fmt.Errorf("failed to push multi-platform manifest list: %w", err)
9999
}
100100

101101
return nil
@@ -125,7 +125,7 @@ func normalizeConfigForPlatform(j []byte, p *types.PatchPlatform) ([]byte, error
125125
// Patch command applies package updates to an OCI image given a vulnerability report.
126126
func Patch(
127127
ctx context.Context, timeout time.Duration,
128-
image, reportFile, reportDirectory, platformSpecificErrors, patchedTag, suffix, workingFolder, scanner, format, output string,
128+
image, reportPath, patchedTag, suffix, workingFolder, scanner, format, output string,
129129
ignoreError, push bool,
130130
bkOpts buildkit.Opts,
131131
) error {
@@ -134,7 +134,7 @@ func Patch(
134134

135135
ch := make(chan error)
136136
go func() {
137-
ch <- patchWithContext(timeoutCtx, ch, image, reportFile, reportDirectory, platformSpecificErrors, patchedTag, suffix, workingFolder, scanner, format, output, ignoreError, push, bkOpts)
137+
ch <- patchWithContext(timeoutCtx, ch, image, reportPath, patchedTag, suffix, workingFolder, scanner, format, output, ignoreError, push, bkOpts)
138138
}()
139139

140140
select {
@@ -162,68 +162,54 @@ func removeIfNotDebug(workingFolder string) {
162162
func patchWithContext(
163163
ctx context.Context,
164164
ch chan error,
165-
image, reportFile, reportDirectory, platformSpecificErrors, patchedTag, suffix, workingFolder, scanner, format, output string,
165+
image, reportPath, patchedTag, suffix, workingFolder, scanner, format, output string,
166166
ignoreError, push bool,
167167
bkOpts buildkit.Opts,
168168
) error {
169-
if reportFile != "" && reportDirectory != "" {
170-
return fmt.Errorf("both report file and directory provided, please provide only one")
171-
}
172-
173-
// try report file
174-
if reportFile != "" {
175-
// check if reportFile exists
176-
if _, err := os.Stat(reportFile); os.IsNotExist(err) {
177-
return fmt.Errorf("report file %s does not exist", reportFile)
178-
}
179-
// check if reportFile is a file
180-
f, err := os.Stat(reportFile)
181-
if err != nil {
182-
// handle common errors
183-
if os.IsNotExist(err) {
184-
return fmt.Errorf("report file %s does not exist", reportFile)
185-
}
186-
return fmt.Errorf("failed to stat report file %s: %w", reportFile, err)
187-
}
188-
if f.IsDir() {
189-
return fmt.Errorf("report file %s is a directory, please provide a file", reportFile)
190-
}
191-
log.Debugf("Using report file: %s", reportFile)
169+
// Handle empty report path - single-arch patching without report
170+
if reportPath == "" {
192171
platform := types.PatchPlatform{
193172
Platform: platforms.Normalize(platforms.DefaultSpec()),
194173
}
195174
if platform.OS != LINUX {
196175
platform.OS = LINUX
197176
}
198-
result, err := patchSingleArchImage(ctx, ch, image, reportFile, patchedTag, suffix, workingFolder, scanner, format, output, platform, ignoreError, push, bkOpts, false)
199-
if err == nil && result != nil {
200-
log.Infof("Patched image (%s): %s\n", platform.OS+"/"+platform.Architecture, result.PatchedRef.String())
201-
}
202-
return err
203-
} else if reportDirectory == "" && reportFile == "" {
204-
platform := types.PatchPlatform{
205-
Platform: platforms.Normalize(platforms.DefaultSpec()),
206-
}
207-
if platform.OS != LINUX {
208-
platform.OS = LINUX
209-
}
210-
result, err := patchSingleArchImage(ctx, ch, image, reportFile, patchedTag, suffix, workingFolder, scanner, format, output, platform, ignoreError, push, bkOpts, false)
177+
result, err := patchSingleArchImage(ctx, ch, image, reportPath, patchedTag, suffix, workingFolder, scanner, format, output, platform, ignoreError, push, bkOpts, false)
211178
if err == nil && result != nil && result.PatchedRef != nil {
212179
log.Infof("Patched image (%s): %s\n", platform.OS+"/"+platform.Architecture, result.PatchedRef)
213180
}
214181
return err
215182
}
216183

217-
// must be dealing with a multi-arch image, check the directory
218-
f, err := os.Stat(reportDirectory)
219-
if err != nil {
220-
return err
184+
// Check if reportPath exists
185+
if _, err := os.Stat(reportPath); os.IsNotExist(err) {
186+
return fmt.Errorf("report path %s does not exist", reportPath)
221187
}
222-
if !f.IsDir() {
223-
return fmt.Errorf("provided report directory path %s is not a directory", reportDirectory)
188+
189+
// Get file info to determine if it's a file or directory
190+
f, err := os.Stat(reportPath)
191+
if err != nil {
192+
return fmt.Errorf("failed to stat report path %s: %w", reportPath, err)
224193
}
225194

226-
return patchMultiArchImage(ctx, ch, platformSpecificErrors, image, reportDirectory, patchedTag, suffix, workingFolder, scanner, format, output, ignoreError, push, bkOpts)
195+
if f.IsDir() {
196+
// Handle directory - multi-platform patching
197+
log.Debugf("Using report directory: %s", reportPath)
198+
return patchMultiPlatformImage(ctx, ch, image, reportPath, patchedTag, suffix, workingFolder, scanner, format, output, ignoreError, push, bkOpts)
199+
}
200+
// Handle file - single-arch patching
201+
log.Debugf("Using report file: %s", reportPath)
202+
platform := types.PatchPlatform{
203+
Platform: platforms.Normalize(platforms.DefaultSpec()),
204+
}
205+
if platform.OS != LINUX {
206+
platform.OS = LINUX
207+
}
208+
result, err := patchSingleArchImage(ctx, ch, image, reportPath, patchedTag, suffix, workingFolder, scanner, format, output, platform, ignoreError, push, bkOpts, false)
209+
if err == nil && result != nil {
210+
log.Infof("Patched image (%s): %s\n", platform.OS+"/"+platform.Architecture, result.PatchedRef.String())
211+
}
212+
return err
227213
}
228214

229215
func patchSingleArchImage(
@@ -234,15 +220,15 @@ func patchSingleArchImage(
234220
targetPlatform types.PatchPlatform,
235221
ignoreError, push bool,
236222
bkOpts buildkit.Opts,
237-
multiArch bool,
223+
multiPlatform bool,
238224
) (*types.PatchResult, error) {
239225
if reportFile == "" && output != "" {
240226
log.Warn("No vulnerability report was provided, so no VEX output will be generated.")
241227
}
242228

243229
// if the target platform is different from the host platform, we need to check if emulation is enabled
244-
// only need to do this check if were patching a multi-arch image
245-
if multiArch {
230+
// only need to do this check if were patching a multi-platform image
231+
if multiPlatform {
246232
hostPlatform := platforms.Normalize(platforms.DefaultSpec())
247233
if hostPlatform.OS != LINUX {
248234
hostPlatform.OS = LINUX
@@ -273,7 +259,7 @@ func patchSingleArchImage(
273259
if err != nil {
274260
return nil, err
275261
}
276-
if multiArch {
262+
if multiPlatform {
277263
patchedTag = archTag(patchedTag, targetPlatform.Architecture, targetPlatform.Variant)
278264
}
279265
patchedImageName := fmt.Sprintf("%s:%s", imageName.Name(), patchedTag)
@@ -760,14 +746,14 @@ func getRepoNameWithDigest(patchedImageName, imageDigest string) string {
760746
return nameWithDigest
761747
}
762748

763-
func patchMultiArchImage(
749+
func patchMultiPlatformImage(
764750
ctx context.Context,
765751
ch chan error,
766-
platformSpecificErrors, image, reportDir, patchedTag, suffix, workingFolder, scanner, format, output string,
752+
image, reportDir, patchedTag, suffix, workingFolder, scanner, format, output string,
767753
ignoreError, push bool,
768754
bkOpts buildkit.Opts,
769755
) error {
770-
log.Debugf("Handling platform specific errors with %s", platformSpecificErrors)
756+
log.Debugf("Handling platform specific errors with ignore-errors=%t", ignoreError)
771757
platforms, err := buildkit.DiscoverPlatforms(image, reportDir, scanner)
772758
if err != nil {
773759
return err
@@ -783,15 +769,12 @@ func patchMultiArchImage(
783769
patchResults := []types.PatchResult{}
784770

785771
handlePlatformErr := func(p types.PatchPlatform, err error) error {
786-
switch platformSpecificErrors {
787-
case "ignore":
788-
return nil
789-
case "skip":
772+
if ignoreError {
790773
log.Warnf("Ignoring error for platform %s: %v", p.OS+"/"+p.Architecture, err)
791774
return nil
792-
default:
793-
return fmt.Errorf("platform %s failed: %w", p.OS+"/"+p.Architecture, err)
794775
}
776+
log.Warnf("Error for platform %s: %v", p.OS+"/"+p.Architecture, err)
777+
return fmt.Errorf("platform %s failed: %w", p.OS+"/"+p.Architecture, err)
795778
}
796779

797780
for _, p := range platforms {
@@ -840,7 +823,7 @@ func patchMultiArchImage(
840823
}
841824

842825
if push {
843-
err = createMultiArchManifest(ctx, patchedImageName, patchResults)
826+
err = createMultiPlatformManifest(ctx, patchedImageName, patchResults)
844827
if err != nil {
845828
return fmt.Errorf("manifest list creation failed: %w", err)
846829
}
@@ -852,7 +835,7 @@ func patchMultiArchImage(
852835
for _, result := range patchResults {
853836
log.Infof(" docker push %s", result.PatchedRef.String())
854837
}
855-
log.Infof("To create and push the multi-arch manifest, run:")
838+
log.Infof("To create and push the multi-platform manifest, run:")
856839
refs := make([]string, len(patchResults))
857840
for i, result := range patchResults {
858841
refs[i] = result.PatchedRef.String()
@@ -864,7 +847,7 @@ func patchMultiArchImage(
864847
}
865848
}
866849

867-
log.Infof("Multi-arch image patched with tag %s", patchedImageName.String())
850+
log.Infof("Multi-platform image patched with tag %s", patchedImageName.String())
868851

869852
return nil
870853
}

pkg/patch/patch_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ func TestPatch_BuildReturnsNilResponse(t *testing.T) {
512512
err := Patch(
513513
context.Background(),
514514
30*time.Second,
515-
"alpine:3.19", "", "", "", "", "", "", "", "", "",
515+
"alpine:3.19", "", "", "", "", "", "", "",
516516
false, true,
517517
buildkit.Opts{},
518518
)

0 commit comments

Comments
 (0)