Skip to content

Commit a2a8352

Browse files
authored
Merge pull request #374 from tri-adam/delete-objects
feat: add `DeleteObjects`
2 parents 94b0b65 + 68683b4 commit a2a8352

8 files changed

+144
-21
lines changed

pkg/sif/delete.go

+44-20
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,29 @@ func OptDeleteWithTime(t time.Time) DeleteOpt {
7474
}
7575
}
7676

77-
// DeleteObject deletes the data object with id, according to opts.
77+
// DeleteObject deletes the data object with id, according to opts. If no matching descriptor is
78+
// found, an error wrapping ErrObjectNotFound is returned.
7879
//
79-
// To zero the data region of the deleted object, use OptDeleteZero. To compact the file following
80-
// object deletion, use OptDeleteCompact.
80+
// To zero the data region of the deleted object, use OptDeleteZero. To remove unused space at the
81+
// end of the FileImage following object deletion, use OptDeleteCompact.
8182
//
8283
// By default, the image modification time is set to the current time for non-deterministic images,
8384
// and unset otherwise. To override this, consider using OptDeleteDeterministic or
8485
// OptDeleteWithTime.
8586
func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
87+
return f.DeleteObjects(WithID(id), opts...)
88+
}
89+
90+
// DeleteObjects deletes the data objects selected by fn, according to opts. If no descriptors are
91+
// selected by fns, an error wrapping ErrObjectNotFound is returned.
92+
//
93+
// To zero the data region of the deleted object, use OptDeleteZero. To remove unused space at the
94+
// end of the FileImage following object deletion, use OptDeleteCompact.
95+
//
96+
// By default, the image modification time is set to the current time for non-deterministic images,
97+
// and unset otherwise. To override this, consider using OptDeleteDeterministic or
98+
// OptDeleteWithTime.
99+
func (f *FileImage) DeleteObjects(fn DescriptorSelectorFunc, opts ...DeleteOpt) error {
86100
do := deleteOpts{}
87101

88102
if !f.isDeterministic() {
@@ -95,29 +109,39 @@ func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
95109
}
96110
}
97111

98-
d, err := f.getDescriptor(WithID(id))
99-
if err != nil {
100-
return fmt.Errorf("%w", err)
101-
}
112+
var selected bool
102113

103-
if do.zero {
104-
if err := f.zero(d); err != nil {
105-
return fmt.Errorf("%w", err)
114+
if err := f.withDescriptors(fn, func(d *rawDescriptor) error {
115+
selected = true
116+
117+
if do.zero {
118+
if err := f.zero(d); err != nil {
119+
return fmt.Errorf("%w", err)
120+
}
106121
}
107-
}
108122

109-
f.h.DescriptorsFree++
110-
f.h.ModifiedAt = do.t.Unix()
123+
f.h.DescriptorsFree++
124+
125+
// If we remove the primary partition, set the global header Arch field to HdrArchUnknown
126+
// to indicate that the SIF file doesn't include a primary partition and no dependency
127+
// on any architecture exists.
128+
if d.isPartitionOfType(PartPrimSys) {
129+
f.h.Arch = hdrArchUnknown
130+
}
131+
132+
// Reset rawDescripter with empty struct
133+
*d = rawDescriptor{}
111134

112-
// If we remove the primary partition, set the global header Arch field to HdrArchUnknown
113-
// to indicate that the SIF file doesn't include a primary partition and no dependency
114-
// on any architecture exists.
115-
if d.isPartitionOfType(PartPrimSys) {
116-
f.h.Arch = hdrArchUnknown
135+
return nil
136+
}); err != nil {
137+
return fmt.Errorf("%w", err)
117138
}
118139

119-
// Reset rawDescripter with empty struct
120-
*d = rawDescriptor{}
140+
if !selected {
141+
return fmt.Errorf("%w", ErrObjectNotFound)
142+
}
143+
144+
f.h.ModifiedAt = do.t.Unix()
121145

122146
if do.compact {
123147
f.h.DataSize = f.calculatedDataSize()

pkg/sif/delete_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,99 @@ func TestDeleteObject(t *testing.T) {
195195
}
196196
}
197197

198+
func TestDeleteObjects(t *testing.T) {
199+
tests := []struct {
200+
name string
201+
createOpts []CreateOpt
202+
fn DescriptorSelectorFunc
203+
opts []DeleteOpt
204+
wantErr error
205+
}{
206+
{
207+
name: "ErrObjectNotFound",
208+
createOpts: []CreateOpt{
209+
OptCreateDeterministic(),
210+
},
211+
fn: WithID(1),
212+
wantErr: ErrObjectNotFound,
213+
},
214+
{
215+
name: "NilSelectFunc",
216+
createOpts: []CreateOpt{
217+
OptCreateDeterministic(),
218+
OptCreateWithDescriptors(
219+
getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}),
220+
getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}),
221+
),
222+
},
223+
fn: nil,
224+
wantErr: errNilSelectFunc,
225+
},
226+
{
227+
name: "DataType",
228+
createOpts: []CreateOpt{
229+
OptCreateDeterministic(),
230+
OptCreateWithDescriptors(
231+
getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}),
232+
getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}),
233+
),
234+
},
235+
fn: WithDataType(DataGeneric),
236+
},
237+
{
238+
name: "DataTypeCompact",
239+
createOpts: []CreateOpt{
240+
OptCreateDeterministic(),
241+
OptCreateWithDescriptors(
242+
getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}),
243+
getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}),
244+
),
245+
},
246+
fn: WithDataType(DataGeneric),
247+
opts: []DeleteOpt{
248+
OptDeleteCompact(true),
249+
},
250+
},
251+
{
252+
name: "PrimaryPartitionCompact",
253+
createOpts: []CreateOpt{
254+
OptCreateDeterministic(),
255+
OptCreateWithDescriptors(
256+
getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce},
257+
OptPartitionMetadata(FsSquash, PartPrimSys, "386"),
258+
),
259+
),
260+
},
261+
fn: WithPartitionType(PartPrimSys),
262+
opts: []DeleteOpt{
263+
OptDeleteCompact(true),
264+
},
265+
},
266+
}
267+
268+
for _, tt := range tests {
269+
t.Run(tt.name, func(t *testing.T) {
270+
var b Buffer
271+
272+
f, err := CreateContainer(&b, tt.createOpts...)
273+
if err != nil {
274+
t.Fatal(err)
275+
}
276+
277+
if got, want := f.DeleteObjects(tt.fn, tt.opts...), tt.wantErr; !errors.Is(got, want) {
278+
t.Errorf("got error %v, want %v", got, want)
279+
}
280+
281+
if err := f.UnloadContainer(); err != nil {
282+
t.Error(err)
283+
}
284+
285+
g := goldie.New(t, goldie.WithTestNameForDir(true))
286+
g.Assert(t, tt.name, b.Bytes())
287+
})
288+
}
289+
}
290+
198291
func TestDeleteObjectAndAddObject(t *testing.T) {
199292
tests := []struct {
200293
name string

pkg/sif/select.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
1+
// Copyright (c) 2021-2024, Sylabs Inc. All rights reserved.
22
// This software is licensed under a 3-clause BSD license. Please consult the
33
// LICENSE file distributed with the sources of this project regarding your
44
// rights to use or distribute this software.
@@ -184,10 +184,16 @@ func multiSelectorFunc(fns ...DescriptorSelectorFunc) DescriptorSelectorFunc {
184184
}
185185
}
186186

187+
var errNilSelectFunc = errors.New("descriptor selector func must not be nil")
188+
187189
// withDescriptors calls onMatchFn with each in-use descriptor in f for which selectFn returns
188190
// true. If selectFn or onMatchFn return a non-nil error, the iteration halts, and the error is
189191
// returned to the caller.
190192
func (f *FileImage) withDescriptors(selectFn DescriptorSelectorFunc, onMatchFn func(*rawDescriptor) error) error {
193+
if selectFn == nil {
194+
return errNilSelectFunc
195+
}
196+
191197
for i, d := range f.rds {
192198
if !d.Used {
193199
continue
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)