Skip to content

Commit

Permalink
Merge pull request #55 from ivanilves/filter-by-regex
Browse files Browse the repository at this point in the history
Filter images by tag with regular expressions
  • Loading branch information
ivanilves authored Sep 23, 2017
2 parents cd8dadd + 71acf53 commit 1c476c6
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 4 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@
# lstags

* *Compare local Docker images with ones present in registry.*
* *Get insights on changes in watched Docker registries, easily.*
* *Get insights on changes in watched Docker registries, easily.*
* *Facilitate maintenance of your own local "proxy" registries.*

**NB!** [Issues](https://github.com/ivanilves/lstags/issues) are welcome, [pull requests](https://github.com/ivanilves/lstags/pulls) are even more welcome! :smile:

### Example invocation
```
$ lstags alpine~/^3\\./
<STATE> <DIGEST> <(local) ID> <Created At> <TAG>
ABSENT sha256:9363d03ef12c8c25a2def8551e609f146 n/a 2017-09-13T16:32:00 alpine:3.1
CHANGED sha256:9866438860a1b28cd9f0c944e42d3f6cd 39be345c901f 2017-09-13T16:32:05 alpine:3.2
ABSENT sha256:ae4d16d132e3c93dd09aec45e4c13e9d7 n/a 2017-09-13T16:32:10 alpine:3.3
CHANGED sha256:0d82f2f4b464452aac758c77debfff138 f64255f97787 2017-09-13T16:32:15 alpine:3.4
PRESENT sha256:129a7f8c0fae8c3251a8df9370577d9d6 074d602a59d7 2017-09-13T16:32:20 alpine:3.5
PRESENT sha256:f006ecbb824d87947d0b51ab8488634bf 76da55c8019d 2017-09-13T16:32:26 alpine:3.6
```

## Why would someone use this?
You could use `lstags`, if you ...
* ... continuously pull Docker images from some public or private registry to speed-up Docker run.
Expand Down
49 changes: 46 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"regexp"
"strings"

"github.com/jessevdk/go-flags"
Expand Down Expand Up @@ -37,6 +38,39 @@ func getVersion() string {
return VERSION
}

func trimFilter(repoWithFilter string) (string, string, error) {
parts := strings.Split(repoWithFilter, "~")

repository := parts[0]

if len(parts) < 2 {
return repository, ".*", nil
}

if len(parts) > 2 {
return "", "", errors.New("Unable to trim filter from repository (too many '~'!): " + repoWithFilter)
}

f := parts[1]

if !strings.HasPrefix(f, "/") || !strings.HasSuffix(f, "/") {
return "", "", errors.New("Filter should be passed in a form: /REGEXP/")
}

filter := f[1 : len(f)-1]

return repository, filter, nil
}

func matchesFilter(s, filter string) bool {
matched, err := regexp.MatchString(filter, s)
if err != nil {
return false
}

return matched
}

func isHostname(s string) bool {
if strings.Contains(s, ".") {
return true
Expand Down Expand Up @@ -105,10 +139,15 @@ func main() {

registry.TraceRequests = o.TraceRequests

registryName := getRegistryName(o.Positional.Repository, o.DefaultRegistry)
repository, filter, err := trimFilter(o.Positional.Repository)
if err != nil {
suicide(err)
}

registryName := getRegistryName(repository, o.DefaultRegistry)

repoRegistryName := registry.FormatRepoName(o.Positional.Repository, registryName)
repoLocalName := local.FormatRepoName(o.Positional.Repository, registryName)
repoRegistryName := registry.FormatRepoName(repository, registryName)
repoLocalName := local.FormatRepoName(repository, registryName)

username, password, err := assignCredentials(registryName, o.Username, o.Password, o.DockerJSON)
if err != nil {
Expand Down Expand Up @@ -140,6 +179,10 @@ func main() {

tg := joinedTags[name]

if !matchesFilter(tg.GetName(), filter) {
continue
}

fmt.Printf(
format,
tg.GetState(),
Expand Down
90 changes: 90 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,97 @@ func TestGetAuthorization(t *testing.T) {
}
}

func TestTrimFilter(t *testing.T) {
flag.Parse()
if *runIntegrationTests {
t.SkipNow()
}

expected := []struct {
repoWithFilter string
repo string
filter string
iserr bool
}{
{"nginx", "nginx", ".*", false},
{"registry.hipster.io/hype/sdn", "registry.hipster.io/hype/sdn", ".*", false},
{"mesosphere/mesos~/^1\\.[0-9]+\\.[0-9]+$/", "mesosphere/mesos", "^1\\.[0-9]+\\.[0-9]+$", false},
{"registry.hipster.io/hype/drone~/v[0-9]+$/", "registry.hipster.io/hype/drone", "v[0-9]+$", false},
{"bogohost:5000/hype/drone~/v[0-9]+$/", "bogohost:5000/hype/drone", "v[0-9]+$", false},
{"registry.clown.bad/cache/merd~x[0-9]", "", "", true},
{"cabron/~plla~x~", "", "", true},
}

for _, e := range expected {
repo, filter, err := trimFilter(e.repoWithFilter)

if repo != e.repo {
t.Fatalf(
"Unexpected repository name '%s' trimmed from '%s' (expected: '%s')",
repo,
e.repoWithFilter,
e.repo,
)
}

if filter != e.filter {
t.Fatalf(
"Unexpected repository filter '%s' trimmed from '%s' (expected: '%s')",
filter,
e.repoWithFilter,
e.filter,
)
}

iserr := err != nil
if iserr != e.iserr {
t.Fatalf("Passing badly formatted repository '%s' should trigger an error", e.repoWithFilter)
}
}
}

func TestMatchesFilter(t *testing.T) {
flag.Parse()
if *runIntegrationTests {
t.SkipNow()
}

expected := []struct {
s string
pattern string
matched bool
}{
{"latest", "^latest$", true},
{"v1.0.1", "^v1\\.0\\.1$", true},
{"barbos", ".*", true},
{"3.4", "*", false},
}

for _, e := range expected {
matched := matchesFilter(e.s, e.pattern)

action := "should"
if !e.matched {
action = "should not"
}

if matched != e.matched {
t.Fatalf(
"String '%s' %s match pattern '%s'",
e.s,
action,
e.pattern,
)
}
}
}

func TestGetRegistryName(t *testing.T) {
flag.Parse()
if *runIntegrationTests {
t.SkipNow()
}

expected := map[string]string{
"mesosphere/marathon": dockerHub,
"bogohost/my/inner/troll": dockerHub,
Expand Down

0 comments on commit 1c476c6

Please sign in to comment.