package main import ( "context" "encoding/json" "errors" "fmt" "log" "os" "regexp" "strconv" "strings" "time" ) type Config struct { Port int Debug bool InitializeSlack bool SlackToken string SlackChannels []string DriverConn string BasicAuthUser string BasicAuthPassword string FillWithTestdata bool OllamaUrl string OllamaModel string RecapPromptIntro string RecapPrompt string AssetPattern string DatacenterPattern string EventNamePattern string driver Driver storage Storage ai AI slackToModelPipeline Pipeline slackScrapePipeline Pipeline modelToPersistencePipeline Pipeline persistenceToRecapPipeline Pipeline } var ( renderAssetPattern = `(dpg|svc|red)-[a-z0-9-]*[a-z0-9]|ip-[0-9]+-[0-9]+-[0-9]+-[0-9]+\.[a-z]+-[a-z]+-[0-9]+\.compute\.internal` renderDatacenterPattern = `[a-z]{4}[a-z]*-[0-9]` renderEventNamePattern = `(\[[^\]]*\] *)?(?P.*)` ) func newConfig(ctx context.Context) (Config, error) { return newConfigFromEnv(ctx, os.Getenv) } func newConfigFromEnv(ctx context.Context, getEnv func(string) string) (Config, error) { def := Config{ Port: 38080, OllamaModel: "llama3", AssetPattern: renderAssetPattern, DatacenterPattern: renderDatacenterPattern, EventNamePattern: renderEventNamePattern, RecapPromptIntro: "A Slack thread began with the following original post.", RecapPrompt: "Summarize all of the following thread responses. Do so in 1 sentence without any leading text.", } var m map[string]any if b, err := json.Marshal(def); err != nil { return Config{}, err } else if err := json.Unmarshal(b, &m); err != nil { return Config{}, err } re := regexp.MustCompile(`[A-Z]`) for k, v := range m { envK := k idxes := re.FindAllIndex([]byte(envK), -1) for i := len(idxes) - 1; i >= 0; i-- { idx := idxes[i] if idx[0] > 0 { envK = fmt.Sprintf("%s_%s", envK[:idx[0]], envK[idx[0]:]) } } envK = strings.ToUpper(envK) s := getEnv(envK) if s == "" { continue } switch v.(type) { case string: m[k] = s case int64, float64: n, err := strconv.ParseFloat(s, 32) if err != nil { return Config{}, err } m[k] = n case bool: got, err := strconv.ParseBool(s) if err != nil { return Config{}, err } m[k] = got case nil, []interface{}: m[k] = strings.Split(s, ",") default: return Config{}, fmt.Errorf("not impl: parse %s as %T", envK, v) } } var result Config if b, err := json.Marshal(m); err != nil { return Config{}, err } else if err := json.Unmarshal(b, &result); err != nil { return Config{}, err } ctx, can := context.WithTimeout(ctx, time.Minute) defer can() driver, err := NewDriver(ctx, result.DriverConn) if err != nil { return Config{}, err } result.driver = driver if !result.FillWithTestdata { //} else if err := result.driver.FillWithTestdata(ctx, result.AssetPattern, result.DatacenterPattern, result.EventNamePattern); err != nil { } else { return Config{}, errors.New("not impl") } if result.Debug { log.Printf("connected to driver at %s (%s @%s)", result.DriverConn, result.driver.engine, result.driver.conn) } storage, err := NewStorage(ctx, result.driver) if err != nil { return Config{}, err } result.storage = storage if result.OllamaUrl != "" { result.ai = NewAIOllama(result.OllamaUrl, result.OllamaModel) } else { result.ai = NewAINoop() } slackToModelPipeline, err := NewSlackToModelPipeline(ctx, result) if err != nil { return Config{}, err } result.slackToModelPipeline = slackToModelPipeline modelToPersistencePipeline, err := NewModelToPersistencePipeline(ctx, result) if err != nil { return Config{}, err } result.modelToPersistencePipeline = modelToPersistencePipeline slackScrapePipeline, err := NewSlackScrapePipeline(ctx, result) if err != nil { return Config{}, err } result.slackScrapePipeline = slackScrapePipeline persistenceToRecapPipeline, err := NewPersistenceToRecapPipeline(ctx, result) if err != nil { return Config{}, err } result.persistenceToRecapPipeline = persistenceToRecapPipeline return result, nil }