github.com/joselitofilho/goreleaser@v0.155.1-0.20210123221854-e4891856c593/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/deprecate"
    20  	"github.com/goreleaser/goreleaser/internal/ids"
    21  	"github.com/goreleaser/goreleaser/internal/linux"
    22  	"github.com/goreleaser/goreleaser/internal/pipe"
    23  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    24  	"github.com/goreleaser/goreleaser/internal/tmpl"
    25  	"github.com/goreleaser/goreleaser/pkg/config"
    26  	"github.com/goreleaser/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  		if fpm.Deb.VersionMetadata != "" {
   106  			deprecate.Notice(ctx, "nfpms.deb.version_metadata")
   107  			fpm.VersionMetadata = fpm.Deb.VersionMetadata
   108  		}
   109  
   110  		if len(fpm.Builds) == 0 {
   111  			for _, b := range ctx.Config.Builds {
   112  				fpm.Builds = append(fpm.Builds, b.ID)
   113  			}
   114  		}
   115  		ids.Inc(fpm.ID)
   116  	}
   117  	return ids.Validate()
   118  }
   119  
   120  // Run the pipe.
   121  func (Pipe) Run(ctx *context.Context) error {
   122  	for _, nfpm := range ctx.Config.NFPMs {
   123  		if len(nfpm.Formats) == 0 {
   124  			// FIXME: this assumes other nfpm configs will fail too...
   125  			return pipe.Skip("no output formats configured")
   126  		}
   127  		if err := doRun(ctx, nfpm); err != nil {
   128  			return err
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  func doRun(ctx *context.Context, fpm config.NFPM) error {
   135  	var linuxBinaries = ctx.Artifacts.Filter(artifact.And(
   136  		artifact.ByType(artifact.Binary),
   137  		artifact.ByGoos("linux"),
   138  		artifact.ByIDs(fpm.Builds...),
   139  	)).GroupByPlatform()
   140  	if len(linuxBinaries) == 0 {
   141  		return fmt.Errorf("no linux binaries found for builds %v", fpm.Builds)
   142  	}
   143  	var g = semerrgroup.New(ctx.Parallelism)
   144  	for _, format := range fpm.Formats {
   145  		for platform, artifacts := range linuxBinaries {
   146  			format := format
   147  			arch := linux.Arch(platform)
   148  			artifacts := artifacts
   149  			g.Go(func() error {
   150  				return create(ctx, fpm, format, arch, artifacts)
   151  			})
   152  		}
   153  	}
   154  	return g.Wait()
   155  }
   156  
   157  func mergeOverrides(fpm config.NFPM, format string) (*config.NFPMOverridables, error) {
   158  	var overridden config.NFPMOverridables
   159  	if err := mergo.Merge(&overridden, fpm.NFPMOverridables); err != nil {
   160  		return nil, err
   161  	}
   162  	perFormat, ok := fpm.Overrides[format]
   163  	if ok {
   164  		err := mergo.Merge(&overridden, perFormat, mergo.WithOverride)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  	}
   169  	return &overridden, nil
   170  }
   171  
   172  func create(ctx *context.Context, fpm config.NFPM, format, arch string, binaries []*artifact.Artifact) error {
   173  	overridden, err := mergeOverrides(fpm, format)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	name, err := tmpl.New(ctx).
   178  		WithArtifact(binaries[0], overridden.Replacements).
   179  		WithExtraFields(tmpl.Fields{
   180  			"Release": fpm.Release,
   181  			"Epoch":   fpm.Epoch,
   182  		}).
   183  		Apply(overridden.FileNameTemplate)
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	var contents = append(files.Contents{}, overridden.Contents...)
   189  
   190  	// FPM meta package should not contain binaries at all
   191  	if !fpm.Meta {
   192  		var log = log.WithField("package", name+"."+format).WithField("arch", arch)
   193  		for _, binary := range binaries {
   194  			src := binary.Path
   195  			dst := filepath.Join(fpm.Bindir, binary.Name)
   196  			log.WithField("src", src).WithField("dst", dst).Debug("adding binary to package")
   197  			contents = append(contents, &files.Content{
   198  				Source:      src,
   199  				Destination: dst,
   200  			})
   201  		}
   202  	}
   203  
   204  	log.WithField("files", destinations(contents)).Debug("all archive files")
   205  
   206  	var info = &nfpm.Info{
   207  		Arch:            arch,
   208  		Platform:        "linux",
   209  		Name:            fpm.PackageName,
   210  		Version:         ctx.Version,
   211  		Section:         fpm.Section,
   212  		Priority:        fpm.Priority,
   213  		Epoch:           fpm.Epoch,
   214  		Release:         fpm.Release,
   215  		Prerelease:      fpm.Prerelease,
   216  		VersionMetadata: fpm.VersionMetadata,
   217  		Maintainer:      fpm.Maintainer,
   218  		Description:     fpm.Description,
   219  		Vendor:          fpm.Vendor,
   220  		Homepage:        fpm.Homepage,
   221  		License:         fpm.License,
   222  		Overridables: nfpm.Overridables{
   223  			Conflicts:    overridden.Conflicts,
   224  			Depends:      overridden.Dependencies,
   225  			Recommends:   overridden.Recommends,
   226  			Suggests:     overridden.Suggests,
   227  			Replaces:     overridden.Replaces,
   228  			EmptyFolders: overridden.EmptyFolders,
   229  			Contents:     contents,
   230  			Scripts: nfpm.Scripts{
   231  				PreInstall:  overridden.Scripts.PreInstall,
   232  				PostInstall: overridden.Scripts.PostInstall,
   233  				PreRemove:   overridden.Scripts.PreRemove,
   234  				PostRemove:  overridden.Scripts.PostRemove,
   235  			},
   236  			Deb: nfpm.Deb{
   237  				Scripts: nfpm.DebScripts{
   238  					Rules:     overridden.Deb.Scripts.Rules,
   239  					Templates: overridden.Deb.Scripts.Templates,
   240  				},
   241  				Triggers: nfpm.DebTriggers{
   242  					Interest:        overridden.Deb.Triggers.Interest,
   243  					InterestAwait:   overridden.Deb.Triggers.InterestAwait,
   244  					InterestNoAwait: overridden.Deb.Triggers.InterestNoAwait,
   245  					Activate:        overridden.Deb.Triggers.Activate,
   246  					ActivateAwait:   overridden.Deb.Triggers.ActivateAwait,
   247  					ActivateNoAwait: overridden.Deb.Triggers.ActivateNoAwait,
   248  				},
   249  				Breaks: overridden.Deb.Breaks,
   250  				Signature: nfpm.DebSignature{
   251  					KeyFile:       overridden.Deb.Signature.KeyFile,
   252  					KeyPassphrase: getPassphraseFromEnv(ctx, "DEB", fpm.ID),
   253  					Type:          overridden.Deb.Signature.Type,
   254  				},
   255  			},
   256  			RPM: nfpm.RPM{
   257  				Summary:     overridden.RPM.Summary,
   258  				Group:       overridden.RPM.Group,
   259  				Compression: overridden.RPM.Compression,
   260  				Signature: nfpm.RPMSignature{
   261  					KeyFile:       overridden.RPM.Signature.KeyFile,
   262  					KeyPassphrase: getPassphraseFromEnv(ctx, "RPM", fpm.ID),
   263  				},
   264  			},
   265  			APK: nfpm.APK{
   266  				Signature: nfpm.APKSignature{
   267  					KeyFile:       overridden.APK.Signature.KeyFile,
   268  					KeyPassphrase: getPassphraseFromEnv(ctx, "APK", fpm.ID),
   269  					KeyName:       overridden.APK.Signature.KeyName,
   270  				},
   271  			},
   272  		},
   273  	}
   274  
   275  	if ctx.SkipSign {
   276  		info.APK.Signature = nfpm.APKSignature{}
   277  		info.RPM.Signature = nfpm.RPMSignature{}
   278  		info.Deb.Signature = nfpm.DebSignature{}
   279  	}
   280  
   281  	if err = nfpm.Validate(info); err != nil {
   282  		return fmt.Errorf("invalid nfpm config: %w", err)
   283  	}
   284  
   285  	packager, err := nfpm.Get(format)
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	var path = filepath.Join(ctx.Config.Dist, name+"."+format)
   291  	log.WithField("file", path).Info("creating")
   292  	w, err := os.Create(path)
   293  	if err != nil {
   294  		return err
   295  	}
   296  	defer w.Close()
   297  	if err := packager.Package(nfpm.WithDefaults(info), w); err != nil {
   298  		return fmt.Errorf("nfpm failed: %w", err)
   299  	}
   300  	if err := w.Close(); err != nil {
   301  		return fmt.Errorf("could not close package file: %w", err)
   302  	}
   303  	ctx.Artifacts.Add(&artifact.Artifact{
   304  		Type:   artifact.LinuxPackage,
   305  		Name:   name + "." + format,
   306  		Path:   path,
   307  		Goos:   binaries[0].Goos,
   308  		Goarch: binaries[0].Goarch,
   309  		Goarm:  binaries[0].Goarm,
   310  		Extra: map[string]interface{}{
   311  			"Builds": binaries,
   312  			"ID":     fpm.ID,
   313  			"Format": format,
   314  			"Files":  contents,
   315  		},
   316  	})
   317  	return nil
   318  }
   319  
   320  func destinations(contents files.Contents) []string {
   321  	var result = make([]string, 0, len(contents))
   322  	for _, f := range contents {
   323  		result = append(result, f.Destination)
   324  	}
   325  	return result
   326  }
   327  
   328  func getPassphraseFromEnv(ctx *context.Context, packager string, nfpmID string) string {
   329  	var passphrase string
   330  
   331  	nfpmID = strings.ToUpper(nfpmID)
   332  	packagerSpecificPassphrase := ctx.Env[fmt.Sprintf(
   333  		"NFPM_%s_%s_PASSPHRASE",
   334  		nfpmID,
   335  		packager,
   336  	)]
   337  	if packagerSpecificPassphrase != "" {
   338  		passphrase = packagerSpecificPassphrase
   339  	} else {
   340  		generalPassphrase := ctx.Env[fmt.Sprintf("NFPM_%s_PASSPHRASE", nfpmID)]
   341  		passphrase = generalPassphrase
   342  	}
   343  
   344  	return passphrase
   345  }