omni/internal/backend/imagefactory/client.go
Utku Ozdemir 176f9d9f57
feat: compute schematic id only from the extensions
When determining the schematic ID of a machine, instead of relying the ID on the schematic ID meta-extension, compute the ID by gathering the extensions on the machine. This way, the extension ID will not contain the META values, labels or the kernel args.

This ID is actually the ID we need, as when we compare the desired schematic with the actual one during a Talos upgrade, we are only interested in the changes in the list of extensions.

This does not cause the kernel args, labels, etc. to disappear, as they are used at installation time and preserved afterward (e.g., during upgrades).

Additionally:
- Remove the list of extensions from the `Schematic` resource, as it relied upon the schematics always being created through Omni. This is not always the case - i.e., when a partial join config is used. Therefore, instead of relying on it, we store the list of extensions by directly reading them from the machine and storing them on the `MachineStatus` resource.
- Skip setting the schematic META section at all if there are no labels set on Download Installation Media screen.

Closes siderolabs/omni#55.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
2024-03-22 14:58:19 +03:00

123 lines
3.6 KiB
Go

// Copyright (c) 2024 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
// Package imagefactory implements image factory operations, enriching them with the Omni state.
package imagefactory
import (
"context"
"fmt"
"time"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/image-factory/pkg/client"
"github.com/siderolabs/image-factory/pkg/schematic"
"github.com/siderolabs/omni/client/pkg/omni/resources"
"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
"github.com/siderolabs/omni/internal/pkg/auth/actor"
)
// Client is the image factory client.
type Client struct {
state state.State
*client.Client
}
// NewClient creates a new image factory client.
func NewClient(omniState state.State, imageFactoryBaseURL string) (*Client, error) {
factoryClient, err := client.New(imageFactoryBaseURL)
if err != nil {
return nil, err
}
return &Client{
state: omniState,
Client: factoryClient,
}, nil
}
// EnsuredSchematic contains information on the ensured schematics.
type EnsuredSchematic struct {
// FullID is the ID of the full schematic - with the whole content of the schematic.Schematic, i.e., the extra kernel arguments, META values, and system extensions.
FullID string
// PlainID is the ID of the plain schematic - with only the system extensions.
PlainID string
}
// EnsureSchematic ensures that the given schematic exists in the image factory.
//
// It ensures two schematics: one with the full content of the schematic.Schematic, and another with only the system extensions.
//
// We do not call the image factory for the schematics that are already known to (cached by) Omni.
func (cli *Client) EnsureSchematic(ctx context.Context, inputSchematic schematic.Schematic) (EnsuredSchematic, error) {
fullSchematicID, err := cli.ensureSingleSchematic(ctx, inputSchematic)
if err != nil {
return EnsuredSchematic{}, fmt.Errorf("failed to ensure single schematic: %w", err)
}
plainSchematic := schematic.Schematic{
Customization: schematic.Customization{
SystemExtensions: schematic.SystemExtensions{
OfficialExtensions: inputSchematic.Customization.SystemExtensions.OfficialExtensions,
},
},
}
plainSchematicID, err := plainSchematic.ID()
if err != nil {
return EnsuredSchematic{}, fmt.Errorf("failed to generate plain schematic ID: %w", err)
}
if plainSchematicID != fullSchematicID {
plainSchematicID, err = cli.ensureSingleSchematic(ctx, plainSchematic)
if err != nil {
return EnsuredSchematic{}, fmt.Errorf("failed to ensure plain schematic: %w", err)
}
}
return EnsuredSchematic{
FullID: fullSchematicID,
PlainID: plainSchematicID,
}, nil
}
func (cli *Client) ensureSingleSchematic(ctx context.Context, schematic schematic.Schematic) (string, error) {
schematicID, err := schematic.ID()
if err != nil {
return "", fmt.Errorf("failed to generate schematic ID: %w", err)
}
schematicResource := omni.NewSchematic(
resources.DefaultNamespace, schematicID,
)
res, err := safe.StateGetByID[*omni.Schematic](ctx, cli.state, schematicID)
if err != nil && !state.IsNotFoundError(err) {
return "", err
}
if res != nil {
return schematicID, nil
}
callCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if _, err = cli.SchematicCreate(callCtx, schematic); err != nil {
return "", fmt.Errorf("failed to create schematic: %w", err)
}
ctx = actor.MarkContextAsInternalActor(ctx)
if err = cli.state.Create(ctx, schematicResource); err != nil && !state.IsConflictError(err) {
return "", err
}
return schematicID, nil
}