Skip to content

Commit 6acd73c

Browse files
authored
Merge pull request #56 from infosiftr/bashbrew-remote-arches
Add new "bashbrew remote arches" command
2 parents 20b5a50 + b20e82c commit 6acd73c

File tree

10 files changed

+467
-145
lines changed

10 files changed

+467
-145
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
!go.sum
77
!manifest/
88
!pkg/
9+
!registry/
910
!scripts/

architecture/oci-platform.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package architecture
22

3-
import "path"
3+
import (
4+
"path"
5+
6+
"github.com/containerd/containerd/platforms"
7+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8+
)
49

510
// https://github.com/opencontainers/image-spec/blob/v1.0.1/image-index.md#image-index-property-descriptions
611
// see "platform" (under "manifests")
7-
type OCIPlatform struct {
8-
OS string `json:"os"`
9-
Architecture string `json:"architecture"`
10-
Variant string `json:"variant,omitempty"`
11-
12-
//OSVersion string `json:"os.version,omitempty"`
13-
//OSFeatures []string `json:"os.features,omitempty"`
14-
}
12+
type OCIPlatform ocispec.Platform
1513

1614
var SupportedArches = map[string]OCIPlatform{
1715
"amd64": {OS: "linux", Architecture: "amd64"},
@@ -36,3 +34,18 @@ func (p OCIPlatform) String() string {
3634
p.Variant,
3735
)
3836
}
37+
38+
func Normalize(p ocispec.Platform) ocispec.Platform {
39+
p = platforms.Normalize(p)
40+
if p.Architecture == "arm64" && p.Variant == "" {
41+
// 😭 https://github.com/containerd/containerd/blob/1c90a442489720eec95342e1789ee8a5e1b9536f/platforms/database.go#L98 (inconsistent normalization of "linux/arm -> linux/arm/v7" vs "linux/arm64/v8 -> linux/arm64")
42+
p.Variant = "v8"
43+
// TODO get pedantic about amd64 variants too? (in our defense, those variants didn't exist when we defined our "amd64", unlike "arm64v8" 👀)
44+
}
45+
return p
46+
}
47+
48+
func (p OCIPlatform) Is(q OCIPlatform) bool {
49+
// (assumes "p" and "q" are both already bashbrew normalized, like one of the SupportedArches above)
50+
return p.OS == q.OS && p.Architecture == q.Architecture && p.Variant == q.Variant
51+
}

architecture/oci-platform_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"testing"
55

66
"github.com/docker-library/bashbrew/architecture"
7+
8+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
79
)
810

911
func TestString(t *testing.T) {
@@ -21,3 +23,45 @@ func TestString(t *testing.T) {
2123
})
2224
}
2325
}
26+
27+
func TestIs(t *testing.T) {
28+
tests := map[bool][][2]architecture.OCIPlatform{
29+
true: {
30+
{architecture.SupportedArches["amd64"], architecture.SupportedArches["amd64"]},
31+
{architecture.SupportedArches["arm32v5"], architecture.SupportedArches["arm32v5"]},
32+
{architecture.SupportedArches["arm32v6"], architecture.SupportedArches["arm32v6"]},
33+
{architecture.SupportedArches["arm32v7"], architecture.SupportedArches["arm32v7"]},
34+
{architecture.SupportedArches["arm64v8"], architecture.OCIPlatform{OS: "linux", Architecture: "arm64", Variant: "v8"}},
35+
{architecture.SupportedArches["windows-amd64"], architecture.OCIPlatform{OS: "windows", Architecture: "amd64", OSVersion: "1.2.3.4"}},
36+
},
37+
false: {
38+
{architecture.SupportedArches["amd64"], architecture.OCIPlatform{OS: "linux", Architecture: "amd64", Variant: "v4"}},
39+
{architecture.SupportedArches["amd64"], architecture.SupportedArches["arm64v8"]},
40+
{architecture.SupportedArches["amd64"], architecture.SupportedArches["i386"]},
41+
{architecture.SupportedArches["amd64"], architecture.SupportedArches["windows-amd64"]},
42+
{architecture.SupportedArches["arm32v7"], architecture.SupportedArches["arm32v6"]},
43+
{architecture.SupportedArches["arm32v7"], architecture.SupportedArches["arm64v8"]},
44+
{architecture.SupportedArches["arm64v8"], architecture.OCIPlatform{OS: "linux", Architecture: "arm64", Variant: "v9"}},
45+
},
46+
}
47+
for expected, test := range tests {
48+
for _, platforms := range test {
49+
t.Run(platforms[0].String()+" vs "+platforms[1].String(), func(t *testing.T) {
50+
if got := platforms[0].Is(platforms[1]); got != expected {
51+
t.Errorf("expected %v; got %v", expected, got)
52+
}
53+
})
54+
}
55+
}
56+
}
57+
58+
func TestNormalize(t *testing.T) {
59+
for arch, expected := range architecture.SupportedArches {
60+
t.Run(arch, func(t *testing.T) {
61+
normal := architecture.OCIPlatform(architecture.Normalize(ocispec.Platform(expected)))
62+
if !expected.Is(normal) {
63+
t.Errorf("expected %#v; got %#v", expected, normal)
64+
}
65+
})
66+
}
67+
}

cmd/bashbrew/cmd-push.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func cmdPush(c *cli.Context) error {
3939
}
4040

4141
// we can't use "r.Tags()" here because it will include SharedTags, which we never want to push directly (see "cmd-put-shared.go")
42+
TagsLoop:
4243
for i, tag := range entry.Tags {
4344
if uniq && i > 0 {
4445
break
@@ -47,10 +48,12 @@ func cmdPush(c *cli.Context) error {
4748

4849
if !force {
4950
localImageId, _ := dockerInspect("{{.Id}}", tag)
50-
registryImageId := fetchRegistryImageId(tag)
51-
if registryImageId != "" && localImageId == registryImageId {
52-
fmt.Fprintf(os.Stderr, "skipping %s (remote image matches local)\n", tag)
53-
continue
51+
registryImageIds := fetchRegistryImageIds(tag)
52+
for _, registryImageId := range registryImageIds {
53+
if localImageId == registryImageId {
54+
fmt.Fprintf(os.Stderr, "skipping %s (remote image matches local)\n", tag)
55+
continue TagsLoop
56+
}
5457
}
5558
}
5659
fmt.Printf("Pushing %s\n", tag)

cmd/bashbrew/cmd-remote-arches.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"sort"
8+
9+
"github.com/docker-library/bashbrew/registry"
10+
11+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12+
"github.com/urfave/cli"
13+
)
14+
15+
func cmdRemoteArches(c *cli.Context) error {
16+
args := c.Args()
17+
if len(args) < 1 {
18+
return fmt.Errorf("expected at least one argument")
19+
}
20+
doJson := c.Bool("json")
21+
ctx := context.Background()
22+
for _, arg := range args {
23+
img, err := registry.Resolve(ctx, arg)
24+
if err != nil {
25+
return err
26+
}
27+
28+
arches, err := img.Architectures(ctx)
29+
if err != nil {
30+
return err
31+
}
32+
33+
if doJson {
34+
ret := struct {
35+
Ref string `json:"ref"`
36+
Desc ocispec.Descriptor `json:"desc"`
37+
Arches map[string][]ocispec.Descriptor `json:"arches"`
38+
}{
39+
Ref: img.ImageRef,
40+
Desc: img.Desc,
41+
Arches: map[string][]ocispec.Descriptor{},
42+
}
43+
for arch, imgs := range arches {
44+
for _, obj := range imgs {
45+
ret.Arches[arch] = append(ret.Arches[arch], obj.Desc)
46+
}
47+
}
48+
out, err := json.Marshal(ret)
49+
if err != nil {
50+
return err
51+
}
52+
fmt.Println(string(out))
53+
} else {
54+
fmt.Printf("%s -> %s\n", img.ImageRef, img.Desc.Digest)
55+
56+
// Go.....
57+
keys := []string{}
58+
for arch := range arches {
59+
keys = append(keys, arch)
60+
}
61+
sort.Strings(keys)
62+
for _, arch := range keys {
63+
for _, obj := range arches[arch] {
64+
fmt.Printf(" %s -> %s\n", arch, obj.Desc.Digest)
65+
}
66+
}
67+
}
68+
}
69+
return nil
70+
}

cmd/bashbrew/main.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ func main() {
239239
Name: "target-namespace",
240240
Usage: `target namespace to act into ("docker tag namespace/repo:tag target-namespace/repo:tag", "docker push target-namespace/repo:tag")`,
241241
},
242+
243+
"json": cli.BoolFlag{
244+
Name: "json",
245+
Usage: "output machine-readable JSON instead of human-readable text",
246+
},
242247
}
243248

244249
app.Commands = []cli.Command{
@@ -395,6 +400,22 @@ func main() {
395400

396401
Category: "plumbing",
397402
},
403+
{
404+
Name: "remote",
405+
Usage: "query registries for bashbrew-related data",
406+
Before: subcommandBeforeFactory("remote"),
407+
Category: "plumbing",
408+
Subcommands: []cli.Command{
409+
{
410+
Name: "arches",
411+
Usage: "returns a list of bashbrew architectures and content descriptors for the specified image(s)",
412+
Flags: []cli.Flag{
413+
commonFlags["json"],
414+
},
415+
Action: cmdRemoteArches,
416+
},
417+
},
418+
},
398419
}
399420

400421
err := app.Run(os.Args)

0 commit comments

Comments
 (0)