如何快速删除harbor镜像

简介: 如何快速删除harbor镜像

背景


最近在巡检过程中,发现harbor存储空间使用率已经达到了80%。于是,去看了一下各项目下的镜像标签数。发现有个别项目下的镜像标签数竟然有好几百个。细问之下得知,该项目目前处于调试阶段,每天调试很多次。既然存储空间不多了,那就去harbor上删除掉之前的镜像标签,保留最近的几个就好了。在手动删除的过程中,发现几百个,每页才展示十个。我得先按照推送时间排序,然后一页一页的删除。心想着这种情况经历一次就好了,不要再有下一次。后来,仔细想想,这个也是不好控制的,每次巡检发现了就得手动删除太麻烦。所以就打算写一个脚本,每次通过脚本去删除镜像的标签,保留最近的几个就好了。刚好最近在学习golang,就用它来写就好了。比较尴尬的是,我脚本写完了,测试没问题后,发现新版本harbor已经可以在UI上设置保留策略了。自我安慰一下,就当作是一种练习、尝试好了!


目标


  1. 通过命令行能够查询当前所有的项目、无论是否公开、仓库数量
  2. 通过命令行能够查询项目下的仓库名和镜像名、拉取次数
  3. 在命令行能够指定标签和保留个数进行删除镜像标签
  4. 能够获取镜像的标签数
  5. 删除后,不支持立刻垃圾清理,请手动进行垃圾清理(考虑清理过程中无法推拉镜像)


声明


  1. 该脚本纯属个人练习所写,不构成任何建议
  2. 初学golang,仅仅是为了实现目标,代码质量极差,请谅解
  3. 本次使用的harbor是v2.3.1
  4. 全部代码请移步至github


实现


  1. 获取harbor中所有的项目,API可通过harbor的 swagger获取


//根据harbor swagger测试出来的结果定义要获取的数据结构
type MetaData struct {
 Public string `json:"public"`
}
type ProjectData struct {
 MetaData MetaData `json:"metadata"`
 ProjectId int `json:"project_id"`
 Name string `json:"name"`
 RepoCount int `json:"repo_count"`
}
type PData []ProjectData
// 提供harbor地址获取project
func GetProject(url string) []map[string]string {
 //定义url
 url = url + "/api/v2.0/projects"
 //url = url + "/api/projects"
    // 构造请求
 request, _ := http.NewRequest(http.MethodGet, url,nil)
    //取消验证
 tr := &http.Transport{
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 }
    //定义客户端
 client := &http.Client{Timeout: 10 * time.Second, Transport: tr}
 //client := &http.Client{Timeout: 10 * time.Second}
 request.Header.Set("accept", "application/json")
    //设置用户和密码
 request.SetBasicAuth("admin", "Harbor12345")
 response, err := client.Do(request)
 if err != nil {
  fmt.Println("excute failed")
  fmt.Println(err)
 }
 // 获取body
 body, _ := ioutil.ReadAll(response.Body)
 defer response.Body.Close()
 ret := PData{}
 json.Unmarshal([]byte(string(body)), &ret)
 var ps = []map[string]string{}
    // 获取返回的数据
 for i := 0; i < len(ret); i++ {
  RData := make(map[string]string)
  RData["name"] = (ret[i].Name)
  RData["project_id"] = strconv.Itoa(ret[i].ProjectId)
  RData["repo_count"] =strconv.Itoa(ret[i].RepoCount)
  RData["public"] = ret[i].MetaData.Public
  ps = append(ps, RData)
 }
 return ps
}


  1. 获取项目下的repo


// 定义要获取的数据结构
type ReposiData struct {
 Id int `json:"id"`
 Name string `json:"name"`
 ProjectId int `json:"project_id"`
 PullCount int `json:"pull_count"`
}
type RepoData []ReposiData
//通过提供harbor地址和对应的项目来获取项目下的repo
func GetRepoData(url string, proj string)  []map[string]string {
 // /api/v2.0/projects/goharbor/repositories
 url = url + "/api/v2.0/projects/" + proj +  "/repositories"
    //构造请求
 request, _ := http.NewRequest(http.MethodGet, url,nil)
    //忽略认证
 tr := &http.Transport{
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 }
 client := &http.Client{Timeout: 10 * time.Second, Transport: tr}
 request.Header.Set("accept", "application/json")
    //设置用户名和密码
 request.SetBasicAuth("admin", "Harbor12345")
 response, err := client.Do(request)
 if err != nil {
  fmt.Println("excute failed")
  fmt.Println(err)
 }
 // 获取body
 body, _ := ioutil.ReadAll(response.Body)
 defer response.Body.Close()
 ret := RepoData{}
 json.Unmarshal([]byte(string(body)), &ret)
 var ps = []map[string]string{}
    // 获取返回的数据
 for i := 0; i < len(ret); i++ {
  RData := make(map[string]string)
  RData["name"] = (ret[i].Name)
  pId := strconv.Itoa(ret[i].ProjectId)
  RData["project_id"] = pId
  RData["id"] =(strconv.Itoa(ret[i].Id))
  RData["pullCount"] = (strconv.Itoa(ret[i].PullCount))
  ps = append(ps, RData)
 }
 return ps
}


  1. 镜像tag操作


//定义要获取的tag数据结构
type Tag struct {
 ArtifactId int  `json:"artifact_id"`
 Id int `json:"id"`
 Name string `json:"name"`
 RepositoryId int `json:"repository_id"`
 PushTimte string `json:"push_time"`
}
type Tag2 struct {
 ArtifactId string  `json:"artifact_id"`
 Id string `json:"id"`
 Name string `json:"name"`
 RepositoryId string `json:"repository_id"`
 PushTimte string `json:"push_time"`
}
type Tag2s []Tag2
// delete tag by specified count,这里通过count先获取要删除的tag列表
func DeleTagsByCount(tags []map[string]string ,count int) []string {
 var re []string
 tt := tags[0]["tags"]
 ss := Tag2s{}
 json.Unmarshal([]byte(tt), &ss)
 // have a sort
 for i := 0; i < len(ss); i++ {
  for j := i + 1; j < len(ss); j++ {
            //根据pushtime进行排序
   if ss[i].PushTimte > ss[j].PushTimte {
    ss[i], ss[j] = ss[j], ss[i]
   }
  }
 }
 // get all tags
 for i := 0; i < len(ss); i++ {
  re = append(re, ss[i].Name)
 }
 // 返回count个会被删除的tag,
 return re[0:count]
}
// delete tag by specified tag 删除指定的tag
func DelTags(url string, project string, repo string, tag string) (int, map[string]interface{})  {
 url = url + "/api/v2.0/projects/" + project + "/repositories/" + repo + "/artifacts/" + tag + "/tags/" + tag
 request, _ := http.NewRequest(http.MethodDelete, url,nil)
 tr := &http.Transport{
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 }
 client := &http.Client{Timeout: 10 * time.Second, Transport: tr}
 request.Header.Set("accept", "application/json")
 request.SetBasicAuth("admin", "Pwd123456")
    // 执行删除tag
 response,_ := client.Do(request)
 defer response.Body.Close()
 var result map[string]interface{}
 bd, err := ioutil.ReadAll(response.Body)
 if err == nil {
  err = json.Unmarshal(bd, &result)
 }
 return response.StatusCode,result
}
//定义要获取的tag数据结构
type ArtiData struct {
 Id int `json:"id"`
 ProjectId int `json:"project_id"`
 RepositoryId int `json:"repository_id"`
 //Digest string `json:"digest"`
 Tags []Tag `json:"tags"`
}
type AData []ArtiData
// 根据harbor地址、project和repo获取tag数据
func GetTags(url string, project string, repo string) []map[string]string {
 url = url + "/api/v2.0/projects/" + project + "/repositories/" + repo + "/artifacts"
 request, _ := http.NewRequest(http.MethodGet, url,nil)
 tr := &http.Transport{
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 }
 client := &http.Client{Timeout: 10 * time.Second, Transport: tr}
 request.Header.Set("accept", "application/json")
 request.Header.Set("X-Accept-Vulnerabilities", "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0")
 request.SetBasicAuth("admin", "Harbor12345")
    // 获取tag
 response, err := client.Do(request)
 if err != nil {
  fmt.Println("excute failed")
  fmt.Println(err)
 }
 body, _ := ioutil.ReadAll(response.Body)
 defer response.Body.Close()
 ret := AData{}
 json.Unmarshal([]byte(string(body)),&ret)
 var ps = []map[string]string{}
 sum := 0
 RData := make(map[string]string)
 RData["name"] = repo
    // 获取返回的数据
 for i := 0; i < len(ret); i++ {
  RData["id"] = (strconv.Itoa(ret[i].Id))
  RData["project_id"] = (strconv.Itoa(ret[i].ProjectId))
  RData["repository_id"] =(strconv.Itoa(ret[i].RepositoryId))
  //RData["digest"] = ret[i].Digest
  var tdata = []map[string]string{}
  sum = len((ret[i].Tags))
        // 获取tag
  for j := 0; j < len((ret[i].Tags)); j++ {
   TagData := make(map[string]string)
   TagData["artifact_id"] = strconv.Itoa((ret[i].Tags)[j].ArtifactId)
   TagData["id"] = strconv.Itoa((ret[i].Tags)[j].Id)
   TagData["name"] = (ret[i].Tags)[j].Name
   TagData["repository_id"] = strconv.Itoa((ret[i].Tags)[j].RepositoryId)
   TagData["push_time"] = (ret[i].Tags)[j].PushTimte
   tdata = append(tdata, TagData)
  }
  RData["count"] = strconv.Itoa(sum)
  ss, err := json.Marshal(tdata)
  if err != nil {
   fmt.Println("failed")
   os.Exit(2)
  }
  RData["tags"] = string(ss)
  ps = append(ps, RData)
 }
 return ps
}


  1. 获取用户命令行输入,列出harbor中所有的项目


// 定义获取harbor中project的相关命令操作
var projectCmd = &cobra.Command{
 Use: "project",
 Short: "to operator project",
 Run: func(cmd *cobra.Command, args []string) {
  output, err := ExecuteCommand("harbor","project", args...)
  if err != nil {
   Error(cmd,args, err)
  }
  fmt.Fprint(os.Stdout, output)
 },
}
// project list
var projectLsCmd = &cobra.Command{
 Use: "ls",
 Short: "list  all project",
 Run: func(cmd *cobra.Command, args []string) {
  url, _ := cmd.Flags().GetString("url")
  if len(url) == 0 {
   fmt.Println("url is null,please specified the harbor url first !!!!")
   os.Exit(2)
  }
        // 获取所有的project
  output := harbor.GetProject(url)
  fmt.Println("项目名 访问级别 仓库数量")
  for i := 0; i < len(output); i++ {
   fmt.Println(output[i]["name"], output[i]["public"], output[i]["repo_count"])
  }
 },
}
// init
func init() {
    // ./harbor project ls -u https://
 rootCmd.AddCommand(projectCmd)
 projectCmd.AddCommand(projectLsCmd)
 projectLsCmd.Flags().StringP("url", "u", "","defaults: [https://127.0.0.1]")
}


  1. 获取repo列表


// repo command
var repoCmd = &cobra.Command{
 Use: "repo",
 Short: "to operator repository",
 Run: func(cmd *cobra.Command, args []string) {
  output, err := ExecuteCommand("harbor","repo", args...)
  if err != nil {
   Error(cmd,args, err)
  }
  fmt.Fprint(os.Stdout, output)
 },
}
// repo list
var repoLsCmd = &cobra.Command{
 Use: "ls",
 Short: "list  project's repository",
 Run: func(cmd *cobra.Command, args []string) {
  url, _ := cmd.Flags().GetString("url")
  project, _ := cmd.Flags().GetString("project")
  if len(project) == 0 {
   fmt.Println("sorry, you must specified the project which you want to show repository !!!")
   os.Exit(2)
  }
        // get all repo 
  output := harbor.GetRepoData(url, project)
        // 展示数据
  fmt.Println("仓库名----------拉取次数")
  for i := 0; i < len(output); i++ {
   fmt.Println(output[i]["name"],output[i]["pullCount"])
  }
 },
}
func init() {
    // ./harbor repo ls -u https:// -p xxx
 rootCmd.AddCommand(repoCmd)
 repoCmd.AddCommand(repoLsCmd)
 repoLsCmd.Flags().StringP("url", "u", "","defaults: [https://127.0.0.1]")
 repoLsCmd.Flags().StringP("project", "p","", "the project")
}


  1. tag操作


// tag command
var tagCmd = &cobra.Command{
 Use: "tag",
 Short: "to operator image",
 Run: func(cmd *cobra.Command, args []string) {
  output, err := ExecuteCommand("harbor","tag", args...)
  if err != nil {
   Error(cmd,args, err)
  }
  fmt.Fprint(os.Stdout, output)
 },
}
// tag ls
var tagLsCmd = &cobra.Command{
 Use: "ls",
 Short: "list  all tags of the repository you have specified which you should specified project at the same time",
 Run: func(cmd *cobra.Command, args []string) {
  url, _ := cmd.Flags().GetString("url")
  project, _ := cmd.Flags().GetString("project")
  repo, _ := cmd.Flags().GetString("repo")
        // get all tags 
  ss := harbor.GetTags(url, project, repo)
  for i := 0; i < len(ss); i++ {
   count, _ := strconv.Atoi((ss[i])["count"])
   fmt.Printf("the repo %s has %d images\n", repo, count)
  }
 },
}
// tag del by tag or the number of image you want to save
var tagDelCmd = &cobra.Command{
 Use: "del",
 Short: "delete the tags of the repository you have specified which you should specified project at the same time",
 Run: func(cmd *cobra.Command, args []string) {
        // 获取用户输入并转格式
  url, _ := cmd.Flags().GetString("url")
  project, _ := cmd.Flags().GetString("project")
  repo, _ := cmd.Flags().GetString("repo")
  tag,_ := cmd.Flags().GetString("tag")
  count,_ := cmd.Flags().GetString("count")
  ret,_ := strconv.Atoi(count)
  if len(tag) != 0 && ret != 0 {
    fmt.Println("You can't choose both between count and tag")
    os.Exit(2)
  } else if len(tag) == 0 && ret != 0 {
            // get all tags
   retu := harbor.GetTags(url, project, repo)
            //delete tag by you hsve specied the number of the images you want to save
   rTagCount, _ := strconv.Atoi((retu[0])["count"])
            // 根据用户输入的count和实际tag数进行对比,决定是否去执行删除tag
   if ret == rTagCount {
    fmt.Printf("the repository %s of the project %s only have %d tags, so you can't delete tags and we will do nothing!!\n", repo, project,ret)
   } else if ret > rTagCount {
    fmt.Printf("the repository %s of the project %s only have %d tags, but you want to delete %d tags, so we suggest you to have a rest and we will do nothing!!\n", repo, project,rTagCount, ret)
   } else {
                // 可以执行删除tag
    fmt.Printf("we will save the latest %d tags  and delete other %d tags !!!\n", ret, (rTagCount - ret))
    tags := harbor.GetTags(url, project, repo)
    retu := harbor.DeleTagsByCount(tags, (rTagCount - ret))
    for i := 0 ; i < len(retu); i++ {
                    // to delete tag
     code, msg := harbor.DelTags(url, project, repo, retu[i])
     fmt.Printf("the tag %s is deleted,status code is %d, msg is %s\n", retu[i], code, msg)
    }
   }
  } else {
            // delete tag by you specied tag
   code, msg := harbor.DelTags(url, project, repo, tag)
   fmt.Println(code, msg["errors"])
  }
 },
}
func init() {
    // ./harbor tag ls -u -p -r
 rootCmd.AddCommand(tagCmd)
 tagCmd.AddCommand(tagLsCmd)
 tagLsCmd.Flags().StringP("url", "u", "","defaults: [https://127.0.0.1]")
 tagLsCmd.Flags().StringP("project", "p", "","the project")
 tagLsCmd.Flags().StringP("repo", "r", "","the repository")
 // ./harbor tag del -u -p -r [-t | -c]
 tagCmd.AddCommand(tagDelCmd)
 tagDelCmd.Flags().StringP("url", "u", "","defaults: [https://127.0.0.1]")
 tagDelCmd.Flags().StringP("project", "p", "","the project which you should specified if you want to delete the tag of any repository ")
 tagDelCmd.Flags().StringP("repo", "r", "","the repository which you should specified if you want to delete the tag")
 tagDelCmd.Flags().StringP("tag", "t", "","the tag, You can't choose  it with tag together")
 tagDelCmd.Flags().StringP("count", "c", "","the total number you want to save.for example: you set --count=10, we will save the 10 latest tags by use push_time to sort,can't choose it with tag together")
}


测试


//获取帮助
harbor % ./harbor -h https://harbor.zaizai.com
Usage:
  harbor [flags]
  harbor [command]
Available Commands:
  completion  generate the autocompletion script for the specified shell
  help        Help about any command
  project     to operator project
  repo        to operator repository
  tag         to operator image
Flags:
  -h, --help   help for harbor
Use "harbor [command] --help" for more information about a command.
//列出所有project
 harbor % ./harbor  project ls -u https://harbor.zaizai.com
项目名 访问级别 仓库数量
goharbor false 3
library true 0
public true 1
//列出所有repo
harbor % ./harbor  repo  ls -u https://harbor.zaizai.com -p goharbor
仓库名----------拉取次数
goharbor/harbor-portal 0
goharbor/harbor-db 1
goharbor/prepare 0
//列出tags harbor % ./harbor  tag   ls -u https://harbor.zaizai.com -p goharbor -r harbor-db
the repo harbor-db has 9 images
// 通过保留最近20个镜像去删除tag
harbor % ./harbor  tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db -c 20    
the repository harbor-db of the project goharbor only have 9 tags, but you want to delete 20 tags, so we suggest you to have a rest and we will do nothing!!
// 通过保留最近10个镜像去删除tag
harbor % ./harbor  tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db -c 10
the repository harbor-db of the project goharbor only have 9 tags, but you want to delete 10 tags, so we suggest you to have a rest and we will do nothing!!
// 通过保留最近5个镜像去删除tag
harbor % ./harbor  tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db -c 5 
we will save the latest 5 tags  and delete other 4 tags !!!
the tag v2.3.9 is deleted,status code is 200, msg is map[]
the tag v2.3.10 is deleted,status code is 200, msg is map[]
the tag v2.3.8 is deleted,status code is 200, msg is map[]
the tag v2.3.7 is deleted,status code is 200, msg is map[]
//指定tag进行删除
caicloud@MacBook-Pro-2 harbor % ./harbor tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db  -t v2.3.6
200 <nil>
!!!! 最后需要手动去harbor UI上进行垃圾回收!!!
相关文章
|
Java 测试技术 API
解决harbor上删除镜像不释放空间,无需停止harbor
解决harbor上删除镜像不释放空间 docker镜像仓库中镜像的清理,一直是个比较麻烦的事情。尤其是在测试环境当中,每天都会有大量的构建。由此会产生大量的历史镜像,而这些镜像,大多数都没有用。
3401 0
|
Linux 网络安全 网络虚拟化
|
Ubuntu Linux
几种Linux系统切换内核启动顺序方法
几种Linux系统切换内核启动顺序方法
|
Rust JavaScript Unix
Nodejs 常见版本管理工具(nvm、n、fnm、nvs、nodenv)
Nodejs 常见版本管理工具(nvm、n、fnm、nvs、nodenv)
12677 0
|
2月前
|
SQL 关系型数据库 MySQL
MySQL慢sql的排查与优化
本文详解MySQL慢查询排查与优化,涵盖EXPLAIN执行计划分析、索引失效场景及10大优化方案,如避免全表扫描、合理使用索引、分页与排序优化等,助力提升数据库性能。
MySQL慢sql的排查与优化
|
7月前
|
NoSQL 测试技术 Redis
Redis批量删除Key的三种方式
Redis批量删除Key是优化数据库性能的重要操作,本文介绍三种高效方法:1) 使用通配符匹配(KEYS/SCAN+DEL),适合不同数据规模;2) Lua脚本实现原子化删除,适用于需要事务保障的场景;3) 管道批量处理提升效率。根据实际需求选择合适方案,注意操作不可逆,建议先备份数据,避免内存溢出或阻塞。
|
存储 Kubernetes 网络协议
还不会 Cert Manager 自动签发证书?一文掌握
本文将介绍如何使用 Cert Manager 实现自动签发证书并与 Rainbond 结合使用。
|
Ubuntu Linux
ubuntu22.04禁止自动休眠的几种方式
在Ubuntu 22.04中禁用自动休眠可以通过多种方法实现,用户可以根据自己的技术水平和需求选择合适的方法。无论是通过图形界面还是命令行,都可以有效地防止系统进入自动休眠状态,确保长时间运行的任务不受干扰。通过理解和应用这些设置,可以更好地管理Ubuntu系统的电源行为,提高工作效率和系统稳定性。
4809 4
|
存储 网络安全 数据安全/隐私保护
Docker--harbor私有仓库部署与管理
Docker--harbor私有仓库部署与管理
Docker--harbor私有仓库部署与管理
|
Kubernetes jenkins 持续交付
jenkins连接k8s
jenkins连接k8s
723 7

热门文章

最新文章