func GetManifest(repository, tag string, exit bool) *Manifest { if manifest, ok := manifests[repository][tag]; ok { return manifest } url := fmt.Sprintf("%s/v2/%s/manifests/%s", config.Config.RegistryHost, repository, tag) logger.Logger.Debug("Requesting url: " + url) req := http.NewRequest(url) resp, err := req.Do() logger.Logger.CheckError(err) resp, err = resolve(resp, exit) if !exit && nil == resp { return nil } body, err := ioutil.ReadAll(resp.Body) logger.Logger.CheckError(err) manifest := NewManifest() err = json.Unmarshal(body, manifest) logger.Logger.CheckError(err) manifest.Digest = resp.Header.Get("Docker-Content-Digest") if _, ok := manifests[repository]; !ok { manifests[repository] = make(map[string]*Manifest, 0) } manifests[repository][tag] = manifest return manifest }
func GetSize(repository, tag string) int { manifest := GetManifest(repository, tag, true) var wg sync.WaitGroup var size int for _, layer := range manifest.FsLayers { wg.Add(1) go func(digest string) { url := fmt.Sprintf("%s/v2/%s/blobs/%s", config.Config.RegistryHost, repository, digest) logger.Logger.Debug("Requesting HEAD for url: " + url) req := http.NewRequest(url) req.SetMethod("HEAD") resp, err := req.Do() logger.Logger.CheckError(err) resp, err = resolve(resp, true) logger.Logger.CheckError(err) // resp.Header.Get("Accept-Ranges") length, err := strconv.Atoi(resp.Header.Get("Content-Length")) logger.Logger.CheckError(err) size += length wg.Done() }(layer["blobSum"]) } wg.Wait() return size }
// see: https://docs.docker.com/registry/spec/api/#listing-repositories func GetRepositories() *Repositories { url := config.Config.RegistryHost + "/v2/_catalog" logger.Logger.Debug("Requesting url: " + url) req := http.NewRequest(url) resp, err := req.Do() logger.Logger.CheckError(err) resp, err = resolve(resp, true) body, err := ioutil.ReadAll(resp.Body) logger.Logger.CheckError(err) repository := &Repositories{} err = json.Unmarshal(body, repository) logger.Logger.CheckError(err) return repository }
func GetTags(repository string) *Tag { url := fmt.Sprintf("%s/v2/%s/tags/list", config.Config.RegistryHost, repository) logger.Logger.Debug("Requesting url: " + url) req := http.NewRequest(url) resp, err := req.Do() logger.Logger.CheckError(err) resp, err = resolve(resp, true) body, err := ioutil.ReadAll(resp.Body) logger.Logger.CheckError(err) tag := &Tag{} err = json.Unmarshal(body, tag) logger.Logger.CheckError(err) return tag }
// ApiCheck will check if the server implements the registry api v2 // If authentication (when needed) fails or server returns no 200 // or 401 status code it wil exit, // see: https://docs.docker.com/registry/spec/api/#api-version-check func ApiCheck() { logger.Logger.Debug("Server version check....") url := config.Config.RegistryHost + "/v2/" logger.Logger.Debug("Requesting HEAD for url: " + url) req := http.NewRequest(url) req.SetMethod("HEAD") req.AddHeader("Host", req.Raw().Host) resp, err := req.Do() logger.Logger.CheckError(err) logger.Logger.Debug("The registry implements the V2(.1) registry API.") if resp.StatusCode == 401 { logger.Logger.Debug("Server required authentication.") } resp, err = resolve(resp, true) if resp.StatusCode == 200 { logger.Logger.Debug("Authentication success.") } }
func Delete(repository, tag string, dry bool) { if dry { fmt.Println("Running a dry-run.") } var wg sync.WaitGroup blobs := NewLockedBlobList() for _, repos := range *GetList() { for _, tagName := range repos.Tags { if repos.Name == repository && tagName == tag { continue } wg.Add(1) go func(list *lockedBlobList, repos, tag string) { manifests := GetManifest(repos, tag, true) for _, blob := range manifests.FsLayers { list.add(blob["blobSum"], repos, tag) } wg.Done() }(blobs, repos.Name, tagName) } } wg.Wait() manifest := GetManifest(repository, tag, true) remove := blobList(make(map[string][]string, 0)) if true != dry { for _, layer := range manifest.FsLayers { if false == blobs.has(layer["blobSum"]) { logger.Logger.Debug(fmt.Sprintf("Adding layer %s to queued for removal", layer["blobSum"])) remove.add(layer["blobSum"], repository, tag) } else { logger.Logger.Debug(fmt.Sprintf("Skipping layer %s is used by: %s", layer["blobSum"], strings.Join(blobs.get(layer["blobSum"]), ", "))) } } } else { // For dry run we just print a overview table := helpers.NewTable("DIGEST", "REMOVING", "USED BY") for _, layer := range manifest.FsLayers { if false == blobs.has(layer["blobSum"]) { table.AddRow(layer["blobSum"], true) } else { table.AddRow(layer["blobSum"], false, strings.Join(blobs.get(layer["blobSum"]), ", ")) } } table.Print() } if true != dry { deleteBlob := func(digest string) { url := fmt.Sprintf("%s/v2/%s/blobs/%s", config.Config.RegistryHost, repository, digest) logger.Logger.Debug("Requesting DELETE for url: " + url) req := http.NewRequest(url) req.SetMethod("DELETE") resp, err := req.Do() logger.Logger.CheckError(err) resp, err = resolve(resp, false) logger.Logger.CheckWarning(err) if err != nil { logger.Logger.Debug("Removed: " + digest) } wg.Done() } first := true for layer, _ := range remove { wg.Add(1) // first one not so token can be cached if first { deleteBlob(layer) first = false } else { go deleteBlob(layer) } } wg.Wait() url := fmt.Sprintf("%s/v2/%s/manifests/%s", config.Config.RegistryHost, repository, manifest.Digest) logger.Logger.Debug("Requesting DELETE for url: " + url) req := http.NewRequest(url) req.SetMethod("DELETE") resp, err := req.Do() resp, err = resolve(resp, true) logger.Logger.CheckError(err) } }