diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 4122e077927f..0d43a849c259 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -264,8 +264,11 @@ func Export(name string, s Reader) io.ReadCloser { reader, writer := io.Pipe() go func() { tw := tar.NewWriter(writer) - defer tw.Close() - defer writer.Close() + defer func() { + // Close the tar writer first, so that the padding and the + // end-of-archive marker are written before the pipe is closed. + writer.CloseWithError(tw.Close()) + }() meta, err := s.GetMetadata(name) if err != nil { writer.CloseWithError(err) diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index 25bfd87c75c0..9640fc7c59ac 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -113,6 +113,35 @@ func TestRemove(t *testing.T) { assert.Equal(t, 0, len(f)) } +func TestExportWritesCompleteArchive(t *testing.T) { + s := New(t.TempDir(), testCfg) + err := s.CreateOrUpdate( + Metadata{ + Endpoints: map[string]any{ + "ep1": endpoint{Foo: "bar"}, + }, + Metadata: context{Bar: "baz"}, + Name: "source", + }) + assert.NilError(t, err) + // "test-data" doesn't fill its 512-byte block, so the archive needs + // padding after it. + assert.NilError(t, s.ResetEndpointTLSMaterial("source", "ep1", &EndpointTLSData{ + Files: map[string][]byte{ + "file1": []byte("test-data"), + }, + })) + + r := Export("source", s) + defer r.Close() + data, err := io.ReadAll(r) + assert.NilError(t, err) + + // A tar archive is made of 512-byte blocks, and ends with two blocks of zeros. + assert.Check(t, is.Equal(len(data)%512, 0)) + assert.Check(t, bytes.HasSuffix(data, make([]byte, 2*512)), "missing end-of-archive marker") +} + func TestListEmptyStore(t *testing.T) { result, err := New(t.TempDir(), testCfg).List() assert.NilError(t, err)