Skip to content

Commit 9a716b4

Browse files
buroautkuozdemir
authored andcommitted
feat: allow specifying a schematic id
Closes #3 Signed-off-by: Steven Kreitzer <[email protected]> Signed-off-by: Utku Ozdemir <[email protected]>
1 parent 30add4f commit 9a716b4

File tree

6 files changed

+64
-23
lines changed

6 files changed

+64
-23
lines changed

cmd/booter/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ func init() {
137137
rootCmd.Flags().BoolVar(&serverOptions.DisableDHCPProxy, "disable-dhcp-proxy", serverOptions.DisableDHCPProxy,
138138
"Disable the DHCP proxy server.")
139139

140+
rootCmd.Flags().StringVar(&serverOptions.SchematicID, "schematic-id", serverOptions.SchematicID,
141+
"The ID of the schematic to use.")
140142
rootCmd.Flags().StringSliceVar(&serverOptions.Extensions, "extensions", serverOptions.Extensions,
141143
"List of Talos extensions to use. The extensions will be used to generate schematic ID from the image factory.")
142144
rootCmd.Flags().StringVarP(&serverOptions.ExtraKernelArgs, extraKernelArgsFlag, "k", serverOptions.ExtraKernelArgs,

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/siderolabs/booter
22

3-
go 1.24.5
3+
go 1.25.3
44

55
replace (
66
github.com/pin/tftp/v3 => github.com/utkuozdemir/pin-tftp/v3 v3.0.0-20241021135417-0dd7dba351ad

internal/server/imagefactory/client.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,11 @@ func NewClient(baseURL, pxeBaseURL string, secureBootEnabled bool, logger *zap.L
3737
}, nil
3838
}
3939

40-
// SchematicIPXEURL ensures a schematic exists on the image factory and returns the iPXE URL to it.
41-
//
42-
// If agentMode is true, the schematic will be created with the firmware extensions and the metal-agent extension.
43-
func (c *Client) SchematicIPXEURL(ctx context.Context, talosVersion, arch string, extensions, extraKernelArgs []string) (string, error) {
44-
logger := c.logger.With(zap.String("talos_version", talosVersion), zap.String("arch", arch),
45-
zap.Strings("extensions", extensions), zap.Strings("extra_kernel_args", extraKernelArgs))
40+
// EnsureSchematic ensures a schematic exists on the image factory and returns its ID.
41+
func (c *Client) EnsureSchematic(ctx context.Context, extensions, extraKernelArgs []string) (string, error) {
42+
logger := c.logger.With(zap.Strings("extensions", extensions), zap.Strings("extra_kernel_args", extraKernelArgs))
4643

47-
logger.Debug("generate schematic iPXE URL")
48-
49-
if talosVersion == "" {
50-
return "", fmt.Errorf("talosVersion is required")
51-
}
44+
logger.Debug("ensure schematic")
5245

5346
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
5447
defer cancel()
@@ -74,12 +67,28 @@ func (c *Client) SchematicIPXEURL(ctx context.Context, talosVersion, arch string
7467
return "", fmt.Errorf("failed to create schematic: %w", err)
7568
}
7669

70+
return schematicID, nil
71+
}
72+
73+
// GetIPXEURL returns the iPXE URL for the given schematic ID, Talos version, and architecture.
74+
func (c *Client) GetIPXEURL(schematicID, talosVersion, arch string) (string, error) {
75+
if schematicID == "" {
76+
return "", fmt.Errorf("schematic ID is required")
77+
}
78+
79+
if talosVersion == "" {
80+
return "", fmt.Errorf("talos version is required")
81+
}
82+
83+
if arch == "" {
84+
return "", fmt.Errorf("arch is required")
85+
}
86+
7787
ipxeURL := fmt.Sprintf("%s/pxe/%s/%s/metal-%s", c.pxeBaseURL, schematicID, talosVersion, arch)
88+
7889
if c.secureBootEnabled {
7990
ipxeURL += "-secureboot"
8091
}
8192

82-
logger.Debug("generated schematic iPXE URL", zap.String("ipxe_url", ipxeURL))
83-
8493
return ipxeURL, nil
8594
}

internal/server/ipxe/handler.go

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ chain --replace %s
3737

3838
// ImageFactoryClient represents an image factory client which ensures a schematic exists on image factory, and returns the PXE URL to it.
3939
type ImageFactoryClient interface {
40-
SchematicIPXEURL(ctx context.Context, talosVersion, arch string, extensions, extraKernelArgs []string) (string, error)
40+
EnsureSchematic(ctx context.Context, extensions, extraKernelArgs []string) (string, error)
41+
GetIPXEURL(schematicID, talosVersion, arch string) (string, error)
4142
}
4243

4344
// HandlerOptions represents the options for the iPXE handler.
4445
type HandlerOptions struct {
4546
APIAdvertiseAddress string
4647
TalosVersion string
4748
ExtraKernelArgs string
49+
SchematicID string
4850
Extensions []string
4951
APIPort int
5052
}
@@ -93,9 +95,12 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
9395

9496
// TODO: later, we can do per-machine kernel args and system extensions here
9597

96-
kernelArgs := slices.Concat(handler.kernelArgs, handler.consoleKernelArgs(arch))
98+
consoleKernelArgs := handler.consoleKernelArgs(arch)
99+
kernelArgs := slices.Concat(handler.kernelArgs, consoleKernelArgs)
97100

98-
body, statusCode, err := handler.bootViaFactoryIPXEScript(ctx, handler.options.TalosVersion, arch, handler.options.Extensions, kernelArgs)
101+
logger.Debug("injected console kernel args to the iPXE request", zap.Strings("console_kernel_args", consoleKernelArgs))
102+
103+
body, statusCode, err := handler.bootViaFactoryIPXEScript(ctx, arch, kernelArgs)
99104
if err != nil {
100105
handler.logger.Error("failed to get iPXE script", zap.Error(err))
101106

@@ -123,9 +128,18 @@ func (handler *Handler) handleInitScript(w http.ResponseWriter) {
123128
}
124129
}
125130

126-
func (handler *Handler) bootViaFactoryIPXEScript(ctx context.Context, talosVersion, arch string, extensions, kernelArgs []string) (body string, statusCode int, err error) {
127-
ipxeURL, err := handler.imageFactoryClient.SchematicIPXEURL(ctx, talosVersion, arch, extensions, kernelArgs)
128-
if err != nil {
131+
func (handler *Handler) bootViaFactoryIPXEScript(ctx context.Context, arch string, kernelArgs []string) (body string, statusCode int, err error) {
132+
schematicID := handler.options.SchematicID
133+
134+
if schematicID == "" {
135+
if schematicID, err = handler.imageFactoryClient.EnsureSchematic(ctx, handler.options.Extensions, kernelArgs); err != nil {
136+
return "", http.StatusInternalServerError, fmt.Errorf("failed to get schematic IPXE URL: %w", err)
137+
}
138+
}
139+
140+
var ipxeURL string
141+
142+
if ipxeURL, err = handler.imageFactoryClient.GetIPXEURL(schematicID, handler.options.TalosVersion, arch); err != nil {
129143
return "", http.StatusInternalServerError, fmt.Errorf("failed to get schematic IPXE URL: %w", err)
130144
}
131145

@@ -145,6 +159,21 @@ func (handler *Handler) consoleKernelArgs(arch string) []string {
145159

146160
// NewHandler creates a new iPXE server.
147161
func NewHandler(ctx context.Context, configServerEnabled bool, imageFactoryClient ImageFactoryClient, options HandlerOptions, logger *zap.Logger) (*Handler, error) {
162+
apiHostPort := net.JoinHostPort(options.APIAdvertiseAddress, strconv.Itoa(options.APIPort))
163+
talosConfigURL := fmt.Sprintf("http://%s/config?u=${uuid}", apiHostPort)
164+
talosConfigKernelArg := fmt.Sprintf("%s=%s", constants.KernelParamConfig, talosConfigURL)
165+
166+
if options.SchematicID != "" {
167+
if len(options.Extensions) > 0 || len(options.ExtraKernelArgs) > 0 {
168+
return nil, fmt.Errorf("schematicID cannot be used with extensions or extraKernelArgs")
169+
}
170+
171+
if configServerEnabled {
172+
logger.Sugar().Warnf("schematic ID is set explicitly to %q and the config server is enabled (e.g., Omni connection is requested), "+
173+
"note that if this schematic does not contain the kernel arg %q, the machines will not be able to connect to the config server", options.SchematicID, talosConfigKernelArg)
174+
}
175+
}
176+
148177
initScript, err := buildInitScript(options.APIAdvertiseAddress, options.APIPort)
149178
if err != nil {
150179
return nil, fmt.Errorf("failed to build init script: %w", err)
@@ -161,10 +190,9 @@ func NewHandler(ctx context.Context, configServerEnabled bool, imageFactoryClien
161190
kernelArgs := strings.Fields(options.ExtraKernelArgs)
162191

163192
if configServerEnabled {
164-
apiHostPort := net.JoinHostPort(options.APIAdvertiseAddress, strconv.Itoa(options.APIPort))
165-
talosConfigURL := fmt.Sprintf("http://%s/config?u=${uuid}", apiHostPort)
193+
kernelArgs = append(kernelArgs, talosConfigKernelArg)
166194

167-
kernelArgs = append(kernelArgs, fmt.Sprintf("%s=%s", constants.KernelParamConfig, talosConfigURL))
195+
logger.Debug("injected talos config kernel arg to the iPXE requests", zap.String("arg", talosConfigKernelArg))
168196
}
169197

170198
return &Handler{

internal/server/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Options struct {
1515
DHCPProxyIfaceOrIP string
1616
TalosVersion string
1717
ExtraKernelArgs string
18+
SchematicID string
1819
Extensions []string
1920
Omni omni.Options
2021
APIPort int

internal/server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func (s *Server) Run(ctx context.Context) error {
9898
Extensions: s.options.Extensions,
9999
ExtraKernelArgs: s.options.ExtraKernelArgs,
100100
TalosVersion: s.options.TalosVersion,
101+
SchematicID: s.options.SchematicID,
101102
}, s.logger.With(zap.String("component", "ipxe_handler")))
102103
if err != nil {
103104
return fmt.Errorf("failed to create iPXE handler: %w", err)

0 commit comments

Comments
 (0)