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