From 0f7fd4ad9e94cc3ef798ddb9c793c5a372a6d3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Bouz=C3=B3n=20Garc=C3=ADa?= Date: Wed, 11 Oct 2017 10:53:18 +0200 Subject: [PATCH 1/4] - Non qualified names working on registry - Dockerfile uses alpine --- Dockerfile | 4 ++-- docker/docker.go | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e79d68a..29ac929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM scratch +FROM alpine ENV DOMAIN=skydns.local ENV RELEASE 0.1 @@ -9,7 +9,7 @@ COPY assets/zoneinfo.zip / # Required for SSL COPY assets/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt -COPY klar / +COPY klar /klar ENTRYPOINT ["/klar"] CMD [""] diff --git a/docker/docker.go b/docker/docker.go index 1a5b165..63f80d0 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -116,9 +116,11 @@ func NewImage(qname, user, password string, insecureTLS, insecureRegistry bool) } part := qname[start:i] start = i + 1 + switch state { case stateInitial: - if part == "localhost" || strings.Contains(part, ".") { + if part == "localhost" || strings.Contains(part, ".") || string(qname[i]) == ":" { + // it's registry, let's check what's next =port of image name registry = part if c == ':' { @@ -187,6 +189,11 @@ func (i *Image) Pull() error { return err } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + fmt.Fprintln(os.Stderr, "Image not found") + os.Exit(1) + } + if resp.StatusCode == http.StatusUnauthorized { i.Token, err = i.requestToken(resp) io.Copy(ioutil.Discard, resp.Body) From 9666f3de1aaf5c9d35124561b942bff406e3c05a Mon Sep 17 00:00:00 2001 From: bougar Date: Thu, 19 Oct 2017 11:59:42 +0200 Subject: [PATCH 2/4] Update Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 29ac929..e79d68a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM scratch ENV DOMAIN=skydns.local ENV RELEASE 0.1 @@ -9,7 +9,7 @@ COPY assets/zoneinfo.zip / # Required for SSL COPY assets/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt -COPY klar /klar +COPY klar / ENTRYPOINT ["/klar"] CMD [""] From a9a3ab812419bd1c52a9859bd31962a55be0be77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Bouz=C3=B3n=20Garc=C3=ADa?= Date: Mon, 23 Oct 2017 19:05:47 +0200 Subject: [PATCH 3/4] - Added test case - Fixed code to work with no_registry_root registries --- docker/docker.go | 10 +++++----- docker/docker_test.go | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 63f80d0..2395ed5 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -119,7 +119,7 @@ func NewImage(qname, user, password string, insecureTLS, insecureRegistry bool) switch state { case stateInitial: - if part == "localhost" || strings.Contains(part, ".") || string(qname[i]) == ":" { + if part == "localhost" || strings.Contains(part, ".") || (string(qname[i]) == ":" && strings.Contains(qname, "/")) { // it's registry, let's check what's next =port of image name registry = part @@ -189,10 +189,10 @@ func (i *Image) Pull() error { return err } defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - fmt.Fprintln(os.Stderr, "Image not found") - os.Exit(1) - } + if resp.StatusCode == http.StatusNotFound { + fmt.Fprintln(os.Stderr, "Image not found") + os.Exit(1) + } if resp.StatusCode == http.StatusUnauthorized { i.Token, err = i.requestToken(resp) diff --git a/docker/docker_test.go b/docker/docker_test.go index e8187e3..1b97d54 100644 --- a/docker/docker_test.go +++ b/docker/docker_test.go @@ -27,6 +27,12 @@ func TestNewImage(t *testing.T) { name: "nginx", tag: "1b29e1531c", }, + "not_qualified": { + image: "registry:5000/nginx:1b29e1531c", + registry: "https://registry:5000/v2", + name: "nginx", + tag: "1b29e1531c", + }, "regular_extended": { image: "docker-registry.domain.com/skynetservices/skydns:2.3", registry: "https://docker-registry.domain.com/v2", From c4d1dd4cd477bbebf37d3ecc8fdc76c81a07479c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Bouz=C3=B3n=20Garc=C3=ADa?= Date: Wed, 25 Oct 2017 15:54:54 +0200 Subject: [PATCH 4/4] - Parse Host, Port, Image and Tag with regular expressions --- docker/docker.go | 83 ++++++++++++++----------------------------- docker/docker_test.go | 18 ++++++++++ utils/regex.go | 71 ++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 56 deletions(-) create mode 100644 utils/regex.go diff --git a/docker/docker.go b/docker/docker.go index 2395ed5..1a6497b 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -104,67 +104,38 @@ func NewImage(qname, user, password string, insecureTLS, insecureRegistry bool) } registry := dockerHub tag := "latest" - var nameParts, tagParts []string - var name, port string - state := stateInitial - start := 0 - for i, c := range qname { - if c == ':' || c == '/' || c == '@' || i == len(qname)-1 { - if i == len(qname)-1 { - // ignore a separator, include the last symbol - i += 1 - } - part := qname[start:i] - start = i + 1 - - switch state { - case stateInitial: - if part == "localhost" || strings.Contains(part, ".") || (string(qname[i]) == ":" && strings.Contains(qname, "/")) { + var name string - // it's registry, let's check what's next =port of image name - registry = part - if c == ':' { - state = statePort - } else { - state = stateName - } - } else { - // it's an image name, if separator is / - // next part is also part of the name - // othrewise it's an offcial image - if c == '/' { - // we got just a part of name, till next time - start = 0 - state = stateName - } else { - state = stateTag - name = fmt.Sprintf("library/%s", part) - } - } - case stateTag: - tag = "" - tagParts = append(tagParts, part) - case statePort: - state = stateName - port = part - case stateName: - if c == ':' || c == '@' { - state = stateTag - } - nameParts = append(nameParts, part) + if !utils.ImageOnlyRegexp.MatchString(string(qname)) || strings.HasPrefix(qname, "localhost/") { + registry = utils.DomainRegexp.FindString(string(qname)) + imageAndTag := utils.ImageRegexp.FindStringSubmatch(string(qname)) + result := make(map[string]string) + for i, name := range utils.ImageRegexp.SubexpNames() { + if i != 0 { + result[name] = imageAndTag[i] + } + } + name = result["name"] + if len(result["tag"]) > 0 { + tag = result["tag"] + } + } else { + imageAndTag := utils.ImageNoRegistryRegexp.FindStringSubmatch(string(qname)) + result := make(map[string]string) + for i, name := range utils.ImageRegexp.SubexpNames() { + if i != 0 { + result[name] = imageAndTag[i] } } + name = result["name"] + if !strings.Contains(name, "/") { + name = "library/" + name + } + if len(result["tag"]) > 0 { + tag = result["tag"] + } } - if port != "" { - registry = fmt.Sprintf("%s:%s", registry, port) - } - if name == "" { - name = strings.Join(nameParts, "/") - } - if tag == "" { - tag = strings.Join(tagParts, ":") - } if insecureRegistry { registry = fmt.Sprintf("http://%s/v2", registry) } else { diff --git a/docker/docker_test.go b/docker/docker_test.go index 1b97d54..9454e7b 100644 --- a/docker/docker_test.go +++ b/docker/docker_test.go @@ -58,6 +58,12 @@ func TestNewImage(t *testing.T) { name: "skynetservices/skydns", tag: "2.3", }, + "no_registry_no_tag": { + image: "skynetservices/skydns", + registry: "https://registry-1.docker.io/v2", + name: "skynetservices/skydns", + tag: "latest", + }, "no_registry_root": { image: "postgres:9.5.1", registry: "https://registry-1.docker.io/v2", @@ -70,6 +76,18 @@ func TestNewImage(t *testing.T) { name: "library/postgres", tag: "sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999", }, + "digest_with_registry": { + image: "registry:8000/postgres@sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999", + registry: "https://registry:8000/v2", + name: "postgres", + tag: "sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999", + }, + "digest_with_localhost_registry": { + image: "localhost/postgres@sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999", + registry: "https://localhost/v2", + name: "postgres", + tag: "sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999", + }, "localhost_no_tag": { image: "localhost/nginx", registry: "https://localhost/v2", diff --git a/utils/regex.go b/utils/regex.go new file mode 100644 index 0000000..a3fac37 --- /dev/null +++ b/utils/regex.go @@ -0,0 +1,71 @@ +package utils + +import "regexp" + +var ( + domainComponentRegexp = match(`[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]`) + + DomainRegexp = expression( + domainComponentRegexp, + optional(repeated(literal(`.`), domainComponentRegexp)), + optional(literal(`:`), match(`[0-9]+`))) + + ImageRegexp = match(`(?:[/])(?P[a-zA-Z0-9/]*)[:@]?(?P.*)?`) + + ImageNoRegistryRegexp = match(`(?P[a-zA-Z0-9/]*)[:@]?(?P.*)?`) + + ImageOnlyRegexp = match(`^([a-zA-Z0-9]*)([/@][^/].*$|[:][a-zA-Z0-9\.]*$)`) +) + +// match compiles the string to a regular expression. +var match = regexp.MustCompile + +// literal compiles s into a literal regular expression, escaping any regexp +// reserved characters. +func literal(s string) *regexp.Regexp { + re := match(regexp.QuoteMeta(s)) + + if _, complete := re.LiteralPrefix(); !complete { + panic("must be a literal") + } + + return re +} + +// expression defines a full expression, where each regular expression must +// follow the previous. +func expression(res ...*regexp.Regexp) *regexp.Regexp { + var s string + for _, re := range res { + s += re.String() + } + + return match(s) +} + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `?`) +} + +// repeated wraps the regexp in a non-capturing group to get one or more +// matches. +func repeated(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `+`) +} + +// group wraps the regexp in a non-capturing group. +func group(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(?:` + expression(res...).String() + `)`) +} + +// capture wraps the expression in a capturing group. +func capture(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(` + expression(res...).String() + `)`) +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...*regexp.Regexp) *regexp.Regexp { + return match(`^` + expression(res...).String() + `$`) +}