extract.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "regexp"
  8. "strings"
  9. )
  10. var legoProvidersSourceFolder string = "./lego/providers/dns/"
  11. var outputDir string = "./acmedns"
  12. var defTemplate string = `package acmedns
  13. /*
  14. THIS MODULE IS GENERATED AUTOMATICALLY
  15. DO NOT EDIT THIS FILE
  16. */
  17. import (
  18. "encoding/json"
  19. "fmt"
  20. "github.com/go-acme/lego/v4/challenge"
  21. {{imports}}
  22. )
  23. //name is the DNS provider name, e.g. cloudflare or gandi
  24. //JSON (js) must be in key-value string that match ConfigableFields Title in providers.json, e.g. {"Username":"far","Password":"boo"}
  25. func GetDNSProviderByJsonConfig(name string, js string)(challenge.Provider, error){
  26. switch name {
  27. {{magic}}
  28. default:
  29. return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
  30. }
  31. }
  32. `
  33. type Field struct {
  34. Title string
  35. Datatype string
  36. }
  37. type ProviderInfo struct {
  38. Name string //Name of this provider
  39. ConfigableFields []*Field //Field that shd be expose to user for filling in
  40. HiddenFields []*Field //Fields that is usable but shd be hidden from user
  41. }
  42. func fileExists(filePath string) bool {
  43. _, err := os.Stat(filePath)
  44. if err == nil {
  45. return true
  46. }
  47. if os.IsNotExist(err) {
  48. return false
  49. }
  50. // For other errors, you may handle them differently
  51. return false
  52. }
  53. // This function define the DNS not supported by zoraxy
  54. func getExcludedDNSProviders() []string {
  55. return []string{
  56. "edgedns", //Too complex data structure
  57. "exec", //Not a DNS provider
  58. "httpreq", //Not a DNS provider
  59. "hurricane", //Multi-credentials arch
  60. "oraclecloud", //Evil company
  61. "acmedns", //Not a DNS provider
  62. "selectelv2", //Not sure why not working with our code generator
  63. "designate", //OpenStack, if you are using this you shd not be using zoraxy
  64. "mythicbeasts", //Module require url.URL, which cannot be automatically parsed
  65. }
  66. }
  67. func isInSlice(str string, slice []string) bool {
  68. for _, s := range slice {
  69. if s == str {
  70. return true
  71. }
  72. }
  73. return false
  74. }
  75. func isExcludedDNSProvider(providerName string) bool {
  76. return isInSlice(providerName, getExcludedDNSProviders())
  77. }
  78. // extractConfigStruct extracts the name of the config struct and its content as plain text from a given source code.
  79. func extractConfigStruct(sourceCode string) (string, string) {
  80. // Regular expression to match the struct declaration.
  81. structRegex := regexp.MustCompile(`type\s+([A-Za-z0-9_]+)\s+struct\s*{([^{}]*)}`)
  82. // Find the first match of the struct declaration.
  83. match := structRegex.FindStringSubmatch(sourceCode)
  84. if len(match) < 3 {
  85. return "", "" // No match found
  86. }
  87. // Extract the struct name and its content.
  88. structName := match[1]
  89. structContent := match[2]
  90. return structName, structContent
  91. }
  92. func main() {
  93. // A map of provider name to information on what can be filled
  94. extractedProviderList := map[string]*ProviderInfo{}
  95. //Search all providers
  96. providers, err := filepath.Glob(filepath.Join(legoProvidersSourceFolder, "/*"))
  97. if err != nil {
  98. panic(err)
  99. }
  100. //Create output folder if not exists
  101. err = os.MkdirAll(outputDir, 0775)
  102. if err != nil {
  103. panic(err)
  104. }
  105. generatedConvertcode := ""
  106. importList := ""
  107. for _, provider := range providers {
  108. providerName := filepath.Base(provider)
  109. if isExcludedDNSProvider(providerName) {
  110. //Ignore this provider
  111. continue
  112. }
  113. //Check if {provider_name}.go exists
  114. providerDef := filepath.Join(provider, providerName+".go")
  115. if !fileExists(providerDef) {
  116. continue
  117. }
  118. fmt.Println("Extracting config structure for: " + providerDef)
  119. providerSrc, err := os.ReadFile(providerDef)
  120. if err != nil {
  121. fmt.Println(err.Error())
  122. return
  123. }
  124. _, strctContent := extractConfigStruct(string(providerSrc))
  125. //Filter and write the content to json
  126. /*
  127. Example of stctContent (Note the tab prefix)
  128. Token string
  129. PropagationTimeout time.Duration
  130. PollingInterval time.Duration
  131. SequenceInterval time.Duration
  132. HTTPClient *http.Client
  133. */
  134. strctContentLines := strings.Split(strctContent, "\n")
  135. configKeys := []*Field{}
  136. hiddenKeys := []*Field{}
  137. for _, lineDef := range strctContentLines {
  138. fields := strings.Fields(lineDef)
  139. if len(fields) < 2 || strings.HasPrefix(fields[0], "//") {
  140. //Ignore this line
  141. continue
  142. }
  143. //Filter out the fields that is not user-filled
  144. switch fields[1] {
  145. case "*url.URL":
  146. fallthrough
  147. case "string":
  148. configKeys = append(configKeys, &Field{
  149. Title: fields[0],
  150. Datatype: fields[1],
  151. })
  152. case "int":
  153. if fields[0] != "TTL" {
  154. configKeys = append(configKeys, &Field{
  155. Title: fields[0],
  156. Datatype: fields[1],
  157. })
  158. } else {
  159. hiddenKeys = append(hiddenKeys, &Field{
  160. Title: fields[0],
  161. Datatype: fields[1],
  162. })
  163. }
  164. case "bool":
  165. if fields[0] == "InsecureSkipVerify" || fields[0] == "SSLVerify" || fields[0] == "Debug" {
  166. configKeys = append(configKeys, &Field{
  167. Title: fields[0],
  168. Datatype: fields[1],
  169. })
  170. } else {
  171. hiddenKeys = append(hiddenKeys, &Field{
  172. Title: fields[0],
  173. Datatype: fields[1],
  174. })
  175. }
  176. default:
  177. //Not used fields
  178. hiddenKeys = append(hiddenKeys, &Field{
  179. Title: fields[0],
  180. Datatype: fields[1],
  181. })
  182. }
  183. }
  184. fmt.Println(strctContent)
  185. extractedProviderList[providerName] = &ProviderInfo{
  186. Name: providerName,
  187. ConfigableFields: configKeys,
  188. HiddenFields: hiddenKeys,
  189. }
  190. //Generate the code for it
  191. //Generate the code for converting incoming json into target config
  192. codeSegment := `
  193. case "` + providerName + `":
  194. cfg := ` + providerName + `.Config{}
  195. err := json.Unmarshal([]byte(js), &cfg)
  196. if err != nil {
  197. return nil, err
  198. }
  199. return ` + providerName + `.NewDNSProviderConfig(&cfg)`
  200. generatedConvertcode += codeSegment
  201. importList += ` "github.com/go-acme/lego/v4/providers/dns/` + providerName + "\"\n"
  202. }
  203. js, err := json.MarshalIndent(extractedProviderList, "", " ")
  204. if err != nil {
  205. panic(err)
  206. }
  207. os.WriteFile(filepath.Join(outputDir, "providers.json"), js, 0775)
  208. fullCodeSnippet := strings.ReplaceAll(defTemplate, "{{magic}}", generatedConvertcode)
  209. fullCodeSnippet = strings.ReplaceAll(fullCodeSnippet, "{{imports}}", importList)
  210. os.WriteFile(filepath.Join(outputDir, "acmedns.go"), []byte(fullCodeSnippet), 0775)
  211. fmt.Println("Output written to file")
  212. }