From 4567a724584ff46f6ff504d3e9dd2a34ec0e6bd5 Mon Sep 17 00:00:00 2001 From: Ivan Ilves Date: Tue, 1 May 2018 20:14:09 +0200 Subject: [PATCH 1/6] chore(registry): move to `api/internal` namespace --- api/{ => internal}/registry/registry.go | 0 api/{ => internal}/registry/registry_test.go | 0 api/v1/v1_test.go | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename api/{ => internal}/registry/registry.go (100%) rename api/{ => internal}/registry/registry_test.go (100%) diff --git a/api/registry/registry.go b/api/internal/registry/registry.go similarity index 100% rename from api/registry/registry.go rename to api/internal/registry/registry.go diff --git a/api/registry/registry_test.go b/api/internal/registry/registry_test.go similarity index 100% rename from api/registry/registry_test.go rename to api/internal/registry/registry_test.go diff --git a/api/v1/v1_test.go b/api/v1/v1_test.go index ef8f6bc..8b6a646 100644 --- a/api/v1/v1_test.go +++ b/api/v1/v1_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ivanilves/lstags/api/registry" + "github.com/ivanilves/lstags/api/internal/registry" "github.com/ivanilves/lstags/repository" ) From 93d264089130edd96d303e5f3e1931136ea5b0a3 Mon Sep 17 00:00:00 2001 From: Ivan Ilves Date: Tue, 1 May 2018 20:48:21 +0200 Subject: [PATCH 2/6] docs(repository): Update comments for GoDoc --- repository/repository.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/repository/repository.go b/repository/repository.go index dc78f80..d7ccb55 100644 --- a/repository/repository.go +++ b/repository/repository.go @@ -1,3 +1,6 @@ +// Package repository provides Repository abstraction to handle Docker repositories. +// This includes parsing and validation of repo specification passed in a text form, +// as well as association of the Docker repository with tags (images) it should contain. package repository import ( @@ -6,10 +9,10 @@ import ( "strings" ) -// InsecureRegistryEx contains a regex string to match insecure registries +// InsecureRegistryEx contains a regex string to match insecure (non-HTTPS) registries var InsecureRegistryEx = `^(127\..*|::1|localhost)(:[0-9]+)?$` -// RefSpec is a description of a valid registry specification +// RefSpec is the description of a valid Docker repository specification const RefSpec = "[REGISTRY[:PORT]/]REPOSITORY[:TAG|=TAG1,TAG2,TAGn|~/FILTER_REGEXP/]" const ( @@ -35,7 +38,7 @@ var validRefExprs = map[string]*regexp.Regexp{ const defaultRegistry = "registry.hub.docker.com" -// Repository represents parsed repository reference +// Repository is a parsed, valid Docker repository reference type Repository struct { ref string registry string @@ -46,7 +49,7 @@ type Repository struct { isSingle bool } -// Ref gets original repository reference +// Ref gets original repository reference string func (r *Repository) Ref() string { return r.ref } @@ -61,12 +64,12 @@ func (r *Repository) IsDefaultRegistry() bool { return r.registry == defaultRegistry } -// Full gives us repository in form REGISTRY[:PORT]/REPOSITORY +// Full gives us repository in a "full" form REGISTRY[:PORT]/REPOSITORY func (r *Repository) Full() string { return r.fullRepo } -// Name is same as full but cuts leading REGISTRY[:PORT]/ if we use default registry (DockerHub) +// Name is same as Full() but cuts leading REGISTRY[:PORT]/ if we use default registry (DockerHub) func (r *Repository) Name() string { if r.IsDefaultRegistry() { return strings.Join(strings.Split(r.Full(), "/")[1:], "/") @@ -75,7 +78,7 @@ func (r *Repository) Name() string { return r.Full() } -// Path gives us remote repository path on the registry e.g. "library/alpine" +// Path gives us repository path without registry hostname e.g. "library/alpine" func (r *Repository) Path() string { path := strings.Join(strings.Split(r.Full(), "/")[1:], "/") @@ -91,7 +94,8 @@ func (r *Repository) HasTags() bool { return r.repoTags != nil && len(r.repoTags) != 0 } -// Tags gives us list of repository tags we use +// Tags gives us list of tags we specified for this repository +// (It will return `[]string{}` if we have not specified any) func (r *Repository) Tags() []string { if !r.HasTags() { return []string{} @@ -114,12 +118,12 @@ func (r *Repository) Filter() string { return r.filterRE.String() } -// IsSecure tells us if we will use secure connection for this repository +// IsSecure tells us if we use secure (HTTPS) connection for this registry/repository func (r *Repository) IsSecure() bool { return r.isSecure } -// WebSchema gives us HTTP protocol we will use to connect to repository +// WebSchema tells us we use "http://" or "https://" to connect to this registry/repository func (r *Repository) WebSchema() string { if !r.IsSecure() { return "http://" @@ -128,7 +132,7 @@ func (r *Repository) WebSchema() string { return "https://" } -// IsSingle tells us if we created repo from "refWithSingleTag" reference +// IsSingle tells us if we created repo from reference having only single tag specified func (r *Repository) IsSingle() bool { return r.isSingle } @@ -219,7 +223,7 @@ func getFullRef(ref, registry string) string { return registry + "/" + ref } -// ParseRef takes a string repository reference and transforms into a Repository +// ParseRef takes a string repository reference and transforms it into a Repository structure func ParseRef(ref string) (*Repository, error) { spec, err := validateRef(ref) if err != nil { From d0b317fdd17f1b60a8378259c3c3367d2764f660 Mon Sep 17 00:00:00 2001 From: Ivan Ilves Date: Wed, 2 May 2018 19:20:53 +0200 Subject: [PATCH 3/6] docs(tag): update comments for GoDoc --- tag/tag.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tag/tag.go b/tag/tag.go index e272fc5..7f2094c 100644 --- a/tag/tag.go +++ b/tag/tag.go @@ -1,3 +1,7 @@ +// Package tag provides Tag abstraction to handle Docker tags (images) +// and their differences between remote registries and Docker daemon, +// i.e. what tags ara available in remote Docker registry, do we have them pulled +// in our local system, or do we have the same tags in our own local registry etc. package tag import ( @@ -18,7 +22,7 @@ type Tag struct { containerID string } -// SortKey returns a sort key +// SortKey returns a sort key (used to sort tags before process or display them) func (tg *Tag) SortKey() string { return tg.GetCreatedKey() + tg.name } @@ -76,12 +80,12 @@ func (tg *Tag) HasImageID() bool { return len(tg.imageID) > 0 } -// SetState sets repo tag state +// SetState sets tag state (a difference between local tag and its remote counterpart) func (tg *Tag) SetState(state string) { tg.state = state } -// GetState gets repo tag state +// GetState gets tag state (a difference between local tag and its remote counterpart) func (tg *Tag) GetState() string { return tg.state } From 46b3a92a72823f13fb9ecbb573600ba0e5419d4b Mon Sep 17 00:00:00 2001 From: Ivan Ilves Date: Thu, 3 May 2018 07:46:36 +0200 Subject: [PATCH 4/6] docs(api/v1): update comments for GoDoc --- api/v1/v1.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/v1/v1.go b/api/v1/v1.go index d399303..a111312 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -1,3 +1,5 @@ +// Package v1 provides lstags v1 API to be used both by the application +// itself and by external projects package v1 import ( @@ -30,14 +32,15 @@ type Config struct { VerboseLogging bool } -// PushConfig holds push-specific configuration +// PushConfig holds push-specific configuration (where to push and with which prefix) type PushConfig struct { Prefix string Registry string UpdateChanged bool } -// API represents application API instance +// API represents configured application API instance, +// the main abstraction you are supposed to work with type API struct { config Config dockerClient *dockerclient.DockerClient From ad86f006bf88afe22e3796a3fa6b62fb2367dae6 Mon Sep 17 00:00:00 2001 From: Ivan Ilves Date: Thu, 3 May 2018 08:03:25 +0200 Subject: [PATCH 5/6] docs(api/v1/collection): update comments for GoDoc --- api/v1/collection/collection.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/v1/collection/collection.go b/api/v1/collection/collection.go index 115f6c3..8d722b3 100644 --- a/api/v1/collection/collection.go +++ b/api/v1/collection/collection.go @@ -1,3 +1,5 @@ +// Package collection provides a "container" structure to store Docker +// registry query results in a well organized and easily accessible form. package collection import ( @@ -27,7 +29,7 @@ func contains(slice []string, str string) bool { return false } -// New creates a new collection of API resources +// New creates a collection of API resources from passed repository references and tags func New(refs []string, tags map[string][]*tag.Tag) (*Collection, error) { repos := make(map[string]*repository.Repository) From c9e70d08cbb6118c6aca2bb76e2234f48454018d Mon Sep 17 00:00:00 2001 From: Ivan Ilves Date: Fri, 4 May 2018 09:15:30 +0200 Subject: [PATCH 6/6] test(api/v1/collection): add tests --- api/v1/collection/collection_test.go | 264 +++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 api/v1/collection/collection_test.go diff --git a/api/v1/collection/collection_test.go b/api/v1/collection/collection_test.go new file mode 100644 index 0000000..ab0e616 --- /dev/null +++ b/api/v1/collection/collection_test.go @@ -0,0 +1,264 @@ +package collection + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ivanilves/lstags/repository" + "github.com/ivanilves/lstags/tag" +) + +func makeTags() []*tag.Tag { + var seed = map[string]string{ + "latest": "sha256:6905a419c4fe7e29acb03cabd2aa9a01226c69277bf718faff52537b1b7b38ab", + "v1.0.1": "sha256:5dac334cf325e506d4304baaf990f2b52e7e4f5c21388e3f1bcad89b9aa830c8", + } + + var tags = make([]*tag.Tag, 0) + + for name, checksum := range seed { + tg, _ := tag.New(name, checksum) + + tags = append(tags, tg) + } + + return tags +} + +func makeRefTags(refs ...string) map[string][]*tag.Tag { + var refTags = make(map[string][]*tag.Tag) + + for _, ref := range refs { + refTags[ref] = makeTags() + } + + return refTags +} + +func TestRefHasTags(t *testing.T) { + var testCases = []struct { + ref string + refTags map[string][]*tag.Tag + hasTags bool + }{ + {"ninja.turtle/rafael", makeRefTags("alpine", "ninja.turtle/rafael"), true}, + {"ninja.turtle/rafael", makeRefTags(), false}, + {"ninja.turtle/leonardo", makeRefTags("alpine", "ninja.turtle/rafael", "ninja.turtle/donatello"), false}, + {"ninja.turtle/donatello", makeRefTags("alpine", "ninja.turtle/rafael", "ninja.turtle/donatello"), true}, + } + + assert := assert.New(t) + + for _, tc := range testCases { + actual := refHasTags(tc.ref, tc.refTags) + + action := "should" + if !tc.hasTags { + action = "should NOT" + } + + assert.Equal( + tc.hasTags, + actual, + "Reference '%s' %s have tags assigned:\n%v", + tc.ref, + action, + tc.refTags, + ) + } +} + +func TestContains(t *testing.T) { + assert := assert.New(t) + + assert.True( + contains([]string{"ninja", "alpine", "busybox"}, "alpine"), + ) + + assert.False( + contains([]string{"ninja", "busybox"}, "alpine"), + ) +} + +func TestNew(t *testing.T) { + var testCases = []struct { + refs []string + refTags map[string][]*tag.Tag + isCorrect bool + }{ + { + []string{"alpine:3.7", "busybox:latest", "quay.io/calico/ctl"}, + makeRefTags("alpine:3.7", "busybox:latest", "quay.io/calico/ctl"), + true, + }, + { + []string{"alpine:3.7", "busybox:latest", "quay.io/calico/ctl"}, + makeRefTags("alpine:3.7", "busybox:stable", "quay.io/calico/ctl"), + false, + }, + { + []string{"quay.io/coreos/flannel", "golang"}, + makeRefTags("quay.io/coreos/flannel", "nginx"), + false, + }, + { + []string{"containers.hype.org/kubernetes/kube-proxy:latest", "quay.io/calico/node:0.9"}, + makeRefTags("containers.hype.org/kubernetes/kube-proxy:latest", "quay.io/calico/node:0.9", "openhype/hipstermind:stable"), + false, + }, + { + []string{}, + makeRefTags("openresty/openresty", "gcr.io/google_containers/pause-amd64"), + false, + }, + { + []string{"openhype/openhype~/v1/", "gcr.io/google_containers/pause-amd64=3.0,3.1"}, + makeRefTags("openhype/openhype~/v1/", "gcr.io/google_containers/pause-amd64=3.0,3.1"), + true, + }, + { + []string{"openhype/openhype~~~/!v1/", "gcr.io/google_containers/pause-amd64=3.0,3.1"}, + makeRefTags("openhype/openhype~~~/!v1/", "gcr.io/google_containers/pause-amd64=3.0,3.1"), + false, + }, + { + []string{"openhype/openhype~/v1/", "gcr.io/google_containers/pause-amd64::3.0,3.1"}, + makeRefTags("openhype/openhype~/v1/", "gcr.io/google_containers/pause-amd64::3.0,3.1"), + false, + }, + } + + assert := assert.New(t) + + for _, tc := range testCases { + cn, err := New(tc.refs, tc.refTags) + + if tc.isCorrect { + assert.NotNil(cn) + assert.Nil(err) + } else { + assert.Nil(cn) + assert.NotNil(err) + } + + if err != nil { + continue + } + } +} + +func TestRefs(t *testing.T) { + var refs = []string{"alpine", "busybox"} + + cn, _ := New(refs, makeRefTags(refs...)) + + assert.Equal(t, refs, cn.Refs()) +} + +func TestRepos(t *testing.T) { + var refs = []string{"alpine", "busybox"} + var repos, _ = repository.ParseRefs(refs) + + cn, _ := New(refs, makeRefTags(refs...)) + + assert.Equal(t, repos, cn.Repos()) +} + +func TestRepo(t *testing.T) { + var refs = []string{"alpine", "busybox"} + var repo, _ = repository.ParseRef("busybox") + + cn, _ := New(refs, makeRefTags(refs...)) + + assert.Equal(t, repo, cn.Repo("busybox")) + assert.Nil(t, cn.Repo("idonotexistanyway")) +} + +func TestTags(t *testing.T) { + var refs = []string{"nginx:stable", "debian:latest"} + var tags = makeTags() + + var refTags = make(map[string][]*tag.Tag) + + for _, ref := range refs { + refTags[ref] = tags + } + + cn, _ := New(refs, refTags) + + assert.Equal(t, tags, cn.Tags("nginx:stable")) + assert.Nil(t, cn.Tags("idonotexist")) +} + +func TestTagMap(t *testing.T) { + var refs = []string{"nginx:stable", "debian:latest"} + + var refTags = makeRefTags(refs...) + + var tagRefMap = make(map[string]map[string]*tag.Tag) + + for ref, tags := range refTags { + tagMap := make(map[string]*tag.Tag) + + for _, tg := range tags { + tagMap[tg.Name()] = tg + } + tagRefMap[ref] = tagMap + } + + cn, _ := New(refs, refTags) + + for _, ref := range refs { + assert.Equal(t, tagRefMap[ref], cn.TagMap(ref)) + } + + assert.Nil(t, cn.TagMap("wellidonotexist")) +} + +func TestRepoCount(t *testing.T) { + var refs = []string{"nginx:stable", "debian:latest"} + + cn, _ := New(refs, makeRefTags(refs...)) + + assert.Equal(t, len(refs), cn.RepoCount()) +} + +func TestTagCount(t *testing.T) { + var refs = []string{"nginx:stable", "debian:latest"} + var refTags = makeRefTags(refs...) + + var tagCount = 0 + + for _, tags := range refTags { + tagCount += len(tags) + } + + cn, _ := New(refs, refTags) + + assert.Equal(t, tagCount, cn.TagCount()) +} + +func TestTaggedRefs(t *testing.T) { + var refs = []string{"nginx", "debian"} + var refTags = makeRefTags(refs...) + + var taggedRefs = make([]string, 0) + + for _, ref := range refs { + repo, _ := repository.ParseRef(ref) + + tags := refTags[ref] + + for _, tg := range tags { + + taggedRef := repo.Name() + ":" + tg.Name() + + taggedRefs = append(taggedRefs, taggedRef) + } + } + + cn, _ := New(refs, refTags) + + assert.Equal(t, taggedRefs, cn.TaggedRefs()) +}