package assets import ( "context" "fmt" "os" "os/exec" "path/filepath" "strings" ) type OSSUtilPublisher struct { ossutilPath string configFile string bucketRoot string publicBaseURL string } func NewOSSUtilPublisher(ossutilPath, configFile, bucketRoot, publicBaseURL string) *OSSUtilPublisher { return &OSSUtilPublisher{ ossutilPath: strings.TrimSpace(ossutilPath), configFile: strings.TrimSpace(configFile), bucketRoot: strings.TrimRight(strings.TrimSpace(bucketRoot), "/"), publicBaseURL: strings.TrimRight(strings.TrimSpace(publicBaseURL), "/"), } } func (p *OSSUtilPublisher) Enabled() bool { return p != nil && p.ossutilPath != "" && p.configFile != "" && p.bucketRoot != "" && p.publicBaseURL != "" } func (p *OSSUtilPublisher) UploadJSON(ctx context.Context, publicURL string, payload []byte) error { if !p.Enabled() { return fmt.Errorf("asset publisher is not configured") } if len(payload) == 0 { return fmt.Errorf("payload is empty") } objectKey, err := p.objectKeyFromPublicURL(publicURL) if err != nil { return err } if _, err := os.Stat(p.ossutilPath); err != nil { return fmt.Errorf("ossutil not found: %w", err) } if _, err := os.Stat(p.configFile); err != nil { return fmt.Errorf("ossutil config not found: %w", err) } tmpFile, err := os.CreateTemp("", "cmr-manifest-*.json") if err != nil { return fmt.Errorf("create temp file: %w", err) } tmpPath := tmpFile.Name() defer os.Remove(tmpPath) if _, err := tmpFile.Write(payload); err != nil { tmpFile.Close() return fmt.Errorf("write temp file: %w", err) } if err := tmpFile.Close(); err != nil { return fmt.Errorf("close temp file: %w", err) } target := p.bucketRoot + "/" + objectKey cmd := exec.CommandContext(ctx, p.ossutilPath, "cp", "-f", tmpPath, target, "--config-file", p.configFile) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("upload object %s failed: %w: %s", objectKey, err, strings.TrimSpace(string(output))) } return nil } func (p *OSSUtilPublisher) objectKeyFromPublicURL(publicURL string) (string, error) { publicURL = strings.TrimSpace(publicURL) if publicURL == "" { return "", fmt.Errorf("public url is required") } if !strings.HasPrefix(publicURL, p.publicBaseURL+"/") { return "", fmt.Errorf("public url %s does not match public base %s", publicURL, p.publicBaseURL) } relative := strings.TrimPrefix(publicURL, p.publicBaseURL+"/") relative = strings.ReplaceAll(relative, "\\", "/") relative = strings.TrimLeft(relative, "/") if relative == "" { return "", fmt.Errorf("public url %s resolved to empty object key", publicURL) } return filepath.ToSlash(relative), nil }