github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/build/docker_builder_test.go (about)

     1  package build
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/opencontainers/go-digest"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/tilt-dev/tilt/internal/container"
    16  	"github.com/tilt-dev/tilt/internal/docker"
    17  	"github.com/tilt-dev/tilt/internal/testutils"
    18  )
    19  
    20  func TestDigestAsTag(t *testing.T) {
    21  	dig := digest.Digest("sha256:cc5f4c463f81c55183d8d737ba2f0d30b3e6f3670dbe2da68f0aac168e93fbb1")
    22  	tag, err := digestAsTag(dig)
    23  	if err != nil {
    24  		t.Fatal(err)
    25  	}
    26  
    27  	expected := "tilt-cc5f4c463f81c551"
    28  	if tag != expected {
    29  		t.Errorf("Expected %s, actual: %s", expected, tag)
    30  	}
    31  }
    32  
    33  func TestDigestMatchesRef(t *testing.T) {
    34  	dig := digest.Digest("sha256:cc5f4c463f81c55183d8d737ba2f0d30b3e6f3670dbe2da68f0aac168e93fbb1")
    35  	tag, err := digestAsTag(dig)
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  
    40  	ref, _ := container.ParseNamedTagged("windmill.build/image:" + tag)
    41  	if !digestMatchesRef(ref, dig) {
    42  		t.Errorf("Expected digest %s to match ref %s", dig, ref)
    43  	}
    44  }
    45  
    46  func TestDigestNotMatchesRef(t *testing.T) {
    47  	dig := digest.Digest("sha256:cc5f4c463f81c55183d8d737ba2f0d30b3e6f3670dbe2da68f0aac168e93fbb1")
    48  	ref, _ := container.ParseNamedTagged("windmill.build/image:tilt-deadbeef")
    49  	if digestMatchesRef(ref, dig) {
    50  		t.Errorf("Expected digest %s to not match ref %s", dig, ref)
    51  	}
    52  }
    53  
    54  func TestDigestAsTagToShort(t *testing.T) {
    55  	dig := digest.Digest("sha256:cc")
    56  	_, err := digestAsTag(dig)
    57  	expected := "too short"
    58  	if err == nil || !strings.Contains(err.Error(), expected) {
    59  		t.Errorf("expected error %q, actual: %v", expected, err)
    60  	}
    61  }
    62  
    63  func TestDigestFromSingleStepOutput(t *testing.T) {
    64  	f := newFakeDockerBuildFixture(t)
    65  
    66  	input := docker.ExampleBuildOutput1
    67  	expected := digest.Digest("sha256:11cd0b38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
    68  	actual, _, err := f.b.getDigestFromBuildOutput(f.ctx, bytes.NewBuffer([]byte(input)))
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	if actual != expected {
    73  		t.Errorf("Expected %s, got %s", expected, actual)
    74  	}
    75  }
    76  
    77  func TestDigestFromOutputV1_23(t *testing.T) {
    78  	f := newFakeDockerBuildFixture(t)
    79  
    80  	input := docker.ExampleBuildOutputV1_23
    81  	expected := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
    82  	f.fakeDocker.Images["11cd0b38bc3c"] = types.ImageInspect{ID: string(expected)}
    83  	actual, _, err := f.b.getDigestFromBuildOutput(f.ctx, bytes.NewBuffer([]byte(input)))
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  	if actual != expected {
    88  		t.Errorf("Expected %s, got %s", expected, actual)
    89  	}
    90  }
    91  
    92  func TestDumpImageDeployRef(t *testing.T) {
    93  	f := newFakeDockerBuildFixture(t)
    94  
    95  	digest := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
    96  	f.fakeDocker.Images["example-image:dev"] = types.ImageInspect{ID: string(digest)}
    97  	ref, err := f.b.DumpImageDeployRef(f.ctx, "example-image:dev")
    98  	require.NoError(t, err)
    99  	assert.Equal(t, "docker.io/library/example-image:tilt-11cd0eb38bc3ceb9", ref.String())
   100  }
   101  
   102  func makeDockerBuildErrorOutput(s string) string {
   103  	b := &bytes.Buffer{}
   104  	err := json.NewEncoder(b).Encode(s)
   105  	if err != nil {
   106  		panic(err)
   107  	}
   108  
   109  	return fmt.Sprintf(`{"errorDetail":{"message":%s},"error":%s}`, b.String(), b.String())
   110  }
   111  
   112  func TestCleanUpBuildKitErrors(t *testing.T) {
   113  	for _, tc := range []struct {
   114  		buildKitError     string
   115  		expectedTiltError string
   116  	}{
   117  		// actual error currently emitted by buildkit when a `RUN` fails
   118  		{
   119  			//nolint
   120  			buildKitError:     "failed to solve with frontend dockerfile.v0: failed to build LLB: executor failed running [/bin/sh -c go install github.com/tilt-dev/servantes/vigoda]: runc did not terminate sucessfully",
   121  			expectedTiltError: "executor failed running [/bin/sh -c go install github.com/tilt-dev/servantes/vigoda]",
   122  		},
   123  		//nolint
   124  		// artificial error - in case docker for some reason doesn't have "executor failed running", don't trim "runc did not terminate sucessfully"
   125  		{
   126  			//nolint
   127  			buildKitError:     "failed to solve with frontend dockerfile.v0: failed to build LLB: [/bin/sh -c go install github.com/tilt-dev/servantes/vigoda]: runc did not terminate sucessfully",
   128  			expectedTiltError: "[/bin/sh -c go install github.com/tilt-dev/servantes/vigoda]: runc did not terminate sucessfully",
   129  		},
   130  		// actual error currently emitted by buildkit when an `ADD` file is missing
   131  		{
   132  			buildKitError:     `failed to solve with frontend dockerfile.v0: failed to build LLB: failed to compute cache key: "/foo.txt" not found: not found`,
   133  			expectedTiltError: `"/foo.txt" not found`,
   134  		},
   135  		// artificial error - in case docker fails to emit the double "not found", don't trim the one at the end
   136  		// output in this case could do without the "failed to compute cache key", but this test is just ensuring we
   137  		// err on the side of caution, rather than that we're emitting an optimal message for an artificial error
   138  		{
   139  			buildKitError:     `failed to solve with frontend dockerfile.v0: failed to build LLB: failed to compute cache key: "/foo.txt": not found`,
   140  			expectedTiltError: `failed to compute cache key: "/foo.txt": not found`,
   141  		},
   142  		// artificial error - in case docker doesn't say "not found" at all
   143  		{
   144  			buildKitError:     `failed to solve with frontend dockerfile.v0: failed to build LLB: failed to compute cache key: "/foo.txt"`,
   145  			expectedTiltError: `failed to compute cache key: "/foo.txt"`,
   146  		},
   147  		// actual error - trying to ADD a file from a directory that doesn't exist locally
   148  		{
   149  			buildKitError:     `failed to compute cache key: failed to walk /var/lib/docker/tmp/buildkit-mount818620576/base/src: lstat /var/lib/docker/tmp/buildkit-mount818620576/base/src: no such file or directory`,
   150  			expectedTiltError: `base/src: no such file or directory`,
   151  		},
   152  		// check an unanticipated error that still has the annoying preamble
   153  		{
   154  			buildKitError:     "failed to solve with frontend dockerfile.v0: failed to build LLB: who knows, some made up explosion",
   155  			expectedTiltError: "who knows, some made up explosion",
   156  		},
   157  		{
   158  			// Error message when using
   159  			// # syntax=docker/dockerfile:experimental
   160  			//nolint
   161  			buildKitError:     "failed to solve with frontend dockerfile.v0: failed to solve with frontend gateway.v0: rpc error: code = Unknown desc = failed to build LLB: executor failed running [/bin/sh -c pip install python-dateutil]: runc did not terminate sucessfully",
   162  			expectedTiltError: "executor failed running [/bin/sh -c pip install python-dateutil]",
   163  		},
   164  	} {
   165  		t.Run(tc.expectedTiltError, func(t *testing.T) {
   166  			f := newFakeDockerBuildFixture(t)
   167  
   168  			ctx, _, _ := testutils.CtxAndAnalyticsForTest()
   169  			s := makeDockerBuildErrorOutput(tc.buildKitError)
   170  			_, _, err := f.b.getDigestFromBuildOutput(ctx, strings.NewReader(s))
   171  			require.NotNil(t, err)
   172  			require.Equal(t, fmt.Sprintf("ImageBuild: %s", tc.expectedTiltError), err.Error())
   173  		})
   174  	}
   175  }