diff --git a/e2e/tests/build/build.go b/e2e/tests/build/build.go index c4e993f4c..8c48082fc 100644 --- a/e2e/tests/build/build.go +++ b/e2e/tests/build/build.go @@ -383,6 +383,49 @@ var _ = ginkgo.Describe("devsy build test suite", ginkgo.Label("build"), ginkgo. }, ) }) + + ginkgo.It( + "kubernetes dockerless build cleans up .devsy-internal after streaming", + ginkgo.SpecTimeout(framework.TimeoutShort()), + func(ctx context.Context) { + if runtime.GOOS == osWindows { + ginkgo.Skip("skipping on windows") + } + + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/build/testdata/kubernetes") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + _ = f.DevsyProviderDelete(ctx, "kubernetes") + err = f.DevsyProviderAdd(ctx, "kubernetes") + framework.ExpectNoError(err) + err = f.DevsyProviderUse(ctx, "kubernetes", "-o", "KUBERNETES_NAMESPACE=devsy") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevsyWorkspaceDelete, tempDir) + + err = f.DevsyUp(ctx, tempDir) + framework.ExpectNoError(err) + + out, err := f.DevsySSH(ctx, tempDir, "echo -n $MY_TEST") + framework.ExpectNoError(err) + framework.ExpectEqual(out, "test456") + + workspaceArtifact := "/workspaces/" + filepath.Base(tempDir) + + "/.devcontainer/" + config.DevsyContextFeatureFolder + _, err = f.DevsySSH(ctx, tempDir, "test -e "+workspaceArtifact) + framework.ExpectError(err) + + hostArtifact := filepath.Join( + tempDir, + ".devcontainer", + config.DevsyContextFeatureFolder, + ) + _, statErr := os.Stat(hostArtifact) + framework.ExpectEqual(os.IsNotExist(statErr), true) + }, + ) }) func validateKubernetesDeploymentWithoutDocker( diff --git a/pkg/agent/tunnelserver/tunnelserver.go b/pkg/agent/tunnelserver/tunnelserver.go index 9c29d15d4..7386280bd 100644 --- a/pkg/agent/tunnelserver/tunnelserver.go +++ b/pkg/agent/tunnelserver/tunnelserver.go @@ -482,8 +482,6 @@ func (t *tunnelServer) StreamWorkspace( } } - excludes = append(excludes, config.BuildArtifactExcludes()...) - buf := bufio.NewWriterSize(NewStreamWriter(stream), 10*1024) err = extract.WriteTarExclude(buf, t.workspace.Source.LocalFolder, false, excludes) if err != nil { @@ -516,7 +514,7 @@ func (t *tunnelServer) StreamMount( return fmt.Errorf("mount %s is not allowed to download", message.Mount) } - excludes := append(t.workspaceIgnoreExcludes(), config.BuildArtifactExcludes()...) + excludes := t.workspaceIgnoreExcludes() buf := bufio.NewWriterSize(NewStreamWriter(stream), 10*1024) err := extract.WriteTarExclude(buf, mount.Source, false, excludes) diff --git a/pkg/agent/tunnelserver/tunnelserver_test.go b/pkg/agent/tunnelserver/tunnelserver_test.go index 12fd047db..4ed62ebb1 100644 --- a/pkg/agent/tunnelserver/tunnelserver_test.go +++ b/pkg/agent/tunnelserver/tunnelserver_test.go @@ -74,6 +74,105 @@ func TestStreamSnapshotVolumes_TarsMountTargets(t *testing.T) { require.True(t, found, "expected tar entry %q not found", wantName) } +type fakeStreamServer struct { + tunnel.Tunnel_StreamWorkspaceServer + chunks [][]byte +} + +func (f *fakeStreamServer) Send(c *tunnel.Chunk) error { + f.chunks = append(f.chunks, c.Content) + return nil +} + +func (f *fakeStreamServer) Context() context.Context { return context.Background() } + +func tarEntryNames(t *testing.T, chunks [][]byte) []string { + t.Helper() + var buf bytes.Buffer + for _, c := range chunks { + buf.Write(c) + } + var names []string + tr := tar.NewReader(&buf) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + names = append(names, hdr.Name) + } + return names +} + +func TestStreamWorkspace_IncludesDevsyInternalBuildArtifacts(t *testing.T) { + dir := t.TempDir() + internalDir := filepath.Join(dir, config.DevsyContextFeatureFolder) + require.NoError(t, os.MkdirAll(internalDir, 0o750)) + require.NoError( + t, + os.WriteFile( + filepath.Join(internalDir, "Dockerfile-without-features"), + []byte("FROM scratch"), + 0o600, + ), + ) + + srv := &tunnelServer{ + workspace: &provider2.Workspace{Source: provider2.WorkspaceSource{LocalFolder: dir}}, + } + + fake := &fakeStreamServer{} + require.NoError(t, srv.StreamWorkspace(&tunnel.Empty{}, fake)) + + names := tarEntryNames(t, fake.chunks) + require.Contains( + t, + names, + filepath.ToSlash(filepath.Join( + config.DevsyContextFeatureFolder, + "Dockerfile-without-features", + )), + ) +} + +func TestStreamMount_IncludesDevsyInternalBuildArtifacts(t *testing.T) { + dir := t.TempDir() + mountSource := filepath.Join(dir, "mount") + internalDir := filepath.Join(mountSource, config.DevsyContextFeatureFolder) + require.NoError(t, os.MkdirAll(internalDir, 0o750)) + require.NoError( + t, + os.WriteFile( + filepath.Join(internalDir, "Dockerfile-without-features"), + []byte("FROM scratch"), + 0o600, + ), + ) + + srv := &tunnelServer{ + mounts: []*config.Mount{ + {Source: mountSource, Target: "/workspaces/e2e"}, + }, + } + + fake := &fakeStreamServer{} + require.NoError( + t, + srv.StreamMount(&tunnel.StreamMountRequest{Mount: srv.mounts[0].String()}, fake), + ) + + names := tarEntryNames(t, fake.chunks) + require.Contains( + t, + names, + filepath.ToSlash(filepath.Join( + config.DevsyContextFeatureFolder, + "Dockerfile-without-features", + )), + ) +} + func TestRunWithResult_CancelBeforeResult(t *testing.T) { srv := New()