mirror of
https://github.com/goreleaser/nfpm.git
synced 2026-06-19 08:05:04 +00:00
feat: support for ipk packages (#818)
Implements #507. * Adds ipk support for keywords used by OpenWRT and Yocto. * MD5sum is explicitly excluded due to insecurity. * SHA256Sum excluded due packages not being individually signed, instead, the feed of packages is checksummed and signed externally. * Adds code to nfpm package to automatically enumerate the supported packaging types where possible.
This commit is contained in:
@@ -65,7 +65,7 @@ jobs:
|
||||
acceptance-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
pkgFormat: [deb, rpm, apk, archlinux]
|
||||
pkgFormat: [deb, rpm, apk, archlinux, ipk]
|
||||
pkgPlatform: [amd64, arm64, 386, ppc64le, armv6, armv7, s390x]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
_ "github.com/goreleaser/nfpm/v2/apk"
|
||||
_ "github.com/goreleaser/nfpm/v2/arch"
|
||||
_ "github.com/goreleaser/nfpm/v2/deb"
|
||||
_ "github.com/goreleaser/nfpm/v2/ipk"
|
||||
_ "github.com/goreleaser/nfpm/v2/rpm"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
var formatArchs = map[string][]string{
|
||||
"apk": {"amd64", "arm64", "386", "ppc64le", "armv6", "armv7", "s390x"},
|
||||
"deb": {"amd64", "arm64", "ppc64le", "armv7", "s390x"},
|
||||
"ipk": {"x86_64", "aarch64_generic"},
|
||||
"rpm": {"amd64", "arm64", "ppc64le"},
|
||||
"archlinux": {"amd64"},
|
||||
}
|
||||
@@ -261,6 +263,38 @@ func TestDebSpecific(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPKSpecific(t *testing.T) {
|
||||
t.Parallel()
|
||||
format := "ipk"
|
||||
testNames := []string{
|
||||
"alternatives",
|
||||
"conflicts",
|
||||
"predepends",
|
||||
}
|
||||
for _, name := range testNames {
|
||||
for _, arch := range formatArchs[format] {
|
||||
func(t *testing.T, testName, testArch string) {
|
||||
t.Run(fmt.Sprintf("%s/%s/%s", format, testArch, testName), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" {
|
||||
t.Skip("ppc64le arch not supported in pipeline")
|
||||
}
|
||||
accept(t, acceptParms{
|
||||
Name: fmt.Sprintf("%s_%s", testName, testArch),
|
||||
Conf: fmt.Sprintf("%s.%s.yaml", format, testName),
|
||||
Format: format,
|
||||
Docker: dockerParams{
|
||||
File: fmt.Sprintf("%s.dockerfile", format),
|
||||
Target: testName,
|
||||
Arch: testArch,
|
||||
},
|
||||
})
|
||||
})
|
||||
}(t, name, arch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPMSign(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, os := range []string{"centos9", "centos8", "fedora34", "fedora36", "fedora38"} {
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ func main() {
|
||||
|
||||
func buildVersion(version, commit, date, builtBy, treeState string) goversion.Info {
|
||||
return goversion.GetVersionInfo(
|
||||
goversion.WithAppDetails("nfpm", "a simple and 0-dependencies deb, rpm, apk and arch linux packager written in Go", website),
|
||||
goversion.WithAppDetails("nfpm", "a simple and 0-dependencies apk, arch linux, deb, ipk, and rpm packager written in Go", website),
|
||||
goversion.WithASCIIName(asciiArt),
|
||||
func(i *goversion.Info) {
|
||||
if commit != "" {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -37,9 +38,12 @@ func newPackageCmd() *packageCmd {
|
||||
_ = cmd.MarkFlagFilename("config", "yaml", "yml")
|
||||
cmd.Flags().StringVarP(&root.target, "target", "t", "", "where to save the generated package (filename, folder or empty for current folder)")
|
||||
_ = cmd.MarkFlagFilename("target")
|
||||
cmd.Flags().StringVarP(&root.packager, "packager", "p", "", "which packager implementation to use [apk|deb|rpm|archlinux]")
|
||||
_ = cmd.RegisterFlagCompletionFunc("packager", cobra.FixedCompletions(
|
||||
[]string{"apk", "deb", "rpm", "archlinux"},
|
||||
|
||||
pkgs := nfpm.Enumerate()
|
||||
|
||||
cmd.Flags().StringVarP(&root.packager, "packager", "p", "",
|
||||
fmt.Sprintf("which packager implementation to use [%s]", strings.Join(pkgs, "|")))
|
||||
_ = cmd.RegisterFlagCompletionFunc("packager", cobra.FixedCompletions(pkgs,
|
||||
cobra.ShellCompDirectiveNoFileComp,
|
||||
))
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
_ "github.com/goreleaser/nfpm/v2/apk" // apk packager
|
||||
_ "github.com/goreleaser/nfpm/v2/arch" // archlinux packager
|
||||
_ "github.com/goreleaser/nfpm/v2/deb" // deb packager
|
||||
_ "github.com/goreleaser/nfpm/v2/ipk" // ipk packager
|
||||
_ "github.com/goreleaser/nfpm/v2/rpm" // rpm packager
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -35,8 +36,8 @@ func newRootCmd(version goversion.Info, exit func(int)) *rootCmd {
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "nfpm",
|
||||
Short: "Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file",
|
||||
Long: `nFPM is a simple and 0-dependencies deb, rpm, apk and arch linux packager written in Go.`,
|
||||
Short: "Packages apps on RPM, Deb, APK, Arch Linux, and ipk formats based on a YAML configuration file",
|
||||
Long: `nFPM is a simple and 0-dependencies apk, arch, deb, ipk and rpm linux packager written in Go.`,
|
||||
Version: version.String(),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
|
||||
+512
@@ -0,0 +1,512 @@
|
||||
// Package ipk implements nfpm.Packager providing .ipk bindings.
|
||||
//
|
||||
// IPK is a package format used by the opkg package manager, which is very
|
||||
// similar to the Debian package format. Generally, the package format is
|
||||
// stripped down and simplified compared to the Debian package format.
|
||||
// Yocto/OpenEmbedded/OpenWRT uses the IPK format for its package management.
|
||||
package ipk
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
"github.com/goreleaser/nfpm/v2/deprecation"
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"github.com/goreleaser/nfpm/v2/internal/modtime"
|
||||
)
|
||||
|
||||
const packagerName = "ipk"
|
||||
|
||||
// nolint: gochecknoinits
|
||||
func init() {
|
||||
nfpm.RegisterPackager(packagerName, Default)
|
||||
}
|
||||
|
||||
// nolint: gochecknoglobals
|
||||
var archToIPK = map[string]string{
|
||||
// all --> all
|
||||
"386": "i386",
|
||||
"amd64": "x86_64",
|
||||
"arm64": "arm64",
|
||||
"arm5": "armel",
|
||||
"arm6": "armhf",
|
||||
"arm7": "armhf",
|
||||
"mips64le": "mips64el",
|
||||
"mipsle": "mipsel",
|
||||
"ppc64le": "ppc64el",
|
||||
"s390": "s390x",
|
||||
}
|
||||
|
||||
func ensureValidArch(info *nfpm.Info) *nfpm.Info {
|
||||
if info.IPK.Arch != "" {
|
||||
info.Arch = info.IPK.Arch
|
||||
} else if arch, ok := archToIPK[info.Arch]; ok {
|
||||
info.Arch = arch
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// Default ipk packager.
|
||||
// nolint: gochecknoglobals
|
||||
var Default = &IPK{}
|
||||
|
||||
// IPK is a ipk packager implementation.
|
||||
type IPK struct{}
|
||||
|
||||
// ConventionalFileName returns a file name according
|
||||
// to the conventions for ipk packages. Ipk packages generally follow
|
||||
// the conventions set by debian. See:
|
||||
// https://manpages.debian.org/buster/dpkg-dev/dpkg-name.1.en.html
|
||||
func (*IPK) ConventionalFileName(info *nfpm.Info) string {
|
||||
info = ensureValidArch(info)
|
||||
|
||||
version := info.Version
|
||||
if info.Prerelease != "" {
|
||||
version += "~" + info.Prerelease
|
||||
}
|
||||
|
||||
if info.VersionMetadata != "" {
|
||||
version += "+" + info.VersionMetadata
|
||||
}
|
||||
|
||||
if info.Release != "" {
|
||||
version += "-" + info.Release
|
||||
}
|
||||
|
||||
// package_version_architecture.package-type
|
||||
return fmt.Sprintf("%s_%s_%s.ipk", info.Name, version, info.Arch)
|
||||
}
|
||||
|
||||
// ConventionalExtension returns the file name conventionally used for IPK packages
|
||||
func (*IPK) ConventionalExtension() string {
|
||||
return ".ipk"
|
||||
}
|
||||
|
||||
// SetPackagerDefaults sets the default values for the IPK packager.
|
||||
func (*IPK) SetPackagerDefaults(info *nfpm.Info) {
|
||||
// Priority should be set on all packages per:
|
||||
// https://www.debian.org/doc/debian-policy/ch-archive.html#priorities
|
||||
// "optional" seems to be the safe/sane default here
|
||||
if info.Priority == "" {
|
||||
info.Priority = "optional"
|
||||
}
|
||||
|
||||
// The safe thing here feels like defaulting to something like below.
|
||||
// That will prevent existing configs from breaking anyway... Wondering
|
||||
// if in the long run we should be more strict about this and error when
|
||||
// not set?
|
||||
if strings.TrimSpace(info.Maintainer) == "" {
|
||||
deprecation.Println("Leaving the 'maintainer' field unset will not be allowed in a future version")
|
||||
info.Maintainer = "Unset Maintainer <unset@localhost>"
|
||||
}
|
||||
}
|
||||
|
||||
// Package writes a new ipk package to the given writer using the given info.
|
||||
func (d *IPK) Package(info *nfpm.Info, ipk io.Writer) error {
|
||||
info = ensureValidArch(info)
|
||||
|
||||
if err := nfpm.PrepareForPackager(info, packagerName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set up some ipk specific defaults
|
||||
d.SetPackagerDefaults(info)
|
||||
|
||||
// Strip out any custom fields that are disallowed.
|
||||
stripDisallowedFields(info)
|
||||
|
||||
contents, err := newTGZ("ipk",
|
||||
func(tw *tar.Writer) error {
|
||||
return createIPK(info, tw)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ipk.Write(contents)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// createIPK creates a new ipk package using the given tar writer and info.
|
||||
func createIPK(info *nfpm.Info, ipk *tar.Writer) error {
|
||||
var installSize int64
|
||||
|
||||
data, err := newTGZ("data.tar.gz",
|
||||
func(tw *tar.Writer) error {
|
||||
var err error
|
||||
installSize, err = populateDataTar(info, tw)
|
||||
return err
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control, err := newTGZ("control.tar.gz",
|
||||
func(tw *tar.Writer) error {
|
||||
return populateControlTar(info, tw, installSize)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mtime := modtime.Get(info.MTime)
|
||||
|
||||
if err := writeToFile(ipk, "debian-binary", []byte("2.0\n"), mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeToFile(ipk, "control.tar.gz", control, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeToFile(ipk, "data.tar.gz", data, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateDataTar populates the data tarball with the files specified in the info.
|
||||
func populateDataTar(info *nfpm.Info, tw *tar.Writer) (instSize int64, err error) {
|
||||
// create files and implicit directories
|
||||
for _, file := range info.Contents {
|
||||
var size int64
|
||||
|
||||
switch file.Type {
|
||||
case files.TypeDir, files.TypeImplicitDir:
|
||||
err = tw.WriteHeader(
|
||||
&tar.Header{
|
||||
Name: files.AsExplicitRelativePath(file.Destination),
|
||||
Typeflag: tar.TypeDir,
|
||||
Format: tar.FormatGNU,
|
||||
ModTime: modtime.Get(info.MTime),
|
||||
Mode: int64(file.FileInfo.Mode),
|
||||
Uname: file.FileInfo.Owner,
|
||||
Gname: file.FileInfo.Group,
|
||||
})
|
||||
case files.TypeSymlink:
|
||||
err = tw.WriteHeader(
|
||||
&tar.Header{
|
||||
Name: files.AsExplicitRelativePath(file.Destination),
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Format: tar.FormatGNU,
|
||||
ModTime: modtime.Get(info.MTime),
|
||||
Linkname: file.Source,
|
||||
})
|
||||
case files.TypeFile, files.TypeTree, files.TypeConfig, files.TypeConfigNoReplace:
|
||||
size, err = writeFile(tw, file)
|
||||
default:
|
||||
// ignore everything else
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
instSize += size
|
||||
}
|
||||
|
||||
return instSize, nil
|
||||
}
|
||||
|
||||
// getScripts returns the scripts for the given info.
|
||||
func getScripts(info *nfpm.Info, mtime time.Time) []files.Content {
|
||||
return []files.Content{
|
||||
{
|
||||
Destination: "preinst",
|
||||
Source: info.Scripts.PreInstall,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
Mode: 0o755,
|
||||
MTime: mtime,
|
||||
},
|
||||
}, {
|
||||
Destination: "postinst",
|
||||
Source: info.Scripts.PostInstall,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
Mode: 0o755,
|
||||
MTime: mtime,
|
||||
},
|
||||
}, {
|
||||
Destination: "prerm",
|
||||
Source: info.Scripts.PreRemove,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
Mode: 0o755,
|
||||
MTime: mtime,
|
||||
},
|
||||
}, {
|
||||
Destination: "postrm",
|
||||
Source: info.Scripts.PostRemove,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
Mode: 0o755,
|
||||
MTime: mtime,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// populateControlTar populates the control tarball with the control files defined
|
||||
// in the info.
|
||||
func populateControlTar(info *nfpm.Info, out *tar.Writer, instSize int64) error {
|
||||
var body bytes.Buffer
|
||||
|
||||
cd := controlData{
|
||||
Info: info,
|
||||
InstalledSize: instSize / 1024,
|
||||
}
|
||||
|
||||
if err := renderControl(&body, cd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mtime := modtime.Get(info.MTime)
|
||||
if err := writeToFile(out, "./control", body.Bytes(), mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeToFile(out, "./conffiles", conffiles(info), mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scripts := getScripts(info, mtime)
|
||||
for _, file := range scripts {
|
||||
if file.Source != "" {
|
||||
if _, err := writeFile(out, &file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// conffiles returns the conffiles file bytes for the given info.
|
||||
func conffiles(info *nfpm.Info) []byte {
|
||||
// nolint: prealloc
|
||||
var confs []string
|
||||
for _, file := range info.Contents {
|
||||
switch file.Type {
|
||||
case files.TypeConfig, files.TypeConfigNoReplace:
|
||||
confs = append(confs, files.NormalizeAbsoluteFilePath(file.Destination))
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(confs, "\n") + "\n")
|
||||
}
|
||||
|
||||
// The ipk format is not formally defined, but it is similar to the deb format.
|
||||
// The two sources that were used to create this template are:
|
||||
// - https://git.yoctoproject.org/opkg/
|
||||
// - https://github.com/openwrt/opkg-lede
|
||||
//
|
||||
// Supported Fields
|
||||
//
|
||||
// R = Required
|
||||
// O = Optional
|
||||
// e = Extra
|
||||
// - = Not Supported/Ignored/Extra
|
||||
//
|
||||
//
|
||||
// OpenWRT Yocto
|
||||
// | |
|
||||
// | Field | W | Y | Status |
|
||||
// |----------------|---|---|--------|
|
||||
// | ABIVersion | O | - | ✓
|
||||
// | Alternatives | O | - | ✓
|
||||
// | Architecture | R | R | ✓
|
||||
// | Auto-Installed | O | O | ✓
|
||||
// | Conffiles | O | O | not needed since config files are listed in .conffiles
|
||||
// | Conflicts | O | O | ✓
|
||||
// | Depends | R | R | ✓
|
||||
// | Description | R | R | ✓
|
||||
// | Essential | O | O | ✓
|
||||
// | Filename | - | - | an opkg field, not a package field
|
||||
// | Homepage | e | e | ✓
|
||||
// | Installed-Size | O | O | ✓
|
||||
// | Installed-Time | - | - | an opkg field, not a package field
|
||||
// | License | e | e | ✓
|
||||
// | Maintainer | R | R | ✓
|
||||
// | MD5sum | - | - | insecure, not supported
|
||||
// | Package | R | R | ✓
|
||||
// | Pre-Depends | e | O | ✓
|
||||
// | Priority | R | R | ✓
|
||||
// | Provides | O | O | ✓
|
||||
// | Recommends | O | O | ✓
|
||||
// | Replaces | O | O | ✓
|
||||
// | Section | O | O | ✓
|
||||
// | SHA256sum | - | - | an opkg field, not a package field
|
||||
// | Size | - | - | an opkg field, not a package field
|
||||
// | Source | - | - | use the Fields field
|
||||
// | Status | - | - | an opkg state, not a package field
|
||||
// | Suggests | O | O | ✓
|
||||
// | Tags | O | O | ✓
|
||||
// | Vendor | e | e | ✓
|
||||
// | Version | R | R | ✓
|
||||
//
|
||||
// If any values in user supplied Fields are found to be duplicates of the above
|
||||
// fields, they will be stripped out.
|
||||
|
||||
// nolint: gochecknoglobals
|
||||
var controlFields = []string{
|
||||
"ABIVersion",
|
||||
"Alternatives",
|
||||
"Architecture",
|
||||
"Auto-Installed",
|
||||
"Conffiles",
|
||||
"Conflicts",
|
||||
"Depends",
|
||||
"Description",
|
||||
"Essential",
|
||||
"Filename",
|
||||
"Homepage",
|
||||
"Installed-Size",
|
||||
"Installed-Time",
|
||||
"License",
|
||||
"Maintainer",
|
||||
"MD5sum",
|
||||
"Package",
|
||||
"Pre-Depends",
|
||||
"Priority",
|
||||
"Provides",
|
||||
"Recommends",
|
||||
"Replaces",
|
||||
"Section",
|
||||
"SHA256sum",
|
||||
"Size",
|
||||
// "Source", Allowed
|
||||
"Status",
|
||||
"Suggests",
|
||||
"Tags",
|
||||
"Vendor",
|
||||
"Version",
|
||||
}
|
||||
|
||||
// stripDisallowedFields strips out any fields that are disallowed in the ipk
|
||||
// format, ignoring case.
|
||||
func stripDisallowedFields(info *nfpm.Info) {
|
||||
for key := range info.IPK.Fields {
|
||||
for _, disallowed := range controlFields {
|
||||
if strings.EqualFold(key, disallowed) {
|
||||
delete(info.IPK.Fields, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const controlTemplate = `
|
||||
{{- /* Mandatory fields */ -}}
|
||||
Architecture: {{.Info.Arch}}
|
||||
Description: {{multiline .Info.Description}}
|
||||
Maintainer: {{.Info.Maintainer}}
|
||||
Package: {{.Info.Name}}
|
||||
Priority: {{.Info.Priority}}
|
||||
Version: {{ if .Info.Epoch}}{{ .Info.Epoch }}:{{ end }}{{.Info.Version}}
|
||||
{{- if .Info.Prerelease}}~{{ .Info.Prerelease }}{{- end }}
|
||||
{{- if .Info.VersionMetadata}}+{{ .Info.VersionMetadata }}{{- end }}
|
||||
{{- if .Info.Release}}-{{ .Info.Release }}{{- end }}
|
||||
{{- /* Optional fields */ -}}
|
||||
{{- if .Info.IPK.ABIVersion}}
|
||||
ABIVersion: {{.Info.IPK.ABIVersion}}
|
||||
{{- end}}
|
||||
{{- if .Info.IPK.Alternatives}}
|
||||
Alternatives: {{ range $index, $element := .Info.IPK.Alternatives }}{{ if $index }}, {{end}}{{ $element.Priority }}:{{ $element.LinkName}}:{{ $element.Target}}{{- end }}
|
||||
{{- end}}
|
||||
{{- if .Info.IPK.AutoInstalled}}
|
||||
Auto-Installed: yes
|
||||
{{- end }}
|
||||
{{- with .Info.Conflicts}}
|
||||
Conflicts: {{join .}}
|
||||
{{- end }}
|
||||
{{- with .Info.Depends}}
|
||||
Depends: {{join .}}
|
||||
{{- end }}
|
||||
{{- if .Info.IPK.Essential}}
|
||||
Essential: yes
|
||||
{{- end }}
|
||||
{{- if .Info.Homepage}}
|
||||
Homepage: {{.Info.Homepage}}
|
||||
{{- end }}
|
||||
{{- if .Info.License}}
|
||||
License: {{.Info.License}}
|
||||
{{- end }}
|
||||
{{- if .InstalledSize }}
|
||||
Installed-Size: {{.InstalledSize}}
|
||||
{{- end }}
|
||||
{{- with .Info.IPK.Predepends}}
|
||||
Pre-Depends: {{join .}}
|
||||
{{- end }}
|
||||
{{- with nonEmpty .Info.Provides}}
|
||||
Provides: {{join .}}
|
||||
{{- end }}
|
||||
{{- with .Info.Recommends}}
|
||||
Recommends: {{join .}}
|
||||
{{- end }}
|
||||
{{- with .Info.Replaces}}
|
||||
Replaces: {{join .}}
|
||||
{{- end }}
|
||||
{{- if .Info.Section}}
|
||||
Section: {{.Info.Section}}
|
||||
{{- end }}
|
||||
{{- with .Info.Suggests}}
|
||||
Suggests: {{join .}}
|
||||
{{- end }}
|
||||
{{- with .Info.IPK.Tags}}
|
||||
Tags: {{join .}}
|
||||
{{- end }}
|
||||
{{- if .Info.Vendor}}
|
||||
Vendor: {{.Info.Vendor}}
|
||||
{{- end }}
|
||||
{{- range $key, $value := .Info.IPK.Fields }}
|
||||
{{- if $value }}
|
||||
{{$key}}: {{$value}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
type controlData struct {
|
||||
Info *nfpm.Info
|
||||
InstalledSize int64
|
||||
}
|
||||
|
||||
func renderControl(w io.Writer, data controlData) error {
|
||||
tmpl := template.New("control")
|
||||
tmpl.Funcs(template.FuncMap{
|
||||
"join": func(strs []string) string {
|
||||
return strings.Trim(strings.Join(strs, ", "), " ")
|
||||
},
|
||||
"multiline": func(strs string) string {
|
||||
var b strings.Builder
|
||||
s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(strs)))
|
||||
s.Scan()
|
||||
b.Write(bytes.TrimSpace(s.Bytes()))
|
||||
for s.Scan() {
|
||||
b.WriteString("\n ")
|
||||
l := bytes.TrimSpace(s.Bytes())
|
||||
if len(l) == 0 {
|
||||
b.WriteByte('.')
|
||||
} else {
|
||||
b.Write(l)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
},
|
||||
"nonEmpty": func(strs []string) []string {
|
||||
var result []string
|
||||
for _, s := range strs {
|
||||
s := strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, s)
|
||||
}
|
||||
return result
|
||||
},
|
||||
})
|
||||
return template.Must(tmpl.Parse(controlTemplate)).Execute(w, data)
|
||||
}
|
||||
+1035
File diff suppressed because it is too large
Load Diff
+108
@@ -0,0 +1,108 @@
|
||||
package ipk
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
)
|
||||
|
||||
// newTGZ creates a new tar.gz archive with the given name and populates it
|
||||
// with the given function.
|
||||
//
|
||||
// The function returns the bytes of the archive, its size and an error if any.
|
||||
func newTGZ(name string, populate func(*tar.Writer) error) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
gz := gzip.NewWriter(&buf)
|
||||
tarball := tar.NewWriter(gz)
|
||||
|
||||
// the writers are properly closed later, this is just in case that we error out
|
||||
defer gz.Close() // nolint: errcheck
|
||||
defer tarball.Close() // nolint: errcheck
|
||||
|
||||
if err := populate(tarball); err != nil {
|
||||
return nil, fmt.Errorf("cannot populate '%s': %w", name, err)
|
||||
}
|
||||
|
||||
if err := tarball.Close(); err != nil {
|
||||
return nil, fmt.Errorf("cannot close '%s': %w", name, err)
|
||||
}
|
||||
|
||||
if err := gz.Close(); err != nil {
|
||||
return nil, fmt.Errorf("cannot close '%s': %w", name, err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// writeFile writes a file from the filesystem to the tarball.
|
||||
func writeFile(out *tar.Writer, file *files.Content) (int64, error) {
|
||||
f, err := os.OpenFile(file.Source, os.O_RDONLY, 0o600) //nolint:gosec
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not open file %s to read and include in the archive: %w", file.Source, err)
|
||||
}
|
||||
defer f.Close() // nolint: errcheck
|
||||
|
||||
header, err := tar.FileInfoHeader(file, file.Source)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size := int64(len(content))
|
||||
|
||||
// tar.FileInfoHeader only uses file.Mode().Perm() which masks the mode with
|
||||
// 0o777 which we don't want because we want to be able to set the suid bit.
|
||||
header.Mode = int64(file.Mode())
|
||||
header.Format = tar.FormatGNU
|
||||
header.Name = files.AsExplicitRelativePath(file.Destination)
|
||||
header.Size = size
|
||||
header.Uname = file.FileInfo.Owner
|
||||
header.Gname = file.FileInfo.Group
|
||||
if err := out.WriteHeader(header); err != nil {
|
||||
return 0, fmt.Errorf("cannot write tar header for file %s to archive: %w", file.Source, err)
|
||||
}
|
||||
|
||||
n, err := out.Write(content)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: failed to copy: %w", file.Source, err)
|
||||
}
|
||||
|
||||
if int64(n) != size {
|
||||
return 0, fmt.Errorf("%s: failed to copy: expected %d bytes, copied %d", file.Source, size, n)
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// writeToFile writes a file to the tarball where the contents are an array of bytes.
|
||||
func writeToFile(out *tar.Writer, filename string, content []byte, mtime time.Time) error {
|
||||
header := tar.Header{
|
||||
Name: files.AsExplicitRelativePath(filename),
|
||||
Size: int64(len(content)),
|
||||
Mode: 0o644,
|
||||
ModTime: mtime,
|
||||
Typeflag: tar.TypeReg,
|
||||
Format: tar.FormatGNU,
|
||||
}
|
||||
|
||||
if err := out.WriteHeader(&header); err != nil {
|
||||
return fmt.Errorf("cannot write file header %s to archive: %w", header.Name, err)
|
||||
}
|
||||
|
||||
_, err := out.Write(content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot write file %s payload: %w", header.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package ipk
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_newTGZ(t *testing.T) {
|
||||
unknownErr := errors.New("unknown error")
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
name string
|
||||
populate func(*tar.Writer) error
|
||||
file string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
description: "simple",
|
||||
name: "simple.tar",
|
||||
populate: func(tw *tar.Writer) error {
|
||||
return writeToFile(tw, "simple.txt", []byte("hello, world"), time.Now())
|
||||
},
|
||||
file: "./simple.txt",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
got, err := newTGZ(tc.name, tc.populate)
|
||||
|
||||
if tc.expectedErr == nil {
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
|
||||
gz, err := gzip.NewReader(bytes.NewReader(got))
|
||||
require.NoError(err)
|
||||
require.NotNil(gz)
|
||||
defer gz.Close() // nolint: errcheck
|
||||
|
||||
assert.True(tarContains(t, gz, tc.file))
|
||||
return
|
||||
}
|
||||
|
||||
require.Error(err)
|
||||
if !errors.Is(tc.expectedErr, unknownErr) {
|
||||
assert.ErrorIs(err, tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func extractFileFromTar(tb testing.TB, tarFile []byte, filename string) []byte {
|
||||
tb.Helper()
|
||||
|
||||
tr := tar.NewReader(bytes.NewReader(tarFile))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break // End of archive
|
||||
}
|
||||
require.NoError(tb, err)
|
||||
|
||||
if path.Join("/", hdr.Name) != path.Join("/", filename) {
|
||||
continue
|
||||
}
|
||||
|
||||
fileContents, err := io.ReadAll(tr)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return fileContents
|
||||
}
|
||||
|
||||
tb.Fatalf("file %q does not exist in tar", filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tarContains(tb testing.TB, r io.Reader, filename string) bool {
|
||||
tb.Helper()
|
||||
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break // End of archive
|
||||
}
|
||||
require.NoError(tb, err)
|
||||
|
||||
if path.Join("/", hdr.Name) == path.Join("/", filename) { // nolint:gosec
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func tarContents(tb testing.TB, tarFile []byte) []string {
|
||||
tb.Helper()
|
||||
|
||||
contents := []string{}
|
||||
|
||||
tr := tar.NewReader(bytes.NewReader(tarFile))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break // End of archive
|
||||
}
|
||||
require.NoError(tb, err)
|
||||
|
||||
contents = append(contents, hdr.Name)
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
func getTree(tb testing.TB, tarFile []byte) []string {
|
||||
tb.Helper()
|
||||
|
||||
var result []string
|
||||
tr := tar.NewReader(bytes.NewReader(tarFile))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break // End of archive
|
||||
}
|
||||
require.NoError(tb, err)
|
||||
|
||||
result = append(result, hdr.Name)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func extractFileHeaderFromTar(tb testing.TB, tarFile []byte, filename string) *tar.Header {
|
||||
tb.Helper()
|
||||
|
||||
tr := tar.NewReader(bytes.NewReader(tarFile))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break // End of archive
|
||||
}
|
||||
require.NoError(tb, err)
|
||||
|
||||
if path.Join("/", hdr.Name) != path.Join("/", filename) { // nolint:gosec
|
||||
continue
|
||||
}
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
tb.Fatalf("file %q does not exist in tar", filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
Architecture: amd64
|
||||
Description: Foo does things
|
||||
Maintainer: Carlos A Becker <pkg@carlosbecker.com>
|
||||
Package: foo
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
Conflicts: zsh
|
||||
Depends: bash
|
||||
Homepage: http://carlosbecker.com
|
||||
Pre-Depends: less
|
||||
Recommends: git
|
||||
Replaces: svn
|
||||
Section: default
|
||||
Suggests: bash
|
||||
Vendor: nope
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
Architecture: amd64
|
||||
Description: Foo does things
|
||||
Maintainer: Carlos A Becker <pkg@carlosbecker.com>
|
||||
Package: foo
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
Conflicts: zsh
|
||||
Depends: bash
|
||||
Homepage: http://carlosbecker.com
|
||||
Installed-Size: 10
|
||||
Pre-Depends: less
|
||||
Provides: bzr
|
||||
Recommends: git
|
||||
Replaces: svn
|
||||
Section: default
|
||||
Suggests: bash
|
||||
Vendor: nope
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
Architecture: amd64
|
||||
Description: Foo does things
|
||||
Maintainer: Carlos A Becker <pkg@carlosbecker.com>
|
||||
Package: foo
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
Homepage: http://carlosbecker.com
|
||||
Installed-Size: 10
|
||||
Section: default
|
||||
Vendor: nope
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
Architecture: amd64
|
||||
Description: Foo does things
|
||||
Maintainer: Carlos A Becker <pkg@carlosbecker.com>
|
||||
Package: foo
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
Homepage: http://carlosbecker.com
|
||||
Installed-Size: 10
|
||||
Section: default
|
||||
Bugs: https://github.com/goreleaser/nfpm/issues
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
Architecture: amd64
|
||||
Description: Foo does things
|
||||
Maintainer: Carlos A Becker <pkg@carlosbecker.com>
|
||||
Package: foo
|
||||
Priority: extra
|
||||
Version: 1.0.0~beta+meta-2
|
||||
Homepage: http://carlosbecker.com
|
||||
Installed-Size: 10
|
||||
Section: default
|
||||
Vendor: nope
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
Architecture: amd64
|
||||
Description: Foo does things
|
||||
Maintainer: Carlos A Becker <pkg@carlosbecker.com>
|
||||
Package: foo
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
ABIVersion: 1
|
||||
Auto-Installed: yes
|
||||
Essential: yes
|
||||
Homepage: http://carlosbecker.com
|
||||
License: MIT
|
||||
Installed-Size: 10
|
||||
Section: default
|
||||
Bugs: https://github.com/goreleaser/nfpm/issues
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
Architecture: arm64
|
||||
Description: Minimal does nothing
|
||||
Maintainer: maintainer
|
||||
Package: minimal
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
Architecture: riscv64
|
||||
Description: This field is a
|
||||
multiline field
|
||||
.
|
||||
that should work.
|
||||
Maintainer: someone
|
||||
Package: multiline
|
||||
Priority: extra
|
||||
Version: 1.0.0
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
Template: templates/lala
|
||||
Type: string
|
||||
Description: Set lala for templates.
|
||||
For the test purpose this templates.golden is created.
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
Architecture: arm64
|
||||
Description: Has an epoch added to it's version
|
||||
Maintainer:
|
||||
Package: withepoch
|
||||
Priority: extra
|
||||
Version: 2:1.0.0
|
||||
Section: default
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -59,6 +60,22 @@ func Get(format string) (Packager, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Enumerate lists the available packagers
|
||||
func Enumerate() []string {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
list := make([]string, 0, len(packagers))
|
||||
for key := range packagers {
|
||||
if key != "" {
|
||||
list = append(list, key)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// Parse decodes YAML data from an io.Reader into a configuration struct.
|
||||
func Parse(in io.Reader) (config Config, err error) {
|
||||
return ParseWithEnvMapping(in, os.Getenv)
|
||||
@@ -252,6 +269,15 @@ func (c *Config) expandEnvVars() {
|
||||
c.Info.Deb.Fields[k] = os.Expand(v, c.envMappingFunc)
|
||||
}
|
||||
c.Info.Deb.Predepends = c.expandEnvVarsStringSlice(c.Info.Deb.Predepends)
|
||||
|
||||
// IPK specific
|
||||
for k, v := range c.Info.IPK.Fields {
|
||||
c.Info.IPK.Fields[k] = os.Expand(v, c.envMappingFunc)
|
||||
}
|
||||
c.Info.IPK.Predepends = c.expandEnvVarsStringSlice(c.Info.IPK.Predepends)
|
||||
|
||||
// RPM specific
|
||||
c.Info.RPM.Packager = os.Expand(c.RPM.Packager, c.envMappingFunc)
|
||||
}
|
||||
|
||||
// Info contains information about a single package.
|
||||
@@ -332,6 +358,7 @@ type Overridables struct {
|
||||
Deb Deb `yaml:"deb,omitempty" json:"deb,omitempty" jsonschema:"title=deb-specific settings"`
|
||||
APK APK `yaml:"apk,omitempty" json:"apk,omitempty" jsonschema:"title=apk-specific settings"`
|
||||
ArchLinux ArchLinux `yaml:"archlinux,omitempty" json:"archlinux,omitempty" jsonschema:"title=archlinux-specific settings"`
|
||||
IPK IPK `yaml:"ipk,omitempty" json:"ipk,omitempty" jsonschema:"title=ipk-specific settings"`
|
||||
}
|
||||
|
||||
type ArchLinux struct {
|
||||
@@ -440,6 +467,25 @@ type DebScripts struct {
|
||||
Config string `yaml:"config,omitempty" json:"config,omitempty" jsonschema:"title=config"`
|
||||
}
|
||||
|
||||
// IPK is custom configs that are only available on deb packages.
|
||||
type IPK struct {
|
||||
ABIVersion string `yaml:"abi_version,omitempty" json:"abi_version,omitempty" jsonschema:"title=abi version"`
|
||||
Alternatives []IPKAlternative `yaml:"alternatives,omitempty" json:"alternatives,omitempty" jsonschema:"title=alternatives"`
|
||||
Arch string `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in deb nomenclature"`
|
||||
AutoInstalled bool `yaml:"auto_installed,omitempty" json:"auto_installed,omitempty" jsonschema:"title=auto installed,default=false"`
|
||||
Essential bool `yaml:"essential,omitempty" json:"essential,omitempty" jsonschema:"title=whether package is essential,default=false"`
|
||||
Fields map[string]string `yaml:"fields,omitempty" json:"fields,omitempty" jsonschema:"title=fields"`
|
||||
Predepends []string `yaml:"predepends,omitempty" json:"predepends,omitempty" jsonschema:"title=predepends directive,example=nfpm"`
|
||||
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty" jsonschema:"title=tags"`
|
||||
}
|
||||
|
||||
// IPKAlternative represents an alternative for an IPK package.
|
||||
type IPKAlternative struct {
|
||||
Priority int `yaml:"priority,omitempty" json:"priority,omitempty" jsonschema:"title=priority"`
|
||||
Target string `yaml:"target,omitempty" json:"target,omitempty" jsonschema:"title=target"`
|
||||
LinkName string `yaml:"link_name,omitempty" json:"link_name,omitempty" jsonschema:"title=link name"`
|
||||
}
|
||||
|
||||
// Scripts contains information about maintainer scripts for packages.
|
||||
type Scripts struct {
|
||||
PreInstall string `yaml:"preinstall,omitempty" json:"preinstall,omitempty" jsonschema:"title=pre install"`
|
||||
|
||||
@@ -299,6 +299,7 @@ func TestParseFile(t *testing.T) {
|
||||
nfpm.RegisterPackager("deb", &fakePackager{})
|
||||
nfpm.RegisterPackager("rpm", &fakePackager{})
|
||||
nfpm.RegisterPackager("apk", &fakePackager{})
|
||||
nfpm.RegisterPackager("ipk", &fakePackager{})
|
||||
_, err = parseAndValidate("./testdata/overrides.yaml")
|
||||
require.NoError(t, err)
|
||||
_, err = parseAndValidate("./testdata/doesnotexist.yaml")
|
||||
|
||||
+6
@@ -62,6 +62,12 @@ contents:
|
||||
file_info:
|
||||
mode: 04755
|
||||
|
||||
- packager: ipk
|
||||
src: ./testdata/fake
|
||||
dst: /usr/bin/fake2
|
||||
file_info:
|
||||
mode: 04755
|
||||
|
||||
scripts:
|
||||
preinstall: ./testdata/acceptance/scripts/preinstall.sh
|
||||
postinstall: ./testdata/acceptance/scripts/postinstall.sh
|
||||
|
||||
+7
@@ -49,3 +49,10 @@ overrides:
|
||||
scripts:
|
||||
postinstall: ./testdata/acceptance/scripts/postinstall.sh
|
||||
preremove: ./testdata/acceptance/scripts/preremove.sh
|
||||
ipk:
|
||||
depends:
|
||||
- bash
|
||||
- fish
|
||||
scripts:
|
||||
postinstall: ./testdata/acceptance/scripts/postinstall.sh
|
||||
preremove: ./testdata/acceptance/scripts/preremove.sh
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+18
@@ -0,0 +1,18 @@
|
||||
name: "foo"
|
||||
arch: "all"
|
||||
platform: "linux"
|
||||
version: "v1.0.0"
|
||||
maintainer: "John Doe <john@example.com>"
|
||||
description: Foo conflicts dummy
|
||||
contents:
|
||||
- src: ./testdata/fake
|
||||
dst: /usr/bin/fake
|
||||
ipk:
|
||||
alternatives:
|
||||
- priority: 100
|
||||
target: /usr/bin/fake
|
||||
link_name: /usr/bin/bar
|
||||
|
||||
- link_name: /usr/bin/baz
|
||||
target: /usr/bin/fake
|
||||
priority: 200
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
name: "foo"
|
||||
arch: "all"
|
||||
platform: "linux"
|
||||
version: "v1.0.0"
|
||||
maintainer: "John Doe <john@example.com>"
|
||||
description: Foo conflicts dummy
|
||||
conflicts:
|
||||
- dummy
|
||||
Vendored
+172
@@ -0,0 +1,172 @@
|
||||
FROM openwrt/rootfs AS test_base
|
||||
ARG package
|
||||
#ARG CACHEBUST=1
|
||||
RUN mkdir -p /var/lock && \
|
||||
mkdir -p /var/run && \
|
||||
mkdir -p /tmp
|
||||
RUN echo "${package}"
|
||||
COPY ${package} /tmp/foo.ipk
|
||||
|
||||
# ---- minimal test ----
|
||||
FROM test_base AS min
|
||||
RUN opkg install /tmp/foo.ipk
|
||||
|
||||
|
||||
# ---- symlink test ----
|
||||
FROM min AS symlink
|
||||
RUN ls -l /path/to/symlink | grep "/path/to/symlink -> /etc/foo/whatever.conf"
|
||||
|
||||
|
||||
# ---- simple test ----
|
||||
FROM min AS simple
|
||||
RUN test -e /usr/bin/fake
|
||||
RUN test -f /etc/foo/whatever.conf
|
||||
RUN echo wat >> /etc/foo/whatever.conf
|
||||
RUN opkg remove foo
|
||||
RUN test -f /etc/foo/whatever.conf
|
||||
RUN test ! -f /usr/bin/fake
|
||||
|
||||
|
||||
# ---- no-glob test ----
|
||||
FROM min AS no-glob
|
||||
RUN test -d /usr/share/whatever/
|
||||
RUN test -f /usr/share/whatever/file1
|
||||
RUN test -f /usr/share/whatever/file2
|
||||
RUN test -d /usr/share/whatever/folder2
|
||||
RUN test -f /usr/share/whatever/folder2/file1
|
||||
RUN test -f /usr/share/whatever/folder2/file2
|
||||
|
||||
|
||||
# ---- complex test ----
|
||||
FROM test_base AS complex
|
||||
RUN opkg install coreutils-stat
|
||||
RUN test "$(opkg status fish)" = ""
|
||||
RUN opkg install /tmp/foo.ipk > install.log
|
||||
RUN opkg depends foo | grep "bash"
|
||||
RUN cat install.log | grep "package foo suggests installing zsh"
|
||||
RUN test "$(opkg status fish)" != ""
|
||||
RUN opkg info foo | grep "Provides: fake"
|
||||
RUN test -e /usr/bin/fake
|
||||
RUN test -f /etc/foo/whatever.conf
|
||||
RUN test -d /usr/share/whatever/
|
||||
RUN test -d /usr/share/whatever/folder
|
||||
RUN test -f /usr/share/whatever/folder/file1
|
||||
RUN test -f /usr/share/whatever/folder/file2
|
||||
RUN test -d /usr/share/whatever/folder/folder2
|
||||
RUN test -f /usr/share/whatever/folder/folder2/file1
|
||||
RUN test -f /usr/share/whatever/folder/folder2/file2
|
||||
RUN test -d /var/log/whatever
|
||||
RUN test -d /usr/share/foo
|
||||
RUN test -d /usr/foo/bar/something
|
||||
RUN test -d /etc/something
|
||||
RUN test -f /etc/something/a
|
||||
RUN test -f /etc/something/b
|
||||
RUN test -d /etc/something/c
|
||||
RUN test -f /etc/something/c/d
|
||||
RUN test $(stat -c %a /usr/bin/fake2) -eq 4755
|
||||
RUN test -f /tmp/preinstall-proof
|
||||
RUN test -f /tmp/postinstall-proof
|
||||
RUN test ! -f /tmp/preremove-proof
|
||||
RUN test ! -f /tmp/postremove-proof
|
||||
RUN echo wat >> /etc/foo/whatever.conf
|
||||
RUN opkg remove foo
|
||||
RUN test -f /etc/foo/whatever.conf
|
||||
RUN test ! -f /usr/bin/fake
|
||||
RUN test ! -f /usr/bin/fake2
|
||||
RUN test -f /tmp/preremove-proof
|
||||
RUN test -f /tmp/postremove-proof
|
||||
|
||||
# ---- overrides test ----
|
||||
FROM min AS overrides
|
||||
RUN test -e /usr/bin/fake
|
||||
RUN test -f /etc/foo/whatever.conf
|
||||
RUN test ! -f /tmp/preinstall-proof
|
||||
RUN test -f /tmp/postinstall-proof
|
||||
RUN test ! -f /tmp/preremove-proof
|
||||
RUN test ! -f /tmp/postremove-proof
|
||||
RUN echo wat >> /etc/foo/whatever.conf
|
||||
RUN opkg remove foo
|
||||
RUN test -f /etc/foo/whatever.conf
|
||||
RUN test ! -f /usr/bin/fake
|
||||
RUN test -f /tmp/preremove-proof
|
||||
RUN test ! -f /tmp/postremove-proof
|
||||
|
||||
# ---- meta test ----
|
||||
FROM test_base AS meta
|
||||
RUN opkg install /tmp/foo.ipk
|
||||
RUN command -v zsh
|
||||
|
||||
# ---- env-var-version test ----
|
||||
FROM min AS env-var-version
|
||||
ENV EXPECTVER="Version: 1.0.0~0.1.b1+git.abcdefgh"
|
||||
RUN opkg info foo | grep "Version" > found
|
||||
RUN export FOUND_VER="$(cat found)" && \
|
||||
echo "Expected: '${EXPECTVER}' :: Found: '${FOUND_VER}'" && \
|
||||
test "${FOUND_VER}" = "${EXPECTVER}"
|
||||
|
||||
# ---- changelog test ----
|
||||
FROM test_base AS withchangelog
|
||||
|
||||
# ---- signed test ----
|
||||
FROM test_base AS signed
|
||||
|
||||
# ----- IPK Specific Tests -----
|
||||
|
||||
# ---- alternatives test ----
|
||||
FROM test_base AS alternatives
|
||||
RUN test ! -e /usr/bin/foo
|
||||
RUN test ! -e /usr/bin/bar
|
||||
RUN test ! -e /usr/bin/baz
|
||||
RUN opkg install /tmp/foo.ipk
|
||||
RUN test -e /usr/bin/fake
|
||||
RUN test -e /usr/bin/bar
|
||||
RUN test -e /usr/bin/baz
|
||||
|
||||
# ---- conflicts test ----
|
||||
FROM test_base AS conflicts
|
||||
COPY dummy.ipk /tmp/dummy.ipk
|
||||
# install dummy package
|
||||
RUN opkg install /tmp/dummy.ipk
|
||||
# make sure foo can't be installed
|
||||
RUN opkg install /tmp/foo.ipk 2>&1 | grep "Cannot install package foo"
|
||||
# make sure foo can be installed if dummy is not installed
|
||||
RUN opkg remove dummy
|
||||
RUN opkg install /tmp/foo.ipk
|
||||
|
||||
# ---- predepends test ----
|
||||
FROM test_base AS predepends
|
||||
COPY dummy.ipk /tmp/dummy.ipk
|
||||
RUN opkg install /tmp/foo.ipk 2>&1 | grep "cannot find dependency dummy for foo"
|
||||
RUN opkg install /tmp/dummy.ipk
|
||||
RUN opkg install /tmp/foo.ipk
|
||||
|
||||
|
||||
# ---- upgrade test ----
|
||||
FROM test_base AS upgrade
|
||||
ARG oldpackage
|
||||
RUN echo "${oldpackage}"
|
||||
COPY ${oldpackage} /tmp/old_foo.ipk
|
||||
RUN opkg install /tmp/old_foo.ipk
|
||||
|
||||
RUN test -f /tmp/preinstall-proof
|
||||
RUN cat /tmp/preinstall-proof | grep "Install"
|
||||
|
||||
RUN test -f /tmp/postinstall-proof
|
||||
RUN cat /tmp/postinstall-proof | grep "Install"
|
||||
|
||||
# The upgrade process doesn't allow a local upgrade.
|
||||
RUN opkg install /tmp/foo.ipk
|
||||
|
||||
RUN test -f /tmp/preremove-proof
|
||||
RUN cat /tmp/preremove-proof | grep "Upgrade"
|
||||
|
||||
RUN test -f /tmp/postremove-proof
|
||||
RUN cat /tmp/postremove-proof | grep "Upgrade"
|
||||
|
||||
RUN test -f /tmp/preinstall-proof
|
||||
RUN cat /tmp/preinstall-proof | grep "Upgrade"
|
||||
|
||||
# The upgrade process doesn't allow a local upgrade,
|
||||
# so the following test will fail.
|
||||
#RUN test -d /tmp/postinstall-proof
|
||||
#RUN cat /tmp/postinstall-proof | grep "Upgrade"
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
name: "foo"
|
||||
arch: "all"
|
||||
platform: "linux"
|
||||
version: "v1.0.0"
|
||||
maintainer: "John Doe <john@example.com>"
|
||||
description: Foo breaks dummy
|
||||
ipk:
|
||||
predepends:
|
||||
- dummy
|
||||
Reference in New Issue
Block a user