publisher.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package assets
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strings"
  9. )
  10. type OSSUtilPublisher struct {
  11. ossutilPath string
  12. configFile string
  13. bucketRoot string
  14. publicBaseURL string
  15. }
  16. func NewOSSUtilPublisher(ossutilPath, configFile, bucketRoot, publicBaseURL string) *OSSUtilPublisher {
  17. return &OSSUtilPublisher{
  18. ossutilPath: strings.TrimSpace(ossutilPath),
  19. configFile: strings.TrimSpace(configFile),
  20. bucketRoot: strings.TrimRight(strings.TrimSpace(bucketRoot), "/"),
  21. publicBaseURL: strings.TrimRight(strings.TrimSpace(publicBaseURL), "/"),
  22. }
  23. }
  24. func (p *OSSUtilPublisher) Enabled() bool {
  25. return p != nil &&
  26. p.ossutilPath != "" &&
  27. p.configFile != "" &&
  28. p.bucketRoot != "" &&
  29. p.publicBaseURL != ""
  30. }
  31. func (p *OSSUtilPublisher) UploadJSON(ctx context.Context, publicURL string, payload []byte) error {
  32. if !p.Enabled() {
  33. return fmt.Errorf("asset publisher is not configured")
  34. }
  35. if len(payload) == 0 {
  36. return fmt.Errorf("payload is empty")
  37. }
  38. objectKey, err := p.objectKeyFromPublicURL(publicURL)
  39. if err != nil {
  40. return err
  41. }
  42. if _, err := os.Stat(p.ossutilPath); err != nil {
  43. return fmt.Errorf("ossutil not found: %w", err)
  44. }
  45. if _, err := os.Stat(p.configFile); err != nil {
  46. return fmt.Errorf("ossutil config not found: %w", err)
  47. }
  48. tmpFile, err := os.CreateTemp("", "cmr-manifest-*.json")
  49. if err != nil {
  50. return fmt.Errorf("create temp file: %w", err)
  51. }
  52. tmpPath := tmpFile.Name()
  53. defer os.Remove(tmpPath)
  54. if _, err := tmpFile.Write(payload); err != nil {
  55. tmpFile.Close()
  56. return fmt.Errorf("write temp file: %w", err)
  57. }
  58. if err := tmpFile.Close(); err != nil {
  59. return fmt.Errorf("close temp file: %w", err)
  60. }
  61. target := p.bucketRoot + "/" + objectKey
  62. cmd := exec.CommandContext(ctx, p.ossutilPath, "cp", "-f", tmpPath, target, "--config-file", p.configFile)
  63. output, err := cmd.CombinedOutput()
  64. if err != nil {
  65. return fmt.Errorf("upload object %s failed: %w: %s", objectKey, err, strings.TrimSpace(string(output)))
  66. }
  67. return nil
  68. }
  69. func (p *OSSUtilPublisher) objectKeyFromPublicURL(publicURL string) (string, error) {
  70. publicURL = strings.TrimSpace(publicURL)
  71. if publicURL == "" {
  72. return "", fmt.Errorf("public url is required")
  73. }
  74. if !strings.HasPrefix(publicURL, p.publicBaseURL+"/") {
  75. return "", fmt.Errorf("public url %s does not match public base %s", publicURL, p.publicBaseURL)
  76. }
  77. relative := strings.TrimPrefix(publicURL, p.publicBaseURL+"/")
  78. relative = strings.ReplaceAll(relative, "\\", "/")
  79. relative = strings.TrimLeft(relative, "/")
  80. if relative == "" {
  81. return "", fmt.Errorf("public url %s resolved to empty object key", publicURL)
  82. }
  83. return filepath.ToSlash(relative), nil
  84. }