Skip to content

Commit 137b0bf

Browse files
authored
Merge pull request #20 from infosiftr/registry
Use containerd resolver/fetcher to query registry for "skip checking"
2 parents ac3e8e9 + a6373f4 commit 137b0bf

File tree

7 files changed

+249
-129
lines changed

7 files changed

+249
-129
lines changed

Dockerfile

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
1+
FROM golang:1.13-buster AS build
2+
3+
SHELL ["bash", "-Eeuo", "pipefail", "-xc"]
4+
5+
WORKDIR /usr/src/bashbrew
6+
7+
COPY go.mod go.sum ./
8+
RUN go mod download; go mod verify
9+
10+
COPY . .
11+
12+
RUN CGO_ENABLED=0 ./bashbrew.sh --version; \
13+
cp -al bin/bashbrew /
14+
115
FROM tianon/docker-tianon
216

317
SHELL ["bash", "-Eeuo", "pipefail", "-xc"]
418

519
RUN apt-get update; \
620
apt-get install -y --no-install-recommends \
721
git \
8-
golang-go \
922
; \
1023
rm -rf /var/lib/apt/lists/*
1124

12-
WORKDIR /usr/src/bashbrew
13-
14-
COPY go.mod go.sum ./
15-
RUN go mod download; go mod verify
16-
17-
COPY . .
18-
RUN export CGO_ENABLED=0; \
19-
bash -x ./bashbrew.sh --version; \
20-
rm -r ~/.cache/go-build; \
21-
mv bin/bashbrew /usr/local/bin/; \
22-
bashbrew --version
25+
COPY --from=build /bashbrew /usr/local/bin/
26+
RUN bashbrew --version
2327

2428
ENV BASHBREW_CACHE /bashbrew-cache
2529
# make sure our default cache dir exists and is writable by anyone (similar to /tmp)

cmd/bashbrew/cmd-push.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"os"
66
"path"
7-
"time"
87

98
"github.com/urfave/cli"
109
)
@@ -47,10 +46,10 @@ func cmdPush(c *cli.Context) error {
4746
tag = tagRepo + ":" + tag
4847

4948
if !force {
50-
created := dockerCreated(tag)
51-
lastUpdated := fetchDockerHubTagMeta(tag).lastUpdatedTime()
52-
if !created.After(lastUpdated) {
53-
fmt.Fprintf(os.Stderr, "skipping %s (created %s, last updated %s)\n", tag, created.Local().Format(time.RFC3339), lastUpdated.Local().Format(time.RFC3339))
49+
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)
5453
continue
5554
}
5655
}

cmd/bashbrew/cmd-put-shared.go

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ import (
44
"fmt"
55
"os"
66
"path"
7+
"reflect"
8+
"sort"
79
"strings"
8-
"time"
910

1011
"github.com/urfave/cli"
1112

1213
"github.com/docker-library/bashbrew/architecture"
1314
"github.com/docker-library/bashbrew/manifest"
1415
)
1516

16-
func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Manifest2822Entry) (string, time.Time, int, error) {
17+
func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Manifest2822Entry) (string, []string, error) {
1718
yaml := ""
18-
mru := time.Time{}
19-
expectedNumber := 0
19+
remoteDigests := []string{}
2020
entryIdentifiers := []string{}
2121
for _, entry := range entries {
2222
entryIdentifiers = append(entryIdentifiers, r.EntryIdentifier(entry))
@@ -41,29 +41,26 @@ func entriesToManifestToolYaml(singleArch bool, r Repo, entries ...*manifest.Man
4141
}
4242

4343
archImage := fmt.Sprintf("%s/%s:%s", archNamespace, r.RepoName, entry.Tags[0])
44-
archImageMeta := fetchDockerHubTagMeta(archImage)
45-
if archU := archImageMeta.lastUpdatedTime(); archU.After(mru) {
46-
mru = archU
47-
}
4844

49-
// count up how many images we expect to push successfully in this manifest list
50-
expectedNumber += len(archImageMeta.Images)
51-
// for non-manifest-list tags, this will be 1 and for failed lookups it'll be 0
45+
// keep track of how many images we expect to push successfully in this manifest list (and what their manifest digests are)
46+
// for non-manifest-list tags, this will be exactly 1 and for failed lookups it'll be 0
5247
// (and if one of _these_ tags is a manifest list, we've goofed somewhere)
53-
if len(archImageMeta.Images) != 1 {
54-
fmt.Fprintf(os.Stderr, "warning: expected 1 image for %q; got %d\n", archImage, len(archImageMeta.Images))
48+
archImageDigests := fetchRegistryManiestListDigests(archImage)
49+
if len(archImageDigests) != 1 {
50+
fmt.Fprintf(os.Stderr, "warning: expected 1 image for %q; got %d\n", archImage, len(archImageDigests))
5551
}
52+
remoteDigests = append(remoteDigests, archImageDigests...)
5653

57-
yaml += fmt.Sprintf(" - image: %s\n platform:\n", archImage)
54+
yaml += fmt.Sprintf(" - image: %s\n", archImage)
55+
yaml += fmt.Sprintf(" platform:\n")
5856
yaml += fmt.Sprintf(" os: %s\n", ociArch.OS)
5957
yaml += fmt.Sprintf(" architecture: %s\n", ociArch.Architecture)
6058
if ociArch.Variant != "" {
6159
yaml += fmt.Sprintf(" variant: %s\n", ociArch.Variant)
6260
}
6361
}
6462
}
65-
66-
return "manifests:\n" + yaml, mru, expectedNumber, nil
63+
return "manifests:\n" + yaml, remoteDigests, nil
6764
}
6865

6966
func tagsToManifestToolYaml(repo string, tags ...string) string {
@@ -130,37 +127,25 @@ func cmdPutShared(c *cli.Context) error {
130127

131128
failed := []string{}
132129
for _, group := range sharedTagGroups {
133-
yaml, mostRecentPush, expectedNumber, err := entriesToManifestToolYaml(singleArch, *r, group.Entries...)
130+
yaml, expectedRemoteDigests, err := entriesToManifestToolYaml(singleArch, *r, group.Entries...)
134131
if err != nil {
135132
return err
136133
}
137134

138-
if expectedNumber < 1 {
139-
// if "expectedNumber" comes back as 0, we've probably got an API issue, so let's count up what we probably _should_ push
135+
sort.Strings(expectedRemoteDigests)
136+
if len(expectedRemoteDigests) < 1 {
137+
// if "expectedRemoteDigests" comes back empty, we've probably got an API issue (or a build error/push timing problem)
140138
fmt.Fprintf(os.Stderr, "warning: no images expected to push for %q\n", fmt.Sprintf("%s:%s", targetRepo, group.SharedTags[0]))
141-
for _, entry := range group.Entries {
142-
expectedNumber += len(entry.Architectures)
143-
}
144139
}
145140

146141
tagsToPush := []string{}
147142
for _, tag := range group.SharedTags {
148143
image := fmt.Sprintf("%s:%s", targetRepo, tag)
149144
if !force {
150-
hubMeta := fetchDockerHubTagMeta(image)
151-
tagUpdated := hubMeta.lastUpdatedTime()
152-
doPush := false
153-
if mostRecentPush.After(tagUpdated) {
154-
// if one of the images that make up the manifest list has been updated since the manifest list was last pushed, we probably need to push
155-
doPush = true
156-
}
157-
if !singleArch && len(hubMeta.Images) != expectedNumber {
158-
// if we're supposed to push more (or less) images than the current manifest list contains, we probably need to push
159-
// this _should_ already be accounting for tags that haven't been pushed yet (see notes above in "entriesToManifestToolYaml" where this is calculated)
160-
doPush = true
161-
}
162-
if !doPush {
163-
fmt.Fprintf(os.Stderr, "skipping %s (created %s, last updated %s)\n", image, mostRecentPush.Local().Format(time.RFC3339), tagUpdated.Local().Format(time.RFC3339))
145+
remoteDigests := fetchRegistryManiestListDigests(image)
146+
sort.Strings(remoteDigests)
147+
if reflect.DeepEqual(remoteDigests, expectedRemoteDigests) {
148+
fmt.Fprintf(os.Stderr, "skipping %s (%d remote digests up-to-date)\n", image, len(remoteDigests))
164149
continue
165150
}
166151
}

cmd/bashbrew/hub.go

Lines changed: 0 additions & 63 deletions
This file was deleted.

cmd/bashbrew/registry.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/containerd/containerd/images"
8+
"github.com/containerd/containerd/reference/docker"
9+
"github.com/containerd/containerd/remotes"
10+
dockerremote "github.com/containerd/containerd/remotes/docker"
11+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12+
)
13+
14+
var registryImageIdCache = map[string]string{}
15+
16+
// assumes the provided image name is NOT a manifest list (used for testing whether we need to "bashbrew push" or whether the remote image is already up-to-date)
17+
// this does NOT handle authentication, and will return the empty string for repositories which require it (causing "bashbrew push" to simply shell out to "docker push" which will handle authentication appropriately)
18+
func fetchRegistryImageId(image string) string {
19+
ctx := context.Background()
20+
21+
ref, resolver, err := fetchRegistryResolveHelper(image)
22+
if err != nil {
23+
return ""
24+
}
25+
26+
name, desc, err := resolver.Resolve(ctx, ref)
27+
if err != nil {
28+
return ""
29+
}
30+
31+
if desc.MediaType != images.MediaTypeDockerSchema2Manifest && desc.MediaType != ocispec.MediaTypeImageManifest {
32+
return ""
33+
}
34+
35+
digest := desc.Digest.String()
36+
if id, ok := registryImageIdCache[digest]; ok {
37+
return id
38+
}
39+
40+
fetcher, err := resolver.Fetcher(ctx, name)
41+
if err != nil {
42+
return ""
43+
}
44+
45+
r, err := fetcher.Fetch(ctx, desc)
46+
if err != nil {
47+
return ""
48+
}
49+
defer r.Close()
50+
51+
var manifest ocispec.Manifest
52+
if err := json.NewDecoder(r).Decode(&manifest); err != nil {
53+
return ""
54+
}
55+
id := manifest.Config.Digest.String()
56+
if id != "" {
57+
registryImageIdCache[digest] = id
58+
}
59+
return id
60+
}
61+
62+
var registryManifestListCache = map[string][]string{}
63+
64+
// returns a list of manifest list element digests for the given image name (which might be just one entry, if it's not a manifest list)
65+
func fetchRegistryManiestListDigests(image string) []string {
66+
ctx := context.Background()
67+
68+
ref, resolver, err := fetchRegistryResolveHelper(image)
69+
if err != nil {
70+
return nil
71+
}
72+
73+
name, desc, err := resolver.Resolve(ctx, ref)
74+
if err != nil {
75+
return nil
76+
}
77+
78+
digest := desc.Digest.String()
79+
if desc.MediaType == images.MediaTypeDockerSchema2Manifest || desc.MediaType == ocispec.MediaTypeImageManifest {
80+
return []string{digest}
81+
}
82+
83+
if desc.MediaType != images.MediaTypeDockerSchema2ManifestList && desc.MediaType != ocispec.MediaTypeImageIndex {
84+
return nil
85+
}
86+
87+
if digests, ok := registryManifestListCache[digest]; ok {
88+
return digests
89+
}
90+
91+
fetcher, err := resolver.Fetcher(ctx, name)
92+
if err != nil {
93+
return nil
94+
}
95+
96+
r, err := fetcher.Fetch(ctx, desc)
97+
if err != nil {
98+
return nil
99+
}
100+
defer r.Close()
101+
102+
var manifestList ocispec.Index
103+
if err := json.NewDecoder(r).Decode(&manifestList); err != nil {
104+
return nil
105+
}
106+
digests := []string{}
107+
for _, manifest := range manifestList.Manifests {
108+
if manifest.Digest != "" {
109+
digests = append(digests, manifest.Digest.String())
110+
}
111+
}
112+
if len(digests) > 0 {
113+
registryManifestListCache[digest] = digests
114+
}
115+
return digests
116+
}
117+
118+
func fetchRegistryResolveHelper(image string) (string, remotes.Resolver, error) {
119+
ref, err := docker.ParseAnyReference(image)
120+
if err != nil {
121+
return "", nil, err
122+
}
123+
if namedRef, ok := ref.(docker.Named); ok {
124+
// add ":latest" if necessary
125+
namedRef = docker.TagNameOnly(namedRef)
126+
ref = namedRef
127+
}
128+
return ref.String(), dockerremote.NewResolver(dockerremote.ResolverOptions{}), nil
129+
}

go.mod

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
module github.com/docker-library/bashbrew
22

3-
go 1.11
3+
go 1.13
44

55
require (
6+
github.com/containerd/containerd v1.4.0
67
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
78
github.com/go-git/go-git/v5 v5.1.0
89
github.com/imdario/mergo v0.3.11 // indirect
10+
github.com/opencontainers/go-digest v1.0.0 // indirect
11+
github.com/opencontainers/image-spec v1.0.1
12+
github.com/pkg/errors v0.9.1 // indirect
13+
github.com/sirupsen/logrus v1.6.0 // indirect
914
github.com/urfave/cli v1.22.4
1015
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
1116
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect
17+
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
1218
golang.org/x/sys v0.0.0-20200821140526-fda516888d29 // indirect
19+
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24 // indirect
20+
google.golang.org/grpc v1.32.0 // indirect
21+
gotest.tools/v3 v3.0.2 // indirect
1322
pault.ag/go/debian v0.0.0-20190530135403-b831f604d664
1423
pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a
1524
)

0 commit comments

Comments
 (0)