-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #107 from dtrudg/instrumented
feat: instrumented Image/Index/Layer
- Loading branch information
Showing
4 changed files
with
430 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// Copyright 2024-2025 Sylabs Inc. All rights reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package instrumented | ||
|
||
import ( | ||
"log/slog" | ||
"time" | ||
|
||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
"github.com/google/go-containerregistry/pkg/v1/partial" | ||
"github.com/google/go-containerregistry/pkg/v1/types" | ||
) | ||
|
||
type wrappedImage struct { | ||
inner v1.Image | ||
log *slog.Logger | ||
} | ||
|
||
// Image returns a wrapped Image that outputs instrumentation to log. | ||
func Image(img v1.Image, log *slog.Logger) (v1.Image, error) { | ||
h, err := img.Digest() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &wrappedImage{ | ||
inner: img, | ||
log: log.With(slog.String("image", h.Hex)), | ||
}, nil | ||
} | ||
|
||
// Descriptor returns a Descriptor for the image manifest. | ||
func (img *wrappedImage) Descriptor() (*v1.Descriptor, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("Descriptor()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return partial.Descriptor(img.inner) | ||
} | ||
|
||
// MediaType of this image's manifest. | ||
func (img *wrappedImage) MediaType() (types.MediaType, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("MediaType()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.MediaType() | ||
} | ||
|
||
// Size returns the size of the manifest. | ||
func (img *wrappedImage) Size() (int64, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("Size()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.Size() | ||
} | ||
|
||
// Digest returns the sha256 of this image's manifest. | ||
func (img *wrappedImage) Digest() (v1.Hash, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("Digest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.Digest() | ||
} | ||
|
||
// Manifest returns this image's Manifest object. | ||
func (img *wrappedImage) Manifest() (*v1.Manifest, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("Manifest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.Manifest() | ||
} | ||
|
||
// RawManifest returns the serialized bytes of Manifest(). | ||
func (img *wrappedImage) RawManifest() ([]byte, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("RawManifest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.RawManifest() | ||
} | ||
|
||
// ConfigName returns the hash of the image's config file, also known as the Image ID. | ||
func (img *wrappedImage) ConfigName() (v1.Hash, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("ConfigName()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.ConfigName() | ||
} | ||
|
||
// ConfigFile returns this image's config file. | ||
func (img *wrappedImage) ConfigFile() (*v1.ConfigFile, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("ConfigFile()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.ConfigFile() | ||
} | ||
|
||
// RawConfigFile returns the serialized bytes of ConfigFile(). | ||
func (img *wrappedImage) RawConfigFile() ([]byte, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("RawConfigFile()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return img.inner.RawConfigFile() | ||
} | ||
|
||
// Layers returns the ordered collection of filesystem layers that comprise this image. | ||
func (img *wrappedImage) Layers() ([]v1.Layer, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("Layers()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
ls, err := img.inner.Layers() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for i, l := range ls { | ||
l, err := Layer(l, img.log) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
ls[i] = l | ||
} | ||
|
||
return ls, nil | ||
} | ||
|
||
// LayerByDigest returns a Layer for interacting with a particular layer of the image, looking it | ||
// up by "digest" (the compressed hash). | ||
func (img *wrappedImage) LayerByDigest(h v1.Hash) (v1.Layer, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("LayerByDigest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
l, err := img.inner.LayerByDigest(h) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return Layer(l, img.log) | ||
} | ||
|
||
// LayerByDiffID is an analog to LayerByDigest, looking up by "diff id" (the uncompressed hash). | ||
func (img *wrappedImage) LayerByDiffID(h v1.Hash) (v1.Layer, error) { | ||
defer func(t time.Time) { | ||
img.log.Info("LayerByDiffID()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
l, err := img.inner.LayerByDiffID(h) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return Layer(l, img.log) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright 2024-2025 Sylabs Inc. All rights reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package instrumented | ||
|
||
import ( | ||
"log/slog" | ||
"time" | ||
|
||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
"github.com/google/go-containerregistry/pkg/v1/types" | ||
) | ||
|
||
type wrappedIndex struct { | ||
inner v1.ImageIndex | ||
log *slog.Logger | ||
} | ||
|
||
// Index returns a wrapped ImageIndex that outputs instrumentation to log. | ||
func Index(ii v1.ImageIndex, log *slog.Logger) (v1.ImageIndex, error) { | ||
h, err := ii.Digest() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &wrappedIndex{ | ||
inner: ii, | ||
log: log.With(slog.String("index", h.Hex)), | ||
}, nil | ||
} | ||
|
||
// MediaType of this image's manifest. | ||
func (ii *wrappedIndex) MediaType() (types.MediaType, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("MediaType()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return ii.inner.MediaType() | ||
} | ||
|
||
// Digest returns the sha256 of this image's manifest. | ||
func (ii *wrappedIndex) Digest() (v1.Hash, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("Digest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return ii.inner.Digest() | ||
} | ||
|
||
// Size returns the size of the manifest. | ||
func (ii *wrappedIndex) Size() (int64, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("Size()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return ii.inner.Size() | ||
} | ||
|
||
// IndexManifest returns this image index's manifest object. | ||
func (ii *wrappedIndex) IndexManifest() (*v1.IndexManifest, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("IndexManifest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return ii.inner.IndexManifest() | ||
} | ||
|
||
// RawManifest returns the serialized bytes of IndexManifest(). | ||
func (ii *wrappedIndex) RawManifest() ([]byte, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("RawManifest()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
return ii.inner.RawManifest() | ||
} | ||
|
||
// Image returns a v1.Image that this ImageIndex references. | ||
func (ii *wrappedIndex) Image(d v1.Hash) (v1.Image, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("Image()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
img, err := ii.inner.Image(d) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return Image(img, ii.log) | ||
} | ||
|
||
// ImageIndex returns a v1.ImageIndex that this ImageIndex references. | ||
func (ii *wrappedIndex) ImageIndex(d v1.Hash) (v1.ImageIndex, error) { | ||
defer func(t time.Time) { | ||
ii.log.Info("ImageIndex()", slog.Duration("dur", time.Since(t))) | ||
}(time.Now()) | ||
|
||
idx, err := ii.inner.ImageIndex(d) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return Index(idx, ii.log) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// Copyright 2024-2025 Sylabs Inc. All rights reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package instrumented | ||
|
||
import ( | ||
"io" | ||
"log/slog" | ||
"time" | ||
|
||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
"github.com/google/go-containerregistry/pkg/v1/partial" | ||
"github.com/google/go-containerregistry/pkg/v1/types" | ||
) | ||
|
||
type wrappedLayer struct { | ||
inner v1.Layer | ||
log *slog.Logger | ||
} | ||
|
||
// Layer returns a wrapped Layer that outputs instrumentation to log. | ||
func Layer(l v1.Layer, log *slog.Logger) (v1.Layer, error) { | ||
h, err := l.Digest() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &wrappedLayer{ | ||
inner: l, | ||
log: log.With(slog.String("layer", h.Hex)), | ||
}, nil | ||
} | ||
|
||
// Digest returns the Hash of the compressed layer. | ||
func (l *wrappedLayer) Digest() (v1.Hash, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("Digest()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
return l.inner.Digest() | ||
} | ||
|
||
// DiffID implements v1.Layer. | ||
func (l *wrappedLayer) DiffID() (v1.Hash, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("DiffID()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
return l.inner.DiffID() | ||
} | ||
|
||
// Compressed returns an io.ReadCloser for the compressed layer contents. | ||
func (l *wrappedLayer) Compressed() (io.ReadCloser, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("Compressed()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
rc, err := l.inner.Compressed() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return readCloser(rc, l.log.With(slog.Bool("compressed", true))), nil | ||
} | ||
|
||
// Uncompressed implements v1.Layer. | ||
func (l *wrappedLayer) Uncompressed() (io.ReadCloser, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("Uncompressed()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
rc, err := l.inner.Uncompressed() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return readCloser(rc, l.log.With(slog.Bool("compressed", false))), nil | ||
} | ||
|
||
// Size returns the compressed size of the Layer. | ||
func (l *wrappedLayer) Size() (int64, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("Size()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
return l.inner.Size() | ||
} | ||
|
||
// MediaType returns the media type of the Layer. | ||
func (l *wrappedLayer) MediaType() (types.MediaType, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("MediaType()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
return l.inner.MediaType() | ||
} | ||
|
||
// Descriptor returns a Descriptor for the layer. | ||
func (l *wrappedLayer) Descriptor() (*v1.Descriptor, error) { | ||
defer func(t time.Time) { | ||
l.log.Info("Descriptor()", | ||
slog.Duration("dur", time.Since(t)), | ||
) | ||
}(time.Now()) | ||
|
||
return partial.Descriptor(l.inner) | ||
} |
Oops, something went wrong.