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  }