extract.go 8.3 KB

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