Skip to content

Commit 5c46ecf

Browse files
Parse PKGINFO once and reuse parsed structure everywhere
Instead of reparsing PKGINFO from the control filesystem over and over again, this does it once and as a tested helper on APKExpanded. Behavior is the same as before with the difference of using `SplitN` over `Split` to not have a buggy example.
1 parent f5fefa0 commit 5c46ecf

File tree

6 files changed

+172
-116
lines changed

6 files changed

+172
-116
lines changed

pkg/apk/apk/implementation.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,10 +1223,15 @@ func (a *APK) cachedPackage(ctx context.Context, pkg InstallablePackage, cacheDi
12231223
exp.SignatureHash = signatureHash[:]
12241224
}
12251225

1226-
datahash, err := a.datahash(exp.ControlFS)
1226+
pkgInfo, err := exp.PkgInfo()
12271227
if err != nil {
1228-
return nil, fmt.Errorf("datahash for %s: %w", pkg, err)
1228+
return nil, fmt.Errorf("reading pkginfo from %s: %w", pkg, err)
12291229
}
1230+
datahashVals := pkgInfo["datahash"]
1231+
if len(datahashVals) != 1 {
1232+
return nil, fmt.Errorf("saw %d datahash values", len(datahashVals))
1233+
}
1234+
datahash := datahashVals[0]
12301235

12311236
dat := filepath.Join(cacheDir, datahash+".dat.tar.gz")
12321237
df, err := os.Stat(dat)
@@ -1484,24 +1489,15 @@ func (a *APK) installPackage(ctx context.Context, pkg *Package, expanded *expand
14841489
}
14851490

14861491
// update the triggers
1487-
if err := a.updateTriggers(pkg, expanded.ControlFS); err != nil {
1488-
return nil, fmt.Errorf("unable to update triggers for pkg %s: %w", pkg.Name, err)
1489-
}
1490-
1491-
return installedFiles, nil
1492-
}
1493-
1494-
func (a *APK) datahash(controlFS fs.FS) (string, error) {
1495-
values, err := a.controlValue(controlFS, "datahash")
1492+
pkgInfo, err := expanded.PkgInfo()
14961493
if err != nil {
1497-
return "", fmt.Errorf("reading datahash from control: %w", err)
1494+
return nil, fmt.Errorf("reading pkginfo from %s: %w", pkg.Name, err)
14981495
}
1499-
1500-
if len(values) != 1 {
1501-
return "", fmt.Errorf("saw %d datahash values", len(values))
1496+
if err := a.updateTriggers(pkg, pkgInfo["triggers"]); err != nil {
1497+
return nil, fmt.Errorf("unable to update triggers for pkg %s: %w", pkg.Name, err)
15021498
}
15031499

1504-
return values[0], nil
1500+
return installedFiles, nil
15051501
}
15061502

15071503
func packageRefs(pkgs []*RepositoryPackage) []string {

pkg/apk/apk/installed.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"errors"
2323
"fmt"
2424
"io"
25-
"io/fs"
2625
"os"
2726
"path/filepath"
2827
"sort"
@@ -190,33 +189,14 @@ func (a *APK) readScriptsTar() (io.ReadCloser, error) {
190189
return a.fs.Open(scriptsFilePath)
191190
}
192191

193-
// TODO: We should probably parse control section on the first pass and reuse it.
194-
func (a *APK) controlValue(controlFs fs.FS, want string) ([]string, error) {
195-
mapping, err := controlValue(controlFs, want)
196-
if err != nil {
197-
return nil, err
198-
}
199-
200-
values, ok := mapping[want]
201-
if !ok {
202-
return []string{}, nil
203-
}
204-
return values, nil
205-
}
206-
207192
// updateTriggers insert the triggers into the triggers file
208-
func (a *APK) updateTriggers(pkg *Package, controlFs fs.FS) error {
193+
func (a *APK) updateTriggers(pkg *Package, values []string) error {
209194
triggers, err := a.fs.OpenFile(triggersFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0)
210195
if err != nil {
211196
return fmt.Errorf("unable to open triggers file %s: %w", triggersFilePath, err)
212197
}
213198
defer triggers.Close()
214199

215-
values, err := a.controlValue(controlFs, "triggers")
216-
if err != nil {
217-
return fmt.Errorf("updating triggers for %s: %w", pkg.Name, err)
218-
}
219-
220200
for _, value := range values {
221201
if _, err := fmt.Fprintf(triggers, "Q1%s %s\n", base64.StdEncoding.EncodeToString(pkg.Checksum), value); err != nil {
222202
return fmt.Errorf("unable to write triggers file %s: %w", triggersFilePath, err)

pkg/apk/apk/installed_test.go

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import (
3535
"github.com/stretchr/testify/assert"
3636
"github.com/stretchr/testify/require"
3737

38-
"chainguard.dev/apko/internal/tarfs"
3938
"chainguard.dev/apko/pkg/apk/expandapk"
4039
)
4140

@@ -433,38 +432,10 @@ func TestUpdateTriggers(t *testing.T) {
433432
Version: "1.0.0",
434433
Checksum: randBytes,
435434
}
436-
// this is not a fully valid PKGINFO file by any stretch, but for now it is sufficient
437435
triggers := "/bin /usr/bin /foo /bar/*"
438-
pkginfo := strings.Join([]string{
439-
fmt.Sprintf("pkgname = %s", pkg.Name),
440-
fmt.Sprintf("pkgver = %s", pkg.Version),
441-
fmt.Sprintf("triggers = %s", triggers),
442-
}, "\n")
443-
// construct the controlTarGz
444-
scripts := map[string][]byte{
445-
".pre-install": []byte("echo 'pre install'"),
446-
".post-install": []byte("echo 'post install'"),
447-
".pre-upgrade": []byte("echo 'pre upgrade'"),
448-
".post-upgrade": []byte("echo 'post upgrade'"),
449-
".PKGINFO": []byte(pkginfo),
450-
}
451-
var buf bytes.Buffer
452-
tw := tar.NewWriter(&buf)
453-
for name, content := range scripts {
454-
_ = tw.WriteHeader(&tar.Header{
455-
Name: name,
456-
Mode: 0o644,
457-
Size: int64(len(content)),
458-
})
459-
_, _ = tw.Write(content)
460-
}
461-
tw.Close()
462436

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

470441
// successfully wrote it; not check that it was written correctly

pkg/apk/apk/util.go

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,6 @@
1414

1515
package apk
1616

17-
import (
18-
"errors"
19-
"fmt"
20-
"io"
21-
"io/fs"
22-
"slices"
23-
"strings"
24-
)
25-
2617
func uniqify[T comparable](s []T) []T {
2718
seen := make(map[T]struct{}, len(s))
2819
uniq := make([]T, 0, len(s))
@@ -37,42 +28,3 @@ func uniqify[T comparable](s []T) []T {
3728

3829
return uniq
3930
}
40-
41-
func controlValue(controlFs fs.FS, want ...string) (map[string][]string, error) {
42-
f, err := controlFs.Open(".PKGINFO")
43-
if err != nil {
44-
if errors.Is(err, fs.ErrNotExist) {
45-
return nil, fmt.Errorf("control file not found")
46-
}
47-
return nil, fmt.Errorf("opening .PKGINFO: %w", err)
48-
}
49-
defer f.Close()
50-
51-
b, err := io.ReadAll(f)
52-
if err != nil {
53-
return nil, fmt.Errorf("unable to read .PKGINFO from control tar.gz file: %w", err)
54-
}
55-
mapping := map[string][]string{}
56-
lines := strings.SplitSeq(string(b), "\n")
57-
for line := range lines {
58-
parts := strings.Split(line, "=")
59-
if len(parts) != 2 {
60-
continue
61-
}
62-
key := strings.TrimSpace(parts[0])
63-
if !slices.Contains(want, key) {
64-
continue
65-
}
66-
67-
values, ok := mapping[key]
68-
if !ok {
69-
values = []string{}
70-
}
71-
72-
value := strings.TrimSpace(parts[1])
73-
values = append(values, value)
74-
75-
mapping[key] = values
76-
}
77-
return mapping, nil
78-
}

pkg/apk/expandapk/expandapk.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,45 @@ type APKExpanded struct {
102102
SignatureSize int64
103103

104104
sync.Mutex
105-
controlData []byte
105+
parsedPkgInfo map[string][]string
106+
controlData []byte
107+
}
108+
109+
// PkgInfo parses and returns the .PKGINFO file as a map of keys to values.
110+
func (a *APKExpanded) PkgInfo() (map[string][]string, error) {
111+
a.Lock()
112+
defer a.Unlock()
113+
if a.parsedPkgInfo != nil {
114+
return a.parsedPkgInfo, nil
115+
}
116+
117+
f, err := a.ControlFS.Open(".PKGINFO")
118+
if err != nil {
119+
return nil, fmt.Errorf("opening .PKGINFO: %w", err)
120+
}
121+
defer f.Close()
122+
123+
scanner := bufio.NewScanner(f)
124+
mapping := make(map[string][]string)
125+
for scanner.Scan() {
126+
line := scanner.Text()
127+
parts := strings.SplitN(line, "=", 2)
128+
if len(parts) != 2 {
129+
continue
130+
}
131+
key := strings.TrimSpace(parts[0])
132+
value := strings.TrimSpace(parts[1])
133+
134+
values := mapping[key]
135+
values = append(values, value)
136+
mapping[key] = values
137+
}
138+
if err := scanner.Err(); err != nil {
139+
return nil, fmt.Errorf("reading .PKGINFO: %w", err)
140+
}
141+
142+
a.parsedPkgInfo = mapping
143+
return a.parsedPkgInfo, nil
106144
}
107145

108146
func (a *APKExpanded) ControlData() ([]byte, error) {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package expandapk
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"testing"
7+
8+
"chainguard.dev/apko/internal/tarfs"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestPkgInfo(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
content string
16+
want map[string][]string
17+
}{{
18+
// See example at https://wiki.alpinelinux.org/wiki/Apk_spec
19+
name: "example",
20+
content: `
21+
# Generated by abuild 3.9.0-r2
22+
# using fakeroot version 1.25.3
23+
# Wed Jul 6 19:09:49 UTC 2022
24+
pkgname = busybox
25+
pkgver = 1.35.0-r18
26+
pkgdesc = Size optimized toolbox of many common UNIX utilities
27+
url = https://busybox.net/
28+
builddate = 1657134589
29+
packager = Buildozer <alpine-devel@lists.alpinelinux.org>
30+
size = 958464
31+
arch = x86_64
32+
origin = busybox
33+
commit = 332d2fff53cd4537d415e15e55e8ceb6fe6eaedb
34+
maintainer = Example <example+alpine@example.com>
35+
provider_priority = 100
36+
license = GPL-2.0-only
37+
replaces = busybox-initscripts
38+
provides = /bin/sh
39+
triggers = /bin /usr/bin /sbin /usr/sbin /lib/modules/*
40+
# automatically detected:
41+
provides = cmd:busybox=1.35.0-r18
42+
provides = cmd:sh=1.35.0-r18
43+
depend = so:libc.musl-x86_64.so.1
44+
datahash = 7d3351ac6c3ebaf18182efb5390061f50d077ce5ade60a15909d91278f70ada7
45+
`,
46+
want: map[string][]string{
47+
"pkgname": {"busybox"},
48+
"pkgver": {"1.35.0-r18"},
49+
"pkgdesc": {"Size optimized toolbox of many common UNIX utilities"},
50+
"url": {"https://busybox.net/"},
51+
"builddate": {"1657134589"},
52+
"packager": {"Buildozer <alpine-devel@lists.alpinelinux.org>"},
53+
"size": {"958464"},
54+
"arch": {"x86_64"},
55+
"origin": {"busybox"},
56+
"commit": {"332d2fff53cd4537d415e15e55e8ceb6fe6eaedb"},
57+
"maintainer": {"Example <example+alpine@example.com>"},
58+
"provider_priority": {"100"},
59+
"license": {"GPL-2.0-only"},
60+
"replaces": {"busybox-initscripts"},
61+
"provides": {
62+
"/bin/sh",
63+
"cmd:busybox=1.35.0-r18",
64+
"cmd:sh=1.35.0-r18",
65+
},
66+
"triggers": {"/bin /usr/bin /sbin /usr/sbin /lib/modules/*"},
67+
"depend": {"so:libc.musl-x86_64.so.1"},
68+
"datahash": {"7d3351ac6c3ebaf18182efb5390061f50d077ce5ade60a15909d91278f70ada7"},
69+
},
70+
}, {
71+
name: "empty",
72+
content: `
73+
# Empty file
74+
`,
75+
want: map[string][]string{},
76+
}, {
77+
name: "weird keys and values",
78+
content: `
79+
foobar
80+
=nokey
81+
novalue=
82+
invalid=pair=with=equals
83+
validkey=validvalue
84+
`,
85+
want: map[string][]string{
86+
"": {"nokey"},
87+
"novalue": {""},
88+
"invalid": {"pair=with=equals"},
89+
"validkey": {"validvalue"},
90+
},
91+
}}
92+
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
var buf bytes.Buffer
96+
tw := tar.NewWriter(&buf)
97+
err := tw.WriteHeader(&tar.Header{
98+
Name: ".PKGINFO",
99+
Mode: 0o644,
100+
Size: int64(len([]byte(tt.content))),
101+
})
102+
require.NoError(t, err)
103+
_, err = tw.Write([]byte(tt.content))
104+
require.NoError(t, err)
105+
require.NoError(t, tw.Close())
106+
107+
controlFs, err := tarfs.New(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
108+
require.NoError(t, err)
109+
110+
exp := &APKExpanded{ControlFS: controlFs}
111+
112+
got, err := exp.PkgInfo()
113+
if err != nil {
114+
t.Fatalf("unexpected error: %v", err)
115+
}
116+
require.Equal(t, tt.want, got)
117+
})
118+
}
119+
}

0 commit comments

Comments
 (0)