Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 13 additions & 46 deletions pkg/apk/apk/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import (
"go.step.sm/crypto/jose"
"golang.org/x/sync/errgroup"
"golang.org/x/sys/unix"
"gopkg.in/ini.v1"

"chainguard.dev/apko/internal/tarfs"
"chainguard.dev/apko/pkg/apk/auth"
Expand Down Expand Up @@ -783,13 +782,14 @@ func (a *APK) InstallPackages(ctx context.Context, sourceDateEpoch *time.Time, a
}

// The data in .PKGINFO is more complete than what is in APKINDEX.
pkgInfo, err := packageInfo(exp)
pkgInfo, err := exp.PkgInfo()
if err != nil {
return fmt.Errorf("failed to read .PKGINFO for %s: %w", pkg, err)
}
infos[i] = pkgInfo
asPackage := pkgInfo.AsPackage(exp.ControlHash, uint64(exp.Size))
infos[i] = asPackage

installedFiles, err := a.installPackage(ctx, pkgInfo, exp, sourceDateEpoch)
installedFiles, err := a.installPackage(ctx, asPackage, exp, sourceDateEpoch)
if err != nil {
return fmt.Errorf("installing %s: %w", pkg, err)
}
Expand Down Expand Up @@ -1223,12 +1223,12 @@ func (a *APK) cachedPackage(ctx context.Context, pkg InstallablePackage, cacheDi
exp.SignatureHash = signatureHash[:]
}

datahash, err := a.datahash(exp.ControlFS)
pkgInfo, err := exp.PkgInfo()
if err != nil {
return nil, fmt.Errorf("datahash for %s: %w", pkg, err)
return nil, fmt.Errorf("reading pkginfo from %s: %w", pkg, err)
}

dat := filepath.Join(cacheDir, datahash+".dat.tar.gz")
dat := filepath.Join(cacheDir, pkgInfo.DataHash+".dat.tar.gz")
df, err := os.Stat(dat)
if err != nil {
return nil, err
Expand All @@ -1237,7 +1237,7 @@ func (a *APK) cachedPackage(ctx context.Context, pkg InstallablePackage, cacheDi
exp.PackageSize = df.Size()
exp.Size += df.Size()

exp.PackageHash, err = hex.DecodeString(datahash)
exp.PackageHash, err = hex.DecodeString(pkgInfo.DataHash)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1411,30 +1411,6 @@ type WriteHeaderer interface {
WriteHeader(hdr tar.Header, tfs fs.FS, pkg *Package) (bool, error)
}

func packageInfo(exp *expandapk.APKExpanded) (*Package, error) {
f, err := exp.ControlFS.Open(".PKGINFO")
if err != nil {
return nil, fmt.Errorf("opening .PKGINFO in %s: %w", exp.ControlFile, err)
}
defer f.Close()

cfg, err := ini.ShadowLoad(f)
if err != nil {
return nil, fmt.Errorf("ini.ShadowLoad(): %w", err)
}

pkg := new(Package)
if err = cfg.MapTo(pkg); err != nil {
return nil, fmt.Errorf("cfg.MapTo(): %w", err)
}
pkg.BuildTime = time.Unix(pkg.BuildDate, 0).UTC()
pkg.InstalledSize = pkg.Size
pkg.Size = uint64(exp.Size)
pkg.Checksum = exp.ControlHash

return pkg, nil
}

// installPackage installs a single package and updates installed db.
func (a *APK) installPackage(ctx context.Context, pkg *Package, expanded *expandapk.APKExpanded, sourceDateEpoch *time.Time) ([]tar.Header, error) {
log := clog.FromContext(ctx)
Expand Down Expand Up @@ -1484,24 +1460,15 @@ func (a *APK) installPackage(ctx context.Context, pkg *Package, expanded *expand
}

// update the triggers
if err := a.updateTriggers(pkg, expanded.ControlFS); err != nil {
return nil, fmt.Errorf("unable to update triggers for pkg %s: %w", pkg.Name, err)
}

return installedFiles, nil
}

func (a *APK) datahash(controlFS fs.FS) (string, error) {
values, err := a.controlValue(controlFS, "datahash")
pkgInfo, err := expanded.PkgInfo()
if err != nil {
return "", fmt.Errorf("reading datahash from control: %w", err)
return nil, fmt.Errorf("reading pkginfo from %s: %w", pkg.Name, err)
}

if len(values) != 1 {
return "", fmt.Errorf("saw %d datahash values", len(values))
if err := a.updateTriggers(pkg, pkgInfo.Triggers); err != nil {
return nil, fmt.Errorf("unable to update triggers for pkg %s: %w", pkg.Name, err)
}

return values[0], nil
return installedFiles, nil
}

func packageRefs(pkgs []*RepositoryPackage) []string {
Expand Down
22 changes: 1 addition & 21 deletions pkg/apk/apk/installed.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -190,33 +189,14 @@ func (a *APK) readScriptsTar() (io.ReadCloser, error) {
return a.fs.Open(scriptsFilePath)
}

// TODO: We should probably parse control section on the first pass and reuse it.
func (a *APK) controlValue(controlFs fs.FS, want string) ([]string, error) {
mapping, err := controlValue(controlFs, want)
if err != nil {
return nil, err
}

values, ok := mapping[want]
if !ok {
return []string{}, nil
}
return values, nil
}

// updateTriggers insert the triggers into the triggers file
func (a *APK) updateTriggers(pkg *Package, controlFs fs.FS) error {
func (a *APK) updateTriggers(pkg *Package, values []string) error {
triggers, err := a.fs.OpenFile(triggersFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0)
if err != nil {
return fmt.Errorf("unable to open triggers file %s: %w", triggersFilePath, err)
}
defer triggers.Close()

values, err := a.controlValue(controlFs, "triggers")
if err != nil {
return fmt.Errorf("updating triggers for %s: %w", pkg.Name, err)
}

for _, value := range values {
if _, err := fmt.Fprintf(triggers, "Q1%s %s\n", base64.StdEncoding.EncodeToString(pkg.Checksum), value); err != nil {
return fmt.Errorf("unable to write triggers file %s: %w", triggersFilePath, err)
Expand Down
32 changes: 1 addition & 31 deletions pkg/apk/apk/installed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"chainguard.dev/apko/internal/tarfs"
"chainguard.dev/apko/pkg/apk/expandapk"
)

Expand Down Expand Up @@ -433,38 +432,9 @@ func TestUpdateTriggers(t *testing.T) {
Version: "1.0.0",
Checksum: randBytes,
}
// this is not a fully valid PKGINFO file by any stretch, but for now it is sufficient
triggers := "/bin /usr/bin /foo /bar/*"
pkginfo := strings.Join([]string{
fmt.Sprintf("pkgname = %s", pkg.Name),
fmt.Sprintf("pkgver = %s", pkg.Version),
fmt.Sprintf("triggers = %s", triggers),
}, "\n")
// construct the controlTarGz
scripts := map[string][]byte{
".pre-install": []byte("echo 'pre install'"),
".post-install": []byte("echo 'post install'"),
".pre-upgrade": []byte("echo 'pre upgrade'"),
".post-upgrade": []byte("echo 'post upgrade'"),
".PKGINFO": []byte(pkginfo),
}
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
for name, content := range scripts {
_ = tw.WriteHeader(&tar.Header{
Name: name,
Mode: 0o644,
Size: int64(len(content)),
})
_, _ = tw.Write(content)
}
tw.Close()

// pass the controltargz to updateScriptsTar
r := bytes.NewReader(buf.Bytes())
fs, err := tarfs.New(r, int64(buf.Len()))
require.NoError(t, err, "unable to create tarfs: %v", err)
err = a.updateTriggers(pkg, fs)
err = a.updateTriggers(pkg, []string{triggers})
require.NoError(t, err, "unable to update triggers: %v", err)

// successfully wrote it; not check that it was written correctly
Expand Down
72 changes: 5 additions & 67 deletions pkg/apk/apk/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ import (
"compress/gzip"
"context"
"crypto/sha1" //nolint:gosec // this is what apk tools is using
"encoding/base64"
"fmt"
"hash"
"io"
"strings"
"time"

"gopkg.in/ini.v1"

"chainguard.dev/apko/pkg/apk/expandapk"
"chainguard.dev/apko/pkg/apk/types"
)

// PackageToInstalled takes a Package and returns it as the string representation of lines in a /usr/lib/apk/db/installed file.
Expand Down Expand Up @@ -102,66 +100,11 @@ type InstallablePackage interface {
}

// PackageInfo represents the information present in .PKGINFO.
type PackageInfo struct {
Name string `ini:"pkgname"`
Version string `ini:"pkgver"`
Arch string `ini:"arch"`
Description string `ini:"pkgdesc"`
License string `ini:"license"`
Origin string `ini:"origin"`
Maintainer string `ini:"maintainer"`
URL string `ini:"url"`
Dependencies []string `ini:"depend,,allowshadow"`
Provides []string `ini:"provides,,allowshadow"`
InstallIf []string `ini:"install_if,,allowshadow"`
Size uint64 `ini:"size"`
ProviderPriority uint64 `ini:"provider_priority"`
BuildDate int64 `ini:"builddate"`
RepoCommit string `ini:"commit"`
Replaces []string `ini:"replaces,,allowshadow"`
DataHash string `ini:"datahash"`
}
type PackageInfo = types.PackageInfo

// Package represents a single package with the information present in an
// APKINDEX.
type Package struct {
Name string `ini:"pkgname"`
Version string `ini:"pkgver"`
Arch string `ini:"arch"`
Description string `ini:"pkgdesc"`
License string `ini:"license"`
Origin string `ini:"origin"`
Maintainer string `ini:"maintainer"`
URL string `ini:"url"`
Checksum []byte
Dependencies []string `ini:"depend,,allowshadow"`
Provides []string `ini:"provides,,allowshadow"`
InstallIf []string
Size uint64 `ini:"size"`
InstalledSize uint64
ProviderPriority uint64 `ini:"provider_priority"`
BuildTime time.Time
BuildDate int64 `ini:"builddate"`
RepoCommit string `ini:"commit"`
Replaces []string `ini:"replaces,,allowshadow"`
DataHash string `ini:"datahash"`
}

func (p *Package) String() string {
return fmt.Sprintf("%s (ver:%s arch:%s)", p.Name, p.Version, p.Arch)
}
func (p *Package) PackageName() string { return p.Name }

// Filename returns the package filename as it's named in a repository.
func (p *Package) Filename() string {
// Note: Doesn't use fmt.Sprintf because we call this a lot when we disqualify images.
return p.Name + "-" + p.Version + ".apk"
}

// ChecksumString returns a human-readable version of the control section checksum.
func (p *Package) ChecksumString() string {
return "Q1" + base64.StdEncoding.EncodeToString(p.Checksum)
}
type Package = types.Package

// ParsePackage parses a .apk file and returns a Package struct
func ParsePackage(ctx context.Context, apkPackage io.Reader, size uint64) (*Package, error) {
Expand Down Expand Up @@ -229,14 +172,9 @@ func ParsePackageInfo(apkPackage io.Reader) (*PackageInfo, hash.Hash, error) {
}

if hdr.Name == ".PKGINFO" {
cfg, err := ini.ShadowLoad(tr)
pkg, err := types.ParsePackageInfo(tr)
if err != nil {
return nil, nil, fmt.Errorf("ini.ShadowLoad(): %w", err)
}

pkg := new(PackageInfo)
if err = cfg.MapTo(pkg); err != nil {
return nil, nil, fmt.Errorf("cfg.MapTo(): %w", err)
return nil, nil, fmt.Errorf("parsing .PKGINFO: %w", err)
}

return pkg, h, nil
Expand Down
48 changes: 0 additions & 48 deletions pkg/apk/apk/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,6 @@

package apk

import (
"errors"
"fmt"
"io"
"io/fs"
"slices"
"strings"
)

func uniqify[T comparable](s []T) []T {
seen := make(map[T]struct{}, len(s))
uniq := make([]T, 0, len(s))
Expand All @@ -37,42 +28,3 @@ func uniqify[T comparable](s []T) []T {

return uniq
}

func controlValue(controlFs fs.FS, want ...string) (map[string][]string, error) {
f, err := controlFs.Open(".PKGINFO")
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("control file not found")
}
return nil, fmt.Errorf("opening .PKGINFO: %w", err)
}
defer f.Close()

b, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("unable to read .PKGINFO from control tar.gz file: %w", err)
}
mapping := map[string][]string{}
lines := strings.SplitSeq(string(b), "\n")
for line := range lines {
parts := strings.Split(line, "=")
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
if !slices.Contains(want, key) {
continue
}

values, ok := mapping[key]
if !ok {
values = []string{}
}

value := strings.TrimSpace(parts[1])
values = append(values, value)

mapping[key] = values
}
return mapping, nil
}
Loading
Loading