package main import ( "fmt" "net/mail" "net/url" "os" "reflect" "regexp" "strings" "text/template" "time" "github.com/spf13/cobra" "github.com/authelia/authelia/v4/internal/configuration/schema" ) // NewRunGenCmd implements the code generation cobra command. func NewRunGenCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ Use: "gen", RunE: runGenE, } return cmd } func runGenE(cmd *cobra.Command, args []string) (err error) { if err = genConfigurationKeys(); err != nil { return err } return nil } func genConfigurationKeys() (err error) { data := loadKeysTemplate() f, err := os.Create("./internal/configuration/schema/keys.go") if err != nil { return err } return keysTemplate.Execute(f, data) } var keysTemplate = template.Must(template.New("keys").Parse(`// Code generated by go generate. DO NOT EDIT. // // Run the following command to generate this file: // go run ./cmd/authelia-scripts gen // package schema // Keys represents the detected schema keys. var Keys = []string{ {{- range .Keys }} {{ printf "%q" . }}, {{- end }} } `)) type keysTemplateStruct struct { Timestamp time.Time Keys []string } func loadKeysTemplate() keysTemplateStruct { config := schema.Configuration{ Storage: schema.StorageConfiguration{ Local: &schema.LocalStorageConfiguration{}, MySQL: &schema.MySQLStorageConfiguration{}, PostgreSQL: &schema.PostgreSQLStorageConfiguration{}, }, Notifier: schema.NotifierConfiguration{ FileSystem: &schema.FileSystemNotifierConfiguration{}, SMTP: &schema.SMTPNotifierConfiguration{ TLS: &schema.TLSConfig{}, }, }, AuthenticationBackend: schema.AuthenticationBackendConfiguration{ File: &schema.FileAuthenticationBackendConfiguration{ Password: &schema.PasswordConfiguration{}, }, LDAP: &schema.LDAPAuthenticationBackendConfiguration{ TLS: &schema.TLSConfig{}, }, }, Session: schema.SessionConfiguration{ Redis: &schema.RedisSessionConfiguration{ TLS: &schema.TLSConfig{}, HighAvailability: &schema.RedisHighAvailabilityConfiguration{}, }, }, IdentityProviders: schema.IdentityProvidersConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{}, }, } return keysTemplateStruct{ Timestamp: time.Now(), Keys: readTags("", reflect.TypeOf(config)), } } var decodedTypes = []reflect.Type{ reflect.TypeOf(mail.Address{}), reflect.TypeOf(regexp.Regexp{}), reflect.TypeOf(url.URL{}), reflect.TypeOf(time.Duration(0)), reflect.TypeOf(schema.Address{}), } func containsType(needle reflect.Type, haystack []reflect.Type) (contains bool) { for _, t := range haystack { if needle.Kind() == reflect.Ptr { if needle.Elem() == t { return true } } else if needle == t { return true } } return false } func readTags(prefix string, t reflect.Type) (tags []string) { tags = make([]string, 0) for i := 0; i < t.NumField(); i++ { field := t.Field(i) tag := field.Tag.Get("koanf") if tag == "" { tags = append(tags, prefix) continue } switch field.Type.Kind() { case reflect.Struct: if !containsType(field.Type, decodedTypes) { tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false), field.Type)...) continue } case reflect.Slice: if field.Type.Elem().Kind() == reflect.Struct { if !containsType(field.Type.Elem(), decodedTypes) { tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false)) tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...) continue } } case reflect.Ptr: switch field.Type.Elem().Kind() { case reflect.Struct: if !containsType(field.Type.Elem(), decodedTypes) { tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, false), field.Type.Elem())...) continue } case reflect.Slice: if field.Type.Elem().Elem().Kind() == reflect.Struct { if !containsType(field.Type.Elem(), decodedTypes) { tags = append(tags, readTags(getKeyNameFromTagAndPrefix(prefix, tag, true), field.Type.Elem())...) continue } } } } tags = append(tags, getKeyNameFromTagAndPrefix(prefix, tag, false)) } return tags } func getKeyNameFromTagAndPrefix(prefix, name string, slice bool) string { nameParts := strings.SplitN(name, ",", 2) if prefix == "" { return nameParts[0] } if len(nameParts) == 2 && nameParts[1] == "squash" { return prefix } if slice { return fmt.Sprintf("%s.%s[]", prefix, nameParts[0]) } return fmt.Sprintf("%s.%s", prefix, nameParts[0]) }