Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor seesing #183

Merged
merged 7 commits into from
Oct 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 203 additions & 124 deletions seed/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,151 +46,230 @@ func New(pp ...Param) *Seeder {
return &Seeder{getters: gg}
}

type fieldMap map[*config.Field]bool

type flagInfo struct {
key string
field *config.Field
value *string
}

// Seed the provided config with values for their sources.
func (s *Seeder) Seed(cfg *config.Config) error {
seedMap := make(map[*config.Field]bool, len(cfg.Fields))
seeded := make(fieldMap, len(cfg.Fields))
flagSet := flag.NewFlagSet("Harvester flags", flag.ContinueOnError)
type flagInfo struct {
key string
field *config.Field
value *string
}

var flagInfos []*flagInfo
for _, f := range cfg.Fields {
seedMap[f] = false
ss := f.Sources()
val, ok := ss[config.SourceSeed]
if ok {
err := f.Set(val, 0)
if err != nil {
return err
}
slog.Debug("seed applied", "value", f, "name", f.Name())
seedMap[f] = true
seeded[f] = false

err := processSeedField(f, seeded)
if err != nil {
return err
}
key, ok := ss[config.SourceEnv]
if ok { //nolint:nestif
val, ok := os.LookupEnv(key)
if ok {
err := f.Set(val, 0)
if err != nil {
return err
}
slog.Debug("env var applied", "value", f, "name", f.Name())
seedMap[f] = true
} else {
if seedMap[f] {
slog.Debug("env var did not exist", "key", key, "name", f.Name())
} else {
slog.Debug("env var did not exist and no seed value provided", "key", key, "name", f.Name())
}
}

err = processEnvField(f, seeded)
if err != nil {
return err
}
key, ok = ss[config.SourceFlag]

fi, ok := processFlagField(f, flagSet)
if ok {
var val string
flagSet.StringVar(&val, key, "", "")
flagInfos = append(flagInfos, &flagInfo{key, f, &val})
flagInfos = append(flagInfos, fi)
}
key, ok = ss[config.SourceFile]
if ok {
body, err := os.ReadFile(key)
if err != nil {
slog.Error("failed to read file", "file", key, "name", f.Name(), "err", err)
} else {
err := f.Set(string(body), 0)
if err != nil {
return err
}

slog.Debug("file based var applied", "value", f, "field", f.Name())
seedMap[f] = true
}

err = processFileField(f, seeded)
if err != nil {
return err
}
key, ok = ss[config.SourceConsul]
if ok {
gtr, ok := s.getters[config.SourceConsul]
if !ok {
return errors.New("consul getter required")
}
value, version, err := gtr.Get(key)
if err != nil {
slog.Error("failed to get consul", "key", key, "field", f.Name(), "err", err)
continue
}
if value == nil {
slog.Error("consul key does not exist", "key", key, "field", f.Name())
continue
}
err = f.Set(*value, version)
if err != nil {
return err
}
slog.Debug("consul value applied", "value", f, "field", f.Name())
seedMap[f] = true

err = s.processConsulField(f, seeded)
if err != nil {
return err
}

key, ok = ss[config.SourceRedis]
if ok {
gtr, ok := s.getters[config.SourceRedis]
if !ok {
return errors.New("redis getter required")
}
value, version, err := gtr.Get(key)
if err != nil {
slog.Error("failed to get redis", "key", key, "field", f.Name(), "err", err)
continue
}
if value == nil {
slog.Error("redis key does not exist", "key", key, "field", f.Name())
continue
}
err = f.Set(*value, version)
if err != nil {
return err
}
slog.Debug("redis value applied", "value", f, "field", f.Name())
seedMap[f] = true
err = s.processRedisField(f, seeded)
if err != nil {
return err
}
}

if len(flagInfos) > 0 { //nolint:nestif
if !flagSet.Parsed() {
// Set the flagSet output to something that will not be displayed, otherwise in case of an error
// it will display the usage, which we don't want.
flagSet.SetOutput(io.Discard)

// Try to parse each flag independently so that if we encounter any unexpected flag (maybe used elsewhere),
// the parsing won't stop, and we make sure we try to parse every flag passed when running the command.
for _, arg := range os.Args[1:] {
if err := flagSet.Parse([]string{arg}); err != nil {
// Simply log errors that can happen, such as parsing unexpected flags. We want this to be silent,
// and we won't want to stop the execution.
slog.Error("could not parse flagSet", "err", err)
}
err := processFlags(flagInfos, flagSet, seeded)
if err != nil {
return err
}

return evaluateSeedMap(seeded)
}

func processSeedField(f *config.Field, seedMap fieldMap) error {
val, ok := f.Sources()[config.SourceSeed]
if !ok {
return nil
}
err := f.Set(val, 0)
if err != nil {
return err
}
slog.Debug("seed applied", "value", f, "name", f.Name())
seedMap[f] = true
return nil
}

func processEnvField(f *config.Field, seedMap fieldMap) error {
key, ok := f.Sources()[config.SourceEnv]
if !ok {
return nil
}
val, ok := os.LookupEnv(key)
if !ok {
if seedMap[f] {
slog.Debug("env var did not exist", "key", key, "name", f.Name())
} else {
slog.Debug("env var did not exist and no seed value provided", "key", key, "name", f.Name())
}
return nil
}

err := f.Set(val, 0)
if err != nil {
return err
}
slog.Debug("env var applied", "value", f, "name", f.Name())
seedMap[f] = true
return nil
}

func processFileField(f *config.Field, seedMap fieldMap) error {
key, ok := f.Sources()[config.SourceFile]
if !ok {
return nil
}

body, err := os.ReadFile(key)
if err != nil {
slog.Error("failed to read file", "file", key, "name", f.Name(), "err", err)
return nil
}

err = f.Set(string(body), 0)
if err != nil {
return err
}

slog.Debug("file based var applied", "value", f, "field", f.Name())
seedMap[f] = true
return nil
}

func (s *Seeder) processConsulField(f *config.Field, seedMap fieldMap) error {
key, ok := f.Sources()[config.SourceConsul]
if !ok {
return nil
}
gtr, ok := s.getters[config.SourceConsul]
if !ok {
return errors.New("consul getter required")
}
value, version, err := gtr.Get(key)
if err != nil {
slog.Error("failed to get consul", "key", key, "field", f.Name(), "err", err)
return nil
}
if value == nil {
slog.Error("consul key does not exist", "key", key, "field", f.Name())
return nil
}
err = f.Set(*value, version)
if err != nil {
return err
}
slog.Debug("consul value applied", "value", f, "field", f.Name())
seedMap[f] = true
return nil
}

func (s *Seeder) processRedisField(f *config.Field, seedMap fieldMap) error {
key, ok := f.Sources()[config.SourceRedis]
if !ok {
return nil
}
gtr, ok := s.getters[config.SourceRedis]
if !ok {
return errors.New("redis getter required")
}
value, version, err := gtr.Get(key)
if err != nil {
slog.Error("failed to get redis", "key", key, "field", f.Name(), "err", err)
return nil
}
if value == nil {
slog.Error("redis key does not exist", "key", key, "field", f.Name())
return nil
}
err = f.Set(*value, version)
if err != nil {
return err
}
slog.Debug("redis value applied", "value", f, "field", f.Name())
seedMap[f] = true
return nil
}

func processFlagField(f *config.Field, flagSet *flag.FlagSet) (*flagInfo, bool) {
key, ok := f.Sources()[config.SourceFlag]
if !ok {
return nil, false
}
var val string
flagSet.StringVar(&val, key, "", "")
return &flagInfo{key, f, &val}, true
}

func processFlags(infos []*flagInfo, flagSet *flag.FlagSet, seedMap fieldMap) error {
if len(infos) == 0 {
return nil
}

if !flagSet.Parsed() {
// Set the flagSet output to something that will not be displayed, otherwise in case of an error
// it will display the usage, which we don't want.
flagSet.SetOutput(io.Discard)

// Try to parse each flag independently so that if we encounter any unexpected flag (maybe used elsewhere),
// the parsing won't stop, and we make sure we try to parse every flag passed when running the command.
for _, arg := range os.Args[1:] {
if err := flagSet.Parse([]string{arg}); err != nil {
// Simply log errors that can happen, such as parsing unexpected flags. We want this to be silent,
// and we won't want to stop the execution.
slog.Error("could not parse flagSet", "err", err)
}
}
for _, flagInfo := range flagInfos {
hasFlag := false
flagSet.Visit(func(f *flag.Flag) {
if f.Name == flagInfo.key {
hasFlag = true
return
}
})
if hasFlag && flagInfo.value != nil {
err := flagInfo.field.Set(*flagInfo.value, 0)
if err != nil {
return err
}
slog.Debug("flag value applied", "value", flagInfo.field, "field", flagInfo.field.Name())
seedMap[flagInfo.field] = true
} else {
slog.Debug("flag var did not exist", "key", flagInfo.key, "field", flagInfo.field.Name())
}

for _, info := range infos {
hasFlag := false
flagSet.Visit(func(f *flag.Flag) {
if f.Name == info.key {
hasFlag = true
return
}
})
if hasFlag && info.value != nil {
err := info.field.Set(*info.value, 0)
if err != nil {
return err
}
slog.Debug("flag value applied", "value", info.field, "field", info.field.Name())
seedMap[info.field] = true
} else {
slog.Debug("flag var did not exist", "key", info.key, "field", info.field.Name())
}
}
return nil
}

func evaluateSeedMap(seedMap fieldMap) error {
sb := strings.Builder{}
for f, seeded := range seedMap {
if !seeded {
Expand Down
Loading