Skip to content

Commit bf13d14

Browse files
authored
consolidate image layer handling; move fs utils (#1690)
Signed-off-by: Joe Lanford <joe.lanford@gmail.com>
1 parent 591c73c commit bf13d14

File tree

12 files changed

+213
-205
lines changed

12 files changed

+213
-205
lines changed

.golangci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ linters-settings:
6666
alias: ctrl
6767
- pkg: github.com/blang/semver/v4
6868
alias: bsemver
69+
- pkg: "^github.com/operator-framework/operator-controller/internal/util/([^/]+)$"
70+
alias: "${1}util"
6971

7072
output:
7173
formats:

catalogd/cmd/catalogd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import (
6363
"github.com/operator-framework/operator-controller/catalogd/internal/storage"
6464
"github.com/operator-framework/operator-controller/catalogd/internal/version"
6565
"github.com/operator-framework/operator-controller/catalogd/internal/webhook"
66-
"github.com/operator-framework/operator-controller/internal/fsutil"
66+
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
6767
)
6868

6969
var (

catalogd/internal/source/containers_image.go

Lines changed: 13 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
package source
22

33
import (
4-
"archive/tar"
54
"context"
65
"errors"
76
"fmt"
8-
"io"
97
"os"
10-
"path"
118
"path/filepath"
12-
"strings"
139
"time"
1410

15-
"github.com/containerd/containerd/archive"
1611
"github.com/containers/image/v5/copy"
1712
"github.com/containers/image/v5/docker"
1813
"github.com/containers/image/v5/docker/reference"
1914
"github.com/containers/image/v5/manifest"
2015
"github.com/containers/image/v5/oci/layout"
21-
"github.com/containers/image/v5/pkg/blobinfocache/none"
22-
"github.com/containers/image/v5/pkg/compression"
2316
"github.com/containers/image/v5/pkg/sysregistriesv2"
2417
"github.com/containers/image/v5/signature"
2518
"github.com/containers/image/v5/types"
@@ -30,8 +23,8 @@ import (
3023
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3124

3225
catalogdv1 "github.com/operator-framework/operator-controller/catalogd/api/v1"
33-
"github.com/operator-framework/operator-controller/internal/fsutil"
34-
"github.com/operator-framework/operator-controller/internal/rukpak/source"
26+
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
27+
imageutil "github.com/operator-framework/operator-controller/internal/util/image"
3528
)
3629

3730
const ConfigDirLabel = "operators.operatorframework.io.index.configs.v1"
@@ -78,10 +71,14 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, catalog *catalogdv
7871
//
7972
//////////////////////////////////////////////////////
8073
unpackPath := i.unpackPath(catalog.Name, canonicalRef.Digest())
81-
if isUnpacked, unpackTime, err := source.IsImageUnpacked(unpackPath); isUnpacked && err == nil {
74+
if unpackTime, err := fsutil.GetDirectoryModTime(unpackPath); err == nil {
8275
l.Info("image already unpacked", "ref", imgRef.String(), "digest", canonicalRef.Digest().String())
8376
return successResult(unpackPath, canonicalRef, unpackTime), nil
84-
} else if err != nil {
77+
} else if errors.Is(err, fsutil.ErrNotDirectory) {
78+
if err := fsutil.DeleteReadOnlyRecursive(unpackPath); err != nil {
79+
return nil, err
80+
}
81+
} else if err != nil && !os.IsNotExist(err) {
8582
return nil, fmt.Errorf("error checking image already unpacked: %w", err)
8683
}
8784

@@ -297,59 +294,11 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
297294
return wrapTerminal(fmt.Errorf("catalog image is missing the required label %q", ConfigDirLabel), specIsCanonical)
298295
}
299296

300-
if err := fsutil.EnsureEmptyDirectory(unpackPath, 0700); err != nil {
301-
return fmt.Errorf("error ensuring empty unpack directory: %w", err)
302-
}
303-
l := log.FromContext(ctx)
304-
l.Info("unpacking image", "path", unpackPath)
305-
for i, layerInfo := range img.LayerInfos() {
306-
if err := func() error {
307-
layerReader, _, err := layoutSrc.GetBlob(ctx, layerInfo, none.NoCache)
308-
if err != nil {
309-
return fmt.Errorf("error getting blob for layer[%d]: %w", i, err)
310-
}
311-
defer layerReader.Close()
312-
313-
if err := applyLayer(ctx, unpackPath, dirToUnpack, layerReader); err != nil {
314-
return fmt.Errorf("error applying layer[%d]: %w", i, err)
315-
}
316-
l.Info("applied layer", "layer", i)
317-
return nil
318-
}(); err != nil {
319-
return errors.Join(err, fsutil.DeleteReadOnlyRecursive(unpackPath))
320-
}
321-
}
322-
if err := fsutil.SetReadOnlyRecursive(unpackPath); err != nil {
323-
return fmt.Errorf("error making unpack directory read-only: %w", err)
324-
}
325-
return nil
326-
}
327-
328-
func applyLayer(ctx context.Context, destPath string, srcPath string, layer io.ReadCloser) error {
329-
decompressed, _, err := compression.AutoDecompress(layer)
330-
if err != nil {
331-
return fmt.Errorf("auto-decompress failed: %w", err)
332-
}
333-
defer decompressed.Close()
334-
335-
_, err = archive.Apply(ctx, destPath, decompressed, archive.WithFilter(applyLayerFilter(srcPath)))
336-
return err
337-
}
338-
339-
func applyLayerFilter(srcPath string) archive.Filter {
340-
cleanSrcPath := path.Clean(strings.TrimPrefix(srcPath, "/"))
341-
return func(h *tar.Header) (bool, error) {
342-
h.Uid = os.Getuid()
343-
h.Gid = os.Getgid()
344-
h.Mode |= 0700
345-
346-
cleanName := path.Clean(strings.TrimPrefix(h.Name, "/"))
347-
relPath, err := filepath.Rel(cleanSrcPath, cleanName)
348-
if err != nil {
349-
return false, fmt.Errorf("error getting relative path: %w", err)
350-
}
351-
return relPath != ".." && !strings.HasPrefix(relPath, "../"), nil
352-
}
297+
applyFilter := imageutil.AllFilters(
298+
imageutil.OnlyPath(dirToUnpack),
299+
imageutil.ForceOwnershipRWX(),
300+
)
301+
return imageutil.ApplyLayersToDisk(ctx, unpackPath, img, layoutSrc, applyFilter)
353302
}
354303

355304
func (i *ContainersImageRegistry) deleteOtherImages(catalogName string, digestToKeep digest.Digest) error {

cmd/operator-controller/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ import (
6363
"github.com/operator-framework/operator-controller/internal/controllers"
6464
"github.com/operator-framework/operator-controller/internal/features"
6565
"github.com/operator-framework/operator-controller/internal/finalizers"
66-
"github.com/operator-framework/operator-controller/internal/fsutil"
6766
"github.com/operator-framework/operator-controller/internal/httputil"
6867
"github.com/operator-framework/operator-controller/internal/resolve"
6968
"github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety"
7069
"github.com/operator-framework/operator-controller/internal/rukpak/source"
7170
"github.com/operator-framework/operator-controller/internal/scheme"
71+
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
7272
"github.com/operator-framework/operator-controller/internal/version"
7373
)
7474

internal/rukpak/source/containers_image.go

Lines changed: 10 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
package source
22

33
import (
4-
"archive/tar"
54
"context"
65
"errors"
76
"fmt"
8-
"io"
97
"os"
108
"path/filepath"
119

12-
"github.com/containerd/containerd/archive"
1310
"github.com/containers/image/v5/copy"
1411
"github.com/containers/image/v5/docker"
1512
"github.com/containers/image/v5/docker/reference"
1613
"github.com/containers/image/v5/manifest"
1714
"github.com/containers/image/v5/oci/layout"
18-
"github.com/containers/image/v5/pkg/blobinfocache/none"
19-
"github.com/containers/image/v5/pkg/compression"
2015
"github.com/containers/image/v5/pkg/sysregistriesv2"
2116
"github.com/containers/image/v5/signature"
2217
"github.com/containers/image/v5/types"
@@ -25,7 +20,8 @@ import (
2520
"sigs.k8s.io/controller-runtime/pkg/log"
2621
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2722

28-
"github.com/operator-framework/operator-controller/internal/fsutil"
23+
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
24+
imageutil "github.com/operator-framework/operator-controller/internal/util/image"
2925
)
3026

3127
var insecurePolicy = []byte(`{"default":[{"type":"insecureAcceptAnything"}]}`)
@@ -71,11 +67,15 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour
7167
//
7268
//////////////////////////////////////////////////////
7369
unpackPath := i.unpackPath(bundle.Name, canonicalRef.Digest())
74-
if isUnpacked, _, err := IsImageUnpacked(unpackPath); isUnpacked && err == nil {
70+
if _, err := fsutil.GetDirectoryModTime(unpackPath); err == nil {
7571
l.Info("image already unpacked", "ref", imgRef.String(), "digest", canonicalRef.Digest().String())
7672
return successResult(bundle.Name, unpackPath, canonicalRef), nil
77-
} else if err != nil {
78-
return nil, fmt.Errorf("error checking bundle already unpacked: %w", err)
73+
} else if errors.Is(err, fsutil.ErrNotDirectory) {
74+
if err := fsutil.DeleteReadOnlyRecursive(unpackPath); err != nil {
75+
return nil, err
76+
}
77+
} else if err != nil && !os.IsNotExist(err) {
78+
return nil, fmt.Errorf("error checking image already unpacked: %w", err)
7979
}
8080

8181
//////////////////////////////////////////////////////
@@ -265,53 +265,7 @@ func (i *ContainersImageRegistry) unpackImage(ctx context.Context, unpackPath st
265265
panic(err)
266266
}
267267
}()
268-
269-
if err := fsutil.EnsureEmptyDirectory(unpackPath, 0700); err != nil {
270-
return fmt.Errorf("error ensuring empty unpack directory: %w", err)
271-
}
272-
l := log.FromContext(ctx)
273-
l.Info("unpacking image", "path", unpackPath)
274-
for i, layerInfo := range img.LayerInfos() {
275-
if err := func() error {
276-
layerReader, _, err := layoutSrc.GetBlob(ctx, layerInfo, none.NoCache)
277-
if err != nil {
278-
return fmt.Errorf("error getting blob for layer[%d]: %w", i, err)
279-
}
280-
defer layerReader.Close()
281-
282-
if err := applyLayer(ctx, unpackPath, layerReader); err != nil {
283-
return fmt.Errorf("error applying layer[%d]: %w", i, err)
284-
}
285-
l.Info("applied layer", "layer", i)
286-
return nil
287-
}(); err != nil {
288-
return errors.Join(err, fsutil.DeleteReadOnlyRecursive(unpackPath))
289-
}
290-
}
291-
if err := fsutil.SetReadOnlyRecursive(unpackPath); err != nil {
292-
return fmt.Errorf("error making unpack directory read-only: %w", err)
293-
}
294-
return nil
295-
}
296-
297-
func applyLayer(ctx context.Context, unpackPath string, layer io.ReadCloser) error {
298-
decompressed, _, err := compression.AutoDecompress(layer)
299-
if err != nil {
300-
return fmt.Errorf("auto-decompress failed: %w", err)
301-
}
302-
defer decompressed.Close()
303-
304-
_, err = archive.Apply(ctx, unpackPath, decompressed, archive.WithFilter(applyLayerFilter()))
305-
return err
306-
}
307-
308-
func applyLayerFilter() archive.Filter {
309-
return func(h *tar.Header) (bool, error) {
310-
h.Uid = os.Getuid()
311-
h.Gid = os.Getgid()
312-
h.Mode |= 0700
313-
return true, nil
314-
}
268+
return imageutil.ApplyLayersToDisk(ctx, unpackPath, img, layoutSrc, imageutil.ForceOwnershipRWX())
315269
}
316270

317271
func (i *ContainersImageRegistry) deleteOtherImages(bundleName string, digestToKeep digest.Digest) error {

internal/rukpak/source/containers_image_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import (
2222
"github.com/stretchr/testify/require"
2323
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2424

25-
"github.com/operator-framework/operator-controller/internal/fsutil"
2625
"github.com/operator-framework/operator-controller/internal/rukpak/source"
26+
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
2727
)
2828

2929
const (

internal/rukpak/source/helpers.go

Lines changed: 0 additions & 24 deletions
This file was deleted.

internal/rukpak/source/helpers_test.go

Lines changed: 0 additions & 47 deletions
This file was deleted.

internal/fsutil/helpers.go renamed to internal/util/fs/fs.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
package fsutil
1+
package fs
22

33
import (
4+
"errors"
45
"fmt"
56
"io/fs"
67
"os"
78
"path/filepath"
9+
"time"
810
)
911

1012
// EnsureEmptyDirectory ensures the directory given by `path` is empty.
@@ -42,7 +44,10 @@ func SetWritableRecursive(root string) error {
4244
return setModeRecursive(root, ownerWritableFileMode, ownerWritableDirMode)
4345
}
4446

45-
// DeleteReadOnlyRecursive deletes read-only directory with path given by `root`
47+
// DeleteReadOnlyRecursive deletes the directory with path given by `root`.
48+
// Prior to deleting the directory, the directory and all descendant files
49+
// and directories are set as writable. If any chmod or deletion error occurs
50+
// it is immediately returned.
4651
func DeleteReadOnlyRecursive(root string) error {
4752
if err := SetWritableRecursive(root); err != nil {
4853
return fmt.Errorf("error making directory writable for deletion: %w", err)
@@ -78,3 +83,21 @@ func setModeRecursive(path string, fileMode os.FileMode, dirMode os.FileMode) er
7883
}
7984
})
8085
}
86+
87+
var (
88+
ErrNotDirectory = errors.New("not a directory")
89+
)
90+
91+
// GetDirectoryModTime returns the modification time of the directory at dirPath.
92+
// If stat(dirPath) fails, an error is returned with a zero-value time.Time.
93+
// If dirPath is not a directory, an ErrNotDirectory error is returned.
94+
func GetDirectoryModTime(dirPath string) (time.Time, error) {
95+
dirStat, err := os.Stat(dirPath)
96+
if err != nil {
97+
return time.Time{}, err
98+
}
99+
if !dirStat.IsDir() {
100+
return time.Time{}, ErrNotDirectory
101+
}
102+
return dirStat.ModTime(), nil
103+
}

0 commit comments

Comments
 (0)