Skip to content

Commit 3daf0cb

Browse files
Add debug tracing behind --debug / AWS_ECR_CLIENT_DEBUG flag
Add a debug mode that prints detailed diagnostic information at every step of the push-and-scan flow to help diagnose the InvalidParameterException on imageDigest that occurs in CI with buildx/inline-cache builds but cannot be reproduced locally. When enabled (--debug or AWS_ECR_CLIENT_DEBUG=true), traces include: - Full raw Docker push stream (all JSON messages from daemon) - Each Aux message: raw JSON, parsed digest/tag/size - Push stream error messages - ImageId after push and after fallback logic - ToImageIdentifier inputs including raw hex bytes of digest - DescribeImageScanFindings parameters (repo, digest, tag, timeout) - Waiter attempt numbers, errors with types - Fallback scan describe results (status, description) All trace lines are prefixed with [DEBUG] for easy filtering. No output is produced when debug mode is off (the default). Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ef3c486 commit 3daf0cb

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

debug.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
3+
Copyright 2021 Andrey Devyatkin.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
*/
18+
19+
package main
20+
21+
import "fmt"
22+
23+
// debugEnabled is set to true via the --debug flag or AWS_ECR_CLIENT_DEBUG=true env var.
24+
var debugEnabled bool
25+
26+
// debugf prints a debug message when debug mode is enabled.
27+
func debugf(format string, args ...interface{}) {
28+
if debugEnabled {
29+
fmt.Printf("[DEBUG] "+format+"\n", args...)
30+
}
31+
}

docker.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type ImageId struct {
4343
// for empty fields to avoid sending empty strings that violate the ECR API
4444
// parameter constraints (imageDigest must match '[a-zA-Z0-9-_+.]{0,20}:[a-fA-F0-9]{1,128}').
4545
func (id ImageId) ToImageIdentifier() (*types.ImageIdentifier, error) {
46+
debugf("ToImageIdentifier: digest=%q (len=%d, bytes=%x) tag=%q", id.digest, len(id.digest), []byte(id.digest), id.tag)
4647
if id.digest == "" && id.tag == "" {
4748
return nil, fmt.Errorf("image identifier must have at least a digest or a tag, but both are empty")
4849
}
@@ -78,6 +79,8 @@ func imagePush(client *dockerClient.Client, authConfig dockerRegistry.AuthConfig
7879
return ImageId{}, err
7980
}
8081

82+
debugf("Full Docker push stream for %s (%d bytes):\n%s", imageRef, buf.Len(), buf.String())
83+
8184
return getImageIdFromDockerDaemonJsonMessages(*buf)
8285
}
8386

@@ -97,16 +100,20 @@ func getImageIdFromDockerDaemonJsonMessages(message bytes.Buffer) (ImageId, erro
97100
return result, err
98101
}
99102
if err := jsonMessage.Error; err != nil {
103+
debugf("Push stream error message: %v", err)
100104
return result, err
101105
}
102106
if jsonMessage.Aux != nil {
107+
debugf("Push stream Aux raw JSON: %s", string(*jsonMessage.Aux))
103108
var r dockerTypes.PushResult
104109
if err := json.Unmarshal(*jsonMessage.Aux, &r); err != nil {
105110
return result, err
106111
}
112+
debugf("Push stream Aux parsed: digest=%q tag=%q size=%d", r.Digest, r.Tag, r.Size)
107113
result.tag = r.Tag
108114
result.digest = r.Digest
109115
}
110116
}
117+
debugf("Final ImageId from push stream: digest=%q (len=%d) tag=%q", result.digest, len(result.digest), result.tag)
111118
return result, nil
112119
}

ecr.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@ func GetImageScanResults(client *ecr.Client, imageId ImageId, ecrRepoName string
173173
if err != nil {
174174
return nil, fmt.Errorf("cannot query scan results: %w", err)
175175
}
176+
digestStr := "<nil>"
177+
if imageIdentifier.ImageDigest != nil {
178+
digestStr = *imageIdentifier.ImageDigest
179+
}
180+
tagStr := "<nil>"
181+
if imageIdentifier.ImageTag != nil {
182+
tagStr = *imageIdentifier.ImageTag
183+
}
184+
debugf("GetImageScanResults: repo=%q imageDigest=%q imageTag=%q timeout=%s", ecrRepoName, digestStr, tagStr, timeout)
176185
input := ecr.DescribeImageScanFindingsInput{
177186
ImageId: imageIdentifier,
178187
RepositoryName: &ecrRepoName,
@@ -192,10 +201,13 @@ func GetImageScanResults(client *ecr.Client, imageId ImageId, ecrRepoName string
192201

193202
var waiterErr error
194203
for attempt := 0; attempt <= maxScanInitRetries; attempt++ {
204+
debugf("WaitForOutput attempt %d/%d", attempt+1, maxScanInitRetries+1)
195205
output, waiterErr = w.WaitForOutput(context.TODO(), &input, timeout)
196206
if waiterErr == nil {
207+
debugf("WaitForOutput succeeded")
197208
break
198209
}
210+
debugf("WaitForOutput error: %v (type: %T)", waiterErr, waiterErr)
199211
// Check if the waiter failed because the scan doesn't exist yet
200212
var scanNotFound *types.ScanNotFoundException
201213
if errors.As(waiterErr, &scanNotFound) {
@@ -213,14 +225,18 @@ func GetImageScanResults(client *ecr.Client, imageId ImageId, ecrRepoName string
213225
}
214226

215227
if waiterErr != nil {
228+
debugf("Waiter failed, attempting fallback DescribeImageScanFindings")
216229
// Handle unsupported images with Clair-based scanning: DescribeImageScanFindings
217230
// returns ScanStatusFailed with "UnsupportedImageError" in the description instead
218231
// of ScanStatusUnsupportedImage. That causes WaitForOutput to return the error.
219232
// So here we have to check the status description for "UnsupportedImageError" separately.
220233
failedOutput, describeErr := client.DescribeImageScanFindings(context.TODO(), &input)
221234
if describeErr != nil {
235+
debugf("Fallback DescribeImageScanFindings also failed: %v", describeErr)
222236
return nil, fmt.Errorf("waiting for scan failed: %w, and describing findings also failed: %w", waiterErr, describeErr)
223237
}
238+
debugf("Fallback DescribeImageScanFindings status=%q description=%v",
239+
failedOutput.ImageScanStatus.Status, failedOutput.ImageScanStatus.Description)
224240
if failedOutput.ImageScanStatus.Status == types.ScanStatusFailed &&
225241
failedOutput.ImageScanStatus.Description != nil &&
226242
strings.Contains(*failedOutput.ImageScanStatus.Description, "UnsupportedImageError") {

main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ func main() {
106106
EnvVars: []string{"AWS_ECR_CLIENT_SKIP_PUSH"},
107107
Destination: &skipPush,
108108
},
109+
&cli.BoolFlag{
110+
Name: "debug",
111+
Value: false,
112+
DefaultText: "false",
113+
Usage: "Enable debug output (prints raw Docker push stream, image identifiers, scan parameters, etc.).",
114+
EnvVars: []string{"AWS_ECR_CLIENT_DEBUG"},
115+
Destination: &debugEnabled,
116+
},
109117
},
110118
}
111119

@@ -156,6 +164,8 @@ func main() {
156164
return err
157165
}
158166

167+
debugf("ImageId after push: digest=%q tag=%q", imageId.digest, imageId.tag)
168+
159169
// Ensure we have at least the tag we pushed with, in case the Docker
160170
// daemon push stream did not include it in the Aux message.
161171
if imageId.tag == "" {
@@ -165,6 +175,7 @@ func main() {
165175
if imageId.digest == "" {
166176
fmt.Printf("Warning: Docker push did not return a digest, will identify image by tag only\n")
167177
}
178+
debugf("ImageId used for scan query: digest=%q tag=%q", imageId.digest, imageId.tag)
168179

169180
fmt.Printf("Checking scan result for the image %s\n", stageImageRef.String())
170181
client, err := GetECRClient()

0 commit comments

Comments
 (0)