Mateusz Urbanek a1e37078e1
feat: add fallback if S3 is missbehaving
Add fallback to direct asset download in case of S3 issues.

Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
2025-08-14 11:35:40 +02:00

117 lines
3.0 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package schematic implements schematic factory: storing image schematics.
package schematic
import (
"context"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
"github.com/siderolabs/image-factory/internal/schematic/storage"
"github.com/siderolabs/image-factory/pkg/schematic"
)
// Factory is the schematic factory.
type Factory struct {
storage storage.Storage
metricGet prometheus.Counter
metricCreate prometheus.Counter
metricDuplicate prometheus.Counter
logger *zap.Logger
options Options
}
// Options for the schematic factory.
type Options struct {
MetricsNamespace string
}
// NewFactory creates a new schematic factory.
func NewFactory(logger *zap.Logger, storage storage.Storage, options Options) *Factory {
return &Factory{
options: options,
storage: storage,
logger: logger.With(zap.String("factory", "schematic")),
metricGet: prometheus.NewCounter(prometheus.CounterOpts{
Name: "image_factory_schematic_get_total",
Help: "Number of times schematics were retrieved.",
Namespace: options.MetricsNamespace,
}),
metricCreate: prometheus.NewCounter(prometheus.CounterOpts{
Name: "image_factory_schematic_create_total",
Help: "Number of new schematics created.",
Namespace: options.MetricsNamespace,
}),
metricDuplicate: prometheus.NewCounter(prometheus.CounterOpts{
Name: "image_factory_schematic_duplicate_create_total",
Help: "Number of new schematics which were created as duplicate.",
Namespace: options.MetricsNamespace,
}),
}
}
// Put stores the schematic.
//
// If the schematic already exists, Put does nothing.
func (s *Factory) Put(ctx context.Context, cfg *schematic.Schematic) (string, error) {
id, err := cfg.ID()
if err != nil {
return "", err
}
if err = s.storage.Head(ctx, id); err == nil {
s.logger.Info("schematic already exists", zap.String("id", id))
s.metricDuplicate.Inc()
return id, nil
}
data, err := cfg.Marshal()
if err != nil {
return "", err
}
err = s.storage.Put(ctx, id, data)
if err == nil {
s.metricCreate.Inc()
s.logger.Info("schematic created", zap.String("id", id), zap.Any("customization", cfg.Customization))
}
return id, err
}
// Get retrieves the stored schematic.
func (s *Factory) Get(ctx context.Context, id string) (*schematic.Schematic, error) {
data, err := s.storage.Get(ctx, id)
if err != nil {
return nil, err
}
s.metricGet.Inc()
return schematic.Unmarshal(data)
}
// Describe implements prom.Collector interface.
func (s *Factory) Describe(ch chan<- *prometheus.Desc) {
prometheus.DescribeByCollect(s, ch)
}
// Collect implements prom.Collector interface.
func (s *Factory) Collect(ch chan<- prometheus.Metric) {
s.metricCreate.Collect(ch)
s.metricGet.Collect(ch)
s.metricDuplicate.Collect(ch)
s.storage.Collect(ch)
}
var _ prometheus.Collector = &Factory{}