2
0

util.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package internal
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "os"
  12. "os/exec"
  13. "regexp"
  14. "time"
  15. )
  16. var rsyncExitValues = map[int]string{
  17. 0: "Success",
  18. 1: "Syntax or usage error",
  19. 2: "Protocol incompatibility",
  20. 3: "Errors selecting input/output files, dirs",
  21. 4: "Requested action not supported: an attempt was made to manipulate 64-bit files on a platform that cannot support them; or an option was specified that is supported by the client and not by the server.",
  22. 5: "Error starting client-server protocol",
  23. 6: "Daemon unable to append to log-file",
  24. 10: "Error in socket I/O",
  25. 11: "Error in file I/O",
  26. 12: "Error in rsync protocol data stream",
  27. 13: "Errors with program diagnostics",
  28. 14: "Error in IPC code",
  29. 20: "Received SIGUSR1 or SIGINT",
  30. 21: "Some error returned by waitpid()",
  31. 22: "Error allocating core memory buffers",
  32. 23: "Partial transfer due to error",
  33. 24: "Partial transfer due to vanished source files",
  34. 25: "The --max-delete limit stopped deletions",
  35. 30: "Timeout in data send/receive",
  36. 35: "Timeout waiting for daemon connection",
  37. }
  38. // GetTLSConfig generate tls.Config from CAFile
  39. func GetTLSConfig(CAFile string) (*tls.Config, error) {
  40. caCert, err := os.ReadFile(CAFile)
  41. if err != nil {
  42. return nil, err
  43. }
  44. caCertPool := x509.NewCertPool()
  45. if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
  46. return nil, errors.New("failed to add CA to pool")
  47. }
  48. tlsConfig := &tls.Config{
  49. RootCAs: caCertPool,
  50. }
  51. // tlsConfig.BuildNameToCertificate()
  52. return tlsConfig, nil
  53. }
  54. // CreateHTTPClient returns a http.Client
  55. func CreateHTTPClient(CAFile string) (*http.Client, error) {
  56. var tlsConfig *tls.Config
  57. var err error
  58. if CAFile != "" {
  59. tlsConfig, err = GetTLSConfig(CAFile)
  60. if err != nil {
  61. return nil, err
  62. }
  63. }
  64. tr := &http.Transport{
  65. MaxIdleConnsPerHost: 20,
  66. TLSClientConfig: tlsConfig,
  67. }
  68. return &http.Client{
  69. Transport: tr,
  70. Timeout: 5 * time.Second,
  71. }, nil
  72. }
  73. // PostJSON posts json object to url
  74. func PostJSON(url string, obj interface{}, client *http.Client) (*http.Response, error) {
  75. if client == nil {
  76. client, _ = CreateHTTPClient("")
  77. }
  78. b := new(bytes.Buffer)
  79. if err := json.NewEncoder(b).Encode(obj); err != nil {
  80. return nil, err
  81. }
  82. return client.Post(url, "application/json; charset=utf-8", b)
  83. }
  84. // GetJSON gets a json response from url
  85. func GetJSON(url string, obj interface{}, client *http.Client) (*http.Response, error) {
  86. if client == nil {
  87. client, _ = CreateHTTPClient("")
  88. }
  89. resp, err := client.Get(url)
  90. if err != nil {
  91. return resp, err
  92. }
  93. if resp.StatusCode != http.StatusOK {
  94. return resp, errors.New("HTTP status code is not 200")
  95. }
  96. defer resp.Body.Close()
  97. body, err := io.ReadAll(resp.Body)
  98. if err != nil {
  99. return resp, err
  100. }
  101. return resp, json.Unmarshal(body, obj)
  102. }
  103. // FindAllSubmatchInFile calls re.FindAllSubmatch to find matches in given file
  104. func FindAllSubmatchInFile(fileName string, re *regexp.Regexp) (matches [][][]byte, err error) {
  105. if fileName == "/dev/null" {
  106. err = errors.New("invalid log file")
  107. return
  108. }
  109. if content, err := os.ReadFile(fileName); err == nil {
  110. matches = re.FindAllSubmatch(content, -1)
  111. // fmt.Printf("FindAllSubmatchInFile: %q\n", matches)
  112. }
  113. return
  114. }
  115. // ExtractSizeFromLog uses a regexp to extract the size from log files
  116. func ExtractSizeFromLog(logFile string, re *regexp.Regexp) string {
  117. matches, _ := FindAllSubmatchInFile(logFile, re)
  118. if len(matches) == 0 {
  119. return ""
  120. }
  121. // return the first capture group of the last occurrence
  122. return string(matches[len(matches)-1][1])
  123. }
  124. // ExtractSizeFromRsyncLog extracts the size from rsync logs
  125. func ExtractSizeFromRsyncLog(logFile string) string {
  126. // (?m) flag enables multi-line mode
  127. re := regexp.MustCompile(`(?m)^Total file size: ([0-9\.]+[KMGTP]?) bytes`)
  128. return ExtractSizeFromLog(logFile, re)
  129. }
  130. // TranslateRsyncErrorCode translates the exit code of rsync to a message
  131. func TranslateRsyncErrorCode(cmdErr error) (exitCode int, msg string) {
  132. if exiterr, ok := cmdErr.(*exec.ExitError); ok {
  133. exitCode = exiterr.ExitCode()
  134. strerr, valid := rsyncExitValues[exitCode]
  135. if valid {
  136. msg = fmt.Sprintf("rsync error: %s", strerr)
  137. }
  138. }
  139. return
  140. }