Skip to content

Commit

Permalink
Merge pull request #72 from /issues/54/repush
Browse files Browse the repository at this point in the history
Re-Push images to specified registry
  • Loading branch information
vonrabbe authored Oct 9, 2017
2 parents 372b19e + 00953aa commit 8d6d14b
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ lstags --pull -u myuser -p mypass registry.ivanilves.local/tools/sicario~/v1\\.[
```
... and following cronjob runs on my CI server to ensure I always have latest Ubuntu 14.04 and 16.04 images to play with:
```
lstags --pull ubuntu~/^1[46]\\.04$/"
lstags --pull ubuntu~/^1[46]\\.04$/
```
My CI server is connected over crappy Internet link and pulling images in advance makes `docker run` much faster. :wink:

Expand Down
13 changes: 13 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package app

import (
"strings"
)

// GenerateRegistryPrefix generates destination Docker registry prefix path from the source registry name
func GenerateRegistryPrefix(registry string) string {
allParts := strings.Split(registry, ":")
hostname := allParts[0]

return "/" + strings.Replace(hostname, ".", "/", -1)
}
70 changes: 67 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/jessevdk/go-flags"

"github.com/ivanilves/lstags/app"
"github.com/ivanilves/lstags/auth"
"github.com/ivanilves/lstags/docker/jsonconfig"
"github.com/ivanilves/lstags/tag"
Expand All @@ -24,6 +25,8 @@ type options struct {
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"`
Expand Down Expand Up @@ -150,6 +153,10 @@ func main() {
suicide(errors.New("Need at least one repository name, e.g. 'nginx~/^1\\\\.13/' or 'mesosphere/chronos'"))
}

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

if o.InsecureRegistry {
auth.WebSchema = "http://"
registry.WebSchema = "http://"
Expand All @@ -162,12 +169,25 @@ func main() {

repoCount := len(o.Positional.Repositories)
pullCount := 0
pushCount := 0

pullAuths := make(map[string]string)

var pushAuth string
if o.PushRegistry != "" {
pushUsername, pushPassword, err := assignCredentials(o.PushRegistry, o.Username, o.Password, o.DockerJSON)
if err != nil {
suicide(err)
}

pushAuth = getPullAuth(pushUsername, pushPassword)
}

type tagResult struct {
Tags []*tag.Tag
Repo string
Tags []*tag.Tag
Repo string
Path string
Registry string
}

trc := make(chan tagResult, repoCount)
Expand Down Expand Up @@ -222,11 +242,12 @@ func main() {
if tg.NeedsPull() {
pullCount++
}
pushCount++

tags = append(tags, tg)
}

trc <- tagResult{Tags: tags, Repo: repoLocalName}
trc <- tagResult{Tags: tags, Repo: repoLocalName, Path: repoRegistryName, Registry: registryName}
}(r, o, trc)
}

Expand Down Expand Up @@ -287,4 +308,47 @@ func main() {
}
}

if o.Pull && o.PushRegistry != "" {
done := make(chan bool, pullCount)

for _, tr := range tagResults {
go func(tags []*tag.Tag, repo, path, registry string, done chan bool) {
for _, tg := range tags {
prefix := o.PushPrefix
if prefix == "" {
prefix = app.GenerateRegistryPrefix(registry)
}

srcRef := repo + ":" + tg.GetName()
dstRef := o.PushRegistry + prefix + "/" + path + ":" + tg.GetName()

fmt.Printf("PUSHING %s => %s\n", srcRef, dstRef)

err := local.Tag(srcRef, dstRef)
if err != nil {
suicide(err)
}

err = local.Push(dstRef, pushAuth)
if err != nil {
suicide(err)
}

done <- true
}
}(tr.Tags, tr.Repo, tr.Path, tr.Registry, done)
}

pushNumber := 0
if pushCount > 0 {
for range done {
pushNumber++

if pushNumber >= pushCount {
close(done)
}
}
}
}

}
33 changes: 32 additions & 1 deletion tag/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"strings"
"time"

// This "Moby" thing does not work for me...
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/moby/moby/client"
Expand Down Expand Up @@ -189,3 +188,35 @@ func Pull(ref, auth string) error {

return err
}

// Push pushes Docker image to a specified registry
func Push(ref, auth string) error {
cli, err := newClient()
if err != nil {
return err
}

pushOptions := types.ImagePushOptions{RegistryAuth: auth}
if auth == "" {
pushOptions = types.ImagePushOptions{}
}

resp, err := cli.ImagePush(context.Background(), ref, pushOptions)
if err != nil {
return err
}

_, err = ioutil.ReadAll(resp)

return err
}

// Tag puts a "dst" tag on "src" Docker image
func Tag(src, dst string) error {
cli, err := newClient()
if err != nil {
return err
}

return cli.ImageTag(context.Background(), src, dst)
}

0 comments on commit 8d6d14b

Please sign in to comment.