Skip to content

Commit 079f74f

Browse files
authored
refactor(xattr): extract xattr operations to dedicated package (#331)
Signed-off-by: chlins <[email protected]>
1 parent 74b878f commit 079f74f

File tree

6 files changed

+301
-108
lines changed

6 files changed

+301
-108
lines changed

pkg/backend/build/builder.go

Lines changed: 61 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ import (
3636
spec "github.com/opencontainers/image-spec/specs-go"
3737
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3838
"github.com/sirupsen/logrus"
39-
"golang.org/x/sys/unix"
4039

4140
buildconfig "github.com/modelpack/modctl/pkg/backend/build/config"
4241
"github.com/modelpack/modctl/pkg/backend/build/hooks"
4342
"github.com/modelpack/modctl/pkg/backend/build/interceptor"
4443
pkgcodec "github.com/modelpack/modctl/pkg/codec"
4544
"github.com/modelpack/modctl/pkg/storage"
45+
"github.com/modelpack/modctl/pkg/xattr"
4646
)
4747

4848
// OutputType defines the type of output to generate.
@@ -288,73 +288,80 @@ func BuildModelConfig(modelConfig *buildconfig.Model, layers []ocispec.Descripto
288288

289289
// computeDigestAndSize computes the digest and size for the encoded content, using xattrs if available.
290290
func computeDigestAndSize(mediaType, path, workDirPath string, info os.FileInfo, reader io.Reader, codec pkgcodec.Codec) (io.Reader, string, int64, error) {
291-
var digest string
292-
var size int64
293-
291+
// Try to retrieve valid digest from xattrs cache.
294292
if pkgcodec.IsRawMediaType(mediaType) {
295-
// By default let's assume the mtime and size has changed.
296-
mtimeChanged := true
297-
sizeChanged := true
298-
299-
if mtime, err := getXattr(path, xattrMtimeKey(mediaType)); err == nil {
300-
if string(mtime) == fmt.Sprintf("%d", info.ModTime().UnixNano()) {
301-
mtimeChanged = false
302-
}
293+
if digest, size, ok := retrieveCachedDigest(path, info); ok {
294+
return reader, digest, size, nil
303295
}
296+
}
304297

305-
if sizeBytes, err := getXattr(path, xattrSizeKey(mediaType)); err == nil {
306-
if parsedSize, err := strconv.ParseInt(string(sizeBytes), 10, 64); err == nil {
307-
if parsedSize == info.Size() {
308-
sizeChanged = false
309-
}
310-
}
311-
}
298+
logrus.Infof("builder: calculating digest for file %s", path)
312299

313-
if !mtimeChanged && !sizeChanged {
314-
// Check xattrs for cached digest and size.
315-
if sha256, err := getXattr(path, xattrSha256Key(mediaType)); err == nil {
316-
digest = string(sha256)
317-
logrus.Infof("builder: retrieved sha256 hash from xattr for file %s [digest: %s]", path, digest)
318-
}
319-
320-
if sizeBytes, err := getXattr(path, xattrSizeKey(mediaType)); err == nil {
321-
if parsedSize, err := strconv.ParseInt(string(sizeBytes), 10, 64); err == nil {
322-
size = parsedSize
323-
logrus.Infof("builder: retrieved size from xattr for file %s [size: %d]", path, size)
324-
}
325-
}
326-
}
300+
hash := sha256.New()
301+
size, err := io.Copy(hash, reader)
302+
if err != nil {
303+
return reader, "", 0, fmt.Errorf("failed to copy content to hash: %w", err)
327304
}
305+
digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
328306

329-
// Compute digest and size if not retrieved from xattrs.
330-
if digest == "" {
331-
logrus.Infof("builder: calculating digest for file %s", path)
332-
var err error
333-
hash := sha256.New()
334-
size, err = io.Copy(hash, reader)
335-
if err != nil {
336-
return reader, "", 0, fmt.Errorf("failed to copy content to hash: %w", err)
337-
}
338-
digest = fmt.Sprintf("sha256:%x", hash.Sum(nil))
339-
logrus.Infof("builder: calculated digest for file %s [digest: %s]", path, digest)
307+
logrus.Infof("builder: calculated digest for file %s [digest: %s]", path, digest)
340308

341-
// Reset reader
342-
reader, err = resetReader(reader, path, workDirPath, codec)
343-
if err != nil {
344-
return reader, "", 0, err
345-
}
309+
// Reset reader for subsequent use.
310+
reader, err = resetReader(reader, path, workDirPath, codec)
311+
if err != nil {
312+
return reader, "", 0, err
313+
}
346314

347-
// Store xattrs if raw media type.
348-
if pkgcodec.IsRawMediaType(mediaType) {
349-
setXattr(path, xattrMtimeKey(mediaType), fmt.Appendf([]byte{}, "%d", info.ModTime().UnixNano()))
350-
setXattr(path, xattrSha256Key(mediaType), []byte(digest))
351-
setXattr(path, xattrSizeKey(mediaType), fmt.Appendf([]byte{}, "%d", size))
315+
// Update xattrs cache.
316+
if pkgcodec.IsRawMediaType(mediaType) {
317+
if err := updateCachedDigest(path, info.ModTime().UnixNano(), size, digest); err != nil {
318+
logrus.Warnf("builder: failed to update xattrs for file %s: %s", path, err)
352319
}
353320
}
354321

355322
return reader, digest, size, nil
356323
}
357324

325+
// retrieveCachedDigest checks if mtime and size match, then returns the cached digest.
326+
func retrieveCachedDigest(path string, info os.FileInfo) (string, int64, bool) {
327+
mtimeData, err := xattr.Get(path, xattr.MakeKey(xattr.KeyMtime))
328+
if err != nil || string(mtimeData) != strconv.FormatInt(info.ModTime().UnixNano(), 10) {
329+
return "", 0, false
330+
}
331+
332+
sizeData, err := xattr.Get(path, xattr.MakeKey(xattr.KeySize))
333+
if err != nil {
334+
return "", 0, false
335+
}
336+
cachedSize, err := strconv.ParseInt(string(sizeData), 10, 64)
337+
if err != nil || cachedSize != info.Size() {
338+
return "", 0, false
339+
}
340+
341+
digestData, err := xattr.Get(path, xattr.MakeKey(xattr.KeySha256))
342+
if err != nil {
343+
return "", 0, false
344+
}
345+
346+
digest := string(digestData)
347+
logrus.Infof("builder: retrieved from xattr cache for file %s [digest: %s]", path, digest)
348+
return digest, cachedSize, true
349+
}
350+
351+
// updateCachedDigest writes mtime, size, and digest to xattrs.
352+
func updateCachedDigest(path string, mtime, size int64, digest string) error {
353+
if err := xattr.Set(path, xattr.MakeKey(xattr.KeyMtime), []byte(strconv.FormatInt(mtime, 10))); err != nil {
354+
return err
355+
}
356+
if err := xattr.Set(path, xattr.MakeKey(xattr.KeySha256), []byte(digest)); err != nil {
357+
return err
358+
}
359+
if err := xattr.Set(path, xattr.MakeKey(xattr.KeySize), []byte(strconv.FormatInt(size, 10))); err != nil {
360+
return err
361+
}
362+
return nil
363+
}
364+
358365
// resetReader resets the reader to the beginning or re-encodes if not seekable.
359366
func resetReader(reader io.Reader, path, workDirPath string, codec pkgcodec.Codec) (io.Reader, error) {
360367
if seeker, ok := reader.(io.ReadSeeker); ok {
@@ -442,52 +449,3 @@ func getFileMetadata(path string) (modelspec.FileMetadata, error) {
442449

443450
return metadata, nil
444451
}
445-
446-
func xattrSha256Key(mediaType string) string {
447-
// Uniformity between linux and mac platforms is simplified by adding the prefix 'user.',
448-
// because the key may be unlimited under mac,
449-
// but on linux, in some cases, the user can only manipulate the user space.
450-
return fmt.Sprintf("user.%s.sha256", mediaType)
451-
}
452-
453-
func xattrSizeKey(mediaType string) string {
454-
// Uniformity between linux and mac platforms is simplified by adding the prefix 'user.',
455-
// because the key may be unlimited under mac,
456-
// but on linux, in some cases, the user can only manipulate the user space.
457-
return fmt.Sprintf("user.%s.size", mediaType)
458-
}
459-
460-
func xattrMtimeKey(mediaType string) string {
461-
// Uniformity between linux and mac platforms is simplified by adding the prefix 'user.',
462-
// because the key may be unlimited under mac,
463-
// but on linux, in some cases, the user can only manipulate the user space.
464-
return fmt.Sprintf("user.%s.mtime", mediaType)
465-
}
466-
467-
// getXattr retrieves an xattr value for a given key.
468-
func getXattr(path, key string) ([]byte, error) {
469-
var value []byte
470-
sz, err := unix.Getxattr(path, key, value)
471-
if err != nil {
472-
logrus.Warnf("builder: failed to get xattr %s for file %s: %v", key, path, err)
473-
return nil, err
474-
}
475-
476-
value = make([]byte, sz)
477-
_, err = unix.Getxattr(path, key, value)
478-
if err != nil {
479-
logrus.Warnf("builder: failed to get xattr %s for file %s: %v", key, path, err)
480-
return nil, err
481-
}
482-
483-
return value, nil
484-
}
485-
486-
// setXattr sets an xattr value for a given key.
487-
func setXattr(path, key string, value []byte) {
488-
if err := unix.Setxattr(path, key, value, 0); err != nil {
489-
logrus.Warnf("builder: failed to set xattr %s for file %s: %v", key, path, err)
490-
} else {
491-
logrus.Infof("builder: set xattr %s for file %s: %s", key, path, string(value))
492-
}
493-
}

pkg/backend/extract.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bufio"
2121
"context"
2222
"encoding/json"
23+
"errors"
2324
"fmt"
2425
"io"
2526

@@ -29,7 +30,7 @@ import (
2930
"github.com/sirupsen/logrus"
3031
"golang.org/x/sync/errgroup"
3132

32-
"github.com/modelpack/modctl/pkg/codec"
33+
pkgcodec "github.com/modelpack/modctl/pkg/codec"
3334
"github.com/modelpack/modctl/pkg/config"
3435
"github.com/modelpack/modctl/pkg/storage"
3536
)
@@ -89,6 +90,11 @@ func exportModelArtifact(ctx context.Context, store storage.Storage, manifest oc
8990

9091
bufferedReader := bufio.NewReaderSize(reader, defaultBufferSize)
9192
if err := extractLayer(layer, cfg.Output, bufferedReader); err != nil {
93+
if errors.Is(err, pkgcodec.ErrAlreadyUpToDate) {
94+
logrus.Debugf("extract: skipped layer %s because target is up-to-date", layer.Digest.String())
95+
return nil
96+
}
97+
9298
return fmt.Errorf("failed to extract layer %s: %w", layer.Digest.String(), err)
9399
}
94100

@@ -118,12 +124,16 @@ func extractLayer(desc ocispec.Descriptor, outputDir string, reader io.Reader) e
118124

119125
}
120126

121-
codec, err := codec.New(codec.TypeFromMediaType(desc.MediaType))
127+
codec, err := pkgcodec.New(pkgcodec.TypeFromMediaType(desc.MediaType))
122128
if err != nil {
123129
return fmt.Errorf("failed to create codec for media type %s: %w", desc.MediaType, err)
124130
}
125131

126132
if err := codec.Decode(outputDir, filepath, reader, desc); err != nil {
133+
if errors.Is(err, pkgcodec.ErrAlreadyUpToDate) {
134+
return err
135+
}
136+
127137
return fmt.Errorf("failed to decode the layer %s to output directory: %w", desc.Digest.String(), err)
128138
}
129139

pkg/backend/pull.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package backend
1919
import (
2020
"context"
2121
"encoding/json"
22+
"errors"
2223
"fmt"
2324
"io"
2425

@@ -30,6 +31,7 @@ import (
3031

3132
internalpb "github.com/modelpack/modctl/internal/pb"
3233
"github.com/modelpack/modctl/pkg/backend/remote"
34+
"github.com/modelpack/modctl/pkg/codec"
3335
"github.com/modelpack/modctl/pkg/config"
3436
"github.com/modelpack/modctl/pkg/storage"
3537
)
@@ -255,9 +257,15 @@ func pullAndExtractFromRemote(ctx context.Context, pb *internalpb.ProgressBar, p
255257
reader = io.TeeReader(reader, hash)
256258

257259
if err := extractLayer(desc, outputDir, reader); err != nil {
258-
err = fmt.Errorf("failed to extract the blob %s to output directory: %w", desc.Digest.String(), err)
259-
pb.Abort(desc.Digest.String(), err)
260-
return err
260+
if errors.Is(err, codec.ErrAlreadyUpToDate) {
261+
logrus.Debugf("pull: skipped extracting blob %s because target is up-to-date", desc.Digest.String())
262+
pb.Complete(desc.Digest.String(), fmt.Sprintf("%s %s", internalpb.NormalizePrompt("Skipped blob"), desc.Digest.String()))
263+
return nil
264+
}
265+
266+
wrapped := fmt.Errorf("failed to extract the blob %s to output directory: %w", desc.Digest.String(), err)
267+
pb.Abort(desc.Digest.String(), wrapped)
268+
return wrapped
261269
}
262270

263271
// validate the digest of the blob.

0 commit comments

Comments
 (0)