github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/pipe/nfpm/nfpm.go (about) 1 // Package nfpm implements the Pipe interface providing nFPM bindings. 2 package nfpm 3 4 import ( 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/apex/log" 11 "github.com/goreleaser/nfpm/v2" 12 _ "github.com/goreleaser/nfpm/v2/apk" // blank import to register the format 13 _ "github.com/goreleaser/nfpm/v2/deb" // blank import to register the format 14 "github.com/goreleaser/nfpm/v2/files" 15 _ "github.com/goreleaser/nfpm/v2/rpm" // blank import to register the format 16 "github.com/imdario/mergo" 17 18 "github.com/goreleaser/goreleaser/internal/artifact" 19 "github.com/goreleaser/goreleaser/internal/ids" 20 "github.com/goreleaser/goreleaser/internal/linux" 21 "github.com/goreleaser/goreleaser/internal/pipe" 22 "github.com/goreleaser/goreleaser/internal/semerrgroup" 23 "github.com/goreleaser/goreleaser/internal/tmpl" 24 "github.com/goreleaser/goreleaser/pkg/config" 25 "github.com/goreleaser/goreleaser/pkg/context" 26 ) 27 28 const defaultNameTemplate = "{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" 29 30 // Pipe for nfpm packaging. 31 type Pipe struct{} 32 33 func (Pipe) String() string { return "linux packages" } 34 func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.NFPMs) == 0 } 35 36 // Default sets the pipe defaults. 37 func (Pipe) Default(ctx *context.Context) error { 38 ids := ids.New("nfpms") 39 for i := range ctx.Config.NFPMs { 40 fpm := &ctx.Config.NFPMs[i] 41 if fpm.ID == "" { 42 fpm.ID = "default" 43 } 44 if fpm.Bindir == "" { 45 fpm.Bindir = "/usr/local/bin" 46 } 47 if fpm.PackageName == "" { 48 fpm.PackageName = ctx.Config.ProjectName 49 } 50 if fpm.FileNameTemplate == "" { 51 fpm.FileNameTemplate = defaultNameTemplate 52 } 53 if len(fpm.Builds) == 0 { // TODO: change this to empty by default and deal with it in the filtering code 54 for _, b := range ctx.Config.Builds { 55 fpm.Builds = append(fpm.Builds, b.ID) 56 } 57 } 58 ids.Inc(fpm.ID) 59 } 60 return ids.Validate() 61 } 62 63 // Run the pipe. 64 func (Pipe) Run(ctx *context.Context) error { 65 for _, nfpm := range ctx.Config.NFPMs { 66 if len(nfpm.Formats) == 0 { 67 // FIXME: this assumes other nfpm configs will fail too... 68 return pipe.Skip("no output formats configured") 69 } 70 if err := doRun(ctx, nfpm); err != nil { 71 return err 72 } 73 } 74 return nil 75 } 76 77 func doRun(ctx *context.Context, fpm config.NFPM) error { 78 linuxBinaries := ctx.Artifacts.Filter(artifact.And( 79 artifact.ByType(artifact.Binary), 80 artifact.ByGoos("linux"), 81 artifact.ByIDs(fpm.Builds...), 82 )).GroupByPlatform() 83 if len(linuxBinaries) == 0 { 84 return fmt.Errorf("no linux binaries found for builds %v", fpm.Builds) 85 } 86 g := semerrgroup.New(ctx.Parallelism) 87 for _, format := range fpm.Formats { 88 for platform, artifacts := range linuxBinaries { 89 format := format 90 arch := linux.Arch(platform) 91 artifacts := artifacts 92 g.Go(func() error { 93 return create(ctx, fpm, format, arch, artifacts) 94 }) 95 } 96 } 97 return g.Wait() 98 } 99 100 func mergeOverrides(fpm config.NFPM, format string) (*config.NFPMOverridables, error) { 101 var overridden config.NFPMOverridables 102 if err := mergo.Merge(&overridden, fpm.NFPMOverridables); err != nil { 103 return nil, err 104 } 105 perFormat, ok := fpm.Overrides[format] 106 if ok { 107 err := mergo.Merge(&overridden, perFormat, mergo.WithOverride) 108 if err != nil { 109 return nil, err 110 } 111 } 112 return &overridden, nil 113 } 114 115 func create(ctx *context.Context, fpm config.NFPM, format, arch string, binaries []*artifact.Artifact) error { 116 overridden, err := mergeOverrides(fpm, format) 117 if err != nil { 118 return err 119 } 120 tmpl := tmpl.New(ctx). 121 WithArtifact(binaries[0], overridden.Replacements). 122 WithExtraFields(tmpl.Fields{ 123 "Release": fpm.Release, 124 "Epoch": fpm.Epoch, 125 "PackageName": fpm.PackageName, 126 }) 127 name, err := tmpl.Apply(overridden.FileNameTemplate) 128 if err != nil { 129 return err 130 } 131 132 binDir, err := tmpl.Apply(fpm.Bindir) 133 if err != nil { 134 return err 135 } 136 137 homepage, err := tmpl.Apply(fpm.Homepage) 138 if err != nil { 139 return err 140 } 141 142 description, err := tmpl.Apply(fpm.Description) 143 if err != nil { 144 return err 145 } 146 147 debKeyFile, err := tmpl.Apply(overridden.Deb.Signature.KeyFile) 148 if err != nil { 149 return err 150 } 151 152 rpmKeyFile, err := tmpl.Apply(overridden.RPM.Signature.KeyFile) 153 if err != nil { 154 return err 155 } 156 157 apkKeyFile, err := tmpl.Apply(overridden.APK.Signature.KeyFile) 158 if err != nil { 159 return err 160 } 161 162 contents := files.Contents{} 163 for _, content := range overridden.Contents { 164 src, err := tmpl.Apply(content.Source) 165 if err != nil { 166 return err 167 } 168 dst, err := tmpl.Apply(content.Destination) 169 if err != nil { 170 return err 171 } 172 contents = append(contents, &files.Content{ 173 Source: src, 174 Destination: dst, 175 Type: content.Type, 176 Packager: content.Packager, 177 FileInfo: content.FileInfo, 178 }) 179 } 180 181 // FPM meta package should not contain binaries at all 182 if !fpm.Meta { 183 log := log.WithField("package", name+"."+format).WithField("arch", arch) 184 for _, binary := range binaries { 185 src := binary.Path 186 dst := filepath.Join(binDir, binary.Name) 187 log.WithField("src", src).WithField("dst", dst).Debug("adding binary to package") 188 contents = append(contents, &files.Content{ 189 Source: filepath.ToSlash(src), 190 Destination: filepath.ToSlash(dst), 191 }) 192 } 193 } 194 195 log.WithField("files", destinations(contents)).Debug("all archive files") 196 197 info := &nfpm.Info{ 198 Arch: arch, 199 Platform: "linux", 200 Name: fpm.PackageName, 201 Version: ctx.Version, 202 Section: fpm.Section, 203 Priority: fpm.Priority, 204 Epoch: fpm.Epoch, 205 Release: fpm.Release, 206 Prerelease: fpm.Prerelease, 207 VersionMetadata: fpm.VersionMetadata, 208 Maintainer: fpm.Maintainer, 209 Description: description, 210 Vendor: fpm.Vendor, 211 Homepage: homepage, 212 License: fpm.License, 213 Overridables: nfpm.Overridables{ 214 Conflicts: overridden.Conflicts, 215 Depends: overridden.Dependencies, 216 Recommends: overridden.Recommends, 217 Suggests: overridden.Suggests, 218 Replaces: overridden.Replaces, 219 EmptyFolders: overridden.EmptyFolders, 220 Contents: contents, 221 Scripts: nfpm.Scripts{ 222 PreInstall: overridden.Scripts.PreInstall, 223 PostInstall: overridden.Scripts.PostInstall, 224 PreRemove: overridden.Scripts.PreRemove, 225 PostRemove: overridden.Scripts.PostRemove, 226 }, 227 Deb: nfpm.Deb{ 228 Scripts: nfpm.DebScripts{ 229 Rules: overridden.Deb.Scripts.Rules, 230 Templates: overridden.Deb.Scripts.Templates, 231 }, 232 Triggers: nfpm.DebTriggers{ 233 Interest: overridden.Deb.Triggers.Interest, 234 InterestAwait: overridden.Deb.Triggers.InterestAwait, 235 InterestNoAwait: overridden.Deb.Triggers.InterestNoAwait, 236 Activate: overridden.Deb.Triggers.Activate, 237 ActivateAwait: overridden.Deb.Triggers.ActivateAwait, 238 ActivateNoAwait: overridden.Deb.Triggers.ActivateNoAwait, 239 }, 240 Breaks: overridden.Deb.Breaks, 241 Signature: nfpm.DebSignature{ 242 PackageSignature: nfpm.PackageSignature{ 243 KeyFile: debKeyFile, 244 KeyPassphrase: getPassphraseFromEnv(ctx, "DEB", fpm.ID), 245 }, 246 Type: overridden.Deb.Signature.Type, 247 }, 248 }, 249 RPM: nfpm.RPM{ 250 Summary: overridden.RPM.Summary, 251 Group: overridden.RPM.Group, 252 Compression: overridden.RPM.Compression, 253 Signature: nfpm.RPMSignature{ 254 PackageSignature: nfpm.PackageSignature{ 255 KeyFile: rpmKeyFile, 256 KeyPassphrase: getPassphraseFromEnv(ctx, "RPM", fpm.ID), 257 }, 258 }, 259 Scripts: nfpm.RPMScripts{ 260 PreTrans: overridden.RPM.Scripts.PreTrans, 261 PostTrans: overridden.RPM.Scripts.PostTrans, 262 }, 263 }, 264 APK: nfpm.APK{ 265 Signature: nfpm.APKSignature{ 266 PackageSignature: nfpm.PackageSignature{ 267 KeyFile: apkKeyFile, 268 KeyPassphrase: getPassphraseFromEnv(ctx, "APK", fpm.ID), 269 }, 270 KeyName: overridden.APK.Signature.KeyName, 271 }, 272 Scripts: nfpm.APKScripts{ 273 PreUpgrade: overridden.APK.Scripts.PreUpgrade, 274 PostUpgrade: overridden.APK.Scripts.PostUpgrade, 275 }, 276 }, 277 }, 278 } 279 280 if ctx.SkipSign { 281 info.APK.Signature = nfpm.APKSignature{} 282 info.RPM.Signature = nfpm.RPMSignature{} 283 info.Deb.Signature = nfpm.DebSignature{} 284 } 285 286 if err = nfpm.Validate(info); err != nil { 287 return fmt.Errorf("invalid nfpm config: %w", err) 288 } 289 290 packager, err := nfpm.Get(format) 291 if err != nil { 292 return err 293 } 294 295 path := filepath.Join(ctx.Config.Dist, name+"."+format) 296 log.WithField("file", path).Info("creating") 297 w, err := os.Create(path) 298 if err != nil { 299 return err 300 } 301 defer w.Close() 302 if err := packager.Package(nfpm.WithDefaults(info), w); err != nil { 303 return fmt.Errorf("nfpm failed: %w", err) 304 } 305 if err := w.Close(); err != nil { 306 return fmt.Errorf("could not close package file: %w", err) 307 } 308 ctx.Artifacts.Add(&artifact.Artifact{ 309 Type: artifact.LinuxPackage, 310 Name: name + "." + format, 311 Path: path, 312 Goos: binaries[0].Goos, 313 Goarch: binaries[0].Goarch, 314 Goarm: binaries[0].Goarm, 315 Extra: map[string]interface{}{ 316 "Builds": binaries, 317 "ID": fpm.ID, 318 "Format": format, 319 "Files": contents, 320 }, 321 }) 322 return nil 323 } 324 325 func destinations(contents files.Contents) []string { 326 result := make([]string, 0, len(contents)) 327 for _, f := range contents { 328 result = append(result, f.Destination) 329 } 330 return result 331 } 332 333 func getPassphraseFromEnv(ctx *context.Context, packager string, nfpmID string) string { 334 var passphrase string 335 336 nfpmID = strings.ToUpper(nfpmID) 337 packagerSpecificPassphrase := ctx.Env[fmt.Sprintf( 338 "NFPM_%s_%s_PASSPHRASE", 339 nfpmID, 340 packager, 341 )] 342 if packagerSpecificPassphrase != "" { 343 passphrase = packagerSpecificPassphrase 344 } else { 345 generalPassphrase := ctx.Env[fmt.Sprintf("NFPM_%s_PASSPHRASE", nfpmID)] 346 passphrase = generalPassphrase 347 } 348 349 return passphrase 350 }