2016-08-13 01:23:13 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2016-09-05 22:44:19 -04:00
|
|
|
"encoding/json"
|
2016-09-20 21:16:34 -04:00
|
|
|
"errors"
|
2016-08-13 01:23:13 -04:00
|
|
|
"fmt"
|
2016-12-20 00:13:34 -05:00
|
|
|
"io/ioutil"
|
2016-08-13 01:23:13 -04:00
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"text/tabwriter"
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
2016-12-20 00:13:34 -05:00
|
|
|
"github.com/docker/distribution/digest"
|
2016-08-13 01:23:13 -04:00
|
|
|
"github.com/docker/docker/cliconfig"
|
2016-09-05 22:44:19 -04:00
|
|
|
"github.com/docker/engine-api/types"
|
2016-09-30 14:09:10 -04:00
|
|
|
"github.com/jessfraz/reg/registry"
|
2016-09-05 22:44:19 -04:00
|
|
|
"github.com/urfave/cli"
|
2016-08-13 01:23:13 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// VERSION is the binary version.
|
2016-09-05 22:44:19 -04:00
|
|
|
VERSION = "v0.2.0"
|
2016-08-13 01:23:13 -04:00
|
|
|
|
|
|
|
dockerConfigPath = ".docker/config.json"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2016-09-05 22:44:19 -04:00
|
|
|
auth types.AuthConfig
|
|
|
|
r *registry.Registry
|
2016-08-13 01:23:13 -04:00
|
|
|
)
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
// preload initializes any global options and configuration
|
|
|
|
// before the main or sub commands are run.
|
|
|
|
func preload(c *cli.Context) (err error) {
|
|
|
|
if c.GlobalBool("debug") {
|
|
|
|
logrus.SetLevel(logrus.DebugLevel)
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
if len(c.Args()) > 0 {
|
2016-09-20 21:16:34 -04:00
|
|
|
if c.Args()[0] != "help" {
|
|
|
|
auth, err = getAuthConfig(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the registry client
|
|
|
|
r, err = registry.New(auth, c.GlobalBool("debug"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
}
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
return nil
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2016-09-05 22:44:19 -04:00
|
|
|
app := cli.NewApp()
|
|
|
|
app.Name = "reg"
|
|
|
|
app.Version = VERSION
|
2016-09-30 14:09:10 -04:00
|
|
|
app.Author = "@jessfraz"
|
2016-09-05 22:44:19 -04:00
|
|
|
app.Email = "no-reply@butts.com"
|
|
|
|
app.Usage = "Docker registry v2 client."
|
|
|
|
app.Before = preload
|
|
|
|
app.Flags = []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "debug, d",
|
|
|
|
Usage: "run in debug mode",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "username, u",
|
|
|
|
Usage: "username for the registry",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "password, p",
|
|
|
|
Usage: "password for the registry",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "registry, r",
|
|
|
|
Usage: "URL to the provate registry (ex. r.j3ss.co)",
|
|
|
|
},
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
app.Commands = []cli.Command{
|
2016-09-20 21:16:34 -04:00
|
|
|
{
|
2016-10-13 11:32:00 -04:00
|
|
|
Name: "delete",
|
|
|
|
Aliases: []string{"rm"},
|
|
|
|
Usage: "delete a specific reference of a repository",
|
2016-09-20 21:16:34 -04:00
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
repo, ref, err := getRepoAndRef(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := r.Delete(repo, ref); err != nil {
|
2016-10-13 11:32:00 -04:00
|
|
|
return fmt.Errorf("Delete %s@%s failed: %v", repo, ref, err)
|
2016-09-20 21:16:34 -04:00
|
|
|
}
|
2016-09-20 21:59:56 -04:00
|
|
|
fmt.Printf("Deleted %s@%s\n", repo, ref)
|
2016-09-20 21:16:34 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
2016-09-05 22:44:19 -04:00
|
|
|
{
|
|
|
|
Name: "list",
|
|
|
|
Aliases: []string{"ls"},
|
|
|
|
Usage: "list all repositories",
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
// get the repositories via catalog
|
|
|
|
repos, err := r.Catalog()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Repositories for %s\n", auth.ServerAddress)
|
|
|
|
|
|
|
|
// setup the tab writer
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
|
|
|
|
|
|
|
// print header
|
|
|
|
fmt.Fprintln(w, "REPO\tTAGS")
|
|
|
|
|
|
|
|
for _, repo := range repos {
|
|
|
|
// get the tags and print to stdout
|
|
|
|
tags, err := r.Tags(repo)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, "%s\t%s\n", repo, strings.Join(tags, ", "))
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Flush()
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2016-09-20 21:16:34 -04:00
|
|
|
Name: "manifest",
|
|
|
|
Usage: "get the json manifest for the specific reference of a repository",
|
2016-09-05 22:44:19 -04:00
|
|
|
Action: func(c *cli.Context) error {
|
2016-09-20 21:16:34 -04:00
|
|
|
repo, ref, err := getRepoAndRef(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-09-05 22:44:19 -04:00
|
|
|
}
|
|
|
|
|
2016-09-20 21:16:34 -04:00
|
|
|
manifest, err := r.Manifest(repo, ref)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := json.MarshalIndent(manifest, " ", " ")
|
2016-09-05 22:44:19 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:16:34 -04:00
|
|
|
fmt.Println(string(b))
|
2016-09-05 22:44:19 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2016-09-20 21:16:34 -04:00
|
|
|
Name: "tags",
|
|
|
|
Usage: "get the tags for a repository",
|
2016-09-05 22:44:19 -04:00
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
if len(c.Args()) < 1 {
|
|
|
|
return fmt.Errorf("pass the name of the repository")
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:16:34 -04:00
|
|
|
tags, err := r.Tags(c.Args()[0])
|
2016-09-05 22:44:19 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// print the tags
|
2016-09-20 21:16:34 -04:00
|
|
|
fmt.Println(strings.Join(tags, "\n"))
|
2016-09-05 22:44:19 -04:00
|
|
|
|
2016-12-19 22:34:37 -05:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2016-12-20 00:13:34 -05:00
|
|
|
Name: "download",
|
|
|
|
Aliases: []string{"layer"},
|
|
|
|
Usage: "download a layer for the specific reference of a repository",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "output, o",
|
|
|
|
Usage: "output file, default to stdout",
|
|
|
|
},
|
|
|
|
},
|
2016-12-19 22:34:37 -05:00
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
repo, ref, err := getRepoAndRef(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-12-20 00:13:34 -05:00
|
|
|
layer, err := r.DownloadLayer(repo, digest.Digest(ref))
|
2016-12-19 22:34:37 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-12-20 00:13:34 -05:00
|
|
|
defer layer.Close()
|
2016-12-19 22:34:37 -05:00
|
|
|
|
2016-12-20 00:13:34 -05:00
|
|
|
b, err := ioutil.ReadAll(layer)
|
2016-12-19 22:34:37 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-12-20 00:13:34 -05:00
|
|
|
if c.String("output") != "" {
|
|
|
|
return ioutil.WriteFile(c.String("output"), b, 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprint(os.Stdout, string(b))
|
2016-12-19 22:34:37 -05:00
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
app.Run(os.Args)
|
|
|
|
}
|
2016-08-13 01:23:13 -04:00
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
func getAuthConfig(c *cli.Context) (types.AuthConfig, error) {
|
|
|
|
if c.GlobalString("username") != "" && c.GlobalString("password") != "" && c.GlobalString("registry") != "" {
|
|
|
|
return types.AuthConfig{
|
|
|
|
Username: c.GlobalString("username"),
|
|
|
|
Password: c.GlobalString("password"),
|
|
|
|
ServerAddress: c.GlobalString("registry"),
|
|
|
|
}, nil
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
dcfg, err := cliconfig.Load(cliconfig.ConfigDir())
|
|
|
|
if err != nil {
|
2016-09-05 22:44:19 -04:00
|
|
|
return types.AuthConfig{}, fmt.Errorf("Loading config file failed: %v", err)
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
|
|
|
|
// return error early if there are no auths saved
|
2016-08-13 01:23:13 -04:00
|
|
|
if !dcfg.ContainsAuth() {
|
2016-09-06 17:56:46 -04:00
|
|
|
if c.GlobalString("registry") != "" {
|
|
|
|
return types.AuthConfig{
|
|
|
|
ServerAddress: c.GlobalString("registry"),
|
|
|
|
}, nil
|
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
return types.AuthConfig{}, fmt.Errorf("No auth was present in %s, please pass a registry, username, and password", cliconfig.ConfigDir())
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
// if they passed a specific registry, return those creds _if_ they exist
|
|
|
|
if c.GlobalString("registry") != "" {
|
|
|
|
if creds, ok := dcfg.AuthConfigs[c.GlobalString("registry")]; ok {
|
|
|
|
return creds, nil
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
2016-09-05 22:44:19 -04:00
|
|
|
return types.AuthConfig{}, fmt.Errorf("No authentication credentials exist for %s", c.GlobalString("registry"))
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// set the auth config as the registryURL, username and Password
|
|
|
|
for _, creds := range dcfg.AuthConfigs {
|
2016-09-05 22:44:19 -04:00
|
|
|
return creds, nil
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
|
|
|
|
2016-09-05 22:44:19 -04:00
|
|
|
return types.AuthConfig{}, fmt.Errorf("Could not find any authentication credentials")
|
2016-08-13 01:23:13 -04:00
|
|
|
}
|
2016-09-20 21:16:34 -04:00
|
|
|
|
|
|
|
func getRepoAndRef(c *cli.Context) (repo, ref string, err error) {
|
|
|
|
if len(c.Args()) < 1 {
|
|
|
|
return "", "", errors.New("pass the name of the repository")
|
|
|
|
}
|
|
|
|
|
|
|
|
arg := c.Args()[0]
|
|
|
|
parts := []string{}
|
|
|
|
if strings.Contains(arg, "@") {
|
|
|
|
parts = strings.Split(c.Args()[0], "@")
|
|
|
|
} else if strings.Contains(arg, ":") {
|
|
|
|
parts = strings.Split(c.Args()[0], ":")
|
|
|
|
} else {
|
|
|
|
parts = []string{arg}
|
|
|
|
}
|
|
|
|
|
|
|
|
repo = parts[0]
|
|
|
|
ref = "latest"
|
|
|
|
if len(parts) > 1 {
|
|
|
|
ref = parts[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|