Skip to content

Commit 983dbfe

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: - Accept both version 1.1 and 1.2 - Refuse future versions we don't support Signed-off-by: Lokesh Mandvekar <[email protected]>
1 parent 14da3c1 commit 983dbfe

File tree

4 files changed

+121
-5
lines changed

4 files changed

+121
-5
lines changed

image/directory/directory_dest.go

Lines changed: 29 additions & 4 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 {
@@ -95,7 +109,8 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im
95109
}
96110
}
97111
// create version file
98-
err = os.WriteFile(ref.versionPath(), []byte(version), 0o644)
112+
// Start with 1.1 for maximum compatibility
113+
err = os.WriteFile(ref.versionPath(), []byte(version1_1), 0o644)
99114
if err != nil {
100115
return nil, fmt.Errorf("creating version file %q: %w", ref.versionPath(), err)
101116
}
@@ -166,6 +181,10 @@ func (d *dirImageDestination) PutBlobWithOptions(ctx context.Context, stream io.
166181
return private.UploadedBlob{}, err
167182
}
168183

184+
if blobDigest.Algorithm() != digest.Canonical {
185+
d.usesNonSHA256Digest = true
186+
}
187+
169188
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
170189
// On Windows, the “permissions of newly created files” argument to syscall.Open is
171190
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
@@ -258,6 +277,12 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
258277
// - Uploaded data MAY be visible to others before CommitWithOptions() is called
259278
// - Uploaded data MAY be removed or MAY remain around if Close() is called without CommitWithOptions() (i.e. rollback is allowed but not guaranteed)
260279
func (d *dirImageDestination) CommitWithOptions(ctx context.Context, options private.CommitOptions) error {
280+
if d.usesNonSHA256Digest {
281+
err := os.WriteFile(d.ref.versionPath(), []byte(version), 0o644)
282+
if err != nil {
283+
return fmt.Errorf("writing version file %q: %w", d.ref.versionPath(), err)
284+
}
285+
}
261286
return nil
262287
}
263288

image/directory/directory_src.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ 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("version file not found at %q: not a valid directory transport image", versionPath)
35+
}
36+
return nil, fmt.Errorf("reading version file %q: %w", versionPath, err)
37+
}
38+
39+
versionStr := string(contents)
40+
if versionStr != version && versionStr != version1_1 {
41+
// Check if it's a future version we don't support
42+
if versionStr > version {
43+
return nil, fmt.Errorf("%w: %q", ErrUnsupportedVersion, versionStr)
44+
}
45+
return nil, fmt.Errorf("invalid version file content: %q", versionStr)
46+
}
47+
3048
s := &dirImageSource{
3149
PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{
3250
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)