github.com/goreleaser/nfpm/v2@v2.44.0/nfpm.go (about)

     1  // Package nfpm provides ways to package programs in some linux packaging
     2  // formats.
     3  package nfpm
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/fs"
    10  	"os"
    11  	"slices"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"dario.cat/mergo"
    18  	"github.com/AlekSi/pointer"
    19  	"github.com/Masterminds/semver/v3"
    20  	"github.com/goreleaser/chglog"
    21  	"github.com/goreleaser/nfpm/v2/files"
    22  	"github.com/goreleaser/nfpm/v2/internal/modtime"
    23  	"gopkg.in/yaml.v3"
    24  )
    25  
    26  // nolint: gochecknoglobals
    27  var (
    28  	packagers = map[string]Packager{}
    29  	lock      sync.Mutex
    30  )
    31  
    32  // RegisterPackager a new packager for the given format.
    33  func RegisterPackager(format string, p Packager) {
    34  	lock.Lock()
    35  	defer lock.Unlock()
    36  	packagers[format] = p
    37  }
    38  
    39  // ClearPackagers clear all registered packagers, used for testing.
    40  func ClearPackagers() {
    41  	lock.Lock()
    42  	defer lock.Unlock()
    43  	packagers = map[string]Packager{}
    44  }
    45  
    46  // ErrNoPackager happens when no packager is registered for the given format.
    47  type ErrNoPackager struct {
    48  	format string
    49  }
    50  
    51  func (e ErrNoPackager) Error() string {
    52  	return fmt.Sprintf("no packager registered for the format %s", e.format)
    53  }
    54  
    55  // Get a packager for the given format.
    56  func Get(format string) (Packager, error) {
    57  	p, ok := packagers[format]
    58  	if !ok {
    59  		return nil, ErrNoPackager{format}
    60  	}
    61  	return p, nil
    62  }
    63  
    64  // Enumerate lists the available packagers
    65  func Enumerate() []string {
    66  	lock.Lock()
    67  	defer lock.Unlock()
    68  
    69  	list := make([]string, 0, len(packagers))
    70  	for key := range packagers {
    71  		if key != "" {
    72  			list = append(list, key)
    73  		}
    74  	}
    75  
    76  	sort.Strings(list)
    77  	return list
    78  }
    79  
    80  // Parse decodes YAML data from an io.Reader into a configuration struct.
    81  func Parse(in io.Reader) (config Config, err error) {
    82  	return ParseWithEnvMapping(in, os.Getenv)
    83  }
    84  
    85  // ParseWithEnvMapping decodes YAML data from an io.Reader into a configuration struct.
    86  func ParseWithEnvMapping(in io.Reader, mapping func(string) string) (config Config, err error) {
    87  	dec := yaml.NewDecoder(in)
    88  	dec.KnownFields(true)
    89  	if err = dec.Decode(&config); err != nil {
    90  		return config, err
    91  	}
    92  	config.envMappingFunc = mapping
    93  	if config.envMappingFunc == nil {
    94  		config.envMappingFunc = func(s string) string { return s }
    95  	}
    96  
    97  	config.expandEnvVars()
    98  	WithDefaults(&config.Info)
    99  	return config, nil
   100  }
   101  
   102  // ParseFile decodes YAML data from a file path into a configuration struct.
   103  func ParseFile(path string) (config Config, err error) {
   104  	if path == "-" {
   105  		return ParseWithEnvMapping(os.Stdin, os.Getenv)
   106  	}
   107  	return ParseFileWithEnvMapping(path, os.Getenv)
   108  }
   109  
   110  // ParseFileWithEnvMapping decodes YAML data from a file path into a configuration struct.
   111  func ParseFileWithEnvMapping(path string, mapping func(string) string) (config Config, err error) {
   112  	var file *os.File
   113  	file, err = os.Open(path) //nolint:gosec
   114  	if err != nil {
   115  		return config, err
   116  	}
   117  	defer file.Close() // nolint: errcheck,gosec
   118  	return ParseWithEnvMapping(file, mapping)
   119  }
   120  
   121  // Packager represents any packager implementation.
   122  type Packager interface {
   123  	Package(info *Info, w io.Writer) error
   124  	ConventionalFileName(info *Info) string
   125  }
   126  
   127  type PackagerWithExtension interface {
   128  	Packager
   129  	ConventionalExtension() string
   130  }
   131  
   132  // Config contains the top level configuration for packages.
   133  type Config struct {
   134  	Info           `yaml:",inline" json:",inline"`
   135  	Overrides      map[string]*Overridables `yaml:"overrides,omitempty" json:"overrides,omitempty" jsonschema:"title=overrides,description=override some fields when packaging with a specific packager,enum=apk,enum=deb,enum=rpm"`
   136  	envMappingFunc func(string) string
   137  }
   138  
   139  // Get returns the Info struct for the given packager format. Overrides
   140  // for the given format are merged into the final struct.
   141  func (c *Config) Get(format string) (info *Info, err error) {
   142  	info = &Info{}
   143  	// make a deep copy of info
   144  	if err = mergo.Merge(info, c.Info, mergo.WithOverride); err != nil {
   145  		return nil, fmt.Errorf("failed to merge config into info: %w", err)
   146  	}
   147  	override, ok := c.Overrides[format]
   148  	if !ok {
   149  		// no overrides
   150  		return info, nil
   151  	}
   152  	if err = mergo.Merge(&info.Overridables, override, mergo.WithOverride); err != nil {
   153  		return nil, fmt.Errorf("failed to merge overrides into info: %w", err)
   154  	}
   155  
   156  	var contents []*files.Content
   157  	for _, f := range info.Contents {
   158  		if f.Packager == format || f.Packager == "" {
   159  			contents = append(contents, f)
   160  		}
   161  	}
   162  	info.Contents = contents
   163  	return info, nil
   164  }
   165  
   166  // Validate ensures that the config is well typed.
   167  func (c *Config) Validate() error {
   168  	if err := Validate(&c.Info); err != nil {
   169  		return err
   170  	}
   171  	for format := range c.Overrides {
   172  		if _, err := Get(format); err != nil {
   173  			return err
   174  		}
   175  	}
   176  	return nil
   177  }
   178  
   179  func (c *Config) expandEnvVarsStringSlice(items []string) []string {
   180  	for i, dep := range items {
   181  		val := strings.TrimSpace(os.Expand(dep, c.envMappingFunc))
   182  		items[i] = val
   183  	}
   184  	for i := 0; i < len(items); i++ {
   185  		if items[i] == "" {
   186  			items = slices.Delete(items, i, i+1)
   187  			i-- // Since we just deleted items[i], we must redo that index
   188  		}
   189  	}
   190  
   191  	return items
   192  }
   193  
   194  func (c *Config) expandEnvVarsContents(contents files.Contents) files.Contents {
   195  	for i := range contents {
   196  		f := contents[i]
   197  		if !f.Expand {
   198  			continue
   199  		}
   200  		f.Destination = strings.TrimSpace(os.Expand(f.Destination, c.envMappingFunc))
   201  		f.Source = strings.TrimSpace(os.Expand(f.Source, c.envMappingFunc))
   202  	}
   203  	return contents
   204  }
   205  
   206  func (c *Config) expandEnvVars() {
   207  	// Version related fields
   208  	c.Release = os.Expand(c.Release, c.envMappingFunc)
   209  	c.Version = os.Expand(c.Version, c.envMappingFunc)
   210  	c.Prerelease = os.Expand(c.Prerelease, c.envMappingFunc)
   211  	c.Platform = os.Expand(c.Platform, c.envMappingFunc)
   212  	c.Arch = os.Expand(c.Arch, c.envMappingFunc)
   213  	for or := range c.Overrides {
   214  		c.Overrides[or].Conflicts = c.expandEnvVarsStringSlice(c.Overrides[or].Conflicts)
   215  		c.Overrides[or].Depends = c.expandEnvVarsStringSlice(c.Overrides[or].Depends)
   216  		c.Overrides[or].Replaces = c.expandEnvVarsStringSlice(c.Overrides[or].Replaces)
   217  		c.Overrides[or].Recommends = c.expandEnvVarsStringSlice(c.Overrides[or].Recommends)
   218  		c.Overrides[or].Provides = c.expandEnvVarsStringSlice(c.Overrides[or].Provides)
   219  		c.Overrides[or].Suggests = c.expandEnvVarsStringSlice(c.Overrides[or].Suggests)
   220  		c.Overrides[or].Contents = c.expandEnvVarsContents(c.Overrides[or].Contents)
   221  	}
   222  	c.Conflicts = c.expandEnvVarsStringSlice(c.Conflicts)
   223  	c.Depends = c.expandEnvVarsStringSlice(c.Depends)
   224  	c.Replaces = c.expandEnvVarsStringSlice(c.Replaces)
   225  	c.Recommends = c.expandEnvVarsStringSlice(c.Recommends)
   226  	c.Provides = c.expandEnvVarsStringSlice(c.Provides)
   227  	c.Suggests = c.expandEnvVarsStringSlice(c.Suggests)
   228  	c.Contents = c.expandEnvVarsContents(c.Contents)
   229  
   230  	// Basic metadata fields
   231  	c.Name = os.Expand(c.Name, c.envMappingFunc)
   232  	c.Homepage = os.Expand(c.Homepage, c.envMappingFunc)
   233  	c.Maintainer = os.Expand(c.Maintainer, c.envMappingFunc)
   234  	c.Vendor = os.Expand(c.Vendor, c.envMappingFunc)
   235  	c.Description = os.Expand(c.Description, c.envMappingFunc)
   236  
   237  	// Package signing related fields
   238  	c.Deb.Signature.KeyFile = os.Expand(c.Deb.Signature.KeyFile, c.envMappingFunc)
   239  	c.RPM.Signature.KeyFile = os.Expand(c.RPM.Signature.KeyFile, c.envMappingFunc)
   240  	c.APK.Signature.KeyFile = os.Expand(c.APK.Signature.KeyFile, c.envMappingFunc)
   241  	c.Deb.Signature.KeyID = pointer.ToString(os.Expand(pointer.GetString(c.Deb.Signature.KeyID), c.envMappingFunc))
   242  	c.RPM.Signature.KeyID = pointer.ToString(os.Expand(pointer.GetString(c.RPM.Signature.KeyID), c.envMappingFunc))
   243  	c.APK.Signature.KeyID = pointer.ToString(os.Expand(pointer.GetString(c.APK.Signature.KeyID), c.envMappingFunc))
   244  
   245  	// Package signing passphrase
   246  	generalPassphrase := os.Expand("$NFPM_PASSPHRASE", c.envMappingFunc)
   247  	c.Deb.Signature.KeyPassphrase = generalPassphrase
   248  	c.RPM.Signature.KeyPassphrase = generalPassphrase
   249  	c.APK.Signature.KeyPassphrase = generalPassphrase
   250  
   251  	debPassphrase := os.Expand("$NFPM_DEB_PASSPHRASE", c.envMappingFunc)
   252  	if debPassphrase != "" {
   253  		c.Deb.Signature.KeyPassphrase = debPassphrase
   254  	}
   255  
   256  	rpmPassphrase := os.Expand("$NFPM_RPM_PASSPHRASE", c.envMappingFunc)
   257  	if rpmPassphrase != "" {
   258  		c.RPM.Signature.KeyPassphrase = rpmPassphrase
   259  	}
   260  
   261  	apkPassphrase := os.Expand("$NFPM_APK_PASSPHRASE", c.envMappingFunc)
   262  	if apkPassphrase != "" {
   263  		c.APK.Signature.KeyPassphrase = apkPassphrase
   264  	}
   265  
   266  	// RPM specific
   267  	c.RPM.Packager = os.Expand(c.RPM.Packager, c.envMappingFunc)
   268  
   269  	// Deb specific
   270  	for k, v := range c.Deb.Fields {
   271  		c.Deb.Fields[k] = os.Expand(v, c.envMappingFunc)
   272  	}
   273  	c.Deb.Predepends = c.expandEnvVarsStringSlice(c.Deb.Predepends)
   274  
   275  	// IPK specific
   276  	for k, v := range c.IPK.Fields {
   277  		c.IPK.Fields[k] = os.Expand(v, c.envMappingFunc)
   278  	}
   279  	c.IPK.Predepends = c.expandEnvVarsStringSlice(c.IPK.Predepends)
   280  
   281  	// RPM specific
   282  	c.RPM.Packager = os.Expand(c.RPM.Packager, c.envMappingFunc)
   283  }
   284  
   285  // Info contains information about a single package.
   286  type Info struct {
   287  	Overridables    `yaml:",inline" json:",inline"`
   288  	Name            string    `yaml:"name" json:"name" jsonschema:"title=package name"`
   289  	Arch            string    `yaml:"arch" json:"arch" jsonschema:"title=target architecture,example=amd64"`
   290  	Platform        string    `yaml:"platform,omitempty" json:"platform,omitempty" jsonschema:"title=target platform,example=linux,default=linux"`
   291  	Epoch           string    `yaml:"epoch,omitempty" json:"epoch,omitempty" jsonschema:"title=version epoch,example=2,default=extracted from version"`
   292  	Version         string    `yaml:"version" json:"version" jsonschema:"title=version,example=v1.0.2,example=2.0.1"`
   293  	VersionSchema   string    `yaml:"version_schema,omitempty" json:"version_schema,omitempty" jsonschema:"title=version schema,enum=semver,enum=none,default=semver"`
   294  	Release         string    `yaml:"release,omitempty" json:"release,omitempty" jsonschema:"title=version release,example=1"`
   295  	Prerelease      string    `yaml:"prerelease,omitempty" json:"prerelease,omitempty" jsonschema:"title=version prerelease,default=extracted from version"`
   296  	VersionMetadata string    `yaml:"version_metadata,omitempty" json:"version_metadata,omitempty" jsonschema:"title=version metadata,example=git"`
   297  	Section         string    `yaml:"section,omitempty" json:"section,omitempty" jsonschema:"title=package section,example=default"`
   298  	Priority        string    `yaml:"priority,omitempty" json:"priority,omitempty" jsonschema:"title=package priority,example=extra"`
   299  	Maintainer      string    `yaml:"maintainer,omitempty" json:"maintainer,omitempty" jsonschema:"title=package maintainer,example=me@example.com"`
   300  	Description     string    `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"title=package description"`
   301  	Vendor          string    `yaml:"vendor,omitempty" json:"vendor,omitempty" jsonschema:"title=package vendor,example=MyCorp"`
   302  	Homepage        string    `yaml:"homepage,omitempty" json:"homepage,omitempty" jsonschema:"title=package homepage,example=https://example.com"`
   303  	License         string    `yaml:"license,omitempty" json:"license,omitempty" jsonschema:"title=package license,example=MIT"`
   304  	Changelog       string    `yaml:"changelog,omitempty" json:"changelog,omitempty" jsonschema:"title=package changelog,example=changelog.yaml,description=see https://github.com/goreleaser/chglog for more details"`
   305  	DisableGlobbing bool      `yaml:"disable_globbing,omitempty" json:"disable_globbing,omitempty" jsonschema:"title=whether to disable file globbing,default=false"`
   306  	MTime           time.Time `yaml:"mtime,omitempty" json:"mtime,omitempty" jsonschema:"title=time to set into the files generated by nFPM"`
   307  	Target          string    `yaml:"-" json:"-"`
   308  }
   309  
   310  func (i *Info) Validate() error {
   311  	return Validate(i)
   312  }
   313  
   314  // GetChangeLog parses the provided changelog file.
   315  func (i *Info) GetChangeLog() (log *chglog.PackageChangeLog, err error) {
   316  	// if the file does not exist chglog.Parse will just silently
   317  	// create an empty changelog but we should notify the user instead
   318  	if _, err = os.Stat(i.Changelog); errors.Is(err, fs.ErrNotExist) {
   319  		return nil, err
   320  	}
   321  
   322  	entries, err := chglog.Parse(i.Changelog)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	return &chglog.PackageChangeLog{
   328  		Name:    i.Name,
   329  		Entries: entries,
   330  	}, nil
   331  }
   332  
   333  func (i *Info) parseSemver() {
   334  	// parse the version as a semver so we can properly split the parts
   335  	// and support proper ordering for both rpm and deb
   336  	if v, err := semver.NewVersion(i.Version); err == nil {
   337  		i.Version = fmt.Sprintf("%d.%d.%d", v.Major(), v.Minor(), v.Patch())
   338  		if i.Prerelease == "" {
   339  			i.Prerelease = v.Prerelease()
   340  		}
   341  
   342  		if i.VersionMetadata == "" {
   343  			i.VersionMetadata = v.Metadata()
   344  		}
   345  	}
   346  }
   347  
   348  // Overridables contain the field which are overridable in a package.
   349  type Overridables struct {
   350  	Replaces   []string       `yaml:"replaces,omitempty" json:"replaces,omitempty" jsonschema:"title=replaces directive,example=nfpm"`
   351  	Provides   []string       `yaml:"provides,omitempty" json:"provides,omitempty" jsonschema:"title=provides directive,example=nfpm"`
   352  	Depends    []string       `yaml:"depends,omitempty" json:"depends,omitempty" jsonschema:"title=depends directive,example=nfpm"`
   353  	Recommends []string       `yaml:"recommends,omitempty" json:"recommends,omitempty" jsonschema:"title=recommends directive,example=nfpm"`
   354  	Suggests   []string       `yaml:"suggests,omitempty" json:"suggests,omitempty" jsonschema:"title=suggests directive,example=nfpm"`
   355  	Conflicts  []string       `yaml:"conflicts,omitempty" json:"conflicts,omitempty" jsonschema:"title=conflicts directive,example=nfpm"`
   356  	Contents   files.Contents `yaml:"contents,omitempty" json:"contents,omitempty" jsonschema:"title=files to add to the package"`
   357  	Umask      os.FileMode    `yaml:"umask,omitempty" json:"umask,omitempty" jsonschema:"title=umask for file contents,example=112"`
   358  	Scripts    Scripts        `yaml:"scripts,omitempty" json:"scripts,omitempty" jsonschema:"title=scripts to execute"`
   359  	RPM        RPM            `yaml:"rpm,omitempty" json:"rpm,omitempty" jsonschema:"title=rpm-specific settings"`
   360  	Deb        Deb            `yaml:"deb,omitempty" json:"deb,omitempty" jsonschema:"title=deb-specific settings"`
   361  	APK        APK            `yaml:"apk,omitempty" json:"apk,omitempty" jsonschema:"title=apk-specific settings"`
   362  	ArchLinux  ArchLinux      `yaml:"archlinux,omitempty" json:"archlinux,omitempty" jsonschema:"title=archlinux-specific settings"`
   363  	IPK        IPK            `yaml:"ipk,omitempty" json:"ipk,omitempty" jsonschema:"title=ipk-specific settings"`
   364  }
   365  
   366  type ArchLinux struct {
   367  	Pkgbase  string           `yaml:"pkgbase,omitempty" json:"pkgbase,omitempty" jsonschema:"title=explicitly specify the name used to refer to a split package, defaults to name"`
   368  	Arch     string           `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in archlinux nomenclature"`
   369  	Packager string           `yaml:"packager,omitempty" json:"packager,omitempty" jsonschema:"title=organization that packaged the software"`
   370  	Scripts  ArchLinuxScripts `yaml:"scripts,omitempty" json:"scripts,omitempty" jsonschema:"title=archlinux-specific scripts"`
   371  }
   372  
   373  type ArchLinuxScripts struct {
   374  	PreUpgrade  string `yaml:"preupgrade,omitempty" json:"preupgrade,omitempty" jsonschema:"title=preupgrade script"`
   375  	PostUpgrade string `yaml:"postupgrade,omitempty" json:"postupgrade,omitempty" jsonschema:"title=postupgrade script"`
   376  }
   377  
   378  // RPM is custom configs that are only available on RPM packages.
   379  type RPM struct {
   380  	Arch        string       `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in rpm nomenclature"`
   381  	BuildHost   string       `yaml:"buildhost,omitempty" json:"buildhost,omitempty" jsonschema:"title=host name of the build environment, default=os.Hostname()"`
   382  	Scripts     RPMScripts   `yaml:"scripts,omitempty" json:"scripts,omitempty" jsonschema:"title=rpm-specific scripts"`
   383  	Group       string       `yaml:"group,omitempty" json:"group,omitempty" jsonschema:"title=package group,example=Unspecified"`
   384  	Summary     string       `yaml:"summary,omitempty" json:"summary,omitempty" jsonschema:"title=package summary"`
   385  	Compression string       `yaml:"compression,omitempty" json:"compression,omitempty" jsonschema:"title=compression algorithm to be used,enum=gzip,enum=lzma,enum=xz,enum=zstd,default=gzip:-1"`
   386  	Signature   RPMSignature `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=rpm signature"`
   387  	Packager    string       `yaml:"packager,omitempty" json:"packager,omitempty" jsonschema:"title=organization that actually packaged the software"`
   388  	Prefixes    []string     `yaml:"prefixes,omitempty" json:"prefixes,omitempty" jsonschema:"title=Prefixes for relocatable packages"`
   389  }
   390  
   391  // RPMScripts represents scripts only available on RPM packages.
   392  type RPMScripts struct {
   393  	PreTrans  string `yaml:"pretrans,omitempty" json:"pretrans,omitempty" jsonschema:"title=pretrans script"`
   394  	PostTrans string `yaml:"posttrans,omitempty" json:"posttrans,omitempty" jsonschema:"title=posttrans script"`
   395  	Verify    string `yaml:"verify,omitempty" json:"verify,omitempty" jsonschema:"title=verify script"`
   396  }
   397  
   398  type PackageSignature struct {
   399  	// PGP secret key, can be ASCII-armored
   400  	KeyFile       string  `yaml:"key_file,omitempty" json:"key_file,omitempty" jsonschema:"title=key file,example=key.gpg"`
   401  	KeyID         *string `yaml:"key_id,omitempty" json:"key_id,omitempty" jsonschema:"title=key id,example=bc8acdd415bd80b3"`
   402  	KeyPassphrase string  `yaml:"-" json:"-"` // populated from environment variable
   403  	// SignFn, if set, will be called with the package-specific data to sign.
   404  	// For deb and rpm packages, data is the full package content.
   405  	// For apk packages, data is the SHA1 digest of control tgz.
   406  	//
   407  	// This allows for signing implementations other than using a local file
   408  	// (for example using a remote signer like KMS).
   409  	SignFn func(data io.Reader) ([]byte, error) `yaml:"-" json:"-"` // populated when used as a library
   410  }
   411  
   412  type RPMSignature struct {
   413  	PackageSignature `yaml:",inline" json:",inline"`
   414  }
   415  
   416  type APK struct {
   417  	Arch      string       `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in apk nomenclature"`
   418  	Signature APKSignature `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=apk signature"`
   419  	Scripts   APKScripts   `yaml:"scripts,omitempty" json:"scripts,omitempty" jsonschema:"title=apk scripts"`
   420  }
   421  
   422  type APKSignature struct {
   423  	PackageSignature `yaml:",inline" json:",inline"`
   424  	// defaults to <maintainer email>.rsa.pub
   425  	KeyName string `yaml:"key_name,omitempty" json:"key_name,omitempty" jsonschema:"title=key name,example=origin,default=maintainer_email.rsa.pub"`
   426  }
   427  
   428  type APKScripts struct {
   429  	PreUpgrade  string `yaml:"preupgrade,omitempty" json:"preupgrade,omitempty" jsonschema:"title=pre upgrade script"`
   430  	PostUpgrade string `yaml:"postupgrade,omitempty" json:"postupgrade,omitempty" jsonschema:"title=post upgrade script"`
   431  }
   432  
   433  // Deb is custom configs that are only available on deb packages.
   434  type Deb struct {
   435  	Arch        string            `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in deb nomenclature"`
   436  	Scripts     DebScripts        `yaml:"scripts,omitempty" json:"scripts,omitempty" jsonschema:"title=scripts"`
   437  	Triggers    DebTriggers       `yaml:"triggers,omitempty" json:"triggers,omitempty" jsonschema:"title=triggers"`
   438  	Breaks      []string          `yaml:"breaks,omitempty" json:"breaks,omitempty" jsonschema:"title=breaks"`
   439  	Signature   DebSignature      `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=signature"`
   440  	Compression string            `yaml:"compression,omitempty" json:"compression,omitempty" jsonschema:"title=compression algorithm to be used,enum=gzip,enum=xz,enum=zstd,enum=none,default=gzip:-1"`
   441  	Fields      map[string]string `yaml:"fields,omitempty" json:"fields,omitempty" jsonschema:"title=fields"`
   442  	Predepends  []string          `yaml:"predepends,omitempty" json:"predepends,omitempty" jsonschema:"title=predepends directive,example=nfpm"`
   443  }
   444  
   445  type DebSignature struct {
   446  	PackageSignature `yaml:",inline" json:",inline"`
   447  	// Only debsign still supported
   448  	Method string `yaml:"method,omitempty" json:"method,omitempty" jsonschema:"title=method role,enum=dpkg-sig,enum=debsign,default=debsign"`
   449  	// origin, maint or archive (defaults to origin)
   450  	Type   string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=signer role,enum=origin,enum=maint,enum=archive,default=origin"`
   451  	Signer string `yaml:"signer,omitempty" json:"signer,omitempty" jsonschema:"title=signer"`
   452  }
   453  
   454  // DebTriggers contains triggers only available for deb packages.
   455  // https://wiki.debian.org/DpkgTriggers
   456  // https://man7.org/linux/man-pages/man5/deb-triggers.5.html
   457  type DebTriggers struct {
   458  	Interest        []string `yaml:"interest,omitempty" json:"interest,omitempty" jsonschema:"title=interest"`
   459  	InterestAwait   []string `yaml:"interest_await,omitempty" json:"interest_await,omitempty" jsonschema:"title=interest await"`
   460  	InterestNoAwait []string `yaml:"interest_noawait,omitempty" json:"interest_noawait,omitempty" jsonschema:"title=interest noawait"`
   461  	Activate        []string `yaml:"activate,omitempty" json:"activate,omitempty" jsonschema:"title=activate"`
   462  	ActivateAwait   []string `yaml:"activate_await,omitempty" json:"activate_await,omitempty" jsonschema:"title=activate await"`
   463  	ActivateNoAwait []string `yaml:"activate_noawait,omitempty" json:"activate_noawait,omitempty" jsonschema:"title=activate noawait"`
   464  }
   465  
   466  // DebScripts is scripts only available on deb packages.
   467  type DebScripts struct {
   468  	Rules     string `yaml:"rules,omitempty" json:"rules,omitempty" jsonschema:"title=rules"`
   469  	Templates string `yaml:"templates,omitempty" json:"templates,omitempty" jsonschema:"title=templates"`
   470  	Config    string `yaml:"config,omitempty" json:"config,omitempty" jsonschema:"title=config"`
   471  }
   472  
   473  // IPK is custom configs that are only available on deb packages.
   474  type IPK struct {
   475  	ABIVersion    string            `yaml:"abi_version,omitempty" json:"abi_version,omitempty" jsonschema:"title=abi version"`
   476  	Alternatives  []IPKAlternative  `yaml:"alternatives,omitempty" json:"alternatives,omitempty" jsonschema:"title=alternatives"`
   477  	Arch          string            `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in deb nomenclature"`
   478  	AutoInstalled bool              `yaml:"auto_installed,omitempty" json:"auto_installed,omitempty" jsonschema:"title=auto installed,default=false"`
   479  	Essential     bool              `yaml:"essential,omitempty" json:"essential,omitempty" jsonschema:"title=whether package is essential,default=false"`
   480  	Fields        map[string]string `yaml:"fields,omitempty" json:"fields,omitempty" jsonschema:"title=fields"`
   481  	Predepends    []string          `yaml:"predepends,omitempty" json:"predepends,omitempty" jsonschema:"title=predepends directive,example=nfpm"`
   482  	Tags          []string          `yaml:"tags,omitempty" json:"tags,omitempty" jsonschema:"title=tags"`
   483  }
   484  
   485  // IPKAlternative represents an alternative for an IPK package.
   486  type IPKAlternative struct {
   487  	Priority int    `yaml:"priority,omitempty" json:"priority,omitempty" jsonschema:"title=priority"`
   488  	Target   string `yaml:"target,omitempty" json:"target,omitempty" jsonschema:"title=target"`
   489  	LinkName string `yaml:"link_name,omitempty" json:"link_name,omitempty" jsonschema:"title=link name"`
   490  }
   491  
   492  // Scripts contains information about maintainer scripts for packages.
   493  type Scripts struct {
   494  	PreInstall  string `yaml:"preinstall,omitempty" json:"preinstall,omitempty" jsonschema:"title=pre install"`
   495  	PostInstall string `yaml:"postinstall,omitempty" json:"postinstall,omitempty" jsonschema:"title=post install"`
   496  	PreRemove   string `yaml:"preremove,omitempty" json:"preremove,omitempty" jsonschema:"title=pre remove"`
   497  	PostRemove  string `yaml:"postremove,omitempty" json:"postremove,omitempty" jsonschema:"title=post remove"`
   498  }
   499  
   500  // ErrFieldEmpty happens when some required field is empty.
   501  type ErrFieldEmpty struct {
   502  	field string
   503  }
   504  
   505  func (e ErrFieldEmpty) Error() string {
   506  	return fmt.Sprintf("package %s must be provided", e.field)
   507  }
   508  
   509  // PrepareForPackager validates the configuration for the given packager and
   510  // prepares the contents for said packager.
   511  func PrepareForPackager(info *Info, packager string) (err error) {
   512  	if info.Name == "" {
   513  		return ErrFieldEmpty{"name"}
   514  	}
   515  
   516  	if info.Arch == "" &&
   517  		((packager == "deb" && info.Deb.Arch == "") ||
   518  			(packager == "rpm" && info.RPM.Arch == "") ||
   519  			(packager == "apk" && info.APK.Arch == "")) {
   520  		return ErrFieldEmpty{"arch"}
   521  	}
   522  	if info.Version == "" {
   523  		return ErrFieldEmpty{"version"}
   524  	}
   525  
   526  	info.Contents, err = files.PrepareForPackager(
   527  		info.Contents,
   528  		info.Umask,
   529  		packager,
   530  		info.DisableGlobbing,
   531  		info.MTime,
   532  	)
   533  
   534  	return err
   535  }
   536  
   537  // Validate the given Info and returns an error if it is invalid. Validate will
   538  // no change the info's contents.
   539  func Validate(info *Info) (err error) {
   540  	if info.Name == "" {
   541  		return ErrFieldEmpty{"name"}
   542  	}
   543  	if info.Arch == "" && (info.Deb.Arch == "" || info.RPM.Arch == "" || info.APK.Arch == "") {
   544  		return ErrFieldEmpty{"arch"}
   545  	}
   546  	if info.Version == "" {
   547  		return ErrFieldEmpty{"version"}
   548  	}
   549  
   550  	for packager := range packagers {
   551  		_, err := files.PrepareForPackager(
   552  			info.Contents,
   553  			info.Umask,
   554  			packager,
   555  			info.DisableGlobbing,
   556  			info.MTime,
   557  		)
   558  		if err != nil {
   559  			return err
   560  		}
   561  	}
   562  
   563  	return nil
   564  }
   565  
   566  // WithDefaults set some sane defaults into the given Info.
   567  func WithDefaults(info *Info) *Info {
   568  	if info.Platform == "" {
   569  		info.Platform = "linux"
   570  	}
   571  	if info.Description == "" {
   572  		info.Description = "no description given"
   573  	}
   574  	if info.Arch == "" {
   575  		info.Arch = "amd64"
   576  	}
   577  	if strings.HasPrefix(info.Arch, "mips") {
   578  		info.Arch = strings.NewReplacer(
   579  			"softfloat", "",
   580  			"hardfloat", "",
   581  		).Replace(info.Arch)
   582  	}
   583  	if info.Version == "" {
   584  		info.Version = "v0.0.0-rc0"
   585  	}
   586  	if info.Umask == 0 {
   587  		info.Umask = 0o02
   588  	}
   589  	if info.MTime.IsZero() {
   590  		info.MTime = modtime.FromEnv()
   591  	}
   592  	switch info.VersionSchema {
   593  	case "none":
   594  		// No change to the version or prerelease info set in the YAML file
   595  		break
   596  	case "semver":
   597  		fallthrough
   598  	default:
   599  		info.parseSemver()
   600  	}
   601  
   602  	return info
   603  }
   604  
   605  // ErrSigningFailure is returned whenever something went wrong during
   606  // the package signing process. The underlying error can be unwrapped
   607  // and could be crypto-related or something that occurred while adding
   608  // the signature to the package.
   609  type ErrSigningFailure struct {
   610  	Err error
   611  }
   612  
   613  func (s *ErrSigningFailure) Error() string {
   614  	return fmt.Sprintf("signing error: %v", s.Err)
   615  }
   616  
   617  func (s *ErrSigningFailure) Unwarp() error {
   618  	return s.Err
   619  }