// 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" "errors" "github.com/prometheus/client_golang/prometheus" "github.com/siderolabs/gen/xerrors" "go.uber.org/zap" "github.com/siderolabs/image-factory/internal/schematic/storage" "github.com/siderolabs/image-factory/pkg/schematic" ) // OwnershipChecker resolves the authenticated user from a context. type OwnershipChecker interface { UsernameFromContext(ctx context.Context) (string, bool) } // 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) } // Get retrieves the stored schematic and enforces ownership. // // If auth is non-nil and the caller is unauthenticated, RequiresAuthenticationTag is returned // even when the schematic is not found, to avoid leaking schematic existence to anonymous callers. func (s *Factory) Get(ctx context.Context, id string, auth OwnershipChecker) (*schematic.Schematic, error) { sc, err := s.get(ctx, id) if err != nil { if auth != nil { if _, ok := auth.UsernameFromContext(ctx); !ok { return nil, xerrors.NewTagged[schematic.RequiresAuthenticationTag](err) } } return nil, err } if sc.Owner == "" && auth == nil { return sc, nil } if auth == nil { return nil, xerrors.NewTagged[schematic.RequiresAuthenticationTag](errors.New("authentication required")) } username, ok := auth.UsernameFromContext(ctx) if !ok { return nil, xerrors.NewTagged[schematic.RequiresAuthenticationTag](errors.New("authentication required")) } if username != sc.Owner { return nil, xerrors.NewTagged[schematic.ForbiddenTag](errors.New("access denied")) } return sc, nil } // 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{}