Skip to content

Commit 1ef2f02

Browse files
committed
feat: add presets to the 'cluster create qemu' command
* add 'iso', 'pxe', 'disk-image', 'maintenance' and 'secureboot' presets * swith the image-factory e2e test to use the create qemu command with presets Signed-off-by: Orzelius <[email protected]>
1 parent ec3bd87 commit 1ef2f02

File tree

16 files changed

+596
-79
lines changed

16 files changed

+596
-79
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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 constants
6+
7+
const (
8+
// ImageFactoryEmptySchematicID is the ID of an empty image factory schematic.
9+
ImageFactoryEmptySchematicID = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
10+
11+
// ImageFactoryURL is the url of the Sidero hosted image factory.
12+
ImageFactoryURL = "https://factory.talos.dev/"
13+
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 preset
6+
7+
import (
8+
"fmt"
9+
"net/url"
10+
11+
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops"
12+
)
13+
14+
// DiskImage configures Talos to boot from a disk image from the image factory.
15+
type DiskImage struct{}
16+
17+
// Name implements the Preset interface.
18+
func (DiskImage) Name() string { return "disk-image" }
19+
20+
// Description implements the Preset interface.
21+
func (DiskImage) Description() string {
22+
return "Configure Talos to boot from a disk image from the image factory."
23+
}
24+
25+
// ModifuOptions implements the Preset interface.
26+
func (DiskImage) ModifuOptions(presetOps Options, cOps *clusterops.Common, qOps *clusterops.Qemu) error {
27+
diskImageURL, err := url.JoinPath(presetOps.ImageFactoryURL.String(), "image", presetOps.SchematicID, cOps.TalosVersion, "metal-"+qOps.TargetArch)
28+
if presetOps.secureBoot {
29+
diskImageURL += secureBootSuffix
30+
}
31+
32+
diskImageURL += ".raw.zst"
33+
34+
if err != nil {
35+
return fmt.Errorf("failed to build an image factory disk-image url: %w", err)
36+
}
37+
38+
qOps.NodeDiskImagePath = diskImageURL
39+
40+
return nil
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 preset
6+
7+
import (
8+
"fmt"
9+
"net/url"
10+
11+
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops"
12+
)
13+
14+
// ISO configures Talos to boot from an iso from the image factory.
15+
type ISO struct{}
16+
17+
// Name implements the Preset interface.
18+
func (ISO) Name() string { return "iso" }
19+
20+
// Description implements the Preset interface.
21+
func (ISO) Description() string {
22+
return "Configure Talos to boot from an ISO from the image factory."
23+
}
24+
25+
// ModifuOptions implements the Preset interface.
26+
func (ISO) ModifuOptions(presetOps Options, cOps *clusterops.Common, qOps *clusterops.Qemu) error {
27+
isoURL, err := url.JoinPath(presetOps.ImageFactoryURL.String(), "image", presetOps.SchematicID, cOps.TalosVersion, "metal-"+qOps.TargetArch)
28+
if err != nil {
29+
return fmt.Errorf("failed to build an image factory iso url: %w", err)
30+
}
31+
32+
if presetOps.secureBoot {
33+
isoURL += secureBootSuffix
34+
}
35+
36+
isoURL += ".iso"
37+
38+
qOps.NodeISOPath = isoURL
39+
40+
return nil
41+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 preset
6+
7+
import "github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops"
8+
9+
// Maintenance configures Talos to boot from a disk image from the image factory.
10+
type Maintenance struct{}
11+
12+
// Name implements the Preset interface.
13+
func (Maintenance) Name() string { return "maintenance" }
14+
15+
// Description implements the Preset interface.
16+
func (Maintenance) Description() string {
17+
return "Skip applying machine configuration and leave the machines in maintenance mode. The machine configuration files are written to the working path."
18+
}
19+
20+
// ModifuOptions implements the Preset interface.
21+
func (Maintenance) ModifuOptions(presetOps Options, cOps *clusterops.Common, qOps *clusterops.Qemu) error {
22+
cOps.SkipInjectingConfig = true
23+
cOps.ApplyConfigEnabled = false
24+
25+
return nil
26+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 preset
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"net/url"
11+
"runtime"
12+
13+
"gopkg.in/typ.v4/slices"
14+
15+
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops"
16+
)
17+
18+
const secureBootSuffix = "-secureboot"
19+
20+
// Preset modifies cluster create options to achieve certain behavior.
21+
type Preset interface {
22+
Name() string
23+
Description() string
24+
25+
// ModifuOptions modifies configs to achieve the desired behavior
26+
ModifuOptions(presetOps Options, cOps *clusterops.Common, qOps *clusterops.Qemu) error
27+
}
28+
29+
// Options are the options required for presets to function.
30+
type Options struct {
31+
SchematicID string
32+
ImageFactoryURL *url.URL
33+
34+
// secureBoot preset also affects other presets so this option needs to be shared.
35+
secureBoot bool
36+
}
37+
38+
// Presets is a list of all available presets.
39+
var Presets = [...]Preset{
40+
ISO{},
41+
PXE{},
42+
DiskImage{},
43+
Maintenance{},
44+
SecureBoot{},
45+
}
46+
47+
// Apply validates and applies a set of multiple presets.
48+
func Apply(presetOps Options, cOps *clusterops.Common, qOps *clusterops.Qemu, presetNames []string) error {
49+
presets, err := slices.MapErr(presetNames, func(name string) (Preset, error) {
50+
if name == (SecureBoot{}).Name() {
51+
presetOps.secureBoot = true
52+
}
53+
54+
for _, p := range Presets {
55+
if p.Name() == name {
56+
return p, nil
57+
}
58+
}
59+
60+
return nil, fmt.Errorf("error: unknown preset: %q", name)
61+
})
62+
if err != nil {
63+
return err
64+
}
65+
66+
err = Validate(presetNames, presetOps)
67+
if err != nil {
68+
return err
69+
}
70+
71+
if err := applyDefaultSettings(presetOps, cOps, qOps); err != nil {
72+
return err
73+
}
74+
75+
for _, p := range presets {
76+
err = p.ModifuOptions(presetOps, cOps, qOps)
77+
if err != nil {
78+
return fmt.Errorf("failed to apply %q preset: %w", p.Name(), err)
79+
}
80+
}
81+
82+
return nil
83+
}
84+
85+
// Validate checks if the provided presets are valid and compatible.
86+
//
87+
//nolint:gocyclo
88+
func Validate(presetNames []string, presetOps Options) error {
89+
bootMethodPresets := []string{ISO{}.Name(), PXE{}.Name(), DiskImage{}.Name()}
90+
91+
// check if at least one boot method preset is selected, but no more than one
92+
bootMethodPresetCount := 0
93+
94+
for _, name := range presetNames {
95+
for _, bm := range bootMethodPresets {
96+
if name == bm {
97+
bootMethodPresetCount++
98+
}
99+
}
100+
}
101+
102+
if bootMethodPresetCount == 0 {
103+
return fmt.Errorf("error: at least one boot method preset must be specified (one of %v)", bootMethodPresets)
104+
}
105+
106+
if bootMethodPresetCount > 1 {
107+
return fmt.Errorf("error: multiple boot method presets specified, please select only one (one of %v)", bootMethodPresets)
108+
}
109+
110+
if presetOps.secureBoot && runtime.GOOS == "darwin" {
111+
return errors.New("error: 'secureboot' preset is currently not supported on darwin")
112+
}
113+
114+
// when secure boot is enabled ensure that iso preset is selected
115+
if presetOps.secureBoot {
116+
found := false
117+
isoPresetName := ISO{}.Name()
118+
119+
for _, name := range presetNames {
120+
if name == isoPresetName {
121+
found = true
122+
}
123+
}
124+
125+
if !found {
126+
return fmt.Errorf("error: secureboot preset can only be used with the iso preset")
127+
}
128+
}
129+
130+
return nil
131+
}
132+
133+
func applyDefaultSettings(presetOps Options, cOps *clusterops.Common, qOps *clusterops.Qemu) error {
134+
installerName := "metal-installer"
135+
if presetOps.secureBoot {
136+
installerName += secureBootSuffix
137+
}
138+
139+
installerURL, err := url.JoinPath(presetOps.ImageFactoryURL.Host, installerName, presetOps.SchematicID+":"+cOps.TalosVersion)
140+
if err != nil {
141+
return fmt.Errorf("failed to build installer image URL: %w", err)
142+
}
143+
144+
qOps.NodeInstallImage = installerURL
145+
146+
return nil
147+
}

0 commit comments

Comments
 (0)