如何快速删除 Harbor 镜像

开发 项目管理
细问之下得知,该项目目前处于调试阶段,每天调试很多次。既然存储空间不多了,那就去harbor上删除掉之前的镜像标签,保留最近的几个就好了。在手动删除的过程中,发现几百个,每页才展示十个。我得先按照推送时间排序,然后一页一页的删除。心想着这种情况经历一次就好了,不要再有下一次。

[[429946]]

本文转载自微信公众号「运维开发故事」,作者华仔。转载本文请联系运维开发故事公众号。

背景

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

目标

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

声明

该脚本纯属个人练习所写,不构成任何建议

初学golang,仅仅是为了实现目标,代码质量极差,请谅解

本次使用的harbor是v2.3.1

全部代码请移步至github

实现

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

  1. //根据harbor swagger测试出来的结果定义要获取的数据结构 
  2. type MetaData struct { 
  3.  Public string `json:"public"
  4. type ProjectData struct { 
  5.  MetaData MetaData `json:"metadata"
  6.  ProjectId int `json:"project_id"
  7.  Name string `json:"name"
  8.  RepoCount int `json:"repo_count"
  9.  
  10. type PData []ProjectData 
  11. // 提供harbor地址获取project 
  12. func GetProject(url string) []map[string]string { 
  13.  //定义url 
  14.  url = url + "/api/v2.0/projects" 
  15.  //url = url + "/api/projects" 
  16.     // 构造请求 
  17.  request, _ := http.NewRequest(http.MethodGet, url,nil) 
  18.     //取消验证 
  19.  tr := &http.Transport{ 
  20.   TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 
  21.  } 
  22.     //定义客户端 
  23.  client := &http.Client{Timeout: 10 * time.Second, Transport: tr} 
  24.  //client := &http.Client{Timeout: 10 * time.Second
  25.  request.Header.Set("accept""application/json"
  26.     //设置用户和密码 
  27.  request.SetBasicAuth("admin""Harbor12345"
  28.  response, err := client.Do(request) 
  29.  
  30.  if err != nil { 
  31.   fmt.Println("excute failed"
  32.   fmt.Println(err) 
  33.  } 
  34.  // 获取body 
  35.  body, _ := ioutil.ReadAll(response.Body) 
  36.  defer response.Body.Close() 
  37.  ret := PData{} 
  38.  json.Unmarshal([]byte(string(body)), &ret) 
  39.  var ps = []map[string]string{} 
  40.     // 获取返回的数据 
  41.  for i := 0; i < len(ret); i++ { 
  42.   RData := make(map[string]string) 
  43.   RData["name"] = (ret[i].Name
  44.   RData["project_id"] = strconv.Itoa(ret[i].ProjectId) 
  45.   RData["repo_count"] =strconv.Itoa(ret[i].RepoCount) 
  46.   RData["public"] = ret[i].MetaData.Public 
  47.  
  48.   ps = append(ps, RData) 
  49.  } 
  50.  return ps 

获取项目下的repo

  1. // 定义要获取的数据结构 
  2. type ReposiData struct { 
  3.  Id int `json:"id"
  4.  Name string `json:"name"
  5.  ProjectId int `json:"project_id"
  6.  PullCount int `json:"pull_count"
  7.  
  8. type RepoData []ReposiData 
  9. //通过提供harbor地址和对应的项目来获取项目下的repo 
  10. func GetRepoData(url string, proj string)  []map[string]string { 
  11.  // /api/v2.0/projects/goharbor/repositories 
  12.  url = url + "/api/v2.0/projects/" + proj +  "/repositories" 
  13.     //构造请求 
  14.  request, _ := http.NewRequest(http.MethodGet, url,nil) 
  15.     //忽略认证 
  16.  tr := &http.Transport{ 
  17.   TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 
  18.  } 
  19.  client := &http.Client{Timeout: 10 * time.Second, Transport: tr} 
  20.  request.Header.Set("accept""application/json"
  21.     //设置用户名和密码 
  22.  request.SetBasicAuth("admin""Harbor12345"
  23.  response, err := client.Do(request) 
  24.  
  25.  if err != nil { 
  26.   fmt.Println("excute failed"
  27.   fmt.Println(err) 
  28.  } 
  29.  // 获取body 
  30.  body, _ := ioutil.ReadAll(response.Body) 
  31.  defer response.Body.Close() 
  32.  ret := RepoData{} 
  33.  json.Unmarshal([]byte(string(body)), &ret) 
  34.  var ps = []map[string]string{} 
  35.     // 获取返回的数据 
  36.  for i := 0; i < len(ret); i++ { 
  37.   RData := make(map[string]string) 
  38.   RData["name"] = (ret[i].Name
  39.   pId := strconv.Itoa(ret[i].ProjectId) 
  40.   RData["project_id"] = pId 
  41.   RData["id"] =(strconv.Itoa(ret[i].Id)) 
  42.   RData["pullCount"] = (strconv.Itoa(ret[i].PullCount)) 
  43.   ps = append(ps, RData) 
  44.  } 
  45.  return ps 

镜像tag操作

  1. //定义要获取的tag数据结构 
  2. type Tag struct { 
  3.  ArtifactId int  `json:"artifact_id"
  4.  Id int `json:"id"
  5.  Name string `json:"name"
  6.  RepositoryId int `json:"repository_id"
  7.  PushTimte string `json:"push_time"
  8.  
  9. type Tag2 struct { 
  10.  ArtifactId string  `json:"artifact_id"
  11.  Id string `json:"id"
  12.  Name string `json:"name"
  13.  RepositoryId string `json:"repository_id"
  14.  PushTimte string `json:"push_time"
  15.  
  16. type Tag2s []Tag2 
  17. // delete tag by specified count,这里通过count先获取要删除的tag列表 
  18. func DeleTagsByCount(tags []map[string]string ,count int) []string { 
  19.  var re []string 
  20.   
  21.  tt := tags[0]["tags"
  22.  ss := Tag2s{} 
  23.  json.Unmarshal([]byte(tt), &ss) 
  24.  
  25.  // have a sort 
  26.  for i := 0; i < len(ss); i++ { 
  27.   for j := i + 1; j < len(ss); j++ { 
  28.             //根据pushtime进行排序 
  29.    if ss[i].PushTimte > ss[j].PushTimte { 
  30.     ss[i], ss[j] = ss[j], ss[i] 
  31.    } 
  32.   } 
  33.  } 
  34.  // get all tags 
  35.  for i := 0; i < len(ss); i++ { 
  36.   re = append(re, ss[i].Name
  37.  } 
  38.  // 返回count个会被删除的tag, 
  39.  return re[0:count
  40.  
  41. // delete tag by specified tag 删除指定的tag 
  42. func DelTags(url string, project string, repo string, tag string) (int, map[string]interface{})  { 
  43.  url = url + "/api/v2.0/projects/" + project + "/repositories/" + repo + "/artifacts/" + tag + "/tags/" + tag 
  44.  request, _ := http.NewRequest(http.MethodDelete, url,nil) 
  45.  tr := &http.Transport{ 
  46.   TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 
  47.  } 
  48.  client := &http.Client{Timeout: 10 * time.Second, Transport: tr} 
  49.  request.Header.Set("accept""application/json"
  50.  request.SetBasicAuth("admin""Pwd123456"
  51.     // 执行删除tag 
  52.  response,_ := client.Do(request) 
  53.  defer response.Body.Close() 
  54.  
  55.  var result map[string]interface{} 
  56.  bd, err := ioutil.ReadAll(response.Body) 
  57.  if err == nil { 
  58.   err = json.Unmarshal(bd, &result) 
  59.  } 
  60.  return response.StatusCode,result 
  61.  
  62.  
  63.  
  64.  
  65.  
  66. //定义要获取的tag数据结构 
  67. type ArtiData struct { 
  68.  Id int `json:"id"
  69.  ProjectId int `json:"project_id"
  70.  RepositoryId int `json:"repository_id"
  71.  //Digest string `json:"digest"
  72.  Tags []Tag `json:"tags"
  73.  
  74. type AData []ArtiData 
  75. // 根据harbor地址、project和repo获取tag数据 
  76. func GetTags(url string, project string, repo string) []map[string]string { 
  77.  url = url + "/api/v2.0/projects/" + project + "/repositories/" + repo + "/artifacts" 
  78.  request, _ := http.NewRequest(http.MethodGet, url,nil) 
  79.  tr := &http.Transport{ 
  80.   TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 
  81.  } 
  82.  client := &http.Client{Timeout: 10 * time.Second, Transport: tr} 
  83.  request.Header.Set("accept""application/json"
  84.  request.Header.Set("X-Accept-Vulnerabilities""application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
  85.  request.SetBasicAuth("admin""Harbor12345"
  86.     // 获取tag 
  87.  response, err := client.Do(request) 
  88.  
  89.  if err != nil { 
  90.   fmt.Println("excute failed"
  91.   fmt.Println(err) 
  92.  } 
  93.  
  94.  body, _ := ioutil.ReadAll(response.Body) 
  95.  defer response.Body.Close() 
  96.  ret := AData{} 
  97.  json.Unmarshal([]byte(string(body)),&ret) 
  98.  var ps = []map[string]string{} 
  99.  sum := 0 
  100.  RData := make(map[string]string) 
  101.  RData["name"] = repo 
  102.     // 获取返回的数据 
  103.  for i := 0; i < len(ret); i++ { 
  104.  
  105.   RData["id"] = (strconv.Itoa(ret[i].Id)) 
  106.   RData["project_id"] = (strconv.Itoa(ret[i].ProjectId)) 
  107.   RData["repository_id"] =(strconv.Itoa(ret[i].RepositoryId)) 
  108.   //RData["digest"] = ret[i].Digest 
  109.   var tdata = []map[string]string{} 
  110.   sum = len((ret[i].Tags)) 
  111.         // 获取tag 
  112.   for j := 0; j < len((ret[i].Tags)); j++ { 
  113.    TagData := make(map[string]string) 
  114.    TagData["artifact_id"] = strconv.Itoa((ret[i].Tags)[j].ArtifactId) 
  115.    TagData["id"] = strconv.Itoa((ret[i].Tags)[j].Id) 
  116.    TagData["name"] = (ret[i].Tags)[j].Name 
  117.    TagData["repository_id"] = strconv.Itoa((ret[i].Tags)[j].RepositoryId) 
  118.    TagData["push_time"] = (ret[i].Tags)[j].PushTimte 
  119.    tdata = append(tdata, TagData) 
  120.   } 
  121.   RData["count"] = strconv.Itoa(sum
  122.   ss, err := json.Marshal(tdata) 
  123.   if err != nil { 
  124.    fmt.Println("failed"
  125.    os.Exit(2) 
  126.   } 
  127.   RData["tags"] = string(ss) 
  128.   ps = append(ps, RData) 
  129.  
  130.  } 
  131.  return ps 

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

  1. // 定义获取harbor中project的相关命令操作 
  2. var projectCmd = &cobra.Command{ 
  3.  Use: "project"
  4.  Short: "to operator project"
  5.  Run: func(cmd *cobra.Command, args []string) { 
  6.  
  7.   output, err := ExecuteCommand("harbor","project", args...) 
  8.   if err != nil { 
  9.    Error(cmd,args, err) 
  10.   } 
  11.   fmt.Fprint(os.Stdout, output
  12.  }, 
  13. // project list 
  14. var projectLsCmd = &cobra.Command{ 
  15.  Use: "ls"
  16.  Short: "list  all project"
  17.  Run: func(cmd *cobra.Command, args []string) { 
  18.   url, _ := cmd.Flags().GetString("url"
  19.   if len(url) == 0 { 
  20.    fmt.Println("url is null,please specified the harbor url first !!!!"
  21.    os.Exit(2) 
  22.   } 
  23.         // 获取所有的project 
  24.   output := harbor.GetProject(url) 
  25.   fmt.Println("项目名 访问级别 仓库数量"
  26.   for i := 0; i < len(output); i++ { 
  27.  
  28.    fmt.Println(output[i]["name"], output[i]["public"], output[i]["repo_count"]) 
  29.   } 
  30.  }, 
  31. // init 
  32. func init() { 
  33.     // ./harbor project ls -u https:// 
  34.  rootCmd.AddCommand(projectCmd) 
  35.  projectCmd.AddCommand(projectLsCmd) 
  36.  projectLsCmd.Flags().StringP("url""u""","defaults: [https://127.0.0.1]"

获取repo列表

  1. // repo command 
  2. var repoCmd = &cobra.Command{ 
  3.  Use: "repo"
  4.  Short: "to operator repository"
  5.  Run: func(cmd *cobra.Command, args []string) { 
  6.  
  7.   output, err := ExecuteCommand("harbor","repo", args...) 
  8.   if err != nil { 
  9.    Error(cmd,args, err) 
  10.   } 
  11.   fmt.Fprint(os.Stdout, output
  12.  }, 
  13. // repo list 
  14. var repoLsCmd = &cobra.Command{ 
  15.  Use: "ls"
  16.  Short: "list  project's repository"
  17.  Run: func(cmd *cobra.Command, args []string) { 
  18.   url, _ := cmd.Flags().GetString("url"
  19.   project, _ := cmd.Flags().GetString("project"
  20.   if len(project) == 0 { 
  21.    fmt.Println("sorry, you must specified the project which you want to show repository !!!"
  22.    os.Exit(2) 
  23.   } 
  24.         // get all repo  
  25.   output := harbor.GetRepoData(url, project) 
  26.         // 展示数据 
  27.   fmt.Println("仓库名----------拉取次数"
  28.   for i := 0; i < len(output); i++ { 
  29.    fmt.Println(output[i]["name"],output[i]["pullCount"]) 
  30.   } 
  31.  }, 
  32.  
  33. func init() { 
  34.     // ./harbor repo ls -u https:// -p xxx 
  35.  rootCmd.AddCommand(repoCmd) 
  36.  repoCmd.AddCommand(repoLsCmd) 
  37.  repoLsCmd.Flags().StringP("url""u""","defaults: [https://127.0.0.1]"
  38.  repoLsCmd.Flags().StringP("project""p","""the project"

tag操作

  1. // tag command 
  2. var tagCmd = &cobra.Command{ 
  3.  Use: "tag"
  4.  Short: "to operator image"
  5.  Run: func(cmd *cobra.Command, args []string) { 
  6.  
  7.   output, err := ExecuteCommand("harbor","tag", args...) 
  8.   if err != nil { 
  9.    Error(cmd,args, err) 
  10.   } 
  11.   fmt.Fprint(os.Stdout, output
  12.  }, 
  13. // tag ls 
  14. var tagLsCmd = &cobra.Command{ 
  15.  Use: "ls"
  16.  Short: "list  all tags of the repository you have specified which you should specified project at the same time"
  17.  Run: func(cmd *cobra.Command, args []string) { 
  18.   url, _ := cmd.Flags().GetString("url"
  19.   project, _ := cmd.Flags().GetString("project"
  20.   repo, _ := cmd.Flags().GetString("repo"
  21.         // get all tags  
  22.   ss := harbor.GetTags(url, project, repo) 
  23.   for i := 0; i < len(ss); i++ { 
  24.    count, _ := strconv.Atoi((ss[i])["count"]) 
  25.    fmt.Printf("the repo %s has %d images\n", repo, count
  26.   } 
  27.  
  28.  }, 
  29.  
  30. // tag del by tag or the number of image you want to save 
  31. var tagDelCmd = &cobra.Command{ 
  32.  Use: "del"
  33.  Short: "delete the tags of the repository you have specified which you should specified project at the same time"
  34.  Run: func(cmd *cobra.Command, args []string) { 
  35.         // 获取用户输入并转格式 
  36.   url, _ := cmd.Flags().GetString("url"
  37.   project, _ := cmd.Flags().GetString("project"
  38.   repo, _ := cmd.Flags().GetString("repo"
  39.   tag,_ := cmd.Flags().GetString("tag"
  40.   count,_ := cmd.Flags().GetString("count"
  41.   ret,_ := strconv.Atoi(count
  42.   if len(tag) != 0 && ret != 0 { 
  43.     fmt.Println("You can't choose both between count and tag"
  44.     os.Exit(2) 
  45.   } else if len(tag) == 0 && ret != 0 { 
  46.             // get all tags 
  47.    retu := harbor.GetTags(url, project, repo) 
  48.             //delete tag by you hsve specied the number of the images you want to save 
  49.    rTagCount, _ := strconv.Atoi((retu[0])["count"]) 
  50.             // 根据用户输入的count和实际tag数进行对比,决定是否去执行删除tag 
  51.    if ret == rTagCount { 
  52.     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) 
  53.    } else if ret > rTagCount { 
  54.     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) 
  55.    } else { 
  56.                 // 可以执行删除tag 
  57.     fmt.Printf("we will save the latest %d tags  and delete other %d tags !!!\n", ret, (rTagCount - ret)) 
  58.     tags := harbor.GetTags(url, project, repo) 
  59.     retu := harbor.DeleTagsByCount(tags, (rTagCount - ret)) 
  60.     for i := 0 ; i < len(retu); i++ { 
  61.                     // to delete tag 
  62.      code, msg := harbor.DelTags(url, project, repo, retu[i]) 
  63.      fmt.Printf("the tag %s is deleted,status code is %d, msg is %s\n", retu[i], code, msg) 
  64.     } 
  65.    } 
  66.   } else { 
  67.             // delete tag by you specied tag 
  68.    code, msg := harbor.DelTags(url, project, repo, tag) 
  69.    fmt.Println(code, msg["errors"]) 
  70.   } 
  71.  }, 
  72.  
  73.  
  74.  
  75. func init() { 
  76.     // ./harbor tag ls -u -p -r 
  77.  rootCmd.AddCommand(tagCmd) 
  78.  tagCmd.AddCommand(tagLsCmd) 
  79.  tagLsCmd.Flags().StringP("url""u""","defaults: [https://127.0.0.1]"
  80.  tagLsCmd.Flags().StringP("project""p""","the project"
  81.  tagLsCmd.Flags().StringP("repo""r""","the repository"
  82.  // ./harbor tag del -u -p -r [-t | -c] 
  83.  tagCmd.AddCommand(tagDelCmd) 
  84.  tagDelCmd.Flags().StringP("url""u""","defaults: [https://127.0.0.1]"
  85.  tagDelCmd.Flags().StringP("project""p""","the project which you should specified if you want to delete the tag of any repository "
  86.  tagDelCmd.Flags().StringP("repo""r""","the repository which you should specified if you want to delete the tag"
  87.  tagDelCmd.Flags().StringP("tag""t""","the tag, You can't choose  it with tag together"
  88.  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"

测试

  1. //获取帮助 
  2. harbor % ./harbor -h https://harbor.zaizai.com 
  3. Usage: 
  4.   harbor [flags] 
  5.   harbor [command] 
  6.  
  7. Available Commands: 
  8.   completion  generate the autocompletion script for the specified shell 
  9.   help        Help about any command 
  10.   project     to operator project 
  11.   repo        to operator repository 
  12.   tag         to operator image 
  13.  
  14. Flags: 
  15.   -h, --help   help for harbor 
  16.  
  17. Use "harbor [command] --help" for more information about a command. 
  18.  
  19. //列出所有project 
  20.  harbor % ./harbor  project ls -u https://harbor.zaizai.com 
  21. 项目名 访问级别 仓库数量 
  22. goharbor false 3 
  23. library true 0 
  24. public true 1 
  25.  
  26. //列出所有repo 
  27. harbor % ./harbor  repo  ls -u https://harbor.zaizai.com -p goharbor 
  28. 仓库名----------拉取次数 
  29. goharbor/harbor-portal 0 
  30. goharbor/harbor-db 1 
  31. goharbor/prepare 0 
  32.  
  33. //列出tags harbor % ./harbor  tag   ls -u https://harbor.zaizai.com -p goharbor -r harbor-db 
  34. the repo harbor-db has 9 images 
  35.  
  36. // 通过保留最近20个镜像去删除tag 
  37. harbor % ./harbor  tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db -c 20     
  38. 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!! 
  39.  
  40. // 通过保留最近10个镜像去删除tag 
  41. harbor % ./harbor  tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db -c 10 
  42. 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!! 
  43.  
  44. // 通过保留最近5个镜像去删除tag 
  45. harbor % ./harbor  tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db -c 5  
  46. we will save the latest 5 tags  and delete other 4 tags !!! 
  47. the tag v2.3.9 is deleted,status code is 200, msg is map[] 
  48. the tag v2.3.10 is deleted,status code is 200, msg is map[] 
  49. the tag v2.3.8 is deleted,status code is 200, msg is map[] 
  50. the tag v2.3.7 is deleted,status code is 200, msg is map[] 
  51.  
  52. //指定tag进行删除 
  53. caicloud@MacBook-Pro-2 harbor % ./harbor tag   del  -u https://harbor.zaizai.com -p goharbor -r harbor-db  -t v2.3.6 
  54. 200 <nil> 
  55.  
  56. !!!! 最后需要手动去harbor UI上进行垃圾回收!!! 

参考

https://github.com/spf13/cobra

 

harbor swagger

 

责任编辑:武晓燕 来源: 运维开发故事
相关推荐

2022-07-11 07:37:55

HarborContainerd

2022-09-16 10:19:36

HarborContainerd

2017-03-24 09:24:21

HarborDocker镜像仓库

2024-01-22 09:12:51

2021-12-22 10:45:56

Docker RegiHarborLinux

2021-02-23 15:05:55

Docker镜像开发

2020-11-25 08:57:29

Redis Docke

2021-02-03 10:43:54

Linux系统磁盘

2023-04-06 10:08:58

2010-09-02 11:38:44

SQL删除

2010-09-01 17:19:49

SQL删除TABLE

2018-08-29 12:30:45

数据库MySQLInnodb

2023-04-06 09:53:21

Harbor企业级服务

2023-03-03 00:07:24

2011-05-13 11:05:59

oracle删除

2010-07-02 13:50:11

SQL Server数

2019-07-30 16:16:11

Windows 10删除快速访问

2022-08-21 16:50:36

Kubeadm​Kubernetes

2010-09-16 16:17:03

TRUNCATE TA

2021-09-23 10:30:21

Docker RegiHarborLinux
点赞
收藏

51CTO技术栈公众号