From 66ac74b2dba021a3de3c968c05a6567b55cf2db6 Mon Sep 17 00:00:00 2001 From: Jake Schuurmans Date: Tue, 24 Sep 2024 17:20:58 -0400 Subject: [PATCH] More updates to get bioscfg ready Update config, making it uniform with other controllers Remove "store" interface Add BMC interface for later use Update rivets --- cmd/run.go | 29 +-- go.mod | 5 +- go.sum | 8 +- internal/config/config.go | 149 +++++++++++++ internal/configuration/configuration.go | 271 ------------------------ internal/handlers/handlers.go | 6 +- internal/store/bmc/bmc.go | 248 ++++++++++++++++++++++ internal/store/bmc/dryRun.go | 184 ++++++++++++++++ internal/store/bmc/interface.go | 18 ++ internal/store/fleetdb/client.go | 55 ++--- internal/store/fleetdb/config.go | 56 +++++ internal/store/fleetdb/errors.go | 15 ++ internal/store/fleetdb/fleetdb.go | 22 +- internal/store/fleetdb/fleetdb_test.go | 1 + internal/store/store.go | 19 -- 15 files changed, 730 insertions(+), 356 deletions(-) create mode 100644 internal/config/config.go delete mode 100644 internal/configuration/configuration.go create mode 100644 internal/store/bmc/bmc.go create mode 100644 internal/store/bmc/dryRun.go create mode 100644 internal/store/bmc/interface.go create mode 100644 internal/store/fleetdb/config.go create mode 100644 internal/store/fleetdb/errors.go delete mode 100644 internal/store/store.go diff --git a/cmd/run.go b/cmd/run.go index 24f07dd..bd347cb 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -9,40 +9,41 @@ import ( "syscall" "github.com/equinix-labs/otel-init-go/otelinit" - "github.com/metal-toolbox/bioscfg/internal/configuration" + "github.com/metal-toolbox/bioscfg/internal/config" "github.com/metal-toolbox/bioscfg/internal/handlers" "github.com/metal-toolbox/bioscfg/internal/log" "github.com/metal-toolbox/bioscfg/internal/metrics" "github.com/metal-toolbox/bioscfg/internal/model" "github.com/metal-toolbox/bioscfg/internal/profiling" - "github.com/metal-toolbox/bioscfg/internal/store" + "github.com/metal-toolbox/bioscfg/internal/store/fleetdb" "github.com/metal-toolbox/bioscfg/internal/version" "github.com/metal-toolbox/ctrl" ) func runWorker(ctx context.Context, args *model.Args) error { - config, err := configuration.Load(args) + cfg, err := config.Load(args.ConfigFile, args.LogLevel) if err != nil { slog.Error("Failed to load configuration", "error", err) return err } - slog.Info("Configuration loaded", config.AsLogFields()...) + slog.Info("Configuration loaded", cfg.AsLogFields()...) - log.SetLevel(config.LogLevel) + log.SetLevel(cfg.LogLevel) // serve metrics endpoint metrics.ListenAndServe() version.ExportBuildInfoMetric() - if config.EnableProfiling { + if args.EnableProfiling { profiling.Enable() } ctx, otelShutdown := otelinit.InitOpenTelemetry(ctx, model.AppName) defer otelShutdown(ctx) - repository, err := store.NewRepository(ctx, config) + log.NewLogrusLogger(cfg.LogLevel) + repository, err := fleetdb.New(ctx, &cfg.Endpoints.FleetDB, log.NewLogrusLogger(cfg.LogLevel)) if err != nil { slog.Error("Failed to create repository", "error", err) return err @@ -62,15 +63,15 @@ func runWorker(ctx context.Context, args *model.Args) error { nc := ctrl.NewNatsController( model.AppName, - config.FacilityCode, + cfg.FacilityCode, model.AppSubject, - config.NatsConfig.NatsURL, - config.NatsConfig.CredsFile, + cfg.Endpoints.Nats.URL, + cfg.Endpoints.Nats.CredsFile, model.AppSubject, - ctrl.WithConcurrency(config.Concurrency), - ctrl.WithKVReplicas(config.NatsConfig.KVReplicas), - ctrl.WithConnectionTimeout(config.NatsConfig.ConnectTimeout), - ctrl.WithLogger(log.NewLogrusLogger(config.LogLevel)), + ctrl.WithConcurrency(cfg.Concurrency), + ctrl.WithKVReplicas(cfg.Endpoints.Nats.KVReplicationFactor), + ctrl.WithConnectionTimeout(cfg.Endpoints.Nats.ConnectTimeout), + ctrl.WithLogger(log.NewLogrusLogger(cfg.LogLevel)), ) if err = nc.Connect(ctx); err != nil { diff --git a/go.mod b/go.mod index b5b14c8..f8a4544 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.1 require ( github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e github.com/bmc-toolbox/bmclib/v2 v2.2.4 + github.com/bombsimon/logrusr/v2 v2.0.1 github.com/coreos/go-oidc v2.2.1+incompatible github.com/equinix-labs/otel-init-go v0.0.9 github.com/google/uuid v1.6.0 @@ -12,7 +13,7 @@ require ( github.com/jeremywohl/flatten v1.0.1 github.com/metal-toolbox/ctrl v0.2.9 github.com/metal-toolbox/fleetdb v1.19.5 - github.com/metal-toolbox/rivets v1.3.8-0.20240923144748-4fa59d630b50 + github.com/metal-toolbox/rivets v1.3.8 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 @@ -24,6 +25,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 + golang.org/x/net v0.28.0 golang.org/x/oauth2 v0.22.0 ) @@ -133,7 +135,6 @@ require ( golang.org/x/arch v0.9.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect diff --git a/go.sum b/go.sum index ee05935..8c2f359 100644 --- a/go.sum +++ b/go.sum @@ -217,6 +217,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -500,6 +501,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -551,8 +553,8 @@ github.com/metal-toolbox/ctrl v0.2.9 h1:Q1Hqpqyb71/gg2PcX/qrfoDE8FlydJt4rPQb7/Z8 github.com/metal-toolbox/ctrl v0.2.9/go.mod h1:QVATUIWFx3dbjOoEX0EnJHtRvypRlXZ9HUGaPLRyTG8= github.com/metal-toolbox/fleetdb v1.19.5 h1:ERgdFAUtWnT/AeVhCGclsENmwPhU88JUcgOZAdxWKYI= github.com/metal-toolbox/fleetdb v1.19.5/go.mod h1:k9MZXQsJX4NfBoANst6g1468papSs0tzsSyzN3gGWuQ= -github.com/metal-toolbox/rivets v1.3.8-0.20240923144748-4fa59d630b50 h1:v5aGsD3WnCOD6IB8o9F4XqR9kB/Vr/+LUQTmaG+aQYI= -github.com/metal-toolbox/rivets v1.3.8-0.20240923144748-4fa59d630b50/go.mod h1:8irU6eXgOa3QkjdcGi/aY4vqoMqCkbwVz7iVTYYPCX8= +github.com/metal-toolbox/rivets v1.3.8 h1:BxzBPBYPMGBwJurIe+8Xji2YL7vHZUHbOmMpszWfPYw= +github.com/metal-toolbox/rivets v1.3.8/go.mod h1:8irU6eXgOa3QkjdcGi/aY4vqoMqCkbwVz7iVTYYPCX8= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -671,6 +673,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.13.1 h1:6UkM3U1OnbhPsYeb1IMkQ6HSNOSikWluwOncJt4Tz/o= @@ -1025,6 +1028,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..d78e9e4 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,149 @@ +package config + +import ( + "os" + "strings" + + "github.com/jeremywohl/flatten" + "github.com/metal-toolbox/bioscfg/internal/store/fleetdb" + "github.com/metal-toolbox/rivets/events" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + "github.com/spf13/viper" +) + +var ( + ErrConfig = errors.New("configuration error") +) + +type Configuration struct { + FacilityCode string `mapstructure:"facility"` + LogLevel string `mapstructure:"log_level"` + Endpoints Endpoints `mapstructure:"endpoints"` + Dryrun bool `mapstructure:"dryrun"` + Concurrency int `mapstructure:"concurrency"` +} + +type Endpoints struct { + // NatsOptions defines the NATs events broker configuration parameters. + Nats events.NatsOptions `mapstructure:"nats"` + + // FleetDBConfig defines the fleetdb client configuration parameters + FleetDB fleetdb.Config `mapstructure:"fleetdb"` +} + +func Load(cfgFilePath, loglevel string) (*Configuration, error) { + v := viper.New() + cfg := &Configuration{} + + err := cfg.envBindVars(v) + if err != nil { + return nil, err + } + + v.SetConfigType("yaml") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.AutomaticEnv() + + err = readInFile(v, cfg, cfgFilePath) + if err != nil { + return nil, err + } + + if loglevel != "" { + cfg.LogLevel = loglevel + } + + err = cfg.validate() + return cfg, err +} + +// Reads in the cfgFile when available and overrides from environment variables. +func readInFile(v *viper.Viper, cfg *Configuration, path string) error { + if cfg == nil { + return ErrConfig + } + + if path != "" { + fh, err := os.Open(path) + if err != nil { + return errors.Wrap(ErrConfig, err.Error()) + } + + if err = v.ReadConfig(fh); err != nil { + return errors.Wrap(ErrConfig, "ReadConfig error:"+err.Error()) + } + } else { + v.AddConfigPath(".") + v.SetConfigName("config") + err := v.ReadInConfig() + if err != nil { + return err + } + } + + err := v.Unmarshal(cfg) + if err != nil { + return err + } + + return nil +} + +func (cfg *Configuration) validate() error { + if cfg == nil { + return ErrConfig + } + + if cfg.FacilityCode == "" { + return errors.Wrap(ErrConfig, "no facility codes") + } + + if cfg.LogLevel == "" { + cfg.LogLevel = "info" + } + + if cfg.Concurrency == 0 { + cfg.Concurrency = 1 + } + + return nil +} + +// envBindVars binds environment variables to the struct +// without a configuration file being unmarshalled, +// this is a workaround for a viper bug, +// +// This can be replaced by the solution in https://github.com/spf13/viper/pull/1429 +// once that PR is merged. +func (cfg *Configuration) envBindVars(v *viper.Viper) error { + envKeysMap := map[string]interface{}{} + if err := mapstructure.Decode(cfg, &envKeysMap); err != nil { + return err + } + + // Flatten nested conf map + flat, err := flatten.Flatten(envKeysMap, "", flatten.DotStyle) + if err != nil { + return errors.Wrap(err, "Unable to flatten config") + } + + for k := range flat { + if err := v.BindEnv(k); err != nil { + return errors.Wrap(ErrConfig, "env var bind error: "+err.Error()) + } + } + + return nil +} + +func (cfg *Configuration) AsLogFields() []any { + return []any{ + "logLevel", cfg.LogLevel, + "concurrency", cfg.Concurrency, + "facilityCode", cfg.FacilityCode, + "authenticate", cfg.Endpoints.FleetDB.Authenticate, + "fleetDBUrl", cfg.Endpoints.FleetDB.URL, + "natsURL", cfg.Endpoints.Nats.URL, + } +} diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go deleted file mode 100644 index fe7425d..0000000 --- a/internal/configuration/configuration.go +++ /dev/null @@ -1,271 +0,0 @@ -package configuration - -import ( - "net/url" - "os" - "strings" - "time" - - "github.com/jeremywohl/flatten" - "github.com/metal-toolbox/bioscfg/internal/model" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "github.com/spf13/viper" -) - -var ( - // NATs streaming configuration - defaultNatsConnectTimeout = 100 * time.Millisecond -) - -// NatsConfig holds NATS specific configuration -type NatsConfig struct { - NatsURL string - CredsFile string - KVReplicas int - ConnectTimeout time.Duration -} - -func newNatsConfig() *NatsConfig { - return &NatsConfig{ - ConnectTimeout: defaultNatsConnectTimeout, - } -} - -// Configuration holds application configuration read from a YAML or set by env variables. -// nolint:govet // prefer readability over field alignment optimization for this case. -type Configuration struct { - // LogLevel is the app verbose logging level. - // one of - info, debug, trace - LogLevel string `mapstructure:"log_level"` - - // Concurrency is the number of concurrent tasks that can be running at once. - Concurrency int `mapstructure:"concurrency"` - - // FacilityCode limits this service to events in a facility. - FacilityCode string `mapstructure:"facility_code"` - - // FleetDBOptions defines the fleetdb client configuration parameters - FleetDBOptions *FleetDBOptions `mapstructure:"fleetdb"` - - // NatsConfig defines the NATs events broker configuration parameters. - NatsConfig *NatsConfig `mapstructure:"nats"` - - EnableProfiling bool `mapstructure:"enable_profiling"` -} - -// New creates an empty configuration struct. -func New() *Configuration { - config := &Configuration{} - - // these are initialized here so viper can read in configuration from env vars - // once https://github.com/spf13/viper/pull/1429 is merged, this can go. - config.FleetDBOptions = &FleetDBOptions{} - config.NatsConfig = newNatsConfig() - - return config -} - -func (c *Configuration) AsLogFields() []any { - return []any{ - "logLevel", c.LogLevel, - "concurrency", c.Concurrency, - "facilityCode", c.FacilityCode, - "disableOAuth", c.FleetDBOptions.DisableOAuth, - "fleetDBUrl", c.FleetDBOptions.Endpoint, - "natsURL", c.NatsConfig.NatsURL, - "enableProfiling", c.EnableProfiling, - } -} - -func (c *Configuration) LoadArgs(args *model.Args) { - c.LogLevel = args.LogLevel - c.EnableProfiling = args.EnableProfiling - c.FacilityCode = args.FacilityCode -} - -// FleetDBOptions defines configuration for the fleetdb client. -// https://github.com/metal-toolbox/fleetdb -type FleetDBOptions struct { - Endpoint string `mapstructure:"endpoint"` - OidcIssuerEndpoint string `mapstructure:"oidc_issuer_endpoint"` - OidcAudienceEndpoint string `mapstructure:"oidc_audience_endpoint"` - OidcClientSecret string `mapstructure:"oidc_client_secret"` - OidcClientID string `mapstructure:"oidc_client_id"` - OidcClientScopes []string `mapstructure:"oidc_client_scopes"` - DisableOAuth bool `mapstructure:"disable_oauth"` -} - -// Load the application configuration -// Reads in the configFile when available and overrides from environment variables. -func Load(args *model.Args) (*Configuration, error) { - viperConfig := viper.New() - viperConfig.SetConfigType("yaml") - viperConfig.SetEnvPrefix(model.AppName) - viperConfig.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viperConfig.AutomaticEnv() - - if args.ConfigFile != "" { - fh, err := os.Open(args.ConfigFile) - if err != nil { - return nil, errors.Wrap(model.ErrConfig, err.Error()) - } - - if err = viperConfig.ReadConfig(fh); err != nil { - return nil, errors.Wrap(model.ErrConfig, "ReadConfig error: "+err.Error()) - } - } - - config := New() - config.LoadArgs(args) - - if err := config.envBindVars(viperConfig); err != nil { - return nil, errors.Wrap(model.ErrConfig, "env var bind error: "+err.Error()) - } - - if err := viperConfig.Unmarshal(config); err != nil { - return nil, errors.Wrap(model.ErrConfig, "Unmarshal error: "+err.Error()) - } - - config.envVarAppOverrides(viperConfig) - - if err := config.envVarNatsOverrides(viperConfig); err != nil { - return nil, errors.Wrap(model.ErrConfig, "nats env overrides error: "+err.Error()) - } - - if err := config.envVarFleetDBOverrides(viperConfig); err != nil { - return nil, errors.Wrap(model.ErrConfig, "fleetdb env overrides error: "+err.Error()) - } - - return config, nil -} - -func (c *Configuration) envVarAppOverrides(viperConfig *viper.Viper) { - logLevel := viperConfig.GetString("log.level") - if logLevel != "" { - c.LogLevel = logLevel - } -} - -// envBindVars binds environment variables to the struct -// without a configuration file being unmarshalled, -// this is a workaround for a viper bug, -// -// This can be replaced by the solution in https://github.com/spf13/viper/pull/1429 -// once that PR is merged. -func (c *Configuration) envBindVars(viperConfig *viper.Viper) error { - envKeysMap := map[string]interface{}{} - if err := mapstructure.Decode(c, &envKeysMap); err != nil { - return err - } - - // Flatten nested conf map - flat, err := flatten.Flatten(envKeysMap, "", flatten.DotStyle) - if err != nil { - return errors.Wrap(err, "Unable to flatten configuration") - } - - for k := range flat { - if err := viperConfig.BindEnv(k); err != nil { - return errors.Wrap(model.ErrConfig, "env var bind error: "+err.Error()) - } - } - - return nil -} - -// nolint:gocyclo // nats env configuration load is cyclomatic -func (c *Configuration) envVarNatsOverrides(viperConfig *viper.Viper) error { - if c.NatsConfig == nil { - c.NatsConfig = newNatsConfig() - } - - if viperConfig.GetString("nats.url") != "" { - c.NatsConfig.NatsURL = viperConfig.GetString("nats.url") - } - - if c.NatsConfig.NatsURL == "" { - return errors.New("missing parameter: nats.url") - } - - if viperConfig.GetString("nats.creds.file") != "" { - c.NatsConfig.CredsFile = viperConfig.GetString("nats.creds.file") - } - - if viperConfig.GetDuration("nats.connect.timeout") != 0 { - c.NatsConfig.ConnectTimeout = viperConfig.GetDuration("nats.connect.timeout") - } - - if viperConfig.GetInt("nats.kv.replicas") != 0 { - c.NatsConfig.KVReplicas = viperConfig.GetInt("nats.kv.replicas") - } - - return nil -} - -// nolint:gocyclo // parameter validation is cyclomatic -func (c *Configuration) envVarFleetDBOverrides(viperConfig *viper.Viper) error { - if c.FleetDBOptions == nil { - c.FleetDBOptions = &FleetDBOptions{} - } - - if viperConfig.GetString("fleetdb.endpoint") != "" { - c.FleetDBOptions.Endpoint = viperConfig.GetString("fleetdb.endpoint") - } - - // Validate endpoint - _, err := url.Parse(c.FleetDBOptions.Endpoint) - if err != nil { - return errors.New("fleetdb endpoint URL error: " + err.Error()) - } - - if viperConfig.GetString("fleetdb.disable.oauth") != "" { - c.FleetDBOptions.DisableOAuth = viperConfig.GetBool("fleetdb.disable.oauth") - } - - if c.FleetDBOptions.DisableOAuth { - return nil - } - - if viperConfig.GetString("fleetdb.oidc.issuer.endpoint") != "" { - c.FleetDBOptions.OidcIssuerEndpoint = viperConfig.GetString("fleetdb.oidc.issuer.endpoint") - } - - if c.FleetDBOptions.OidcIssuerEndpoint == "" { - return errors.New("fleetdb oidc.issuer.endpoint not defined") - } - - if viperConfig.GetString("fleetdb.oidc.audience.endpoint") != "" { - c.FleetDBOptions.OidcAudienceEndpoint = viperConfig.GetString("fleetdb.oidc.audience.endpoint") - } - - if c.FleetDBOptions.OidcAudienceEndpoint == "" { - return errors.New("fleetdb oidc.audience.endpoint not defined") - } - - if viperConfig.GetString("fleetdb.oidc.client.secret") != "" { - c.FleetDBOptions.OidcClientSecret = viperConfig.GetString("fleetdb.oidc.client.secret") - } - - if c.FleetDBOptions.OidcClientSecret == "" { - return errors.New("fleetdb.oidc.client.secret not defined") - } - - if viperConfig.GetString("fleetdb.oidc.client.id") != "" { - c.FleetDBOptions.OidcClientID = viperConfig.GetString("fleetdb.oidc.client.id") - } - - if c.FleetDBOptions.OidcClientID == "" { - return errors.New("fleetdb.oidc.client.id not defined") - } - - if viperConfig.GetString("fleetdb.oidc.client.scopes") != "" { - c.FleetDBOptions.OidcClientScopes = viperConfig.GetStringSlice("fleetdb.oidc.client.scopes") - } - - if len(c.FleetDBOptions.OidcClientScopes) == 0 { - return errors.New("fleetdb oidc.client.scopes not defined") - } - - return nil -} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 75dfc3c..f626ae8 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -7,7 +7,7 @@ import ( "github.com/bmc-toolbox/bmclib/v2" "github.com/metal-toolbox/bioscfg/internal/model" - "github.com/metal-toolbox/bioscfg/internal/store" + "github.com/metal-toolbox/bioscfg/internal/store/fleetdb" "github.com/metal-toolbox/bioscfg/internal/tasks" "github.com/metal-toolbox/ctrl" rctypes "github.com/metal-toolbox/rivets/condition" @@ -15,11 +15,11 @@ import ( // HandlerFactory has the data and business logic for the application type HandlerFactory struct { - repository store.Repository + repository *fleetdb.Store } // NewHandlerFactory returns a new instance of the Handler -func NewHandlerFactory(repository store.Repository) *HandlerFactory { +func NewHandlerFactory(repository *fleetdb.Store) *HandlerFactory { return &HandlerFactory{ repository: repository, } diff --git a/internal/store/bmc/bmc.go b/internal/store/bmc/bmc.go new file mode 100644 index 0000000..c441513 --- /dev/null +++ b/internal/store/bmc/bmc.go @@ -0,0 +1,248 @@ +package bmc + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "net/http/cookiejar" + "path" + "runtime" + "strings" + "time" + + "github.com/bmc-toolbox/bmclib/v2" + "github.com/bmc-toolbox/bmclib/v2/constants" + "github.com/bmc-toolbox/bmclib/v2/providers" + logrusrv2 "github.com/bombsimon/logrusr/v2" + "github.com/metal-toolbox/bioscfg/internal/model" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" + "golang.org/x/net/publicsuffix" +) + +const ( + logoutTimeout = 1 * time.Minute + loginTimeout = 1 * time.Minute +) + +var ( + errBMCLogin = errors.New("bmc login error") + errBMCLogout = errors.New("bmc logout error") + errBMCNotImplemented = errors.New("method not implemented") +) + +// Bmc is an implementation of the Queryor interface +type Client struct { + client *bmclib.Client + asset *model.Asset + logger *logrus.Entry +} + +// NewBMCClient creates a new Queryor interface for a BMC +func NewBMCClient(asset *model.Asset, logger *logrus.Entry) *Client { + client := newBmclibClient(asset, logger) + + return &Client{ + client, + asset, + logger, + } +} + +// Open creates a BMC session +func (b *Client) Open(ctx context.Context) error { + if b.client == nil { + return errors.Wrap(errBMCLogin, "client not initialized") + } + defer b.tracelog() + + return b.client.Open(ctx) +} + +// Close logs out of the BMC +func (b *Client) Close(traceCtx context.Context) error { + if b.client == nil { + return nil + } + + ctxClose, cancel := context.WithTimeout(traceCtx, logoutTimeout) + defer cancel() + + defer b.tracelog() + + if err := b.client.Close(ctxClose); err != nil { + return errors.Wrap(errBMCLogout, err.Error()) + } + + return nil +} + +// GetPowerState returns the device power status +func (b *Client) GetPowerState(ctx context.Context) (string, error) { + defer b.tracelog() + return b.client.GetPowerState(ctx) +} + +// SetPowerState sets the given power state on the device +func (b *Client) SetPowerState(ctx context.Context, state string) error { + defer b.tracelog() + _, err := b.client.SetPowerState(ctx, state) + return err +} + +// SetBootDevice sets the boot device of the remote device, and validates it was set +// +//nolint:gocritic // its a TODO +func (b *Client) SetBootDevice(ctx context.Context, device string, persistent, efiBoot bool) error { + ok, err := b.client.SetBootDevice(ctx, device, persistent, efiBoot) + if err != nil { + return err + } + + if !ok { + return errors.New("setting boot device failed") + } + + // Now lets validate the boot device order + // TODO; This is a WIP. We do not know yet if This is the right bmc call to get boot device + // override, err := b.client.GetBootDeviceOverride(ctx) + // if err != nil { + // return err + // } + + // if device != string(override.Device) { + // return errors.New("setting boot device failed to propagate") + // } + + // if efiBoot != override.IsEFIBoot { + // return errors.New("setting boot device EFI boot failed to propagate") + // } + + // if persistent != override.IsPersistent { + // return errors.New("setting boot device Persistent boot failed to propagate") + // } + + return nil +} + +// GetBootDevice gets the boot device information of the remote device +func (b *Client) GetBootDevice(_ context.Context) (device string, persistent, efiBoot bool, err error) { + return "", false, false, errors.Wrap(errBMCNotImplemented, "GetBootDevice") +} + +// PowerCycleBMC sets a power cycle action on the BMC of the remote device +func (b *Client) PowerCycleBMC(ctx context.Context) error { + defer b.tracelog() + _, err := b.client.ResetBMC(ctx, "GracefulRestart") + return err +} + +func (b *Client) HostBooted(ctx context.Context) (bool, error) { + defer b.tracelog() + status, _, err := b.client.PostCode(ctx) + if err != nil { + return false, err + } + return status == constants.POSTStateOS, nil +} + +func (b *Client) ResetBios(ctx context.Context) error { + defer b.tracelog() + return b.client.ResetBiosConfiguration(ctx) +} + +func (b *Client) tracelog() { + pc, _, _, _ := runtime.Caller(1) + funcName := path.Base(runtime.FuncForPC(pc).Name()) + + mapstr := func(m map[string]string) string { + if m == nil { + return "" + } + + var s []string + for k, v := range m { + s = append(s, k+": "+v) + } + + return strings.Join(s, ", ") + } + + b.logger.WithFields( + logrus.Fields{ + "attemptedProviders": strings.Join(b.client.GetMetadata().ProvidersAttempted, ","), + "successfulProvider": b.client.GetMetadata().SuccessfulProvider, + "successfulOpens": strings.Join(b.client.GetMetadata().SuccessfulOpenConns, ","), + "successfulCloses": strings.Join(b.client.GetMetadata().SuccessfulCloseConns, ","), + "failedProviderDetail": mapstr(b.client.GetMetadata().FailedProviderDetail), + }).Trace(funcName + ": connection metadata") +} + +func newHTTPClient() *http.Client { + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + panic(err) + } + + // nolint:gomnd // time duration declarations are clear as is. + return &http.Client{ + Timeout: time.Second * 600, + Jar: jar, + Transport: &http.Transport{ + // nolint:gosec // BMCs don't have valid certs. + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, + Dial: (&net.Dialer{ + Timeout: 180 * time.Second, + KeepAlive: 180 * time.Second, + }).Dial, + TLSHandshakeTimeout: 180 * time.Second, + ResponseHeaderTimeout: 600 * time.Second, + IdleConnTimeout: 180 * time.Second, + }, + } +} + +// newBmclibClient initializes a bmclib client with the given credentials +func newBmclibClient(asset *model.Asset, l *logrus.Entry) *bmclib.Client { + logger := logrus.New() + logger.Formatter = l.Logger.Formatter + + // setup a logr logger for bmclib + // bmclib uses logr, for which the trace logs are logged with log.V(3), + // this is a hax so the logrusr lib will enable trace logging + // since any value that is less than (logrus.LogLevel - 4) >= log.V(3) is ignored + // https://github.com/bombsimon/logrusr/blob/master/logrusr.go#L64 + switch l.Logger.GetLevel() { + case logrus.TraceLevel: + logger.Level = 7 + case logrus.DebugLevel: + logger.Level = 5 + } + + logruslogr := logrusrv2.New(logger) + + bmcClient := bmclib.NewClient( + asset.BmcAddress.String(), + asset.BmcUsername, + asset.BmcPassword, + bmclib.WithLogger(logruslogr), + bmclib.WithHTTPClient(newHTTPClient()), + bmclib.WithPerProviderTimeout(loginTimeout), + bmclib.WithRedfishEtagMatchDisabled(true), + bmclib.WithTracerProvider(otel.GetTracerProvider()), + ) + + bmcClient.Registry.Drivers = bmcClient.Registry.Supports( + providers.FeatureBmcReset, + providers.FeatureBootDeviceSet, + providers.FeaturePowerSet, + providers.FeaturePowerState, + ) + + // NOTE: remove the .Using("redfish") before this ends up in prod + // this is kept here since ipmitool doesn't work well in the docker sandbox env. + return bmcClient.Using("redfish") +} diff --git a/internal/store/bmc/dryRun.go b/internal/store/bmc/dryRun.go new file mode 100644 index 0000000..ff2edb1 --- /dev/null +++ b/internal/store/bmc/dryRun.go @@ -0,0 +1,184 @@ +package bmc + +import ( + "context" + "errors" + "time" + + "github.com/metal-toolbox/bioscfg/internal/model" +) + +type server struct { + powerStatus string + bootTime time.Time + bootDevice string + previousBootDevice string + persistent bool + efiBoot bool +} + +var ( + errBmcCantFindServer = errors.New("dryrun BMC couldnt find server to set state") + errBmcServerOffline = errors.New("dryrun BMC couldnt set boot device, server is off") + serverStates = make(map[string]server) +) + +// DryRunBMC is an simulated implementation of the Queryor interface +type DryRunBMCClient struct { + id string +} + +// NewDryRunBMCClient creates a new Queryor interface for a simulated BMC +func NewDryRunBMCClient(asset *model.Asset) *DryRunBMCClient { + _, ok := serverStates[asset.ID.String()] + if !ok { + serverStates[asset.ID.String()] = getDefaultSettings() + } + + return &DryRunBMCClient{ + asset.ID.String(), + } +} + +// Open simulates creating a BMC session +func (b *DryRunBMCClient) Open(_ context.Context) error { + return nil +} + +// Close simulates logging out of the BMC +func (b *DryRunBMCClient) Close(_ context.Context) error { + return nil +} + +// GetPowerState simulates returning the device power status +func (b *DryRunBMCClient) GetPowerState(_ context.Context) (string, error) { + server, err := b.getServer() + if err != nil { + return "", err + } + + return server.powerStatus, nil +} + +// SetPowerState simulates setting the given power state on the device +func (b *DryRunBMCClient) SetPowerState(_ context.Context, state string) error { + server, err := b.getServer() + if err != nil { + return err + } + + if isRestarting(state) { + server.bootTime = getRestartTime(state) + } + + server.powerStatus = state + serverStates[b.id] = *server + return nil +} + +// SetBootDevice simulates setting the boot device of the remote device +func (b *DryRunBMCClient) SetBootDevice(_ context.Context, device string, persistent, efiBoot bool) error { + server, err := b.getServer() + if err != nil { + return err + } + + if server.powerStatus != "on" { + return errBmcServerOffline + } + + server.previousBootDevice = server.bootDevice + server.bootDevice = device + server.persistent = persistent + server.efiBoot = efiBoot + + return nil +} + +// GetBootDevice simulates getting the boot device information of the remote device +func (b *DryRunBMCClient) GetBootDevice(_ context.Context) (device string, persistent, efiBoot bool, err error) { + server, err := b.getServer() + if err != nil { + return "", false, false, err + } + + if server.powerStatus != "on" { + return "", false, false, errBmcServerOffline + } + + return server.bootDevice, server.persistent, server.efiBoot, nil +} + +// PowerCycleBMC simulates a power cycle action on the BMC of the remote device +func (b *DryRunBMCClient) PowerCycleBMC(_ context.Context) error { + return nil +} + +// HostBooted reports whether or not the device has booted the host OS +func (b *DryRunBMCClient) HostBooted(_ context.Context) (bool, error) { + return true, nil +} + +func (b *DryRunBMCClient) ResetBios(ctx context.Context) error { + _, ok := serverStates[b.id] + if !ok { + return errBmcCantFindServer + } + + serverStates[b.id] = getDefaultSettings() + + return b.SetPowerState(ctx, "cycle") +} + +// getServer gets a simulateed server state, and update power status and boot device if required +func (b *DryRunBMCClient) getServer() (*server, error) { + state, ok := serverStates[b.id] + if !ok { + return nil, errBmcCantFindServer + } + + if isRestarting(state.powerStatus) { + if time.Now().After(state.bootTime) { + state.powerStatus = "on" + + if !state.persistent { + state.bootDevice = state.previousBootDevice + } + } + } + + return &state, nil +} + +func isRestarting(state string) bool { + switch state { + case "reset", "cycle": + return true + default: + return false + } +} + +func getRestartTime(state string) time.Time { + switch state { + case "reset": + return time.Now().Add(time.Second * 30) // Soft reboot should take longer than a hard reboot + case "cycle": + return time.Now().Add(time.Second * 20) + default: + return time.Now() // No reboot necessary + } +} + +func getDefaultSettings() server { + status := server{} + + status.powerStatus = "on" + status.bootDevice = "disk" + status.previousBootDevice = "disk" + status.persistent = true + status.efiBoot = false + status.bootTime = time.Now() + + return status +} diff --git a/internal/store/bmc/interface.go b/internal/store/bmc/interface.go new file mode 100644 index 0000000..20f3953 --- /dev/null +++ b/internal/store/bmc/interface.go @@ -0,0 +1,18 @@ +package bmc + +import ( + "context" +) + +// Queryor interface abstracts calls to remote devices +type BMC interface { + Open(ctx context.Context) error + Close(ctx context.Context) error + GetPowerState(ctx context.Context) (state string, err error) + SetPowerState(ctx context.Context, state string) error + SetBootDevice(ctx context.Context, device string, persistent, efiBoot bool) error + GetBootDevice(ctx context.Context) (device string, persistent, efiBoot bool, err error) + PowerCycleBMC(ctx context.Context) error + HostBooted(ctx context.Context) (bool, error) + ResetBios(ctx context.Context) error +} diff --git a/internal/store/fleetdb/client.go b/internal/store/fleetdb/client.go index 76b945c..3e15ffd 100644 --- a/internal/store/fleetdb/client.go +++ b/internal/store/fleetdb/client.go @@ -3,58 +3,53 @@ package fleetdb import ( "context" "io" - "log/slog" "net/http" "net/url" "time" "github.com/coreos/go-oidc" "github.com/hashicorp/go-retryablehttp" - "github.com/metal-toolbox/bioscfg/internal/configuration" - fleetdbapi "github.com/metal-toolbox/fleetdb/pkg/api/v1" - "github.com/pkg/errors" + "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "golang.org/x/oauth2/clientcredentials" + + fleetdbapi "github.com/metal-toolbox/fleetdb/pkg/api/v1" ) var ( // timeout for requests made by this client. - timeout = 30 * time.Second - ErrConfig = errors.New("error in fleetdb client configuration") + timeout = 30 * time.Second ) // NewFleetDBClient instantiates and returns a serverService client -func NewFleetDBClient(ctx context.Context, cfg *configuration.FleetDBOptions) (*fleetdbapi.Client, error) { - if cfg == nil { - return nil, errors.Wrap(ErrConfig, "configuration is nil") +func NewFleetDBClient(ctx context.Context, cfg *Config, logger *logrus.Logger) (*fleetdbapi.Client, error) { + err := cfg.validate() + if err != nil { + return nil, err } - if cfg.DisableOAuth { - return newFleetDBClientWithOtel(cfg, cfg.Endpoint) + if cfg.Authenticate { + return newFleetDBClientWithOAuthOtel(ctx, cfg, logger) } - return newFleetDBClientWithOAuthOtel(ctx, cfg, cfg.Endpoint) + return newFleetDBClientWithOtel(cfg, logger) } // returns a fleetdb retryable client with Otel -func newFleetDBClientWithOtel(cfg *configuration.FleetDBOptions, endpoint string) (*fleetdbapi.Client, error) { - if cfg == nil { - return nil, errors.Wrap(ErrConfig, "configuration is nil") - } - +func newFleetDBClientWithOtel(cfg *Config, logger *logrus.Logger) (*fleetdbapi.Client, error) { // init retryable http client retryableClient := retryablehttp.NewClient() - // log hook fo 500 errors since the retryablehttp client masks them + // log hook fo 500 errors since the the retryablehttp client masks them logHookFunc := func(_ retryablehttp.Logger, r *http.Response) { if r.StatusCode == http.StatusInternalServerError { b, err := io.ReadAll(r.Body) if err != nil { - slog.Warn("fleetdb query returned 500 status code; error reading body", "error", err) + logger.Warn("fleetdb query returned 500 error, got error reading body: ", err.Error()) return } - slog.Warn("fleetdb query returned 500 status code", "body", string(b)) + logger.Warn("fleetdb query returned 500 error, body: ", string(b)) } } @@ -69,18 +64,14 @@ func newFleetDBClientWithOtel(cfg *configuration.FleetDBOptions, endpoint string return fleetdbapi.NewClientWithToken( "dummy", - endpoint, + cfg.URL, client, ) } // returns a fleetdb retryable http client with Otel and Oauth wrapped in -func newFleetDBClientWithOAuthOtel(ctx context.Context, cfg *configuration.FleetDBOptions, endpoint string) (*fleetdbapi.Client, error) { - if cfg == nil { - return nil, errors.Wrap(ErrConfig, "configuration is nil") - } - - slog.Info("fleetdb client ctor") +func newFleetDBClientWithOAuthOtel(ctx context.Context, cfg *Config, logger *logrus.Logger) (*fleetdbapi.Client, error) { + logger.Info("fleetdb client ctor") // init retryable http client retryableClient := retryablehttp.NewClient() @@ -89,13 +80,13 @@ func newFleetDBClientWithOAuthOtel(ctx context.Context, cfg *configuration.Fleet retryableClient.HTTPClient = otelhttp.DefaultClient // setup oidc provider - provider, err := oidc.NewProvider(ctx, cfg.OidcIssuerEndpoint) + provider, err := oidc.NewProvider(ctx, cfg.OidcIssuerURL) if err != nil { return nil, err } - // clientID defaults to 'bioscfg' - clientID := "bioscfg" + // clientID defaults to 'flipflop' + clientID := "flipflop" if cfg.OidcClientID != "" { clientID = cfg.OidcClientID @@ -107,7 +98,7 @@ func newFleetDBClientWithOAuthOtel(ctx context.Context, cfg *configuration.Fleet ClientSecret: cfg.OidcClientSecret, TokenURL: provider.Endpoint().TokenURL, Scopes: cfg.OidcClientScopes, - EndpointParams: url.Values{"audience": []string{cfg.OidcAudienceEndpoint}}, + EndpointParams: url.Values{"audience": []string{cfg.OidcAudienceURL}}, } // wrap OAuth transport, cookie jar in the retryable client @@ -122,7 +113,7 @@ func newFleetDBClientWithOAuthOtel(ctx context.Context, cfg *configuration.Fleet return fleetdbapi.NewClientWithToken( cfg.OidcClientSecret, - endpoint, + cfg.URL, client, ) } diff --git a/internal/store/fleetdb/config.go b/internal/store/fleetdb/config.go new file mode 100644 index 0000000..d45e640 --- /dev/null +++ b/internal/store/fleetdb/config.go @@ -0,0 +1,56 @@ +package fleetdb + +import ( + "net/url" + + "github.com/pkg/errors" +) + +// FleetDBConfig defines configuration for the Serverservice client. +// https://github.com/metal-toolbox/fleetdb +type Config struct { + URL string `mapstructure:"url"` + OidcIssuerURL string `mapstructure:"oidc_issuer_url"` + OidcAudienceURL string `mapstructure:"oidc_audience_url"` + OidcClientSecret string `mapstructure:"oidc_client_secret"` + OidcClientID string `mapstructure:"oidc_client_id"` + OidcClientScopes []string `mapstructure:"oidc_client_scopes"` + Authenticate bool `mapstructure:"authenticate"` +} + +func (cfg *Config) validate() error { + if cfg == nil { + return errors.Wrap(ErrFleetDBConfig, "config was nil") + } + + if cfg.URL == "" { + return errors.Wrap(ErrFleetDBConfig, "url was empty") + } + + _, err := url.Parse(cfg.URL) + if err != nil { + return errors.Wrap(ErrFleetDBConfig, "url failed to parse, isnt a valid url") + } + + if !cfg.Authenticate { + return nil + } + + if cfg.OidcIssuerURL == "" { + return errors.Wrap(ErrFleetDBConfig, "oidc issuer url was empty") + } + + if cfg.OidcClientSecret == "" { + return errors.Wrap(ErrFleetDBConfig, "oidc secret was empty") + } + + if cfg.OidcClientID == "" { + return errors.Wrap(ErrFleetDBConfig, "oidc client id was empty") + } + + if len(cfg.OidcClientScopes) == 0 { + return errors.Wrap(ErrFleetDBConfig, "oidc scopes was empty") + } + + return nil +} diff --git a/internal/store/fleetdb/errors.go b/internal/store/fleetdb/errors.go new file mode 100644 index 0000000..016031b --- /dev/null +++ b/internal/store/fleetdb/errors.go @@ -0,0 +1,15 @@ +package fleetdb + +import "github.com/pkg/errors" + +var ( + ErrSlugs = errors.New("slugs error") + ErrServerServiceRegisterChanges = errors.New("error in server service API register changes") + ErrAssetObject = errors.New("asset object error") + ErrAssetObjectConversion = errors.New("error converting asset object") + ErrFleetDBObject = errors.New("serverService object error") + ErrChangeList = errors.New("error building change list") + ErrServerServiceAttrObject = errors.New("error in server service attribute object") + ErrFleetDBConfig = errors.New("fleetdb configuration error") + ErrInventoryQuery = errors.New("fleetdb query returned error") +) diff --git a/internal/store/fleetdb/fleetdb.go b/internal/store/fleetdb/fleetdb.go index 97d75f7..2ff86dc 100644 --- a/internal/store/fleetdb/fleetdb.go +++ b/internal/store/fleetdb/fleetdb.go @@ -3,43 +3,40 @@ package fleetdb import ( "context" "encoding/json" - "log/slog" "net" "github.com/google/uuid" - "github.com/metal-toolbox/bioscfg/internal/configuration" "github.com/metal-toolbox/bioscfg/internal/model" - fleetdbapi "github.com/metal-toolbox/fleetdb/pkg/api/v1" "github.com/metal-toolbox/rivets/fleetdb" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" + + fleetdbapi "github.com/metal-toolbox/fleetdb/pkg/api/v1" ) const ( pkgName = "internal/store" ) -var ( - ErrInventoryQuery = errors.New("fleetdb query returned error") - ErrFleetDBObject = errors.New("fleetdb object error") -) - // Store is an asset inventory store type Store struct { api *fleetdbapi.Client - config *configuration.FleetDBOptions + logger *logrus.Logger + config *Config } // New returns a fleetdb store queryor to lookup and publish assets to, from the store. -func New(ctx context.Context, cfg *configuration.FleetDBOptions) (*Store, error) { - apiclient, err := NewFleetDBClient(ctx, cfg) +func New(ctx context.Context, cfg *Config, logger *logrus.Logger) (*Store, error) { + apiclient, err := NewFleetDBClient(ctx, cfg, logger) if err != nil { return nil, err } s := &Store{ api: apiclient, + logger: logger, config: cfg, } @@ -79,8 +76,7 @@ func toAsset(server *fleetdbapi.Server, credential *fleetdbapi.ServerCredential) serverAttributes, err := serverAttributes(server.Attributes) if err != nil { - slog.Error("error getting server attributes", "error", err) - return nil, errors.Wrap(ErrFleetDBObject, err.Error()) + return nil, errors.Wrap(err, "error getting server attributes") } asset := &model.Asset{ diff --git a/internal/store/fleetdb/fleetdb_test.go b/internal/store/fleetdb/fleetdb_test.go index 9bc76f5..a2243e9 100644 --- a/internal/store/fleetdb/fleetdb_test.go +++ b/internal/store/fleetdb/fleetdb_test.go @@ -2,6 +2,7 @@ package fleetdb import ( "net" + "testing" "github.com/google/uuid" diff --git a/internal/store/store.go b/internal/store/store.go deleted file mode 100644 index 36f2a2b..0000000 --- a/internal/store/store.go +++ /dev/null @@ -1,19 +0,0 @@ -package store - -import ( - "context" - - "github.com/google/uuid" - "github.com/metal-toolbox/bioscfg/internal/configuration" - "github.com/metal-toolbox/bioscfg/internal/model" - "github.com/metal-toolbox/bioscfg/internal/store/fleetdb" -) - -type Repository interface { - // AssetByID returns asset based on the identifier. - AssetByID(ctx context.Context, assetID uuid.UUID) (*model.Asset, error) -} - -func NewRepository(ctx context.Context, config *configuration.Configuration) (Repository, error) { - return fleetdb.New(ctx, config.FleetDBOptions) -}