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  }