package main import ( "fmt" "log" "os" "path/filepath" "reflect" "regexp" "strings" "text/template" "sort" {{- range $key, $value := .}} pkg{{$key}} "{{$value}}" {{- end}} ) // ConfigField is the representation of one configuration field type ConfigField struct { EnvVars []string DefaultValue string Type string Description string VersionInfo string DeprecationLink string } // DeprecationField holds information about deprecation type DeprecationField struct { DeprecationVersion string DeprecationInfo string DeprecationReplacement string RemovalVersion string } // EnvVar holds information about one envvar type EnvVar struct { Name string DefaultValue string Type string Description string Services []string } type templateData struct { ExtensionName string Fields []ConfigField Deprecations []DeprecationField HasDeprecations bool } func main() { fmt.Println("Generating adoc documentation for environment variables:") adoc, err := os.ReadFile("../../docs/templates/ADOC.tmpl") if err != nil { log.Fatal(err) } dpr, err := os.ReadFile("../../docs/templates/ADOC_deprecation.tmpl") if err != nil { log.Fatal(err) } replacer := strings.NewReplacer( "github.com/owncloud/ocis/v2/services/", "", "/pkg/config/defaults", "", ) var ( fields []ConfigField deprecations []DeprecationField adocFile *os.File dprFile *os.File ) adoctpl := template.Must(template.New("").Parse(string(adoc))) dprtpl := template.Must(template.New("").Parse(string(dpr))) m := map[string]interface{}{ {{- range $key, $value := .}} "{{$value}}": *pkg{{$key}}.FullDefaultConfig(), {{- end }} } targetFolder := "../../docs/services/_includes/adoc/" all := make(map[string]EnvVar) for pkg, conf := range m { service := replacer.Replace(pkg) fields, deprecations = GetAnnotatedVariables(conf) var hasDeprecations bool if len(deprecations) > 0 { hasDeprecations = true } for _, f := range fields { for _, e := range f.EnvVars { if env, ok := all[e]; ok { env.Services = append(env.Services, service) sort.Slice(env.Services, func(i, j int) bool { return env.Services[i] < env.Services[j] }) all[e] = env } else { all[e] = EnvVar{ Name: e, Description: f.Description, Type: f.Type, DefaultValue: f.DefaultValue, Services: []string{service}, } } } } if len(fields) > 0 || len(deprecations) > 0 { fmt.Printf("... %s\n", pkg) td := templateData{ ExtensionName: service, Fields: fields, Deprecations: deprecations, HasDeprecations: hasDeprecations , } adocFile, err = os.Create(filepath.Join(targetFolder, service + "_configvars.adoc")) if err != nil { log.Fatalf("Failed to create target file: %s", err) } defer adocFile.Close() if err := adoctpl.Execute(adocFile, td); err != nil { log.Fatalf("Failed to execute template: %s", err) } dprFile, err = os.Create(filepath.Join(targetFolder, service + "_deprecation.adoc")) if err != nil { log.Fatalf("Failed to create target file: %s", err) } defer dprFile.Close() if err := dprtpl.Execute(dprFile, td); err != nil { log.Fatalf("Failed to execute template: %s", err) } } } // render global env vars tmplValues := make([]map[string]interface{}, 0) for _, env := range all { if len(env.Services) > 1 { tmplValues = append(tmplValues, map[string]interface{}{ "Name": env.Name, "Services": env.Services, "Description": env.Description, "DefaultValue": env.DefaultValue, "Type": env.Type, }) } } // sort sort.Slice(tmplValues, func(i, j int) bool { return tmplValues[i]["Name"].(string) < tmplValues[j]["Name"].(string) }) glc, err := os.ReadFile("../../docs/templates/ADOC_global.tmpl") if err != nil { log.Fatal(err) } gltpl := template.Must(template.New("").Parse(string(glc))) glfile, err := os.Create(filepath.Join(targetFolder, "global_configvars.adoc")) if err != nil { log.Fatalf("Failed to create target file: %s", err) } if err := gltpl.Execute(glfile, tmplValues); err != nil { log.Printf("Failed to execute template: %s", err) } fmt.Println("done") } func GetAnnotatedVariables(s interface{}) ([]ConfigField, []DeprecationField) { t := reflect.TypeOf(s) v := reflect.ValueOf(s) var fields []ConfigField var deprecations []DeprecationField for i := 0; i < t.NumField(); i++ { field := t.Field(i) value := v.Field(i) switch value.Kind() { default: desc := field.Tag.Get("desc") env, ok := field.Tag.Lookup("env") deprecationLink := "" if !ok { continue } deprecationVersion, _ := field.Tag.Lookup("deprecationVersion") removalVersion, _ := field.Tag.Lookup("removalVersion") deprecationInfo, _ := field.Tag.Lookup("deprecationInfo") deprecationReplacement, _ := field.Tag.Lookup("deprecationReplacement") if deprecationVersion != "" || removalVersion != "" || deprecationInfo != "" || deprecationReplacement != "" { deprecationLink = "xref:deprecation-note[Deprecation Note]" } v := fmt.Sprintf("%v", value.Interface()) td := strings.Split(env, ";") // re := regexp.MustCompile(`^(https?:\/\/)`) // v = re.ReplaceAllString(v,"\\$1") re := regexp.MustCompile(`(https?:\/\/)`) desc = re.ReplaceAllString(desc, "\\$1") re = regexp.MustCompile(`(\|)`) v = re.ReplaceAllString(v, "\\$1") typeName := value.Type().Name() if typeName == "" { typeName = value.Type().String() } fields = append(fields, ConfigField{ EnvVars: td, DefaultValue: v, Description: desc, Type: typeName, DeprecationLink: deprecationLink, }) if deprecationLink != "" { deprecations = append(deprecations, DeprecationField{ DeprecationVersion: deprecationVersion, DeprecationInfo: deprecationInfo, DeprecationReplacement: deprecationReplacement, RemovalVersion: removalVersion, }) } case reflect.Ptr: // PolicySelectors in the Proxy are being skipped atm // they are not configurable via env vars, if that changes // they are probably added to the Sanitize() function // and this should not be an issue then if !value.IsZero() && value.Elem().CanInterface() { f, d := GetAnnotatedVariables(value.Elem().Interface()) fields = append(fields, f...) deprecations = append(deprecations, d...) } case reflect.Struct: f, d := GetAnnotatedVariables(value.Interface()) fields = append(fields, f...) deprecations = append(deprecations, d...) } } return fields, deprecations }