Skip to content

Commit

Permalink
feat: ReplaceImage / ReplaceIndex
Browse files Browse the repository at this point in the history
Implement `ReplaceImage` / `ReplaceIndex` to replace manifests selected
by a provided matcher with a new Image or ImageIndex.

Fixes #85
  • Loading branch information
dtrudg committed Oct 1, 2024
1 parent a8566db commit 8f4142f
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 5 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
58 changes: 53 additions & 5 deletions pkg/sif/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,17 +408,27 @@ func (f *OCIFileImage) append(add mutate.Appendable, opts ...AppendOpt) error {
return err
}

ri, err = appendToIndex(ri, add, ao)
if err != nil {
return err
}

return f.UpdateRootIndex(ri, OptUpdateTempDir(ao.tempDir))
}

func appendToIndex(base v1.ImageIndex, add mutate.Appendable, ao appendOpts) (v1.ImageIndex, error) {
ia := mutate.IndexAddendum{Add: add}

var err error
if ao.ref != nil {
ri, err = removeRefAnnotation(ri, ao.ref)
base, err = removeRefAnnotation(base, ao.ref)
if err != nil {
return err
return nil, err
}

d, err := partial.Descriptor(add)
if err != nil {
return err
return nil, err
}
if d.Annotations != nil {
ia.Annotations = maps.Clone(d.Annotations)
Expand All @@ -427,9 +437,8 @@ func (f *OCIFileImage) append(add mutate.Appendable, opts ...AppendOpt) error {
}
ia.Annotations[imagespec.AnnotationRefName] = ao.ref.Name()
}
ri = mutate.AppendManifests(ri, ia)

return f.UpdateRootIndex(ri, OptUpdateTempDir(ao.tempDir))
return mutate.AppendManifests(base, ia), nil
}

// removeRefAnnotation removes an existing "org.opencontainers.image.ref.name"
Expand Down Expand Up @@ -462,3 +471,42 @@ func (f *OCIFileImage) RemoveManifests(matcher match.Matcher) error {
}
return f.UpdateRootIndex(mutate.RemoveManifests(ri, matcher))
}

// ReplaceImage writes img to the SIF, replacing any existing manifest that is
// selected by the matcher. Any blobs in the SIF that are no longer referenced
// are removed from the SIF.
func (f *OCIFileImage) ReplaceImage(img v1.Image, matcher match.Matcher, opts ...AppendOpt) error {
return f.replace(img, matcher, opts...)
}

// ReplaceIndex writes ii to the SIF, replacing any existing manifest that is
// selected by the matcher. Any blobs in the SIF that are no longer referenced
// are removed from the SIF.
func (f *OCIFileImage) ReplaceIndex(ii v1.ImageIndex, matcher match.Matcher, opts ...AppendOpt) error {
return f.replace(ii, matcher, opts...)
}

func (f *OCIFileImage) replace(add mutate.Appendable, matcher match.Matcher, opts ...AppendOpt) error {
ao := appendOpts{
tempDir: os.TempDir(),
}
for _, opt := range opts {
if err := opt(&ao); err != nil {
return err
}
}

ri, err := f.RootIndex()
if err != nil {
return err
}

ri = mutate.RemoveManifests(ri, matcher)

ri, err = appendToIndex(ri, add, ao)
if err != nil {
return err
}

return f.UpdateRootIndex(ri, OptUpdateTempDir(ao.tempDir))
}
92 changes: 92 additions & 0 deletions pkg/sif/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,95 @@ func TestRemoveManifests(t *testing.T) {
})
}
}

//nolint:dupl
func TestReplace(t *testing.T) {
r := rand.NewSource(randomSeed)
newImage, err := random.Image(64, 1, random.WithSource(r))
if err != nil {
t.Fatal(err)
}
newIndex, err := random.Index(64, 1, 1, random.WithSource(r))
if err != nil {
t.Fatal(err)
}

replaceImage := func(ofi *sif.OCIFileImage, m match.Matcher) error { return ofi.ReplaceImage(newImage, m) }
replaceIndex := func(ofi *sif.OCIFileImage, m match.Matcher) error { return ofi.ReplaceIndex(newIndex, m) }
tests := []struct {
name string
base string
replacement func(ofi *sif.OCIFileImage, m match.Matcher) error
matcher match.Matcher
}{
{
name: "ReplaceImageManifest",
base: "hello-world-docker-v2-manifest",
replacement: replaceImage,
matcher: match.Platforms(v1.Platform{OS: "linux", Architecture: "arm64", Variant: "v8"}),
},
{
name: "ReplaceImageManifestList",
base: "hello-world-docker-v2-manifest-list",
replacement: replaceImage,
matcher: match.Platforms(v1.Platform{OS: "linux", Architecture: "arm64", Variant: "v8"}),
},
{
name: "ReplaceImageNoMatch",
base: "hello-world-docker-v2-manifest",
replacement: replaceImage,
matcher: match.Platforms(v1.Platform{OS: "linux", Architecture: "m68k"}),
},
{
name: "ReplaceIndexManifest",
base: "hello-world-docker-v2-manifest",
replacement: replaceIndex,
matcher: match.Platforms(v1.Platform{OS: "linux", Architecture: "arm64", Variant: "v8"}),
},
{
name: "ReplaceIndexManifestList",
base: "hello-world-docker-v2-manifest-list",
replacement: replaceIndex,
matcher: match.Platforms(v1.Platform{OS: "linux", Architecture: "arm64", Variant: "v8"}),
},
{
name: "ReplaceIndexNoMatch",
base: "hello-world-docker-v2-manifest",
replacement: replaceIndex,
matcher: match.Platforms(v1.Platform{OS: "linux", Architecture: "m68k"}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sifPath := corpus.SIF(t, tt.base, sif.OptWriteWithSpareDescriptorCapacity(8))
fi, err := ssif.LoadContainerFromPath(sifPath)
if err != nil {
t.Fatal(err)
}

ofi, err := sif.FromFileImage(fi)
if err != nil {
t.Fatal(err)
}

if err := tt.replacement(ofi, tt.matcher); err != nil {
t.Fatal(err)
}

if err := fi.UnloadContainer(); err != nil {
t.Fatal(err)
}

b, err := os.ReadFile(sifPath)
if err != nil {
t.Fatal(err)
}

g := goldie.New(t,
goldie.WithTestNameForDir(true),
)

g.Assert(t, tt.name, b)
})
}
}

0 comments on commit 8f4142f

Please sign in to comment.