本文转载自微信公众号「运维开发故事」,作者华仔。转载本文请联系运维开发故事公众号。
背景
最近在巡检过程中,发现harbor存储空间使用率已经达到了80%。于是,去看了一下各项目下的镜像标签数。发现有个别项目下的镜像标签数竟然有好几百个。细问之下得知,该项目目前处于调试阶段,每天调试很多次。既然存储空间不多了,那就去harbor上删除掉之前的镜像标签,保留最近的几个就好了。在手动删除的过程中,发现几百个,每页才展示十个。我得先按照推送时间排序,然后一页一页的删除。心想着这种情况经历一次就好了,不要再有下一次。后来,仔细想想,这个也是不好控制的,每次巡检发现了就得手动删除太麻烦。所以就打算写一个脚本,每次通过脚本去删除镜像的标签,保留最近的几个就好了。刚好最近在学习golang,就用它来写就好了。比较尴尬的是,我脚本写完了,测试没问题后,发现新版本harbor已经可以在UI上设置保留策略了。自我安慰一下,就当作是一种练习、尝试好了!
目标
- 通过命令行能够查询当前所有的项目、无论是否公开、仓库数量
- 通过命令行能够查询项目下的仓库名和镜像名、拉取次数
- 在命令行能够指定标签和保留个数进行删除镜像标签
- 能够获取镜像的标签数
- 删除后,不支持立刻垃圾清理,请手动进行垃圾清理(考虑清理过程中无法推拉镜像)
声明
该脚本纯属个人练习所写,不构成任何建议
初学golang,仅仅是为了实现目标,代码质量极差,请谅解
本次使用的harbor是v2.3.1
全部代码请移步至github
实现
获取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
- }
获取项目下的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
- }
镜像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
- }
获取用户命令行输入,列出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]")
- }
获取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")
- }
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上进行垃圾回收!!!
参考
https://github.com/spf13/cobra
harbor swagger