omni/internal/backend/imagefactory/client.go
Utku Ozdemir 0e76483bab
Some checks failed
default / default (push) Has been cancelled
default / e2e-backups (push) Has been cancelled
default / e2e-forced-removal (push) Has been cancelled
default / e2e-omni-upgrade (push) Has been cancelled
default / e2e-scaling (push) Has been cancelled
default / e2e-short (push) Has been cancelled
default / e2e-short-secureboot (push) Has been cancelled
default / e2e-templates (push) Has been cancelled
default / e2e-upgrades (push) Has been cancelled
default / e2e-workload-proxy (push) Has been cancelled
chore: rekres, bump deps, Go, Talos and k8s versions, satisfy linters
- Bump some deps, namely cosi-runtime and Talos machinery.
- Update `auditState` to implement the new methods in COSI's `state.State`.
- Bump default Talos and Kubernetes versions to their latest.
- Rekres, which brings Go 1.24.5. Also update it in go.mod files.
- Fix linter errors coming from new linters.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
2025-07-11 18:23:48 +02:00

141 lines
4.0 KiB
Go

// Copyright (c) 2025 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"
"net/url"
"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 {
*client.Client
state state.State
host string
}
// 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
}
baseURL, err := url.Parse(imageFactoryBaseURL)
if err != nil {
return nil, fmt.Errorf("failed to parse image factory base URL %q: %w", imageFactoryBaseURL, err)
}
return &Client{
state: omniState,
Client: factoryClient,
host: baseURL.Host,
}, nil
}
// Host returns the host of the image factory client.
func (cli *Client) Host() string {
if cli == nil {
return ""
}
return cli.host
}
// 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
}