123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- package main
- import (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
- )
- /*
- Usage
- git clone {repo_link_for_lego}
- go run extract.go
- //go run extract.go -- "win7"
- */
- var legoProvidersSourceFolder string = "./lego/providers/dns/"
- var outputDir string = "./acmedns"
- var defTemplate string = `package acmedns
- /*
- THIS MODULE IS GENERATED AUTOMATICALLY
- DO NOT EDIT THIS FILE
- */
- import (
- "encoding/json"
- "fmt"
- "time"
- "github.com/go-acme/lego/v4/challenge"
- {{imports}}
- )
- //name is the DNS provider name, e.g. cloudflare or gandi
- //JSON (js) must be in key-value string that match ConfigableFields Title in providers.json, e.g. {"Username":"far","Password":"boo"}
- func GetDNSProviderByJsonConfig(name string, js string, propagationTimeout int64, pollingInterval int64)(challenge.Provider, error){
- pgDuration := time.Duration(propagationTimeout) * time.Second
- plInterval := time.Duration(pollingInterval) * time.Second
- switch name {
- {{magic}}
- default:
- return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
- }
- }
- `
- type Field struct {
- Title string
- Datatype string
- }
- type ProviderInfo struct {
- Name string //Name of this provider
- ConfigableFields []*Field //Field that shd be expose to user for filling in
- HiddenFields []*Field //Fields that is usable but shd be hidden from user
- }
- func fileExists(filePath string) bool {
- _, err := os.Stat(filePath)
- if err == nil {
- return true
- }
- if os.IsNotExist(err) {
- return false
- }
- // For other errors, you may handle them differently
- return false
- }
- // This function define the DNS not supported by zoraxy
- func getExcludedDNSProviders() []string {
- return []string{
- "edgedns", //Too complex data structure
- "exec", //Not a DNS provider
- "httpreq", //Not a DNS provider
- //"hurricane", //Multi-credentials arch
- "oraclecloud", //Evil company
- "acmedns", //Not a DNS provider
- "selectelv2", //Not sure why not working with our code generator
- "designate", //OpenStack, if you are using this you shd not be using zoraxy
- "mythicbeasts", //Module require url.URL, which cannot be automatically parsed
- //The following are incomaptible with Zoraxy due to dependencies issue,
- //might be resolved in future
- "corenetworks",
- "timewebcloud",
- "volcengine",
- "exoscale",
- }
- }
- // Exclude list for Windows build, due to limitations for lego versions
- func getExcludedDNSProvidersNT61() []string {
- fmt.Println("Windows 7 support is deprecated, please consider upgrading to a newer version of Windows.")
- return append(getExcludedDNSProviders(), []string{"cpanel",
- "mailinabox",
- "shellrent",
- }...)
- }
- func isInSlice(str string, slice []string) bool {
- for _, s := range slice {
- if s == str {
- return true
- }
- }
- return false
- }
- func isExcludedDNSProvider(providerName string) bool {
- return isInSlice(providerName, getExcludedDNSProviders())
- }
- func isExcludedDNSProviderNT61(providerName string) bool {
- return isInSlice(providerName, getExcludedDNSProvidersNT61())
- }
- // extractConfigStruct extracts the name of the config struct and its content as plain text from a given source code.
- func extractConfigStruct(sourceCode string) (string, string) {
- // Regular expression to match the struct declaration.
- structRegex := regexp.MustCompile(`type\s+([A-Za-z0-9_]+)\s+struct\s*{([^{}]*)}`)
- // Find the first match of the struct declaration.
- match := structRegex.FindStringSubmatch(sourceCode)
- if len(match) < 3 {
- return "", "" // No match found
- }
- // Extract the struct name and its content.
- structName := match[1]
- structContent := match[2]
- if structName != "Config" {
- allStructs := structRegex.FindAllStringSubmatch(sourceCode, 10)
- for _, thisStruct := range allStructs {
- //fmt.Println("Name => ", test[1])
- //fmt.Println("Content => ", test[2])
- if thisStruct[1] == "Config" {
- structName = "Config"
- structContent = thisStruct[2]
- break
- }
- }
- if structName != "Config" {
- panic("Unable to find Config for this provider")
- }
- }
- return structName, structContent
- }
- func main() {
- // A map of provider name to information on what can be filled
- extractedProviderList := map[string]*ProviderInfo{}
- buildForWindowsSeven := (len(os.Args) > 2 && os.Args[2] == "win7")
- //Search all providers
- providers, err := filepath.Glob(filepath.Join(legoProvidersSourceFolder, "/*"))
- if err != nil {
- panic(err)
- }
- //Create output folder if not exists
- err = os.MkdirAll(outputDir, 0775)
- if err != nil {
- panic(err)
- }
- generatedConvertcode := ""
- importList := ""
- for _, provider := range providers {
- providerName := filepath.Base(provider)
- if buildForWindowsSeven {
- if isExcludedDNSProviderNT61(providerName) {
- //Ignore this provider
- continue
- }
- } else {
- if isExcludedDNSProvider(providerName) {
- //Ignore this provider
- continue
- }
- }
- //Check if {provider_name}.go exists
- providerDef := filepath.Join(provider, providerName+".go")
- if !fileExists(providerDef) {
- continue
- }
- fmt.Println("Extracting config structure for: " + providerDef)
- providerSrc, err := os.ReadFile(providerDef)
- if err != nil {
- fmt.Println(err.Error())
- return
- }
- _, strctContent := extractConfigStruct(string(providerSrc))
- //Filter and write the content to json
- /*
- Example of stctContent (Note the tab prefix)
- Token string
- PropagationTimeout time.Duration
- PollingInterval time.Duration
- SequenceInterval time.Duration
- HTTPClient *http.Client
- */
- strctContentLines := strings.Split(strctContent, "\n")
- configKeys := []*Field{}
- hiddenKeys := []*Field{}
- for _, lineDef := range strctContentLines {
- fields := strings.Fields(lineDef)
- if len(fields) < 2 || strings.HasPrefix(fields[0], "//") {
- //Ignore this line
- continue
- }
- //Filter out the fields that is not user-filled
- switch fields[1] {
- case "*url.URL":
- fallthrough
- case "string":
- //Add exception rule for gandi baseURL
- if (providerName == "gandi" || providerName == "gandiv5") && fields[0] == "BaseURL" {
- //Not useful stuff. Ignore this field
- continue
- }
- configKeys = append(configKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- case "int":
- if fields[0] != "TTL" {
- configKeys = append(configKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- } else if fields[0] == "TTL" {
- //haveTTLField = true
- } else {
- hiddenKeys = append(hiddenKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- }
- case "bool":
- if fields[0] == "InsecureSkipVerify" || fields[0] == "SSLVerify" || fields[0] == "Debug" {
- configKeys = append(configKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- } else {
- hiddenKeys = append(hiddenKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- }
- case "time.Duration":
- if fields[0] == "PropagationTimeout" || fields[0] == "PollingInterval" {
- configKeys = append(configKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- }
- default:
- //Not used fields
- hiddenKeys = append(hiddenKeys, &Field{
- Title: fields[0],
- Datatype: fields[1],
- })
- }
- }
- fmt.Println(strctContent)
- extractedProviderList[providerName] = &ProviderInfo{
- Name: providerName,
- ConfigableFields: configKeys,
- HiddenFields: hiddenKeys,
- }
- //Generate the code for converting incoming json into target config
- codeSegment := `
- case "` + providerName + `":
- cfg := ` + providerName + `.NewDefaultConfig()
- err := json.Unmarshal([]byte(js), &cfg)
- if err != nil {
- return nil, err
- }
- cfg.PropagationTimeout = pgDuration
- cfg.PollingInterval = plInterval
- return ` + providerName + `.NewDNSProviderConfig(cfg)`
- generatedConvertcode += codeSegment
- importList += ` "github.com/go-acme/lego/v4/providers/dns/` + providerName + "\"\n"
- }
- js, err := json.MarshalIndent(extractedProviderList, "", " ")
- if err != nil {
- panic(err)
- }
- fullCodeSnippet := strings.ReplaceAll(defTemplate, "{{magic}}", generatedConvertcode)
- fullCodeSnippet = strings.ReplaceAll(fullCodeSnippet, "{{imports}}", importList)
- outJsonFilename := "providers.json"
- outGoFilename := "acmedns.go"
- if buildForWindowsSeven {
- outJsonFilename = "providers_nt61.json"
- outGoFilename = "acmedns_nt61.go"
- }
- os.WriteFile(filepath.Join(outputDir, outJsonFilename), js, 0775)
- os.WriteFile(filepath.Join(outputDir, outGoFilename), []byte(fullCodeSnippet), 0775)
- fmt.Println("Output written to file")
- }
|