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