diff --git a/go.mod b/go.mod index 76c36d4d7..75d2c3faf 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.21.5 require ( github.com/chainguard-dev/clog v1.2.3-0.20240116182827-04bee692f7a8 - github.com/chainguard-dev/go-apk v0.0.0-20240130195846-91a06ffe6715 + github.com/chainguard-dev/go-apk v0.0.0-20240131184508-79426d58f590 github.com/dominodatalab/os-release v0.0.0-20190522011736-bcdb4a3e3c2f github.com/go-git/go-git/v5 v5.11.0 github.com/google/go-cmp v0.6.0 diff --git a/go.sum b/go.sum index 0a3ac874a..e221ae8f8 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chainguard-dev/clog v1.2.3-0.20240116182827-04bee692f7a8 h1:E2y3L/nM7vRzwyROQgmuB+Egm/d1rHOyip0Bq4AyVow= github.com/chainguard-dev/clog v1.2.3-0.20240116182827-04bee692f7a8/go.mod h1:cV516KZWqYc/phZsCNwF36u/KMGS+Gj5Uqeb8Hlp95Y= -github.com/chainguard-dev/go-apk v0.0.0-20240130195846-91a06ffe6715 h1:riuOFg3Ay1Js10GQtCAsCL2Hp2DJweUlYjKaxXteYV8= -github.com/chainguard-dev/go-apk v0.0.0-20240130195846-91a06ffe6715/go.mod h1:OdsmvVJb8RNVcTVQ7x07L319LLeiRaRnnsmj8qBBgb4= +github.com/chainguard-dev/go-apk v0.0.0-20240131184508-79426d58f590 h1:JrEDEsQm7KqVApLL//o9JvSqESe7pn+L3oXCHQuvPl0= +github.com/chainguard-dev/go-apk v0.0.0-20240131184508-79426d58f590/go.mod h1:OdsmvVJb8RNVcTVQ7x07L319LLeiRaRnnsmj8qBBgb4= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= diff --git a/internal/cli/dot.go b/internal/cli/dot.go index 3f2e12c09..c12856f07 100644 --- a/internal/cli/dot.go +++ b/internal/cli/dot.go @@ -16,6 +16,7 @@ package cli import ( "context" + "errors" "fmt" "net" "net/http" @@ -131,9 +132,9 @@ func DotCmd(ctx context.Context, configFile string, archs []types.Architecture, } log.Infof("using working directory %s", wd) - pkgs, _, err := bc.BuildPackageList(ctx) - if err != nil { - return fmt.Errorf("failed to get package list for image: %w", err) + pkgs, _, resolveErr := bc.BuildPackageList(ctx) + if resolveErr != nil { + log.Errorf("failed to get package list for image: %v", resolveErr) } dmap := map[string][]string{} @@ -290,6 +291,13 @@ func DotCmd(ctx context.Context, configFile string, archs []types.Architecture, renderProvs(pkg) } + if resolveErr != nil { + errorNode := dot.NewNode("❌ error") + + out.AddNode(errorNode) + walkErrors(out, resolveErr, errorNode) + } + return out } @@ -363,3 +371,71 @@ func link(args []string, pkg string) string { } return ret } + +type unwrapper interface { + Unwrap() error +} + +type unwrappers interface { + Unwrap() []error +} + +func canUnwrap(err error) bool { + if _, ok := err.(unwrapper); ok { //nolint:errorlint + return true + } + + if _, ok := err.(unwrappers); ok { //nolint:errorlint + return true + } + + return false +} + +func makeNode(out *dot.Graph, err error, parent *dot.Node) *dot.Node { + nodeName, label := errToNode(err) + if nodeName == "" { + if canUnwrap(err) { + return parent + } + + nodeName = "❌ " + err.Error() + } + + node := dot.NewNode(nodeName) + out.AddNode(node) + edge := dot.NewEdge(parent, node) + if label != "" { + if err := edge.Set("label", label); err != nil { + panic(err) + } + } + out.AddEdge(edge) + + return node +} + +func walkErrors(out *dot.Graph, err error, parent *dot.Node) { + node := makeNode(out, err, parent) + + if wrapped := errors.Unwrap(err); wrapped != nil { + walkErrors(out, wrapped, node) + } else if mw, ok := err.(unwrappers); ok { //nolint:errorlint + for _, wrapped := range mw.Unwrap() { + walkErrors(out, wrapped, node) + } + } +} + +func errToNode(err error) (string, string) { + switch v := err.(type) { //nolint:errorlint + case *apk.ConstraintError: + return v.Constraint, "solving constraint" + case *apk.DepError: + return pkgver(v.Package), "resolving deps" + case *apk.DisqualifiedError: + return pkgver(v.Package), "" + } + + return "", "" +}