Skip to content

Commit

Permalink
Slim down main
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanilves committed Oct 11, 2017
1 parent cc07611 commit 2b0e063
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 214 deletions.
128 changes: 128 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,137 @@
package app

import (
"errors"
"os"
"regexp"
"strings"

"github.com/jessevdk/go-flags"
)

const dockerHub = "registry.hub.docker.com"

// Options represents configuration options we extract from passed command line arguments
type Options struct {
DefaultRegistry string `short:"r" long:"default-registry" default:"registry.hub.docker.com" description:"Default Docker registry to use" env:"DEFAULT_REGISTRY"`
DockerJSON string `short:"j" long:"docker-json" default:"~/.docker/config.json" description:"JSON file with credentials (use it, please <3)" env:"DOCKER_JSON"`
Username string `short:"u" long:"username" default:"" description:"Override Docker registry username (not recommended, please use JSON file)" env:"USERNAME"`
Password string `short:"p" long:"password" default:"" description:"Override Docker registry password (not recommended, please use JSON file)" env:"PASSWORD"`
ConcurrentRequests int `short:"c" long:"concurrent-requests" default:"32" description:"Limit of concurrent requests to the registry" env:"CONCURRENT_REQUESTS"`
Pull bool `short:"P" long:"pull" description:"Pull Docker images matched by filter (will use local Docker deamon)" env:"PULL"`
PushRegistry string `short:"U" long:"push-registry" description:"[Re]Push pulled images to a specified remote registry" env:"PUSH_REGISTRY"`
PushPrefix string `short:"R" long:"push-prefix" description:"[Re]Push pulled images with a specified repo path prefix" env:"PUSH_PREFIX"`
InsecureRegistry bool `short:"i" long:"insecure-registry" description:"Use insecure plain-HTTP connection to registries (not recommended!)" env:"INSECURE_REGISTRY"`
TraceRequests bool `short:"T" long:"trace-requests" description:"Trace Docker registry HTTP requests" env:"TRACE_REQUESTS"`
Version bool `short:"V" long:"version" description:"Show version and exit"`
Positional struct {
Repositories []string `positional-arg-name:"REPO1 REPO2 REPOn" description:"Docker repositories to operate on, e.g.: alpine nginx~/1\\.13\\.5$/ busybox~/1.27.2/"`
} `positional-args:"yes" required:"yes"`
}

// ParseFlags parses command line arguments and applies some additional post-processing
func ParseFlags() (*Options, error) {
var err error

o := &Options{}

_, err = flags.Parse(o)
if err != nil {
os.Exit(1)
}

err = o.postprocess()
if err != nil {
return nil, err
}

return o, nil
}

func (o *Options) postprocess() error {
if !o.Version && len(o.Positional.Repositories) == 0 {
return errors.New("Need at least one repository name, e.g. 'nginx~/^1\\\\.13/' or 'mesosphere/chronos'")
}

if o.PushRegistry != "" {
o.Pull = true
}

return nil
}

// GetWebSchema gets web schema we will use to talk to Docker registry (HTTP||HTTPS)
func (o *Options) GetWebSchema() string {
if o.InsecureRegistry {
return "http"
}

return "https"
}

// SeparateFilterAndRepo separates repository name from optional regex filter
func SeparateFilterAndRepo(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
}

// DoesMatch wraps over regexp.MatchString to cowardly escape errors
func DoesMatch(s, ex string) bool {
matched, err := regexp.MatchString(ex, s)
if err != nil {
return false
}

return matched
}

func isHostname(s string) bool {
if strings.Contains(s, ".") {
return true
}

if strings.Contains(s, ":") {
return true
}

if s == "localhost" {
return true
}

return false
}

// GetRegistryNameFromRepo tries to get Docker registry name from repository name
// .. if it is not possible it returns default registry name (usually Docker Hub)
func GetRegistryNameFromRepo(repository, defaultRegistry string) string {
r := strings.Split(repository, "/")[0]

if isHostname(r) {
return r
}

return defaultRegistry
}

// GeneratePathFromHostname generates "/"-delimited path from a hostname[:port]
func GeneratePathFromHostname(hostname string) string {
allParts := strings.Split(hostname, ":")
Expand Down
97 changes: 97 additions & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,103 @@ import (
"testing"
)

func TestSeparateFilterAndRepo(t *testing.T) {
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 := SeparateFilterAndRepo(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 TestDoesMatch(t *testing.T) {
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 := DoesMatch(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 TestGetRegistryNameFromRepo(t *testing.T) {
expected := map[string]string{
"mesosphere/marathon": dockerHub,
"bogohost/my/inner/troll": dockerHub,
"registry.hipsta.io/hype/hotshit": "registry.hipsta.io",
"localhost/my/image": "localhost",
"bogohost:5000/mymymy/img": "bogohost:5000",
}

for repo, expectedRegistryName := range expected {
registryName := GetRegistryNameFromRepo(repo, dockerHub)

if registryName != expectedRegistryName {
t.Fatalf(
"Got unexpected Docker registry name '%s' from repo '%s' (expected: '%s')",
registryName,
repo,
expectedRegistryName,
)
}
}
}
func TestGeneratePathFromHostname(t *testing.T) {
examples := map[string]string{
"localhost": "/localhost",
Expand Down
Loading

0 comments on commit 2b0e063

Please sign in to comment.