github.com/triarius/goreleaser@v1.12.5/internal/exec/exec.go (about) 1 // Package exec can execute commands on the OS. 2 package exec 3 4 import ( 5 "bytes" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 11 "github.com/caarlos0/go-shellwords" 12 "github.com/caarlos0/log" 13 "github.com/triarius/goreleaser/internal/artifact" 14 "github.com/triarius/goreleaser/internal/extrafiles" 15 "github.com/triarius/goreleaser/internal/gio" 16 "github.com/triarius/goreleaser/internal/logext" 17 "github.com/triarius/goreleaser/internal/pipe" 18 "github.com/triarius/goreleaser/internal/semerrgroup" 19 "github.com/triarius/goreleaser/internal/tmpl" 20 "github.com/triarius/goreleaser/pkg/config" 21 "github.com/triarius/goreleaser/pkg/context" 22 ) 23 24 // Environment variables to pass through to exec 25 var passthroughEnvVars = []string{"HOME", "USER", "USERPROFILE", "TMPDIR", "TMP", "TEMP", "PATH"} 26 27 // Execute the given publisher 28 func Execute(ctx *context.Context, publishers []config.Publisher) error { 29 if ctx.SkipPublish { 30 return pipe.ErrSkipPublishEnabled 31 } 32 33 for _, p := range publishers { 34 log.WithField("name", p.Name).Debug("executing custom publisher") 35 err := executePublisher(ctx, p) 36 if err != nil { 37 return err 38 } 39 } 40 41 return nil 42 } 43 44 func executePublisher(ctx *context.Context, publisher config.Publisher) error { 45 log.Debugf("filtering %d artifacts", len(ctx.Artifacts.List())) 46 artifacts := filterArtifacts(ctx.Artifacts, publisher) 47 48 extraFiles, err := extrafiles.Find(ctx, publisher.ExtraFiles) 49 if err != nil { 50 return err 51 } 52 53 for name, path := range extraFiles { 54 artifacts = append(artifacts, &artifact.Artifact{ 55 Name: name, 56 Path: path, 57 Type: artifact.UploadableFile, 58 }) 59 } 60 61 log.Debugf("will execute custom publisher with %d artifacts", len(artifacts)) 62 63 g := semerrgroup.New(ctx.Parallelism) 64 for _, artifact := range artifacts { 65 artifact := artifact 66 g.Go(func() error { 67 c, err := resolveCommand(ctx, publisher, artifact) 68 if err != nil { 69 return err 70 } 71 72 return executeCommand(c, artifact) 73 }) 74 } 75 76 return g.Wait() 77 } 78 79 func executeCommand(c *command, artifact *artifact.Artifact) error { 80 log.WithField("args", c.Args). 81 WithField("env", c.Env). 82 WithField("artifact", artifact.Name). 83 Debug("executing command") 84 85 // nolint: gosec 86 cmd := exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...) 87 cmd.Env = []string{} 88 for _, key := range passthroughEnvVars { 89 if value := os.Getenv(key); value != "" { 90 cmd.Env = append(cmd.Env, key+"="+value) 91 } 92 } 93 cmd.Env = append(cmd.Env, c.Env...) 94 95 if c.Dir != "" { 96 cmd.Dir = c.Dir 97 } 98 99 fields := log.Fields{ 100 "cmd": c.Args[0], 101 "artifact": artifact.Name, 102 } 103 var b bytes.Buffer 104 w := gio.Safe(&b) 105 cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w) 106 cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w) 107 108 log.WithFields(fields).Info("publishing") 109 if err := cmd.Run(); err != nil { 110 return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String()) 111 } 112 113 log.WithFields(fields).Debugf("command %s finished successfully", c.Args[0]) 114 return nil 115 } 116 117 func filterArtifacts(artifacts artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact { 118 filters := []artifact.Filter{ 119 artifact.ByType(artifact.UploadableArchive), 120 artifact.ByType(artifact.UploadableFile), 121 artifact.ByType(artifact.LinuxPackage), 122 artifact.ByType(artifact.UploadableBinary), 123 artifact.ByType(artifact.DockerImage), 124 artifact.ByType(artifact.DockerManifest), 125 } 126 127 if publisher.Checksum { 128 filters = append(filters, artifact.ByType(artifact.Checksum)) 129 } 130 131 if publisher.Signature { 132 filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate)) 133 } 134 135 filter := artifact.Or(filters...) 136 137 if len(publisher.IDs) > 0 { 138 filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...)) 139 } 140 141 return artifacts.Filter(filter).List() 142 } 143 144 type command struct { 145 Ctx *context.Context 146 Dir string 147 Env []string 148 Args []string 149 } 150 151 // resolveCommand returns the a command based on publisher template with replaced variables 152 // Those variables can be replaced by the given context, goos, goarch, goarm and more. 153 func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) { 154 var err error 155 156 replacements := make(map[string]string) 157 // TODO: Replacements should be associated only with relevant artifacts/archives 158 archives := ctx.Config.Archives 159 if len(archives) > 0 { 160 replacements = archives[0].Replacements 161 } 162 163 dir := publisher.Dir 164 if dir != "" { 165 dir, err = tmpl.New(ctx). 166 WithArtifact(artifact, replacements). 167 Apply(dir) 168 if err != nil { 169 return nil, err 170 } 171 } 172 173 cmd := publisher.Cmd 174 if cmd != "" { 175 cmd, err = tmpl.New(ctx). 176 WithArtifact(artifact, replacements). 177 Apply(cmd) 178 if err != nil { 179 return nil, err 180 } 181 } 182 183 args, err := shellwords.Parse(cmd) 184 if err != nil { 185 return nil, err 186 } 187 188 env := make([]string, len(publisher.Env)) 189 for i, e := range publisher.Env { 190 e, err = tmpl.New(ctx). 191 WithArtifact(artifact, replacements). 192 Apply(e) 193 if err != nil { 194 return nil, err 195 } 196 env[i] = e 197 } 198 199 return &command{ 200 Ctx: ctx, 201 Dir: dir, 202 Env: env, 203 Args: args, 204 }, nil 205 }