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) } }
func main() { switch config.Config.Command { case config.DELETE: api.Delete( *config.Config.Input["delete.repository"].(*string), *config.Config.Input["delete.tag"].(*string), *config.Config.Input["delete.dry"].(*bool), ) if true != *config.Config.Input["delete.dry"].(*bool) { fmt.Printf("Image %s:%s removed\n", *config.Config.Input["delete.repository"].(*string), *config.Config.Input["delete.tag"].(*string)) } case config.TOKEN: token, err := config.Config.TokenManager.GetToken(&http.AuthChallenge{ Service: *config.Config.Input["token.service"].(*string), Realm: *config.Config.Input["token.realm"].(*string), Scope: *config.Config.Input["token.scope"].(*string), }) logger.Logger.CheckError(err) fmt.Println("") fmt.Println("Expires: " + token.ExpireTime().Format("2006-01-02 15:04:05 UTC")) fmt.Println(token.Token) fmt.Println("") case config.LIST: table := helpers.NewTable("REPOSITORY", "TAG", "DATE", "AUTHOR", "SIZE(MB)") list := api.GetList() list.Sort() for _, info := range *list { for _, tag := range info.Tags { manifest := api.GetManifest(info.Name, tag, true) author := "" for _, history := range manifest.History { if value, exist := history.Unpack()["author"]; exist { author = value.(string) break } } time := time.Time{} time.UnmarshalText([]byte(manifest.History[len(manifest.History)-1].Unpack()["created"].(string))) table.AddRow( info.Name, tag, time.Format("2006-01-02 15:04:05.000000"), author, api.GetSize(info.Name, tag)/(1024*1024), ) } } table.Print() case config.SIZE: size := api.GetSize( *config.Config.Input["size.repository"].(*string), *config.Config.Input["size.tag"].(*string), ) fmt.Printf("\n%dMB\n", size/(1024*1024)) case config.HISTORY: manifest := api.GetManifest( *config.Config.Input["history.repository"].(*string), *config.Config.Input["history.tag"].(*string), true, ) if config.Config.Verbose { // Print json set to less path, err := exec.LookPath("less") logger.Logger.CheckError(err) cmd := exec.Command(path, "-egR") stdin, err := cmd.StdinPipe() logger.Logger.CheckError(err) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout for i := len(manifest.History) - 1; i >= 0; i-- { io.WriteString(stdin, string(manifest.History[i].Print().Bytes())) io.WriteString(stdin, "\n") } stdin.Close() err = cmd.Run() logger.Logger.CheckError(err) } else { // Print only date and command for i := len(manifest.History) - 1; i >= 0; i-- { data := manifest.History[i].Unpack() cmd := data["container_config"].(map[string]interface{})["Cmd"] if cmd != nil { line := "" for _, part := range cmd.([]interface{}) { line += part.(string) + " " } time := time.Time{} time.UnmarshalText([]byte(data["created"].(string))) fmt.Printf("[%s] %s\n", time.Format("2006-01-02 15:04:05.000000"), line) } } } case config.REPOSITORIES: repositories := api.GetRepositories() table := helpers.NewTable("REPOSITORIES") for _, name := range repositories.Images { table.AddRow(name) } table.Print() case config.TAGS: tags := api.GetTags(*config.Config.Input["tag.repository"].(*string)) if tags != nil { table := helpers.NewTable("TAGS(" + *config.Config.Input["tag.repository"].(*string) + ")") for _, name := range tags.Tags { table.AddRow(name) } table.Print() } } }