|
| 1 | +package image |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "encoding/json" |
| 6 | + "fmt" |
| 7 | + "io" |
| 8 | + "io/fs" |
| 9 | + "os" |
| 10 | + "path/filepath" |
| 11 | + "time" |
| 12 | + |
| 13 | + "github.com/containers/image/v5/docker/reference" |
| 14 | + "github.com/containers/image/v5/types" |
| 15 | + "github.com/opencontainers/go-digest" |
| 16 | + specsv1 "github.com/opencontainers/image-spec/specs-go/v1" |
| 17 | + "helm.sh/helm/v3/pkg/registry" |
| 18 | + |
| 19 | + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" |
| 20 | +) |
| 21 | + |
| 22 | +func hasChart(imgCloser types.ImageCloser) bool { |
| 23 | + config := imgCloser.ConfigInfo() |
| 24 | + return config.MediaType == registry.ConfigMediaType |
| 25 | +} |
| 26 | + |
| 27 | +type ExtendCache interface { |
| 28 | + StoreChart(string, string, reference.Canonical, io.Reader) (fs.FS, time.Time, error) |
| 29 | +} |
| 30 | + |
| 31 | +func (a *diskCache) StoreChart(ownerID, filename string, canonicalRef reference.Canonical, src io.Reader) (fs.FS, time.Time, error) { |
| 32 | + dest := a.unpackPath(ownerID, canonicalRef.Digest()) |
| 33 | + |
| 34 | + if err := fsutil.EnsureEmptyDirectory(dest, 0700); err != nil { |
| 35 | + return nil, time.Time{}, fmt.Errorf("error ensuring empty charts directory: %w", err) |
| 36 | + } |
| 37 | + |
| 38 | + // Destination file |
| 39 | + chart, err := os.Create(filepath.Join(dest, filename)) |
| 40 | + if err != nil { |
| 41 | + return nil, time.Time{}, fmt.Errorf("creating chart file; %w", err) |
| 42 | + } |
| 43 | + defer chart.Close() |
| 44 | + |
| 45 | + _, err = io.Copy(chart, src) |
| 46 | + if err != nil { |
| 47 | + return nil, time.Time{}, fmt.Errorf("copying chart to %s; %w", filename, err) |
| 48 | + } |
| 49 | + |
| 50 | + modTime, err := fsutil.GetDirectoryModTime(dest) |
| 51 | + if err != nil { |
| 52 | + return nil, time.Time{}, fmt.Errorf("error getting mod time of unpack directory: %w", err) |
| 53 | + } |
| 54 | + return os.DirFS(filepath.Dir(dest)), modTime, nil |
| 55 | +} |
| 56 | + |
| 57 | +func pullChart(ctx context.Context, ownerID string, img types.ImageSource, canonicalRef reference.Canonical, cache Cache, imgRef types.ImageReference) (fs.FS, time.Time, error) { |
| 58 | + raw, _, err := img.GetManifest(ctx, nil) |
| 59 | + if err != nil { |
| 60 | + return nil, time.Time{}, fmt.Errorf("get OCI helm chart manifest; %w", err) |
| 61 | + } |
| 62 | + |
| 63 | + chartManifest := specsv1.Manifest{} |
| 64 | + if err := json.Unmarshal(raw, &chartManifest); err != nil { |
| 65 | + return nil, time.Time{}, fmt.Errorf("unmarshaling chart manifest; %w", err) |
| 66 | + } |
| 67 | + |
| 68 | + var chartDataLayerDigest digest.Digest |
| 69 | + if len(chartManifest.Layers) == 1 && |
| 70 | + (chartManifest.Layers[0].MediaType == registry.ChartLayerMediaType) { |
| 71 | + chartDataLayerDigest = chartManifest.Layers[0].Digest |
| 72 | + } |
| 73 | + |
| 74 | + filename := fmt.Sprintf("%s-%s.tgz", |
| 75 | + chartManifest.Annotations["org.opencontainers.image.title"], |
| 76 | + chartManifest.Annotations["org.opencontainers.image.version"], |
| 77 | + ) |
| 78 | + |
| 79 | + // Source file |
| 80 | + tarball, err := os.Open(filepath.Join( |
| 81 | + imgRef.PolicyConfigurationIdentity(), "blobs", |
| 82 | + "sha256", chartDataLayerDigest.Encoded()), |
| 83 | + ) |
| 84 | + if err != nil { |
| 85 | + return nil, time.Time{}, fmt.Errorf("opening chart data; %w", err) |
| 86 | + } |
| 87 | + defer tarball.Close() |
| 88 | + |
| 89 | + return cache.StoreChart(ownerID, filename, canonicalRef, tarball) |
| 90 | +} |
0 commit comments