Skip to content
Commits on Source (16)
......@@ -15,26 +15,28 @@ In-depth documentation for this package can be found on [pkg.go.dev][go-dev]
### apkbuild
```go
import "gitlab.alpinelinux.org/alpine/go/pkg/apkbuild"
import "gitlab.alpinelinux.org/alpine/go/apkbuild"
```
Currently only parses secfixes in APKBUILD files.
Package apkbuild parses APKBUILD files and returns the metadata.
### repository
```go
import "gitlab.alpinelinux.org/alpine/go/pkg/repository"
import "gitlab.alpinelinux.org/alpine/go/repository"
```
Parses .apk files and APKINDEX files, providing information about all packages in the index.
Package repository parses .apk and APKINDEX files, providing information about
all packages in the index.
### releases
```go
import "gitlab.alpinelinux.org/alpine/go/pkg/releases"
import "gitlab.alpinelinux.org/alpine/go/releases"
```
Provides functions and structures to get information about Alpine Linux releases.
Package releases provides functions and structures to get information about
Alpine Linux releases.
```go
rels, err := releases.Fetch()
......@@ -44,6 +46,22 @@ for _, releaseBranch := range rels.ReleaseBranches {
}
```
### version
``` go
import "gitlab.alpinelinux.org/alpine/go/version"
```
Package version tokenizes a apk package version using the same algorithm as
apk-tools uses.
## Community projects using alpine/go
| Project | Description |
|----------------------------------------------------------------------|-------------------------------------------------------------------|
| [apkcirkledep](https://gitlab.alpinelinux.org/ptrcnull/apkcircledep) | Report circular dependencies between packages in aports |
| [apkgquery](https://gitlab.alpinelinux.org/kdaudt/apkgquery) | Query aports using an expression language |
## License
MIT
......
module gitlab.alpinelinux.org/alpine/go
go 1.16
go 1.19
require (
github.com/MakeNowJust/heredoc v1.0.0
github.com/stretchr/testify v1.8.0
golang.org/x/exp v0.0.0-20221212164502-fae10dda9338
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0
mvdan.cc/sh/v3 v3.5.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/exp v0.0.0-20221212164502-fae10dda9338 h1:OvjRkcNHnf6/W5FZXSxODbxwD+X7fspczG7Jn/xQVD4=
golang.org/x/exp v0.0.0-20221212164502-fae10dda9338/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
......@@ -50,6 +33,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
......@@ -26,14 +26,20 @@ type (
Date string `json:"date"`
}
Key struct {
Url string `json:"url"`
DeprecatedSince string `json:"deprecated_since"`
}
ReleaseBranch struct {
Arches []string `json:"arches"`
BranchDate string `json:"branch_date"`
EolDate string `json:"eol_date"`
GitBranch string `json:"git_branch"`
RelBranch string `json:"rel_branch"`
Releases []Release `json:"releases"`
Repos []Repo `json:"repos"`
Arches []string `json:"arches"`
BranchDate string `json:"branch_date"`
EolDate string `json:"eol_date"`
GitBranch string `json:"git_branch"`
RelBranch string `json:"rel_branch"`
Releases []Release `json:"releases"`
Repos []Repo `json:"repos"`
Keys map[string][]Key `json:"keys"`
}
Releases struct {
......@@ -50,6 +56,14 @@ func NewFromBuffer(buffer []byte) (releases *Releases, err error) {
return
}
func NewFromReader(r io.Reader) (releases *Releases, err error) {
releases = &Releases{}
decoder := json.NewDecoder(r)
err = decoder.Decode(releases)
return
}
// NewFromURL can be used to parse releases.json directly from a url
func NewFromURL(url string) (releases *Releases, err error) {
resp, err := http.Get(url)
......@@ -78,8 +92,9 @@ func Fetch() (releases *Releases, err error) {
// GerRelBranch returns the ReleaseBranch for a certain version.
// For example:
// releases := Fetch()
// relBranch := releases.GetRelBranch("v3.13")
//
// releases := Fetch()
// relBranch := releases.GetRelBranch("v3.13")
func (r *Releases) GetRelBranch(relBranch string) (rb *ReleaseBranch) {
for _, release := range r.ReleaseBranches {
if release.RelBranch == relBranch {
......
......@@ -3,10 +3,12 @@ package releases
import (
"io"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
)
func TestNewFromBuffer(t *testing.T) {
......@@ -35,3 +37,36 @@ func TestNewFromBuffer(t *testing.T) {
assert.Equal("3.13-stable", secondBranch.GitBranch)
assert.Equal("2022-11-01", secondBranch.Repos[1].EOLDate)
}
func TestNewFromBufferParsesKeys(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
file, err := os.Open("testdata/releases.json")
require.Nil(err)
releases, err := NewFromReader(file)
require.Nil(err)
firstBranch := releases.ReleaseBranches[0]
assert.Len(
firstBranch.Keys,
2,
"expected 2 keys in the first release branch, present: %s",
strings.Join(maps.Keys(firstBranch.Keys), ", "),
)
assert.Contains(firstBranch.Keys, "x86_64", "expect edge branch to have keys for x86_64")
x86_64 := firstBranch.Keys["x86_64"]
require.Len(x86_64, 2, "expect edge/x86_64 to have 2 keys")
assert.Equal("https://alpinelinux.org/keys/key1.pub", x86_64[0].Url)
assert.Empty(x86_64[0].DeprecatedSince)
assert.Equal("https://alpinelinux.org/keys/key2.pub", x86_64[1].Url)
assert.Equal("2022-05-12", x86_64[1].DeprecatedSince)
assert.Contains(firstBranch.Keys, "aarch64", "expect edge branch to have keys for aarch64")
aarch64 := firstBranch.Keys["aarch64"]
require.Len(aarch64, 1, "expect edge/aarch64 to have 1 key")
assert.Equal("https://alpinelinux.org/keys/key3.pub", aarch64[0].Url)
assert.Empty(aarch64[0].DeprecatedSince)
}
......@@ -8,7 +8,18 @@
{"name": "main"},
{"name": "community"},
{"name": "testing"}
]
],
"keys": {
"x86_64": [{
"url": "https://alpinelinux.org/keys/key1.pub"
}, {
"url": "https://alpinelinux.org/keys/key2.pub",
"deprecated_since": "2022-05-12"
}],
"aarch64": [{
"url": "https://alpinelinux.org/keys/key3.pub"
}]
}
},{
"rel_branch": "v3.13",
"arches": ["x86_64", "aarch64"],
......
......@@ -134,6 +134,8 @@ func ParsePackageIndex(apkIndexUnpacked io.Reader) (packages []*Package, err err
pkg.Provides = strings.Split(val, " ")
case "c":
pkg.RepoCommit = val
case "r":
pkg.Replaces = val
case "t":
i, err := strconv.ParseInt(val, 10, 64)
if err != nil {
......
......@@ -99,6 +99,7 @@ func ParsePackage(apkPackage io.Reader) (*Package, error) {
Size: uint64(expanded.Size),
InstalledSize: uint64(info.Size),
RepoCommit: info.Commit,
Replaces: info.Replaces,
}, nil
}
......@@ -114,6 +115,7 @@ type apkInfo struct {
Commit string `ini:"commit"`
Maintainer string `ini:"maintainer"`
License string `ini:"license"`
Replaces string `ini:"replaces"`
Depend []string `ini:"depend,,allowshadow"`
Provides []string `ini:"provides,,allowshadow"`
}
package version_test
import (
"fmt"
"gitlab.alpinelinux.org/alpine/go/version"
)
func Example() {
v := version.Version("1.2.3-r0")
nv := &v
t := version.Digit
var pt version.Token
var q int64
for t != version.End {
pt = t
t, nv, q = nv.GetToken(t)
if pt == version.Digit {
fmt.Printf("%d\n", q)
}
if pt == version.RevisionDigit {
fmt.Printf("r%d\n", q)
}
}
// Output:
// 1
// 2
// 3
// r0
}
// Package version tokenizes a apk package version using the same algorithm as
// apk-tools uses.
//
// The version specification is based on what
package version
import (
"strings"
"unicode"
)
type Token int
const (
Invalid Token = -1
DigitOrZero Token = 0
Digit Token = 1
Letter Token = 2
Suffix Token = 3
SuffixDigit Token = 4
RevisionDigit Token = 5
End Token = 6
)
type Version string
// NextToken advances the parsing state machine. On the first call,
// it should be called with t set to Digit.
func (v *Version) NextToken(t Token) (Token, *Version) {
var nextToken Token
token := []rune(*v)
switch {
case *v == "":
nextToken = End
case (t == DigitOrZero || t == Digit) && unicode.IsLower(token[0]):
nextToken = Letter
case (t == Letter) && unicode.IsDigit(token[0]):
nextToken = Digit
case (t == Suffix) && unicode.IsDigit(token[0]):
nextToken = SuffixDigit
case token[0] == '.':
nextToken = DigitOrZero
case token[0] == '_':
nextToken = Suffix
case token[0] == '-' && token[1] == 'r':
nextToken = RevisionDigit
default:
nextToken = t
}
var ver Version
switch nextToken {
case RevisionDigit:
ver = Version(token[2:])
case Letter:
fallthrough
case SuffixDigit:
fallthrough
case Digit:
fallthrough
case End:
ver = *v
default:
ver = Version(token[1:])
}
return nextToken, &ver
}
var (
preSuffixes = []string{"alpha", "beta", "pre", "rc"}
postSuffixes = []string{"cvs", "svn", "git", "hg", "p"}
)
// GetToken performs the same function as NextToken, but also parses
// the version into an integral component.
func (v *Version) GetToken(t Token) (Token, *Version, int64) {
nextToken := Invalid
token := []rune(*v)
weightedVersion := int64(0)
if *v == "" {
return nextToken, v, weightedVersion
}
i := int(0)
switch t {
case DigitOrZero:
for i < len(token) && token[i] == '0' {
i++
}
nextToken = Digit
weightedVersion = int64(-i)
case Digit:
fallthrough
case SuffixDigit:
fallthrough
case RevisionDigit:
for i < len(token) && unicode.IsDigit(token[i]) {
weightedVersion *= 10
weightedVersion += int64(token[i] - '0')
i++
}
if i >= 18 {
nextToken = Invalid
return nextToken, nil, -1
}
case Letter:
weightedVersion = int64(token[i])
i++
case Suffix:
nextToken = SuffixDigit
for pos, suf := range preSuffixes {
if strings.HasPrefix(string(*v), suf) {
i += len(suf)
weightedVersion -= int64(pos)
break
}
}
if i == 0 {
for pos, suf := range postSuffixes {
if strings.HasPrefix(string(*v), suf) {
i += len(suf)
weightedVersion = int64(pos)
break
}
}
}
default:
nextToken = Invalid
}
// Skip ahead by i bytes.
token = token[i:]
ov := Version(token)
outVer := &ov
if *outVer == "" {
nextToken = End
} else if nextToken == Invalid {
nextToken, outVer = outVer.NextToken(t)
}
return nextToken, outVer, weightedVersion
}
package version
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestVersionTokenization(t *testing.T) {
v := Version("12.34.56.78-r0")
nt, nv, q := v.GetToken(Digit)
assert.Equal(t, nt, Token(DigitOrZero))
assert.Equal(t, int64(12), q)
nt, nv, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(DigitOrZero))
assert.Equal(t, int64(34), q)
nt, nv, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(DigitOrZero))
assert.Equal(t, int64(56), q)
nt, nv, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(RevisionDigit))
assert.Equal(t, int64(78), q)
nt, _, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(End))
assert.Equal(t, int64(0), q)
v = Version("1.1.3i-r0")
nt, nv, q = v.GetToken(Digit)
assert.Equal(t, nt, Token(DigitOrZero))
assert.Equal(t, int64(1), q)
nt, nv, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(DigitOrZero))
assert.Equal(t, int64(1), q)
nt, nv, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(Letter))
assert.Equal(t, int64(3), q)
nt, nv, q = nv.GetToken(nt)
assert.Equal(t, nt, Token(RevisionDigit))
assert.Equal(t, int64(105), q)
nt, _, q = nv.GetToken(nt)
assert.Equal(t, nt, Token(End))
assert.Equal(t, int64(0), q)
v = Version("1.1_pre2-r0")
nt, nv, q = v.GetToken(Digit)
assert.Equal(t, nt, Token(DigitOrZero))
assert.Equal(t, int64(1), q)
nt, nv, q = nv.GetToken(Digit)
assert.Equal(t, nt, Token(Suffix))
assert.Equal(t, int64(1), q)
nt, nv, q = nv.GetToken(Suffix)
assert.Equal(t, nt, Token(SuffixDigit))
assert.Equal(t, int64(-2), q)
nt, nv, q = nv.GetToken(SuffixDigit)
assert.Equal(t, nt, Token(RevisionDigit))
assert.Equal(t, int64(2), q)
nt, _, q = nv.GetToken(RevisionDigit)
assert.Equal(t, nt, Token(End))
assert.Equal(t, int64(0), q)
}