Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pkg/devcontainer/buildkit/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ func getImageDetails(
remote.WithContext(ctx),
)
if err != nil {
return nil, err
return nil, image.SanitizeRegistryError(err)
}
imageConfig, err := remoteImage.ConfigFile()
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions pkg/devcontainer/feature/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/devsy-org/devsy/pkg/extract"
"github.com/devsy-org/devsy/pkg/hash"
devsyhttp "github.com/devsy-org/devsy/pkg/http"
"github.com/devsy-org/devsy/pkg/image"
"github.com/devsy-org/devsy/pkg/log"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
Expand Down Expand Up @@ -169,6 +170,7 @@ func processOCIFeature(id string) (string, error) {
log.Debugf("fetching OCI image: reference=%s", ref.String())
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
err = image.SanitizeRegistryError(err)
log.Errorf("failed to fetch OCI image: error=%v, reference=%s", err, ref.String())
return "", err
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/image/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package image

import (
"errors"
"fmt"
"net/http"
"strings"

"github.com/google/go-containerregistry/pkg/v1/remote/transport"
)

var htmlMarkers = []string{"<!DOCTYPE", "<html", "<HTML", "<head", "<HEAD"}

func containsHTML(s string) bool {
for _, marker := range htmlMarkers {
if strings.Contains(s, marker) {
return true
}
}
return false
}

func SanitizeRegistryError(err error) error {
if err == nil {
return nil
}

var terr *transport.Error
if !errors.As(err, &terr) {
return err
}

if !containsHTML(err.Error()) {
return err
}

return fmt.Errorf(
"unexpected status code %d %s",
terr.StatusCode,
http.StatusText(terr.StatusCode),
)
}
92 changes: 92 additions & 0 deletions pkg/image/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package image

import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"testing"

"github.com/google/go-containerregistry/pkg/v1/remote/transport"
)

func makeTransportErrorWithBody(statusCode int, body string) error {
req, _ := http.NewRequest("GET", "https://mcr.microsoft.com/v2/", nil)
resp := &http.Response{
StatusCode: statusCode,
Body: io.NopCloser(strings.NewReader(body)),
Request: req,
}
return transport.CheckError(resp, http.StatusOK)
}

func TestSanitizeRegistryError_HTMLBody(t *testing.T) {
htmlBody := `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"><html><body><h1>403 Forbidden</h1></body></html>`
inner := makeTransportErrorWithBody(http.StatusForbidden, htmlBody)
wrapped := fmt.Errorf("retrieve image mcr.microsoft.com/devcontainers/base:ubuntu: %w", inner)

sanitized := SanitizeRegistryError(wrapped)
if sanitized == wrapped {
t.Fatal("expected error to be sanitized, got original back")
}
want := "unexpected status code 403 Forbidden"
if sanitized.Error() != want {
t.Errorf("got %q, want %q", sanitized.Error(), want)
}
}

func TestSanitizeRegistryError_HTMLWithSmallTag(t *testing.T) {
htmlBody := `<html><head><title>Error</title></head><body>Service Unavailable</body></html>`
inner := makeTransportErrorWithBody(http.StatusServiceUnavailable, htmlBody)

sanitized := SanitizeRegistryError(inner)
want := "unexpected status code 503 Service Unavailable"
if sanitized.Error() != want {
t.Errorf("got %q, want %q", sanitized.Error(), want)
}
}

func TestSanitizeRegistryError_JSONBodyPassesThrough(t *testing.T) {
jsonBody := `{"errors":[{"code":"DENIED","message":"access denied"}]}`
inner := makeTransportErrorWithBody(http.StatusForbidden, jsonBody)
wrapped := fmt.Errorf("retrieve image example.com/foo:latest: %w", inner)

sanitized := SanitizeRegistryError(wrapped)
if sanitized != wrapped {
t.Error("expected JSON-body error to pass through unchanged")
}
}

func TestSanitizeRegistryError_PlainTextBodyPassesThrough(t *testing.T) {
inner := makeTransportErrorWithBody(http.StatusInternalServerError, "internal server error")
wrapped := fmt.Errorf("retrieve image example.com/foo:latest: %w", inner)

sanitized := SanitizeRegistryError(wrapped)
if sanitized != wrapped {
t.Error("expected plain text error to pass through unchanged")
}
}

func TestSanitizeRegistryError_NonTransportError(t *testing.T) {
err := errors.New("network timeout")
sanitized := SanitizeRegistryError(err)
if sanitized != err {
t.Error("expected non-transport error to pass through unchanged")
}
}

func TestSanitizeRegistryError_Nil(t *testing.T) {
sanitized := SanitizeRegistryError(nil)
if sanitized != nil {
t.Error("expected nil to pass through as nil")
}
}

func TestSanitizeRegistryError_EmptyBodyPassesThrough(t *testing.T) {
inner := makeTransportErrorWithBody(http.StatusForbidden, "")
sanitized := SanitizeRegistryError(inner)
if sanitized != inner {
t.Error("expected empty-body error to pass through unchanged")
}
}
4 changes: 2 additions & 2 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func GetImage(ctx context.Context, image string) (v1.Image, error) {

img, err := remote.Image(ref, remote.WithAuthFromKeychain(keychain))
if err != nil {
return nil, fmt.Errorf("retrieve image %s: %w", image, err)
return nil, fmt.Errorf("retrieve image %s: %w", image, SanitizeRegistryError(err))
}

return img, err
Expand All @@ -54,7 +54,7 @@ func GetImageForArch(ctx context.Context, image, arch string) (v1.Image, error)

img, err := remote.Image(ref, remoteOptions...)
if err != nil {
return nil, fmt.Errorf("retrieve image %s: %w", image, err)
return nil, fmt.Errorf("retrieve image %s: %w", image, SanitizeRegistryError(err))
}

return img, err
Expand Down
Loading