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