github.com/windmeup/goreleaser@v1.21.95/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/windmeup/goreleaser/internal/artifact" 14 "github.com/windmeup/goreleaser/internal/extrafiles" 15 "github.com/windmeup/goreleaser/internal/gio" 16 "github.com/windmeup/goreleaser/internal/logext" 17 "github.com/windmeup/goreleaser/internal/pipe" 18 "github.com/windmeup/goreleaser/internal/semerrgroup" 19 "github.com/windmeup/goreleaser/internal/tmpl" 20 "github.com/windmeup/goreleaser/pkg/config" 21 "github.com/windmeup/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("env", c.Env). 90 WithField("artifact", artifact.Name). 91 Debug("executing command") 92 93 // nolint: gosec 94 cmd := exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...) 95 cmd.Env = []string{} 96 for _, key := range passthroughEnvVars { 97 if value := os.Getenv(key); value != "" { 98 cmd.Env = append(cmd.Env, key+"="+value) 99 } 100 } 101 cmd.Env = append(cmd.Env, c.Env...) 102 103 if c.Dir != "" { 104 cmd.Dir = c.Dir 105 } 106 107 var b bytes.Buffer 108 w := gio.Safe(&b) 109 cmd.Stderr = io.MultiWriter(logext.NewWriter(), w) 110 cmd.Stdout = io.MultiWriter(logext.NewWriter(), w) 111 112 log := log.WithField("cmd", c.Args[0]). 113 WithField("artifact", artifact.Name) 114 115 log.Info("publishing") 116 if err := cmd.Run(); err != nil { 117 return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String()) 118 } 119 120 log.Debug("command finished successfully") 121 return nil 122 } 123 124 func filterArtifacts(artifacts *artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact { 125 filters := []artifact.Filter{ 126 artifact.ByType(artifact.UploadableArchive), 127 artifact.ByType(artifact.UploadableFile), 128 artifact.ByType(artifact.LinuxPackage), 129 artifact.ByType(artifact.UploadableBinary), 130 artifact.ByType(artifact.DockerImage), 131 artifact.ByType(artifact.DockerManifest), 132 } 133 134 if publisher.Checksum { 135 filters = append(filters, artifact.ByType(artifact.Checksum)) 136 } 137 138 if publisher.Signature { 139 filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate)) 140 } 141 142 filter := artifact.Or(filters...) 143 144 if len(publisher.IDs) > 0 { 145 filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...)) 146 } 147 148 return artifacts.Filter(filter).List() 149 } 150 151 type command struct { 152 Ctx *context.Context 153 Dir string 154 Env []string 155 Args []string 156 } 157 158 // resolveCommand returns the a command based on publisher template with replaced variables 159 // Those variables can be replaced by the given context, goos, goarch, goarm and more. 160 func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) { 161 var err error 162 dir := publisher.Dir 163 164 // nolint:staticcheck 165 tpl := tmpl.New(ctx).WithArtifact(artifact) 166 if dir != "" { 167 dir, err = tpl.Apply(dir) 168 if err != nil { 169 return nil, err 170 } 171 } 172 173 cmd := publisher.Cmd 174 if cmd != "" { 175 cmd, err = tpl.Apply(cmd) 176 if err != nil { 177 return nil, err 178 } 179 } 180 181 args, err := shellwords.Parse(cmd) 182 if err != nil { 183 return nil, err 184 } 185 186 env := make([]string, len(publisher.Env)) 187 for i, e := range publisher.Env { 188 e, err = tpl.Apply(e) 189 if err != nil { 190 return nil, err 191 } 192 env[i] = e 193 } 194 195 return &command{ 196 Ctx: ctx, 197 Dir: dir, 198 Env: env, 199 Args: args, 200 }, nil 201 }