Skip to content

Commit

Permalink
refactor(docker): Handling pagination for registries
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent Boutour <[email protected]>
  • Loading branch information
ViBiOh committed May 25, 2021
1 parent ce5ddb6 commit d5affd3
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 41 deletions.
102 changes: 62 additions & 40 deletions pkg/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import (
"github.com/ViBiOh/ketchup/pkg/semver"
)

const (
registryURL = "https://index.docker.io"
authURL = "https://auth.docker.io/token"
)

type authResponse struct {
AccessToken string `json:"access_token"`
}
Expand Down Expand Up @@ -43,10 +48,8 @@ type app struct {
// Flags adds flags for configuring package
func Flags(fs *flag.FlagSet, prefix string, overrides ...flags.Override) Config {
return Config{
registryURL: flags.New(prefix, "docker").Name("Registry").Default(flags.Default("Registry", "https://index.docker.io/v2/", overrides)).Label("Registry API URL").ToString(fs),
authURL: flags.New(prefix, "docker").Name("OAuth URL").Default(flags.Default("Registry", "https://auth.docker.io/token", overrides)).Label("Registry OAuth URL").ToString(fs),
username: flags.New(prefix, "docker").Name("Username").Default(flags.Default("Username", "", overrides)).Label("Registry Username").ToString(fs),
password: flags.New(prefix, "docker").Name("Password").Default(flags.Default("Password", "", overrides)).Label("Registry Password").ToString(fs),
username: flags.New(prefix, "docker").Name("Username").Default(flags.Default("Username", "", overrides)).Label("Registry Username").ToString(fs),
password: flags.New(prefix, "docker").Name("Password").Default(flags.Default("Password", "", overrides)).Label("Registry Password").ToString(fs),
}
}

Expand All @@ -68,61 +71,80 @@ func (a app) LatestVersions(repository string, patterns []string) (map[string]se
return nil, fmt.Errorf("unable to prepare pattern matching: %s", err)
}

var resp *http.Response
var registry, bearerToken string

parts := strings.Split(repository, "/")
if len(parts) < 3 {
if len(parts) == 1 {
repository = fmt.Sprintf("library/%s", repository)
}

token, err := a.login(ctx, repository)
if err != nil {
return nil, fmt.Errorf("unable to authenticate to docker hub: %s", err)
}

if registryURL, parseErr := url.Parse(repository); parseErr == nil && len(registryURL.Host) == 0 {
resp, err = a.dockerRegistry(ctx, repository)
bearerToken = token
registry = registryURL
} else {
resp, err = request.New().Get(fmt.Sprintf("%s/tags/list", repository)).Send(ctx, nil)
registry = fmt.Sprintf("https://%s", parts[0])
repository = strings.Join(parts[1:], "/")
}

if err != nil {
return nil, fmt.Errorf("unable to fetch tags: %s", err)
}
url := fmt.Sprintf("%s/v2/%s/tags/list", registry, repository)

done := make(chan struct{})
versionsStream := make(chan interface{}, runtime.NumCPU())
for len(url) != 0 {
req := request.New().Get(url)

go func() {
defer close(done)
if registry == registryURL {
req.Header("Authorization", fmt.Sprintf("Bearer %s", bearerToken))
}

resp, err := req.Send(ctx, nil)
if err != nil {
return nil, fmt.Errorf("unable to fetch tags: %s", err)
}

for tag := range versionsStream {
tagVersion, err := semver.Parse(*(tag.(*string)))
if err != nil {
continue
done := make(chan struct{})
versionsStream := make(chan interface{}, runtime.NumCPU())

go func() {
defer close(done)

for tag := range versionsStream {
tagVersion, err := semver.Parse(*(tag.(*string)))
if err != nil {
continue
}

model.CheckPatternsMatching(versions, compiledPatterns, tagVersion)
}
}()

model.CheckPatternsMatching(versions, compiledPatterns, tagVersion)
if err := httpjson.Stream(resp.Body, func() interface{} {
return new(string)
}, versionsStream, "tags"); err != nil {
return nil, fmt.Errorf("unable to read tags: %s", err)
}
}()

if err := httpjson.Stream(resp.Body, func() interface{} {
return new(string)
}, versionsStream, "tags"); err != nil {
return nil, fmt.Errorf("unable to read tags: %s", err)
}
<-done

<-done
url = getNextURL(resp, registry)
}

return versions, nil
}

func (a app) dockerRegistry(ctx context.Context, repository string) (*http.Response, error) {
if !strings.Contains(repository, "/") {
repository = fmt.Sprintf("library/%s", repository)
func getNextURL(resp *http.Response, registry string) string {
link := resp.Header.Get("link")
if len(link) == 0 {
return ""
}

bearerToken, err := a.login(ctx, repository)
if err != nil {
return nil, fmt.Errorf("unable to login to registry: %s", err)
}

resp, err := request.New().Get(fmt.Sprintf("%s%s/tags/list", a.registryURL, repository)).Header("Authorization", fmt.Sprintf("Bearer %s", bearerToken)).Send(ctx, nil)
if err != nil {
return nil, fmt.Errorf("unable to fetch tags: %s", err)
}
parts := strings.Split(link, ";")
path := strings.Trim(strings.Trim(parts[0], "<"), ">")

return resp, nil
return fmt.Sprintf("%s%s", registry, path)
}

func (a app) login(ctx context.Context, repository string) (string, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/semver/semver.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (v Version) IsGreater(other Version) bool {
return v.suffix > other.suffix
}

return false
return v.Name > other.Name
}

// Compare return version diff in semver nomenclture
Expand Down

0 comments on commit d5affd3

Please sign in to comment.