From cd155b05f8968b7b502b84a2e5ceae5c201c14f8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Braun Date: Tue, 25 Feb 2020 16:35:02 +0100 Subject: [PATCH] feat(server): implement tags/list endpoint When a git source is used branch names are exposed in the tags list. For other source types, only "latest" is exposed. Fixes: #85 --- config/pkgsource.go | 35 +++++++++++++++++++++++++++++++++-- main.go | 22 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/config/pkgsource.go b/config/pkgsource.go index 95236c4..4f9078e 100644 --- a/config/pkgsource.go +++ b/config/pkgsource.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "regexp" "strings" @@ -32,13 +33,16 @@ type PkgSource interface { // for calling Nix. Render(tag string) (string, string) - // Create a key by which builds for this source and iamge + // Create a key by which builds for this source and image // combination can be cached. // // The empty string means that this value is not cacheable due // to the package source being a moving target (such as a // channel). CacheKey(pkgs []string, tag string) string + + // Return available docker tags for the current PkgSource + Tags() ([]string, error) } type GitSource struct { @@ -91,6 +95,25 @@ func (g *GitSource) CacheKey(pkgs []string, tag string) string { return hashed } +// Regex to determine valid docker tags +var tagRegex = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) + +func (g *GitSource) Tags() ([]string, error) { + tags := []string{"latest"} + heads := filepath.Join(g.repository, ".git/refs/heads") + err := filepath.Walk(heads, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + // latest tag is always present and represent the master branch + if tagRegex.MatchString(info.Name()) && info.Name() != "master" { + tags = append(tags, info.Name()) + } + return nil + }) + return tags, err +} + type NixChannel struct { channel string } @@ -113,6 +136,10 @@ func (n *NixChannel) CacheKey(pkgs []string, tag string) string { return hashed } +func (n *NixChannel) Tags() ([]string, error) { + return []string{"latest"}, nil +} + type PkgsPath struct { path string } @@ -128,6 +155,10 @@ func (p *PkgsPath) CacheKey(pkgs []string, tag string) string { return "" } +func (n *PkgsPath) Tags() ([]string, error) { + return []string{"latest"}, nil +} + // Retrieve a package source from the environment. If no source is // specified, the Nix code will default to a recent NixOS channel. func pkgSourceFromEnv() (PkgSource, error) { @@ -140,7 +171,7 @@ func pkgSourceFromEnv() (PkgSource, error) { } if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { - log.WithField("repo", git).Info("using NIx package set from git repository") + log.WithField("repo", git).Info("using Nix package set from git repository") return &GitSource{ repository: git, diff --git a/main.go b/main.go index 6cad937..8832ae1 100644 --- a/main.go +++ b/main.go @@ -55,6 +55,7 @@ var version string = "devel" var ( manifestRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/manifests/([\w|\-|\.|\_]+)$`) layerRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/blobs/sha256:(\w+)$`) + tagsRegex = regexp.MustCompile(`^/v2/([\w|\-|\.|\_|\/]+)/tags/list$`) ) // Downloads the popularity information for the package set from the @@ -184,6 +185,27 @@ func (h *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + tagsMatches := tagsRegex.FindStringSubmatch(r.RequestURI) + if len(tagsMatches) == 2 { + tags, err := h.state.Cfg.Pkgs.Tags() + if err != nil { + writeError(w, 500, "UNKNOWN", "failed to list tags") + + log.WithError(err).WithFields(log.Fields{ + "image": tagsMatches[1], + }).Error(err) + + return + } + manifest, _ := json.Marshal(map[string]interface{}{ + "name": tagsMatches[1], + "tags": tags, + }) + w.Header().Add("Content-Type", manifestMediaType) + w.Write(manifest) + return + } + log.WithField("uri", r.RequestURI).Info("unsupported registry route") w.WriteHeader(404)