extract.go 8.3 KB

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