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