extract.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. }
  73. }
  74. // Exclude list for Windows build, due to limitations for lego versions
  75. func getExcludedDNSProvidersNT61() []string {
  76. return []string{
  77. "edgedns", //Too complex data structure
  78. "exec", //Not a DNS provider
  79. "httpreq", //Not a DNS provider
  80. "hurricane", //Multi-credentials arch
  81. "oraclecloud", //Evil company
  82. "acmedns", //Not a DNS provider
  83. "selectelv2", //Not sure why not working with our code generator
  84. "designate", //OpenStack, if you are using this you shd not be using zoraxy
  85. "mythicbeasts", //Module require url.URL, which cannot be automatically parsed
  86. //The following suppliers was not in lego v1.15 (Windows 7 last supported version of lego)
  87. "cpanel",
  88. "mailinabox",
  89. "shellrent",
  90. }
  91. }
  92. func isInSlice(str string, slice []string) bool {
  93. for _, s := range slice {
  94. if s == str {
  95. return true
  96. }
  97. }
  98. return false
  99. }
  100. func isExcludedDNSProvider(providerName string) bool {
  101. return isInSlice(providerName, getExcludedDNSProviders())
  102. }
  103. func isExcludedDNSProviderNT61(providerName string) bool {
  104. return isInSlice(providerName, getExcludedDNSProvidersNT61())
  105. }
  106. // extractConfigStruct extracts the name of the config struct and its content as plain text from a given source code.
  107. func extractConfigStruct(sourceCode string) (string, string) {
  108. // Regular expression to match the struct declaration.
  109. structRegex := regexp.MustCompile(`type\s+([A-Za-z0-9_]+)\s+struct\s*{([^{}]*)}`)
  110. // Find the first match of the struct declaration.
  111. match := structRegex.FindStringSubmatch(sourceCode)
  112. if len(match) < 3 {
  113. return "", "" // No match found
  114. }
  115. // Extract the struct name and its content.
  116. structName := match[1]
  117. structContent := match[2]
  118. if structName != "Config" {
  119. allStructs := structRegex.FindAllStringSubmatch(sourceCode, 10)
  120. for _, thisStruct := range allStructs {
  121. //fmt.Println("Name => ", test[1])
  122. //fmt.Println("Content => ", test[2])
  123. if thisStruct[1] == "Config" {
  124. structName = "Config"
  125. structContent = thisStruct[2]
  126. break
  127. }
  128. }
  129. if structName != "Config" {
  130. panic("Unable to find Config for this provider")
  131. }
  132. }
  133. return structName, structContent
  134. }
  135. func main() {
  136. // A map of provider name to information on what can be filled
  137. extractedProviderList := map[string]*ProviderInfo{}
  138. buildForWindowsSeven := (len(os.Args) > 2 && os.Args[2] == "win7")
  139. //Search all providers
  140. providers, err := filepath.Glob(filepath.Join(legoProvidersSourceFolder, "/*"))
  141. if err != nil {
  142. panic(err)
  143. }
  144. //Create output folder if not exists
  145. err = os.MkdirAll(outputDir, 0775)
  146. if err != nil {
  147. panic(err)
  148. }
  149. generatedConvertcode := ""
  150. importList := ""
  151. for _, provider := range providers {
  152. providerName := filepath.Base(provider)
  153. if buildForWindowsSeven {
  154. if isExcludedDNSProviderNT61(providerName) {
  155. //Ignore this provider
  156. continue
  157. }
  158. } else {
  159. if isExcludedDNSProvider(providerName) {
  160. //Ignore this provider
  161. continue
  162. }
  163. }
  164. //Check if {provider_name}.go exists
  165. providerDef := filepath.Join(provider, providerName+".go")
  166. if !fileExists(providerDef) {
  167. continue
  168. }
  169. fmt.Println("Extracting config structure for: " + providerDef)
  170. providerSrc, err := os.ReadFile(providerDef)
  171. if err != nil {
  172. fmt.Println(err.Error())
  173. return
  174. }
  175. _, strctContent := extractConfigStruct(string(providerSrc))
  176. //Filter and write the content to json
  177. /*
  178. Example of stctContent (Note the tab prefix)
  179. Token string
  180. PropagationTimeout time.Duration
  181. PollingInterval time.Duration
  182. SequenceInterval time.Duration
  183. HTTPClient *http.Client
  184. */
  185. strctContentLines := strings.Split(strctContent, "\n")
  186. configKeys := []*Field{}
  187. hiddenKeys := []*Field{}
  188. for _, lineDef := range strctContentLines {
  189. fields := strings.Fields(lineDef)
  190. if len(fields) < 2 || strings.HasPrefix(fields[0], "//") {
  191. //Ignore this line
  192. continue
  193. }
  194. //Filter out the fields that is not user-filled
  195. switch fields[1] {
  196. case "*url.URL":
  197. fallthrough
  198. case "string":
  199. configKeys = append(configKeys, &Field{
  200. Title: fields[0],
  201. Datatype: fields[1],
  202. })
  203. case "int":
  204. if fields[0] != "TTL" {
  205. configKeys = append(configKeys, &Field{
  206. Title: fields[0],
  207. Datatype: fields[1],
  208. })
  209. } else if fields[0] == "TTL" {
  210. //haveTTLField = true
  211. } else {
  212. hiddenKeys = append(hiddenKeys, &Field{
  213. Title: fields[0],
  214. Datatype: fields[1],
  215. })
  216. }
  217. case "bool":
  218. if fields[0] == "InsecureSkipVerify" || fields[0] == "SSLVerify" || fields[0] == "Debug" {
  219. configKeys = append(configKeys, &Field{
  220. Title: fields[0],
  221. Datatype: fields[1],
  222. })
  223. } else {
  224. hiddenKeys = append(hiddenKeys, &Field{
  225. Title: fields[0],
  226. Datatype: fields[1],
  227. })
  228. }
  229. default:
  230. //Not used fields
  231. hiddenKeys = append(hiddenKeys, &Field{
  232. Title: fields[0],
  233. Datatype: fields[1],
  234. })
  235. }
  236. }
  237. fmt.Println(strctContent)
  238. extractedProviderList[providerName] = &ProviderInfo{
  239. Name: providerName,
  240. ConfigableFields: configKeys,
  241. HiddenFields: hiddenKeys,
  242. }
  243. //Generate the code for converting incoming json into target config
  244. codeSegment := `
  245. case "` + providerName + `":
  246. cfg := ` + providerName + `.NewDefaultConfig()
  247. err := json.Unmarshal([]byte(js), &cfg)
  248. if err != nil {
  249. return nil, err
  250. }
  251. return ` + providerName + `.NewDNSProviderConfig(cfg)`
  252. //Add fixed for Netcup timeout
  253. if strings.ToLower(providerName) == "netcup" {
  254. codeSegment = `
  255. case "` + providerName + `":
  256. cfg := ` + providerName + `.NewDefaultConfig()
  257. err := json.Unmarshal([]byte(js), &cfg)
  258. if err != nil {
  259. return nil, err
  260. }
  261. cfg.PropagationTimeout = 1200 * time.Second
  262. return ` + providerName + `.NewDNSProviderConfig(cfg)`
  263. }
  264. generatedConvertcode += codeSegment
  265. importList += ` "github.com/go-acme/lego/v4/providers/dns/` + providerName + "\"\n"
  266. }
  267. js, err := json.MarshalIndent(extractedProviderList, "", " ")
  268. if err != nil {
  269. panic(err)
  270. }
  271. fullCodeSnippet := strings.ReplaceAll(defTemplate, "{{magic}}", generatedConvertcode)
  272. fullCodeSnippet = strings.ReplaceAll(fullCodeSnippet, "{{imports}}", importList)
  273. outJsonFilename := "providers.json"
  274. outGoFilename := "acmedns.go"
  275. if buildForWindowsSeven {
  276. outJsonFilename = "providers_nt61.json"
  277. outGoFilename = "acmedns_nt61.go"
  278. }
  279. os.WriteFile(filepath.Join(outputDir, outJsonFilename), js, 0775)
  280. os.WriteFile(filepath.Join(outputDir, outGoFilename), []byte(fullCodeSnippet), 0775)
  281. fmt.Println("Output written to file")
  282. }