Skip to content

Commit 00c6c83

Browse files
committed
feat: add support for extra (custom) extensions
wip Signed-off-by: Orzelius <33936483+Orzelius@users.noreply.github.com>
1 parent bee4fe3 commit 00c6c83

28 files changed

Lines changed: 615 additions & 208 deletions

cmd/image-factory/cmd/options.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,17 @@ type EnterpriseOptions struct {
502502

503503
// VEX contains configuration for VEX data fetching.
504504
VEX VEXOptions `koanf:"vex"`
505+
506+
// ExtraExtensions contains configuration for extra (custom) extensions.
507+
ExtraExtensions ExtraExtensionsOptions `koanf:"extraExtensions"`
508+
}
509+
510+
// ExtraExtensionsOptions configures custom extensions offered alongside the official ones.
511+
type ExtraExtensionsOptions struct {
512+
// Manifest specifies the OCI repository holding the extra extensions manifest image.
513+
//
514+
// It may live in a different registry than the official images.
515+
Manifest OCIRepositoryOptions `koanf:"manifest"`
505516
}
506517

507518
// SPDXOptions configures SPDX document generation and caching.

cmd/image-factory/cmd/service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@ func buildArtifactsManager(logger *zap.Logger, opts Options) (*artifacts.Manager
485485
OverlayManifestImage: opts.Artifacts.Core.Components.OverlayManifest,
486486
TalosctlImage: opts.Artifacts.Core.Components.Talosctl,
487487

488+
// Extra extensions manifest may live in its own registry, so it's passed as a full reference.
489+
ExtraExtensionsManifest: artifacts.RepositoryRef{
490+
Image: opts.Enterprise.ExtraExtensions.Manifest.String(),
491+
Insecure: opts.Enterprise.ExtraExtensions.Manifest.Insecure,
492+
},
493+
488494
ExternalURL: opts.HTTP.ExternalURL,
489495
})
490496
if err != nil {

enterprise/spdx/builder/builder.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (b *Builder) PayloadHash(ctx context.Context, schematicID, versionTag strin
9292
return "", fmt.Errorf("failed to get schematic: %w", err)
9393
}
9494

95-
return Hash(sc.Customization.SystemExtensions.OfficialExtensions, versionTag, string(arch)), nil
95+
return Hash(sc.Customization.SystemExtensions.AllExtensions(), versionTag, string(arch)), nil
9696
}
9797

9898
// Build returns an SPDX bundle, building and caching if necessary.
@@ -118,7 +118,7 @@ func (b *Builder) Build(ctx context.Context, schematicID, versionTag string, arc
118118
// Compute cache key from only the inputs that affect the SBOM content
119119
// (extensions list, version, architecture), so that schematics differing
120120
// in other fields share the same cached bundle.
121-
sbomHash := Hash(sc.Customization.SystemExtensions.OfficialExtensions, versionTag, string(arch))
121+
sbomHash := Hash(sc.Customization.SystemExtensions.AllExtensions(), versionTag, string(arch))
122122

123123
// Check cache first
124124
if err := b.storage.Head(ctx, sbomHash); err == nil {
@@ -177,10 +177,10 @@ func (b *Builder) buildBundle(reqID string, sc *schematicpkg.Schematic, schemati
177177
}
178178

179179
logger.Debug("building SPDX bundle from extensions",
180-
zap.Int("extensions", len(sc.Customization.SystemExtensions.OfficialExtensions)))
180+
zap.Int("extensions", len(sc.Customization.SystemExtensions.AllExtensions())))
181181

182-
// Extract SPDX from extensions
183-
if len(sc.Customization.SystemExtensions.OfficialExtensions) > 0 {
182+
// Extract SPDX from extensions (official and extra)
183+
if len(sc.Customization.SystemExtensions.AllExtensions()) > 0 {
184184
if err = b.extractExtensionsSPDX(ctx, bundle, sc, versionTag, arch); err != nil {
185185
logger.Warn("failed to extract SPDX from some extensions", zap.Error(err))
186186
}
@@ -350,12 +350,12 @@ func (b *Builder) extractSPDXFromInitramfs(bundle *Bundle, bootAsset asset.BootA
350350
func (b *Builder) extractExtensionsSPDX(ctx context.Context, bundle *Bundle, schematicData *schematicpkg.Schematic, versionTag string, arch artifacts.Arch) error {
351351
logger := ctxlog.Logger(ctx, b.logger)
352352

353-
availableExtensions, err := b.artifactsManager.GetOfficialExtensions(ctx, versionTag)
353+
availableExtensions, err := b.artifactsManager.GetAllAvailableExtensions(ctx, versionTag)
354354
if err != nil {
355355
return fmt.Errorf("failed to get official extensions: %w", err)
356356
}
357357

358-
for _, extensionName := range schematicData.Customization.SystemExtensions.OfficialExtensions {
358+
for _, extensionName := range schematicData.Customization.SystemExtensions.AllExtensions() {
359359
extensionRef := findExtension(availableExtensions, extensionName)
360360

361361
if value.IsZero(extensionRef) {

hack/dev/config.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,22 @@ artifacts:
99
registry: registry.local:5000
1010
namespace: image-factory
1111
repository: schematic
12+
insecure: true
1213

1314
installer:
1415
# internal registry namespace to push installer images to
1516
internal:
1617
registry: registry.local:5000
1718
namespace: siderolabs
19+
insecure: true
1820

1921
cache:
2022
oci:
2123
# private registry repository for cached assets
2224
registry: registry.local:5000
2325
namespace: image-factory
2426
repository: cache
27+
insecure: true
2528

2629
# path to the ECDSA private key (to sign cached assets)
2730
signingKeyPath: /etc/image-factory/cache-signing-key.key
@@ -30,17 +33,27 @@ http:
3033
# external URL the Image Factory is available at
3134
externalURL: http://localhost:8080
3235

36+
containerSignature:
37+
disabled: true
38+
3339
authentication:
3440
enabled: false
3541
htpasswdPath: /etc/image-factory/htpasswd
3642

3743
enterprise:
44+
extraExtensions:
45+
manifest:
46+
registry: host.docker.internal:5005
47+
namespace: extension-testing
48+
repository: extensions
49+
insecure: true
3850
spdx:
3951
cache:
4052
# private registry repository for cached spdx
4153
registry: registry.local:5000
4254
namespace: image-factory
4355
repository: spdx-cache
56+
insecure: true
4457
vex:
4558
data:
4659
registry: registry.ghcr.io:5000

internal/artifacts/artifacts.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,23 @@ type Options struct { //nolint:govet
4343
OverlayManifestImage string
4444
TalosctlImage string
4545

46+
// ExtraExtensionsManifest references the extra extensions manifest. Unlike the official
47+
// images it may live in a separate registry, so it is resolved independently of ImageRegistry.
48+
// Its zero value disables extra extensions.
49+
ExtraExtensionsManifest RepositoryRef
50+
4651
// External identification.
4752
ExternalURL string
4853
}
4954

55+
// RepositoryRef identifies an image repository that may live in its own registry.
56+
type RepositoryRef struct {
57+
// Image is the full reference (registry/namespace/repository).
58+
Image string
59+
// Insecure allows connecting to the registry over HTTP or with invalid TLS certificates.
60+
Insecure bool
61+
}
62+
5063
// ImageVerifyOptions are the options for verifying the image signature.
5164
type ImageVerifyOptions = verify.VerifyOptions
5265

internal/artifacts/fetch.go

Lines changed: 14 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -14,83 +14,23 @@ import (
1414
"path/filepath"
1515
"strings"
1616

17-
"github.com/google/go-containerregistry/pkg/crane"
1817
"github.com/google/go-containerregistry/pkg/name"
19-
v1 "github.com/google/go-containerregistry/pkg/v1"
20-
"github.com/google/go-containerregistry/pkg/v1/empty"
21-
"github.com/google/go-containerregistry/pkg/v1/layout"
2218
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
2319
"go.uber.org/zap"
24-
"golang.org/x/sync/errgroup"
2520

21+
"github.com/siderolabs/image-factory/internal/artifacts/imagehandler"
2622
"github.com/siderolabs/image-factory/internal/image/verify"
2723
)
2824

29-
type imageHandler func(ctx context.Context, logger *zap.Logger, img v1.Image) error
30-
31-
// imageExportHandler exports the image for further processing.
32-
func imageExportHandler(exportHandler func(logger *zap.Logger, r io.Reader) error) imageHandler {
33-
return func(_ context.Context, logger *zap.Logger, img v1.Image) error {
34-
logger.Info("extracting the image")
35-
36-
r, w := io.Pipe()
37-
38-
var eg errgroup.Group
39-
40-
eg.Go(func() error {
41-
defer w.Close() //nolint:errcheck
42-
43-
return crane.Export(img, w)
44-
})
45-
46-
eg.Go(func() error {
47-
err := exportHandler(logger, r)
48-
if err != nil {
49-
r.CloseWithError(err) // signal the exporter to stop
50-
}
51-
52-
return err
53-
})
54-
55-
if err := eg.Wait(); err != nil {
56-
return fmt.Errorf("error extracting the image: %w", err)
57-
}
58-
59-
return nil
60-
}
61-
}
62-
63-
// imageOCIHandler exports the image to the OCI format.
64-
func imageOCIHandler(path string) imageHandler {
65-
return func(_ context.Context, logger *zap.Logger, img v1.Image) error {
66-
if err := os.RemoveAll(path); err != nil {
67-
return fmt.Errorf("error removing the directory %q: %w", path, err)
68-
}
69-
70-
l, err := layout.Write(path, empty.Index)
71-
if err != nil {
72-
return fmt.Errorf("error creating layout: %w", err)
73-
}
74-
75-
logger.Info("exporting the image", zap.String("destination", path))
76-
77-
if err = l.AppendImage(img); err != nil {
78-
return fmt.Errorf("error exporting the image: %w", err)
79-
}
80-
81-
return nil
82-
}
83-
}
84-
8525
// fetchImageByTag contains combined logic of image handling: heading, downloading, verifying signatures, and exporting.
86-
func (m *Manager) fetchImageByTag(imageName, tag string, architecture Arch, imageHandler imageHandler) error {
26+
func (m *Manager) fetchImageByTag(repo name.Repository, tag string, architecture Arch, insecure bool, handler imagehandler.Handler) error {
8727
// set a timeout for fetching, but don't bind it to any context, as we want fetch operation to finish
8828
ctx, cancel := context.WithTimeout(context.Background(), FetchTimeout)
8929
defer cancel()
9030

9131
// light check first - if the image exists, and resolve the digest
9232
// it's important to do further checks by digest exactly
93-
repoRef := m.imageRegistry.Repo(imageName).Tag(tag)
33+
repoRef := repo.Tag(tag)
9434

9535
m.logger.Debug("heading the image", zap.Stringer("image", repoRef))
9636

@@ -101,11 +41,11 @@ func (m *Manager) fetchImageByTag(imageName, tag string, architecture Arch, imag
10141

10242
digestRef := repoRef.Digest(descriptor.Digest.String())
10343

104-
return m.fetchImageByDigest(digestRef, architecture, imageHandler)
44+
return m.fetchImageByDigest(digestRef, architecture, insecure, handler)
10545
}
10646

10747
// fetchImageByDigest fetches an image by digest, verifies signatures, and exports it to the storage.
108-
func (m *Manager) fetchImageByDigest(digestRef name.Digest, architecture Arch, imageHandler imageHandler) error {
48+
func (m *Manager) fetchImageByDigest(digestRef name.Digest, architecture Arch, insecure bool, handler imagehandler.Handler) error {
10949
var err error
11050
// set a timeout for fetching, but don't bind it to any context, as we want fetch operation to finish
11151
ctx, cancel := context.WithTimeout(context.Background(), FetchTimeout)
@@ -118,7 +58,7 @@ func (m *Manager) fetchImageByDigest(digestRef name.Digest, architecture Arch, i
11858

11959
var nameOptions []name.Option
12060

121-
if m.options.InsecureImageRegistry {
61+
if insecure {
12262
nameOptions = append(nameOptions, name.Insecure)
12363
}
12464

@@ -142,14 +82,14 @@ func (m *Manager) fetchImageByDigest(digestRef name.Digest, architecture Arch, i
14282
return fmt.Errorf("error creating image from descriptor: %w", err)
14383
}
14484

145-
return imageHandler(ctx, logger, img)
85+
return handler(ctx, logger, img)
14686
}
14787

14888
// fetchImager fetches 'imager' container, and saves to the storage path.
14989
func (m *Manager) fetchImager(tag string) error {
15090
destinationPath := filepath.Join(m.storagePath, tag)
15191

152-
if err := m.fetchImageByTag(m.options.ImagerImage, tag, ArchAmd64, imageExportHandler(func(logger *zap.Logger, r io.Reader) error {
92+
if err := m.fetchImageByTag(m.imageRegistry.Repo(m.options.ImagerImage), tag, ArchAmd64, m.options.InsecureImageRegistry, imagehandler.Export(func(logger *zap.Logger, r io.Reader) error {
15393
return untarWithPrefix(logger, r, usrInstallPrefix, destinationPath+tmpSuffix)
15494
})); err != nil {
15595
return err
@@ -164,7 +104,7 @@ func (m *Manager) extractOverlay(arch Arch, ref OverlayRef) error {
164104

165105
destinationPath := filepath.Join(m.storagePath, string(arch)+"-"+ref.Digest+"-overlay")
166106

167-
if err := m.fetchImageByDigest(imageRef, arch, imageExportHandler(func(logger *zap.Logger, r io.Reader) error {
107+
if err := m.fetchImageByDigest(imageRef, arch, m.options.InsecureImageRegistry, imagehandler.Export(func(logger *zap.Logger, r io.Reader) error {
168108
return untarWithPrefix(logger, r, overlaysPrefix, destinationPath+tmpSuffix)
169109
})); err != nil {
170110
return err
@@ -175,9 +115,9 @@ func (m *Manager) extractOverlay(arch Arch, ref OverlayRef) error {
175115

176116
// fetchExtensionImage fetches a specified extension image and exports it to the storage as OCI.
177117
func (m *Manager) fetchExtensionImage(arch Arch, ref ExtensionRef, destPath string) error {
178-
imageRef := m.imageRegistry.Repo(ref.TaggedReference.RepositoryStr()).Digest(ref.Digest)
118+
imageRef := ref.Registry.Repo(ref.TaggedReference.RepositoryStr()).Digest(ref.Digest)
179119

180-
if err := m.fetchImageByDigest(imageRef, arch, imageOCIHandler(destPath+tmpSuffix)); err != nil {
120+
if err := m.fetchImageByDigest(imageRef, arch, ref.Insecure, imagehandler.OCI(destPath+tmpSuffix)); err != nil {
181121
return err
182122
}
183123

@@ -188,7 +128,7 @@ func (m *Manager) fetchExtensionImage(arch Arch, ref ExtensionRef, destPath stri
188128
func (m *Manager) fetchOverlayImage(arch Arch, ref OverlayRef, destPath string) error {
189129
imageRef := m.imageRegistry.Repo(ref.TaggedReference.RepositoryStr()).Digest(ref.Digest)
190130

191-
if err := m.fetchImageByDigest(imageRef, arch, imageOCIHandler(destPath+tmpSuffix)); err != nil {
131+
if err := m.fetchImageByDigest(imageRef, arch, m.options.InsecureImageRegistry, imagehandler.OCI(destPath+tmpSuffix)); err != nil {
192132
return err
193133
}
194134

@@ -206,7 +146,7 @@ func (m *Manager) InstallerImageName(versionTag string) string {
206146

207147
// fetchInstallerImage fetches a Talos installer image and exports it to the storage.
208148
func (m *Manager) fetchInstallerImage(arch Arch, versionTag string, destPath string) error {
209-
if err := m.fetchImageByTag(m.InstallerImageName(versionTag), versionTag, arch, imageOCIHandler(destPath+tmpSuffix)); err != nil {
149+
if err := m.fetchImageByTag(m.imageRegistry.Repo(m.InstallerImageName(versionTag)), versionTag, arch, m.options.InsecureImageRegistry, imagehandler.OCI(destPath+tmpSuffix)); err != nil {
210150
return err
211151
}
212152

@@ -215,7 +155,7 @@ func (m *Manager) fetchInstallerImage(arch Arch, versionTag string, destPath str
215155

216156
// fetchTalosctlImage fetches a Talosctl image and exports it to the storage.
217157
func (m *Manager) fetchTalosctlImage(versionTag string, destPath string) error {
218-
if err := m.fetchImageByTag(m.options.TalosctlImage, versionTag, ArchAmd64, imageExportHandler(func(logger *zap.Logger, r io.Reader) error {
158+
if err := m.fetchImageByTag(m.imageRegistry.Repo(m.options.TalosctlImage), versionTag, ArchAmd64, m.options.InsecureImageRegistry, imagehandler.Export(func(logger *zap.Logger, r io.Reader) error {
219159
return untarWithPrefix(logger, r, "", destPath+tmpSuffix)
220160
})); err != nil {
221161
return err

0 commit comments

Comments
 (0)