├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── main.go ├── optparse.go └── registry.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM google/golang 2 | 3 | # these are useful for "go get" 4 | RUN apt-get update && apt-get install -yq bzr git mercurial 5 | 6 | RUN go get github.com/dustin/go-humanize 7 | 8 | WORKDIR /gopath/src/app 9 | ADD . /gopath/src/app/ 10 | RUN go install app 11 | 12 | # RUNTIME CONFIG 13 | # Set the following here in the Dockerfile 14 | # or use docker run -e USER_CREDS=username:password 15 | # (the -e option is safer, just in case you 16 | # accidentally ever push this image to the Index) 17 | # ENV USER_CREDS=username:password 18 | CMD [] 19 | ENTRYPOINT ["/gopath/bin/app"] 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sam Alba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | docker-registry-debug 2 | ===================== 3 | 4 | Usage: ./registry-debug [options] command 5 | 6 | options: 7 | -i="https://index.docker.io": override index endpoint 8 | -q=false: disable debug logs 9 | -r="": override registry endpoint 10 | 11 | commands: 12 | info : lookup repos meta-data 13 | layerinfo : lookup layer meta-data 14 | curlme : print a curl command for fetching the layer 15 | 16 | 17 | Examples: 18 | 19 | ./registry-debug info ubuntu 20 | ./registry-debug layerinfo ubuntu 3db9c44f45209632d6050b35958829c3a2aa256d81b9a7be45b362ff85c54710 21 | ./registry-debug curlme ubuntu 3db9c44f45209632d6050b35958829c3a2aa256d81b9a7be45b362ff85c54710 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dustin/go-humanize" 6 | "os" 7 | "strings" 8 | "text/tabwriter" 9 | ) 10 | 11 | var Quiet = false 12 | 13 | func fatal(msg error) { 14 | fmt.Fprintf(os.Stderr, "%s\n", msg) 15 | os.Exit(1) 16 | } 17 | 18 | func log(format string, args ...interface{}) { 19 | if Quiet == true { 20 | return 21 | } 22 | format = fmt.Sprintf("# %s\n", format) 23 | fmt.Fprintf(os.Stderr, format, args...) 24 | } 25 | 26 | func readUserCredentials() (string, string) { 27 | log("Reading user/passwd from env var \"USER_CREDS\"") 28 | v := os.Getenv("USER_CREDS") 29 | if v == "" { 30 | log("No password provided, disabling auth") 31 | return "", "" 32 | } 33 | user := strings.SplitN(v, ":", 2) 34 | if len(user) < 2 { 35 | return user[0], "" 36 | } 37 | return user[0], user[1] 38 | } 39 | 40 | func initRegistry(config *Config, reposName string) (*Registry, string) { 41 | registry, e := NewRegistry(config.IndexDomain, config.RegistryDomain) 42 | if e != nil { 43 | fatal(e) 44 | } 45 | registry.Logger = log 46 | user, passwd := readUserCredentials() 47 | log("Getting token from %s", config.IndexDomain) 48 | token, e := registry.GetToken(user, passwd, reposName) 49 | if e != nil { 50 | fatal(e) 51 | } 52 | return registry, token 53 | } 54 | 55 | func CmdInfo(config *Config, args []string) { 56 | registry, token := initRegistry(config, args[0]) 57 | tags, err := registry.ReposTags(token, args[0]) 58 | if err != nil { 59 | fatal(err) 60 | } 61 | w := new(tabwriter.Writer) 62 | w.Init(os.Stdout, 0, 0, 4, ' ', 0) 63 | fmt.Println("- Repository:", args[0]) 64 | fmt.Println("- Tags:") 65 | for k, v := range tags { 66 | fmt.Fprintf(w, "\t%s\t%s\n", k, v) 67 | } 68 | w.Flush() 69 | } 70 | 71 | func CmdLayerInfo(config *Config, args []string) { 72 | registry, token := initRegistry(config, args[0]) 73 | info, err := registry.LayerJson(token, args[1]) 74 | if err != nil { 75 | fatal(err) 76 | } 77 | ancestry, err := registry.LayerAncestry(token, args[1]) 78 | if err != nil { 79 | fatal(err) 80 | } 81 | w := new(tabwriter.Writer) 82 | w.Init(os.Stdout, 0, 0, 4, ' ', 0) 83 | fmt.Fprintf(w, "- Id\t%s\n", info.Id) 84 | fmt.Fprintf(w, "- Parent\t%s\n", info.Parent) 85 | fmt.Fprintf(w, "- Size\t%s\n", humanize.Bytes(uint64(info.Size))) 86 | fmt.Fprintf(w, "- Created\t%s\n", info.Created) 87 | fmt.Fprintf(w, "- DockerVersion\t%s\n", info.DockerVersion) 88 | fmt.Fprintf(w, "- Author\t%s\n", info.Author) 89 | fmt.Fprintf(w, "- Ancestry:") 90 | for _, id := range *ancestry { 91 | fmt.Fprintf(w, "\t%s\n", id) 92 | } 93 | w.Flush() 94 | } 95 | 96 | func CmdCurlme(config *Config, args []string) { 97 | registry, token := initRegistry(config, args[0]) 98 | fmt.Printf("curl -i --location-trusted -I -X GET -H \"Authorization: Token %s\" %s/v1/images/%s/layer\n", 99 | token, registry.RegistryHost, args[1]) 100 | } 101 | 102 | func main() { 103 | OptParse() 104 | } 105 | -------------------------------------------------------------------------------- /optparse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type Config struct { 10 | IndexDomain string 11 | RegistryDomain string 12 | } 13 | 14 | type command struct { 15 | name string 16 | argsdesc string 17 | desc string 18 | nargs int 19 | fn func(*Config, []string) 20 | } 21 | 22 | var commands = [...]command{ 23 | command{"info", "", "lookup repos meta-data", 1, CmdInfo}, 24 | command{"layerinfo", " ", "lookup layer meta-data", 2, CmdLayerInfo}, 25 | command{"curlme", " ", "print a curl command for fetching the layer", 2, CmdCurlme}, 26 | } 27 | 28 | func OptParse() { 29 | flag.Usage = func() { 30 | fmt.Fprintf(os.Stderr, "Usage: %s [options] command\n", os.Args[0]) 31 | fmt.Fprintln(os.Stderr, "") 32 | fmt.Fprintln(os.Stderr, "options:") 33 | flag.PrintDefaults() 34 | fmt.Fprintln(os.Stderr, "") 35 | fmt.Fprintln(os.Stderr, "commands:") 36 | for _, c := range commands { 37 | fmt.Fprintf(os.Stderr, " %s %s: %s\n", c.name, c.argsdesc, c.desc) 38 | } 39 | } 40 | 41 | config := &Config{} 42 | flag.StringVar(&config.IndexDomain, "i", "https://index.docker.io", "override index endpoint") 43 | flag.StringVar(&config.RegistryDomain, "r", "", "override registry endpoint") 44 | flag.BoolVar(&Quiet, "q", false, "disable debug logs") 45 | flag.Parse() 46 | 47 | for _, c := range commands { 48 | if flag.Arg(0) == c.name { 49 | args := flag.Args()[1:] 50 | if len(args) != c.nargs { 51 | s := "" 52 | if c.nargs > 1 { 53 | s = "s" 54 | } 55 | fmt.Fprintf(os.Stderr, "%s takes %d argument%s: %s\n", c.name, c.nargs, s, c.argsdesc) 56 | os.Exit(2) 57 | } 58 | c.fn(config, args) 59 | return 60 | } 61 | } 62 | flag.Usage() 63 | os.Exit(2) 64 | } 65 | -------------------------------------------------------------------------------- /registry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "strconv" 10 | ) 11 | 12 | type Registry struct { 13 | Url *url.URL 14 | Host string 15 | RegistryHost string 16 | Logger func(format string, args ...interface{}) 17 | client *http.Client 18 | } 19 | 20 | type LayerJson struct { 21 | Id string 22 | Parent string 23 | DockerVersion string `json:"docker_version"` 24 | Created string 25 | Author string 26 | Size int 27 | } 28 | 29 | func NewRegistry(endpoint string, registryDomain string) (*Registry, error) { 30 | u, e := url.Parse(endpoint) 31 | if e != nil { 32 | return nil, e 33 | } 34 | if u.Host == "" { 35 | u.Host = u.Path 36 | } 37 | origUrl := u 38 | host := fmt.Sprintf("%s://%s", u.Scheme, u.Host) 39 | registryHost := "" 40 | if registryDomain != "" { 41 | u, e = url.Parse(registryDomain) 42 | if e != nil { 43 | return nil, e 44 | } 45 | if u.Host == "" { 46 | u.Host = u.Path 47 | } 48 | registryHost = fmt.Sprintf("%s://%s", u.Scheme, u.Host) 49 | } 50 | client := &http.Client{} 51 | return &Registry{ 52 | Url: origUrl, 53 | Host: host, 54 | RegistryHost: registryHost, 55 | client: client}, nil 56 | } 57 | 58 | func (reg *Registry) log(format string, args ...interface{}) { 59 | if reg.Logger == nil { 60 | return 61 | } 62 | reg.Logger(format, args...) 63 | } 64 | 65 | func (reg *Registry) GetToken(username string, password string, reposName string) (string, error) { 66 | u := fmt.Sprintf("%s/v1/repositories/%s/images", reg.Host, reposName) 67 | req, e := http.NewRequest("GET", u, nil) 68 | if e != nil { 69 | return "", e 70 | } 71 | if username != "" && password != "" { 72 | req.SetBasicAuth(username, password) 73 | } 74 | req.Header.Add("X-Docker-Token", "true") 75 | res, e := reg.client.Do(req) 76 | if e != nil { 77 | return "", e 78 | } 79 | defer res.Body.Close() 80 | if res.StatusCode != 200 { 81 | return "", fmt.Errorf("HTTP Error: %s", res.Status) 82 | } 83 | if reg.RegistryHost == "" { 84 | reg.RegistryHost = fmt.Sprintf("%s://%s", reg.Url.Scheme, res.Header.Get("X-Docker-Endpoints")) 85 | reg.log("Got registry endpoint from the server: %s", reg.RegistryHost) 86 | } else { 87 | reg.log("Registry endpoint overridden to %s", reg.RegistryHost) 88 | } 89 | token := res.Header.Get("X-Docker-Token") 90 | reg.log("Got token: %s", token) 91 | return token, nil 92 | } 93 | 94 | func (reg *Registry) doGet(token string, url string) (*http.Response, error) { 95 | req, e := http.NewRequest("GET", url, nil) 96 | if e != nil { 97 | return nil, e 98 | } 99 | req.Header.Add("Authorization", fmt.Sprintf("Token %s", token)) 100 | res, e := reg.client.Do(req) 101 | if e != nil { 102 | return nil, e 103 | } 104 | if res.StatusCode != 200 { 105 | res.Body.Close() 106 | return nil, fmt.Errorf("HTTP Error: %s", res.Status) 107 | } 108 | return res, nil 109 | } 110 | 111 | func (reg *Registry) ReposTags(token string, reposName string) (map[string]string, error) { 112 | u := fmt.Sprintf("%s/v1/repositories/%s/tags", reg.RegistryHost, reposName) 113 | res, e := reg.doGet(token, u) 114 | if e != nil { 115 | return nil, e 116 | } 117 | defer res.Body.Close() 118 | rawJSON, err := ioutil.ReadAll(res.Body) 119 | if err != nil { 120 | return nil, err 121 | } 122 | result := make(map[string]string) 123 | if err := json.Unmarshal(rawJSON, &result); err != nil { 124 | return nil, err 125 | } 126 | return result, nil 127 | } 128 | 129 | func (reg *Registry) LayerJson(token string, layerId string) (*LayerJson, error) { 130 | u := fmt.Sprintf("%s/v1/images/%s/json", reg.RegistryHost, layerId) 131 | res, e := reg.doGet(token, u) 132 | if e != nil { 133 | return nil, e 134 | } 135 | defer res.Body.Close() 136 | rawJSON, err := ioutil.ReadAll(res.Body) 137 | if err != nil { 138 | return nil, err 139 | } 140 | result := &LayerJson{} 141 | if err := json.Unmarshal(rawJSON, &result); err != nil { 142 | return nil, err 143 | } 144 | result.Size, _ = strconv.Atoi(res.Header.Get("X-Docker-Size")) 145 | return result, nil 146 | } 147 | 148 | func (reg *Registry) LayerAncestry(token string, layerId string) (*[]string, error) { 149 | u := fmt.Sprintf("%s/v1/images/%s/ancestry", reg.RegistryHost, layerId) 150 | res, e := reg.doGet(token, u) 151 | if e != nil { 152 | return nil, e 153 | } 154 | defer res.Body.Close() 155 | rawJSON, err := ioutil.ReadAll(res.Body) 156 | if err != nil { 157 | return nil, err 158 | } 159 | result := new([]string) 160 | if err := json.Unmarshal(rawJSON, &result); err != nil { 161 | return nil, err 162 | } 163 | return result, nil 164 | } 165 | --------------------------------------------------------------------------------