Skip to content

Commit 9cd39ec

Browse files
committed
image/directory: assign version based on digest algorithm
Introduce version 1.2 and dynamically assign versions based on the digest algorithms used: - Version 1.1 for sha256-only images (backward compatibility) - Version 1.2 for images using non-sha256 digest algorithms (e.g., sha512) Add validation in both ImageDestination and ImageSource to: - Assume 1.1 if no version file found in dir transport images - Accept both version 1.1 and 1.2 - Refuse unsupported future versions Signed-off-by: Lokesh Mandvekar <[email protected]>
1 parent 517d3d3 commit 9cd39ec

File tree

4 files changed

+120
-9
lines changed

4 files changed

+120
-9
lines changed

image/directory/directory_dest.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,30 @@ import (
2020
"go.podman.io/storage/pkg/fileutils"
2121
)
2222

23-
const version = "Directory Transport Version: 1.1\n"
23+
// Write version file based on digest algorithm used
24+
// 1.1 for sha256-only images, 1.2 otherwise.
25+
const (
26+
versionPrefix = "Directory Transport Version: "
27+
version = versionPrefix + "1.2\n"
28+
version1_1 = versionPrefix + "1.1\n"
29+
)
2430

2531
// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
2632
// using the 'dir' transport
2733
var ErrNotContainerImageDir = errors.New("not a containers image directory, don't want to overwrite important data")
2834

35+
// ErrUnsupportedVersion indicates that the directory uses a version newer than we support
36+
var ErrUnsupportedVersion = errors.New("unsupported directory transport version")
37+
2938
type dirImageDestination struct {
3039
impl.Compat
3140
impl.PropertyMethodsInitialize
3241
stubs.IgnoresOriginalOCIConfig
3342
stubs.NoPutBlobPartialInitialize
3443
stubs.AlwaysSupportsSignatures
3544

36-
ref dirReference
45+
ref dirReference
46+
usesNonSHA256Digest bool
3747
}
3848

3949
// newImageDestination returns an ImageDestination for writing to a directory.
@@ -76,7 +86,11 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
7686
return nil, err
7787
}
7888
// check if contents of version file is what we expect it to be
79-
if string(contents) != version {
89+
versionStr := string(contents)
90+
if versionStr != version && versionStr != version1_1 {
91+
if versionStr > version {
92+
return nil, fmt.Errorf("%w: %q", ErrUnsupportedVersion, versionStr)
93+
}
8094
return nil, ErrNotContainerImageDir
8195
}
8296
} else {
@@ -94,11 +108,6 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
94108
return nil, fmt.Errorf("unable to create directory %q: %w", ref.resolvedPath, err)
95109
}
96110
}
97-
// create version file
98-
err = os.WriteFile(ref.versionPath(), []byte(version), 0o644)
99-
if err != nil {
100-
return nil, fmt.Errorf("creating version file %q: %w", ref.versionPath(), err)
101-
}
102111

103112
d := &dirImageDestination{
104113
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
@@ -166,6 +175,10 @@ func (d *dirImageDestination) PutBlobWithOptions(ctx context.Context, stream io.
166175
return private.UploadedBlob{}, err
167176
}
168177

178+
if blobDigest.Algorithm() != digest.Canonical {
179+
d.usesNonSHA256Digest = true
180+
}
181+
169182
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
170183
// On Windows, the “permissions of newly created files” argument to syscall.Open is
171184
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
@@ -258,6 +271,14 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
258271
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
259272
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
260273
func (d *dirImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
274+
versionToWrite := version1_1
275+
if d.usesNonSHA256Digest {
276+
versionToWrite = version
277+
}
278+
err := os.WriteFile(d.ref.versionPath(), []byte(versionToWrite), 0o644)
279+
if err != nil {
280+
return fmt.Errorf("writing version file %q: %w", d.ref.versionPath(), err)
281+
}
261282
return nil
262283
}
263284

image/directory/directory_src.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ type dirImageSource struct {
2727
// newImageSource returns an ImageSource reading from an existing directory.
2828
// The caller must call .Close() on the returned ImageSource.
2929
func newImageSource(ref dirReference) (private.ImageSource, error) {
30+
versionPath := ref.versionPath()
31+
contents, err := os.ReadFile(versionPath)
32+
if err != nil {
33+
if !os.IsNotExist(err) {
34+
return nil, fmt.Errorf("reading version file %q: %w", versionPath, err)
35+
}
36+
} else {
37+
versionStr := string(contents)
38+
if versionStr != version && versionStr != version1_1 {
39+
// Check if it's a future version we don't support
40+
if versionStr > version {
41+
return nil, fmt.Errorf("%w: %q", ErrUnsupportedVersion, versionStr)
42+
}
43+
return nil, fmt.Errorf("invalid version file content: %q", versionStr)
44+
}
45+
}
46+
3047
s := &dirImageSource{
3148
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
3249
HasThreadSafeGetBlob: false,

image/directory/directory_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"io"
88
"os"
9+
"path/filepath"
910
"testing"
1011

1112
"github.com/opencontainers/go-digest"
@@ -201,8 +202,78 @@ func TestGetPutSignatures(t *testing.T) {
201202
assert.Equal(t, signatures, sigs)
202203
}
203204

205+
func TestVersionAssignment(t *testing.T) {
206+
t.Run("SHA256 gets version 1.1", func(t *testing.T) {
207+
ref, tmpDir := refToTempDir(t)
208+
cache := memory.New()
209+
210+
dest, err := ref.NewImageDestination(context.Background(), nil)
211+
require.NoError(t, err)
212+
defer dest.Close()
213+
214+
blob := []byte("test-blob-sha256")
215+
_, err = dest.PutBlob(context.Background(), bytes.NewReader(blob), types.BlobInfo{Digest: "", Size: int64(len(blob))}, cache, false)
216+
require.NoError(t, err)
217+
218+
err = dest.Commit(context.Background(), nil)
219+
require.NoError(t, err)
220+
221+
versionBytes, err := os.ReadFile(filepath.Join(tmpDir, "version"))
222+
require.NoError(t, err)
223+
assert.Equal(t, "Directory Transport Version: 1.1\n", string(versionBytes))
224+
})
225+
226+
t.Run("Non-SHA256 gets version 1.2", func(t *testing.T) {
227+
ref, tmpDir := refToTempDir(t)
228+
cache := memory.New()
229+
230+
dest, err := ref.NewImageDestination(context.Background(), nil)
231+
require.NoError(t, err)
232+
defer dest.Close()
233+
234+
blob := []byte("test-blob-sha512")
235+
sha512Digest := digest.SHA512.FromBytes(blob)
236+
_, err = dest.PutBlob(context.Background(), bytes.NewReader(blob), types.BlobInfo{Digest: sha512Digest, Size: int64(len(blob))}, cache, false)
237+
require.NoError(t, err)
238+
239+
err = dest.Commit(context.Background(), nil)
240+
require.NoError(t, err)
241+
242+
versionBytes, err := os.ReadFile(filepath.Join(tmpDir, "version"))
243+
require.NoError(t, err)
244+
assert.Equal(t, "Directory Transport Version: 1.2\n", string(versionBytes))
245+
})
246+
247+
t.Run("Mixed digests get version 1.2", func(t *testing.T) {
248+
ref, tmpDir := refToTempDir(t)
249+
cache := memory.New()
250+
251+
dest, err := ref.NewImageDestination(context.Background(), nil)
252+
require.NoError(t, err)
253+
defer dest.Close()
254+
255+
blob1 := []byte("test-blob-sha256")
256+
_, err = dest.PutBlob(context.Background(), bytes.NewReader(blob1), types.BlobInfo{Digest: "", Size: int64(len(blob1))}, cache, false)
257+
require.NoError(t, err)
258+
259+
blob2 := []byte("test-blob-sha512")
260+
sha512Digest := digest.SHA512.FromBytes(blob2)
261+
_, err = dest.PutBlob(context.Background(), bytes.NewReader(blob2), types.BlobInfo{Digest: sha512Digest, Size: int64(len(blob2))}, cache, false)
262+
require.NoError(t, err)
263+
264+
err = dest.Commit(context.Background(), nil)
265+
require.NoError(t, err)
266+
267+
versionBytes, err := os.ReadFile(filepath.Join(tmpDir, "version"))
268+
require.NoError(t, err)
269+
assert.Equal(t, "Directory Transport Version: 1.2\n", string(versionBytes))
270+
})
271+
}
272+
204273
func TestSourceReference(t *testing.T) {
205274
ref, tmpDir := refToTempDir(t)
275+
err := os.WriteFile(filepath.Join(tmpDir, "version"), []byte("Directory Transport Version: 1.1\n"), 0o644)
276+
require.NoError(t, err)
206277

207278
src, err := ref.NewImageSource(context.Background(), nil)
208279
require.NoError(t, err)

image/directory/directory_transport_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ func TestReferenceNewImageNoValidManifest(t *testing.T) {
172172
}
173173

174174
func TestReferenceNewImageSource(t *testing.T) {
175-
ref, _ := refToTempDir(t)
175+
ref, tmpDir := refToTempDir(t)
176+
err := os.WriteFile(filepath.Join(tmpDir, "version"), []byte("Directory Transport Version: 1.1\n"), 0o644)
177+
require.NoError(t, err)
176178
src, err := ref.NewImageSource(context.Background(), nil)
177179
assert.NoError(t, err)
178180
defer src.Close()

0 commit comments

Comments
 (0)