talos

Форк
0
/
out.go 
500 строк · 15.0 Кб
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
package imager
6

7
import (
8
	"context"
9
	"encoding/pem"
10
	"fmt"
11
	"log"
12
	"os"
13
	"path/filepath"
14
	"strings"
15
	"time"
16

17
	"github.com/google/go-containerregistry/pkg/name"
18
	v1 "github.com/google/go-containerregistry/pkg/v1"
19
	"github.com/google/go-containerregistry/pkg/v1/empty"
20
	"github.com/google/go-containerregistry/pkg/v1/mutate"
21
	"github.com/google/go-containerregistry/pkg/v1/tarball"
22
	"github.com/google/go-containerregistry/pkg/v1/types"
23
	"github.com/siderolabs/go-pointer"
24
	"github.com/siderolabs/go-procfs/procfs"
25
	"gopkg.in/yaml.v3"
26

27
	"github.com/siderolabs/talos/cmd/installer/pkg/install"
28
	"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
29
	"github.com/siderolabs/talos/internal/pkg/secureboot/database"
30
	"github.com/siderolabs/talos/internal/pkg/secureboot/pesign"
31
	"github.com/siderolabs/talos/pkg/imager/filemap"
32
	"github.com/siderolabs/talos/pkg/imager/iso"
33
	"github.com/siderolabs/talos/pkg/imager/ova"
34
	"github.com/siderolabs/talos/pkg/imager/profile"
35
	"github.com/siderolabs/talos/pkg/imager/qemuimg"
36
	"github.com/siderolabs/talos/pkg/imager/utils"
37
	"github.com/siderolabs/talos/pkg/machinery/constants"
38
	"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
39
	"github.com/siderolabs/talos/pkg/reporter"
40
)
41

42
func (i *Imager) outInitramfs(path string, report *reporter.Reporter) error {
43
	printf := progressPrintf(report, reporter.Update{Message: "copying initramfs...", Status: reporter.StatusRunning})
44

45
	if err := utils.CopyFiles(printf, utils.SourceDestination(i.initramfsPath, path)); err != nil {
46
		return err
47
	}
48

49
	report.Report(reporter.Update{Message: "initramfs output ready", Status: reporter.StatusSucceeded})
50

51
	return nil
52
}
53

54
func (i *Imager) outKernel(path string, report *reporter.Reporter) error {
55
	printf := progressPrintf(report, reporter.Update{Message: "copying kernel...", Status: reporter.StatusRunning})
56

57
	if err := utils.CopyFiles(printf, utils.SourceDestination(i.prof.Input.Kernel.Path, path)); err != nil {
58
		return err
59
	}
60

61
	report.Report(reporter.Update{Message: "kernel output ready", Status: reporter.StatusSucceeded})
62

63
	return nil
64
}
65

66
func (i *Imager) outUKI(path string, report *reporter.Reporter) error {
67
	printf := progressPrintf(report, reporter.Update{Message: "copying kernel...", Status: reporter.StatusRunning})
68

69
	if err := utils.CopyFiles(printf, utils.SourceDestination(i.ukiPath, path)); err != nil {
70
		return err
71
	}
72

73
	report.Report(reporter.Update{Message: "UKI output ready", Status: reporter.StatusSucceeded})
74

75
	return nil
76
}
77

78
func (i *Imager) outCmdline(path string) error {
79
	return os.WriteFile(path, []byte(i.cmdline), 0o644)
80
}
81

82
//nolint:gocyclo
83
func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Reporter) error {
84
	printf := progressPrintf(report, reporter.Update{Message: "building ISO...", Status: reporter.StatusRunning})
85

86
	scratchSpace := filepath.Join(i.tempDir, "iso")
87

88
	var err error
89

90
	if i.prof.SecureBootEnabled() {
91
		isoOptions := pointer.SafeDeref(i.prof.Output.ISOOptions)
92

93
		var signer pesign.CertificateSigner
94

95
		signer, err = i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx)
96
		if err != nil {
97
			return fmt.Errorf("failed to get SecureBoot signer: %w", err)
98
		}
99

100
		derCrtPath := filepath.Join(i.tempDir, "uki.der")
101

102
		if err = os.WriteFile(derCrtPath, signer.Certificate().Raw, 0o600); err != nil {
103
			return fmt.Errorf("failed to write uki.der: %w", err)
104
		}
105

106
		options := iso.UEFIOptions{
107
			UKIPath:    i.ukiPath,
108
			SDBootPath: i.sdBootPath,
109

110
			SDBootSecureBootEnrollKeys: isoOptions.SDBootEnrollKeys.String(),
111

112
			UKISigningCertDerPath: derCrtPath,
113

114
			PlatformKeyPath:    i.prof.Input.SecureBoot.PlatformKeyPath,
115
			KeyExchangeKeyPath: i.prof.Input.SecureBoot.KeyExchangeKeyPath,
116
			SignatureKeyPath:   i.prof.Input.SecureBoot.SignatureKeyPath,
117

118
			Arch:    i.prof.Arch,
119
			Version: i.prof.Version,
120

121
			ScratchDir: scratchSpace,
122
			OutPath:    path,
123
		}
124

125
		if i.prof.Input.SecureBoot.PlatformKeyPath == "" {
126
			report.Report(reporter.Update{Message: "generating SecureBoot database...", Status: reporter.StatusRunning})
127

128
			// generate the database automatically from provided values
129
			enrolledPEM := pem.EncodeToMemory(&pem.Block{
130
				Type:  "CERTIFICATE",
131
				Bytes: signer.Certificate().Raw,
132
			})
133

134
			var entries []database.Entry
135

136
			entries, err = database.Generate(enrolledPEM, signer)
137
			if err != nil {
138
				return fmt.Errorf("failed to generate database: %w", err)
139
			}
140

141
			for _, entry := range entries {
142
				entryPath := filepath.Join(i.tempDir, entry.Name)
143

144
				if err = os.WriteFile(entryPath, entry.Contents, 0o600); err != nil {
145
					return err
146
				}
147

148
				switch entry.Name {
149
				case constants.PlatformKeyAsset:
150
					options.PlatformKeyPath = entryPath
151
				case constants.KeyExchangeKeyAsset:
152
					options.KeyExchangeKeyPath = entryPath
153
				case constants.SignatureKeyAsset:
154
					options.SignatureKeyPath = entryPath
155
				default:
156
					return fmt.Errorf("unknown database entry: %s", entry.Name)
157
				}
158
			}
159
		} else {
160
			options.PlatformKeyPath = i.prof.Input.SecureBoot.PlatformKeyPath
161
			options.KeyExchangeKeyPath = i.prof.Input.SecureBoot.KeyExchangeKeyPath
162
			options.SignatureKeyPath = i.prof.Input.SecureBoot.SignatureKeyPath
163
		}
164

165
		err = iso.CreateUEFI(printf, options)
166
	} else {
167
		err = iso.CreateGRUB(printf, iso.GRUBOptions{
168
			KernelPath:    i.prof.Input.Kernel.Path,
169
			InitramfsPath: i.initramfsPath,
170
			Cmdline:       i.cmdline,
171
			Version:       i.prof.Version,
172

173
			ScratchDir: scratchSpace,
174
			OutPath:    path,
175
		})
176
	}
177

178
	if err != nil {
179
		return err
180
	}
181

182
	report.Report(reporter.Update{Message: "ISO ready", Status: reporter.StatusSucceeded})
183

184
	return nil
185
}
186

187
func (i *Imager) outImage(ctx context.Context, path string, report *reporter.Reporter) error {
188
	printf := progressPrintf(report, reporter.Update{Message: "creating disk image...", Status: reporter.StatusRunning})
189

190
	if err := i.buildImage(ctx, path, printf); err != nil {
191
		return err
192
	}
193

194
	switch i.prof.Output.ImageOptions.DiskFormat {
195
	case profile.DiskFormatRaw:
196
		// nothing to do
197
	case profile.DiskFormatQCOW2:
198
		if err := qemuimg.Convert("raw", "qcow2", i.prof.Output.ImageOptions.DiskFormatOptions, path, printf); err != nil {
199
			return err
200
		}
201
	case profile.DiskFormatVPC:
202
		if err := qemuimg.Convert("raw", "vpc", i.prof.Output.ImageOptions.DiskFormatOptions, path, printf); err != nil {
203
			return err
204
		}
205
	case profile.DiskFormatOVA:
206
		scratchPath := filepath.Join(i.tempDir, "ova")
207

208
		if err := ova.CreateOVAFromRAW(path, i.prof.Arch, scratchPath, i.prof.Output.ImageOptions.DiskSize, printf); err != nil {
209
			return err
210
		}
211
	case profile.DiskFormatUnknown:
212
		fallthrough
213
	default:
214
		return fmt.Errorf("unsupported disk format: %s", i.prof.Output.ImageOptions.DiskFormat)
215
	}
216

217
	report.Report(reporter.Update{Message: "disk image ready", Status: reporter.StatusSucceeded})
218

219
	return nil
220
}
221

222
func (i *Imager) buildImage(ctx context.Context, path string, printf func(string, ...any)) error {
223
	if err := utils.CreateRawDisk(printf, path, i.prof.Output.ImageOptions.DiskSize); err != nil {
224
		return err
225
	}
226

227
	printf("attaching loopback device")
228

229
	var (
230
		loDevice string
231
		err      error
232
	)
233

234
	if loDevice, err = utils.Loattach(path); err != nil {
235
		return err
236
	}
237

238
	defer func() {
239
		printf("detaching loopback device")
240

241
		if e := utils.Lodetach(loDevice); e != nil {
242
			log.Println(e)
243
		}
244
	}()
245

246
	cmdline := procfs.NewCmdline(i.cmdline)
247

248
	scratchSpace := filepath.Join(i.tempDir, "image")
249

250
	opts := &install.Options{
251
		Disk:       loDevice,
252
		Platform:   i.prof.Platform,
253
		Arch:       i.prof.Arch,
254
		Board:      i.prof.Board,
255
		MetaValues: install.FromMeta(i.prof.Customization.MetaContents),
256

257
		ImageSecureboot: i.prof.SecureBootEnabled(),
258
		Version:         i.prof.Version,
259
		BootAssets: options.BootAssets{
260
			KernelPath:      i.prof.Input.Kernel.Path,
261
			InitramfsPath:   i.initramfsPath,
262
			UKIPath:         i.ukiPath,
263
			SDBootPath:      i.sdBootPath,
264
			DTBPath:         i.prof.Input.DTB.Path,
265
			UBootPath:       i.prof.Input.UBoot.Path,
266
			RPiFirmwarePath: i.prof.Input.RPiFirmware.Path,
267
		},
268
		MountPrefix: scratchSpace,
269
		Printf:      printf,
270
	}
271

272
	if i.overlayInstaller != nil {
273
		opts.OverlayInstaller = i.overlayInstaller
274
		opts.ExtraOptions = i.prof.Overlay.ExtraOptions
275
		opts.OverlayExtractedDir = i.tempDir
276
	}
277

278
	if opts.Board == "" {
279
		opts.Board = constants.BoardNone
280
	}
281

282
	installer, err := install.NewInstaller(ctx, cmdline, install.ModeImage, opts)
283
	if err != nil {
284
		return fmt.Errorf("failed to create installer: %w", err)
285
	}
286

287
	if err := installer.Install(ctx, install.ModeImage); err != nil {
288
		return fmt.Errorf("failed to install: %w", err)
289
	}
290

291
	return nil
292
}
293

294
//nolint:gocyclo,cyclop
295
func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter.Reporter) error {
296
	printf := progressPrintf(report, reporter.Update{Message: "building installer...", Status: reporter.StatusRunning})
297

298
	baseInstallerImg, err := i.prof.Input.BaseInstaller.Pull(ctx, i.prof.Arch, printf)
299
	if err != nil {
300
		return err
301
	}
302

303
	baseLayers, err := baseInstallerImg.Layers()
304
	if err != nil {
305
		return fmt.Errorf("failed to get layers: %w", err)
306
	}
307

308
	configFile, err := baseInstallerImg.ConfigFile()
309
	if err != nil {
310
		return fmt.Errorf("failed to get config file: %w", err)
311
	}
312

313
	config := *configFile.Config.DeepCopy()
314

315
	printf("creating empty image")
316

317
	newInstallerImg := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
318
	newInstallerImg = mutate.ConfigMediaType(newInstallerImg, types.OCIConfigJSON)
319

320
	newInstallerImg, err = mutate.Config(newInstallerImg, config)
321
	if err != nil {
322
		return fmt.Errorf("failed to set config: %w", err)
323
	}
324

325
	newInstallerImg, err = mutate.CreatedAt(newInstallerImg, v1.Time{Time: time.Now()})
326
	if err != nil {
327
		return fmt.Errorf("failed to set created at: %w", err)
328
	}
329

330
	// Talos v1.5+ optimizes the install layers to be easily replaceable with new artifacts
331
	// other Talos versions will have an overhead of artifacts being stored twice
332
	if len(baseLayers) == 2 {
333
		// optimized for installer image for artifacts replacements
334
		baseLayers = baseLayers[:1]
335
	}
336

337
	newInstallerImg, err = mutate.AppendLayers(newInstallerImg, baseLayers...)
338
	if err != nil {
339
		return fmt.Errorf("failed to append layers: %w", err)
340
	}
341

342
	var artifacts []filemap.File
343

344
	printf("generating artifacts layer")
345

346
	if i.prof.SecureBootEnabled() {
347
		artifacts = append(artifacts,
348
			filemap.File{
349
				ImagePath:  strings.TrimLeft(fmt.Sprintf(constants.UKIAssetPath, i.prof.Arch), "/"),
350
				SourcePath: i.ukiPath,
351
			},
352
			filemap.File{
353
				ImagePath:  strings.TrimLeft(fmt.Sprintf(constants.SDBootAssetPath, i.prof.Arch), "/"),
354
				SourcePath: i.sdBootPath,
355
			},
356
		)
357
	} else {
358
		artifacts = append(artifacts,
359
			filemap.File{
360
				ImagePath:  strings.TrimLeft(fmt.Sprintf(constants.KernelAssetPath, i.prof.Arch), "/"),
361
				SourcePath: i.prof.Input.Kernel.Path,
362
			},
363
			filemap.File{
364
				ImagePath:  strings.TrimLeft(fmt.Sprintf(constants.InitramfsAssetPath, i.prof.Arch), "/"),
365
				SourcePath: i.initramfsPath,
366
			},
367
		)
368
	}
369

370
	if !quirks.New(i.prof.Version).SupportsOverlay() {
371
		for _, extraArtifact := range []struct {
372
			sourcePath string
373
			imagePath  string
374
		}{
375
			{
376
				sourcePath: i.prof.Input.DTB.Path,
377
				imagePath:  strings.TrimLeft(fmt.Sprintf(constants.DTBAssetPath, i.prof.Arch), "/"),
378
			},
379
			{
380
				sourcePath: i.prof.Input.UBoot.Path,
381
				imagePath:  strings.TrimLeft(fmt.Sprintf(constants.UBootAssetPath, i.prof.Arch), "/"),
382
			},
383
			{
384
				sourcePath: i.prof.Input.RPiFirmware.Path,
385
				imagePath:  strings.TrimLeft(fmt.Sprintf(constants.RPiFirmwareAssetPath, i.prof.Arch), "/"),
386
			},
387
		} {
388
			if extraArtifact.sourcePath == "" {
389
				continue
390
			}
391

392
			var extraFiles []filemap.File
393

394
			extraFiles, err = filemap.Walk(extraArtifact.sourcePath, extraArtifact.imagePath)
395
			if err != nil {
396
				return fmt.Errorf("failed to walk extra artifact %s: %w", extraArtifact.sourcePath, err)
397
			}
398

399
			artifacts = append(artifacts, extraFiles...)
400
		}
401
	}
402

403
	artifactsLayer, err := filemap.Layer(artifacts)
404
	if err != nil {
405
		return fmt.Errorf("failed to create artifacts layer: %w", err)
406
	}
407

408
	newInstallerImg, err = mutate.AppendLayers(newInstallerImg, artifactsLayer)
409
	if err != nil {
410
		return fmt.Errorf("failed to append artifacts layer: %w", err)
411
	}
412

413
	if i.overlayInstaller != nil {
414
		tempOverlayPath := filepath.Join(i.tempDir, "overlay-installer", constants.ImagerOverlayBasePath)
415

416
		if err := os.MkdirAll(tempOverlayPath, 0o755); err != nil {
417
			return fmt.Errorf("failed to create overlay directory: %w", err)
418
		}
419

420
		if err := i.prof.Input.OverlayInstaller.Extract(
421
			ctx,
422
			tempOverlayPath,
423
			i.prof.Arch,
424
			progressPrintf(report, reporter.Update{Message: "pulling overlay for installer...", Status: reporter.StatusRunning}),
425
		); err != nil {
426
			return err
427
		}
428

429
		extraOpts, internalErr := yaml.Marshal(i.prof.Overlay.ExtraOptions)
430
		if internalErr != nil {
431
			return fmt.Errorf("failed to marshal extra options: %w", internalErr)
432
		}
433

434
		if internalErr = os.WriteFile(filepath.Join(i.tempDir, constants.ImagerOverlayExtraOptionsPath), extraOpts, 0o644); internalErr != nil {
435
			return fmt.Errorf("failed to write extra options yaml: %w", internalErr)
436
		}
437

438
		printf("generating overlay installer layer")
439

440
		var overlayArtifacts []filemap.File
441

442
		for _, extraArtifact := range []struct {
443
			sourcePath string
444
			imagePath  string
445
			mode       os.FileMode
446
		}{
447
			{
448
				sourcePath: filepath.Join(i.tempDir, "overlay-installer", constants.ImagerOverlayArtifactsPath),
449
				imagePath:  strings.TrimLeft(constants.ImagerOverlayArtifactsPath, "/"),
450
			},
451
			{
452
				sourcePath: filepath.Join(i.tempDir, "overlay-installer", constants.ImagerOverlayInstallersPath, i.prof.Overlay.Name),
453
				imagePath:  strings.TrimLeft(constants.ImagerOverlayInstallerDefaultPath, "/"),
454
				mode:       0o755,
455
			},
456
			{
457
				sourcePath: filepath.Join(i.tempDir, constants.ImagerOverlayExtraOptionsPath),
458
				imagePath:  strings.TrimLeft(constants.ImagerOverlayExtraOptionsPath, "/"),
459
			},
460
		} {
461
			var extraFiles []filemap.File
462

463
			extraFiles, err = filemap.Walk(extraArtifact.sourcePath, extraArtifact.imagePath)
464
			if err != nil {
465
				return fmt.Errorf("failed to walk extra artifact %s: %w", extraArtifact.sourcePath, err)
466
			}
467

468
			for i := range extraFiles {
469
				extraFiles[i].ImageMode = int64(extraArtifact.mode)
470
			}
471

472
			overlayArtifacts = append(overlayArtifacts, extraFiles...)
473
		}
474

475
		overlayArtifactsLayer, internalErr := filemap.Layer(overlayArtifacts)
476
		if internalErr != nil {
477
			return fmt.Errorf("failed to create overlay artifacts layer: %w", internalErr)
478
		}
479

480
		newInstallerImg, internalErr = mutate.AppendLayers(newInstallerImg, overlayArtifactsLayer)
481
		if internalErr != nil {
482
			return fmt.Errorf("failed to append overlay artifacts layer: %w", internalErr)
483
		}
484
	}
485

486
	ref, err := name.ParseReference(i.prof.Input.BaseInstaller.ImageRef)
487
	if err != nil {
488
		return fmt.Errorf("failed to parse image reference: %w", err)
489
	}
490

491
	printf("writing image tarball")
492

493
	if err := tarball.WriteToFile(path, ref, newInstallerImg); err != nil {
494
		return fmt.Errorf("failed to write image tarball: %w", err)
495
	}
496

497
	report.Report(reporter.Update{Message: "installer container image ready", Status: reporter.StatusSucceeded})
498

499
	return nil
500
}
501

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.