package main import ( "flag" "fmt" "encoding/json" "io/ioutil" "github.com/gorilla/mux" "log" "net/http" "crypto/tls" ) var ( registryType = flag.String("type", "quay.io", "Registry type (distribution|quay.io)") ignoreCertErrors = flag.Bool("ignoreCertErrors", true, "Ignore certificate errors") listenAddr = flag.String("listen", ":80", "HTTP service address") namespace = flag.String("namespace", "", "Registry namespace (if needed)") registryServer = flag.String("registry", "https://registry", "Docker registry endpoint") ) type repositoryEntry struct { Namespace string `json:"namespsace"` Name string `json:"name"` Description string `json:"description"` Link string `json:"link"` Tags []string `json:"tags"` } func MakeHttpRequest(url string) ([]byte, error) { // get an HTTP client and optionally allow bad certificates tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: *ignoreCertErrors}, } client := &http.Client{Transport: tr} res, err := client.Get(url) if err != nil { return nil, err } content, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { return nil, err } return content, nil } func Handle_Quay_AllRepos(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") responseData := struct{ Repositories []repositoryEntry `json:"repositories"` }{} // // get all of the repositories // url1 := fmt.Sprintf("%s/api/v1/repository?public=true&namespace=%s", *registryServer, *namespace) content, err := MakeHttpRequest(url1) if err != nil { log.Fatal(err) } repositories := struct{ Repositories []map[string]interface{} `json:"repositories"` }{} err = json.Unmarshal([]byte(content), &repositories) if err != nil { log.Fatal(err) } // // now, for each repository, get the available tags and add // everything to the response data map // for _, each := range repositories.Repositories { ns := each["namespace"].(string) name := each["name"].(string) link := fmt.Sprintf("%s/repository/%s/%s", *registryServer, ns, name) desc := "" if each["description"] != nil { desc = each["description"].(string) } url2 := fmt.Sprintf("%s/api/v1/repository/%s/%s/tag/", *registryServer, ns, name) content, err := MakeHttpRequest(url2) if err != nil { log.Fatal(err) } tagsSourceData := struct{ HasAdditional bool `json:"has_additional"` Page int `json:"page"` Tags []map[string]interface{} `json:"tags"` }{} err = json.Unmarshal([]byte(content), &tagsSourceData) if err != nil { log.Fatal(err) } tags := []string{} for _, each := range tagsSourceData.Tags { tagName := each["name"].(string) tags = append(tags, tagName) } // // add to the response // responseData.Repositories = append(responseData.Repositories, repositoryEntry{ ns, name, desc, link, tags }) } enc := json.NewEncoder(w) enc.Encode(responseData) } func Handle_Distribution_AllRepos(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") responseData := struct{ Repositories []repositoryEntry `json:"repositories"` }{} // // get all of the repositories // url1 := fmt.Sprintf("%s/v2/_catalog", *registryServer) content, err := MakeHttpRequest(url1) if err != nil { log.Fatal(err) } repositories := struct{ Repositories []string `json:"repositories"` }{} err = json.Unmarshal([]byte(content), &repositories) if err != nil { log.Fatal(err) } // // now, for each repository, get the available tags and add // everything to the response data map // for _, each := range repositories.Repositories { url2 := fmt.Sprintf("%s/v2/%s/tags/list", *registryServer, each) content, err := MakeHttpRequest(url2) if err != nil { log.Fatal(err) } tags := struct{ Name string `json:"name"` Tags []string `json:"tags"` }{} err = json.Unmarshal([]byte(content), &tags) if err != nil { log.Fatal(err) } // // add to the response // responseData.Repositories = append(responseData.Repositories, repositoryEntry{ "", tags.Name, "", "", tags.Tags }) } enc := json.NewEncoder(w) enc.Encode(responseData) } //func Handle_Distribution_RegistryRepositories(w http.ResponseWriter, r *http.Request) { // w.Header().Set("Content-Type", "application/json") // url := fmt.Sprintf("%s/v2/_catalog", *registryServer) // content, err := MakeHttpRequest(url) // if err != nil { // log.Fatal(err) // } // w.Write(content) //} // //func Handle_Distribution_RepositoryTags(w http.ResponseWriter, r *http.Request) { // w.Header().Set("Content-Type", "application/json") // repository_param := mux.Vars(r)["repository"] // url := fmt.Sprintf("%s/v2/%s/tags/list", *registryServer, repository_param) // content, err := MakeHttpRequest(url) // if err != nil { // log.Fatal(err) // } // w.Write(content) //} // //func Handle_Distribution_RepositoryManifest(w http.ResponseWriter, r *http.Request) { // w.Header().Set("Content-Type", "application/json") // repository_param := mux.Vars(r)["repository"] // tag_param := mux.Vars(r)["tag"] // url := fmt.Sprintf("%s/v2/%s/manifests/%s", *registryServer, repository_param, tag_param) // content, err := MakeHttpRequest(url) // if err != nil { // log.Fatal(err) // } // w.Write(content) //} func main() { flag.Parse() log.Println("Listening on:", *listenAddr) log.Println("Registry server:", *registryServer) log.Println("Ignore certificate errors:", *ignoreCertErrors) log.Println("Registry type:", *registryType) // // handle routes for access to data // r := mux.NewRouter() s := r.Methods("GET").PathPrefix("/registry").Subrouter() // combined list of repositories and available tags if *registryType == "quay.io" { s.HandleFunc("/all", Handle_Quay_AllRepos) } else if *registryType == "distribution" { s.HandleFunc("/all", Handle_Distribution_AllRepos) } else { log.Fatal("No valid registry type specified!") } // list of repositories //s.HandleFunc("/repositories", Handle_RegistryRepositories) // single repository //s.HandleFunc("/{repository}/tags", Handle_RepositoryTags) //s.HandleFunc("/{repository}/manifests/{tag}", Handle_RepositoryManifest) http.Handle("/registry/", r) // // handle static content // http.Handle("/", http.FileServer(http.Dir("./static/"))) // GO! err := http.ListenAndServe(*listenAddr, nil) if err != nil { log.Fatal("http.ListenAndServe: ", err) } }