
背景
最近在巡检过程中,发现harbor存储空间使用率已经达到了80%。于是,去看了一下各项目下的镜像标签数。发现有个别项目下的镜像标签数竟然有好几百个。细问之下得知,该项目目前处于调试阶段,每天调试很多次。既然存储空间不多了,那就去harbor上删除掉之前的镜像标签,保留最近的几个就好了。在手动删除的过程中,发现几百个,每页才展示十个。我得先按照推送时间排序,然后一页一页的删除。心想着这种情况经历一次就好了,不要再有下一次。后来,仔细想想,这个也是不好控制的,每次巡检发现了就得手动删除太麻烦。所以就打算写一个脚本,每次通过脚本去删除镜像的标签,保留最近的几个就好了。刚好最近在学习golang,就用它来写就好了。比较尴尬的是,我脚本写完了,测试没问题后,发现新版本harbor已经可以在UI上设置保留策略了。自我安慰一下,就当作是一种练习、尝试好了!
目标
- 通过命令行能够查询当前所有的项目、无论是否公开、仓库数量
- 通过命令行能够查询项目下的仓库名和镜像名、拉取次数
- 在命令行能够指定标签和保留个数进行删除镜像标签
- 能够获取镜像的标签数
- 删除后,不支持立刻垃圾清理,请手动进行垃圾清理(考虑清理过程中无法推拉镜像)
声明
该脚本纯属个人练习所写,不构成任何建议
初学golang,仅仅是为了实现目标,代码质量极差,请谅解
本次使用的harbor是v2.3.1
全部代码请移步至github
实现
获取harbor中所有的项目,API可通过harbor的 swagger获取
- //根据harborswagger测试出来的结果定义要获取的数据结构
- typeMetaDatastruct{
- Publicstring`json:"public"`
- }
- typeProjectDatastruct{
- MetaDataMetaData`json:"metadata"`
- ProjectIdint`json:"project_id"`
- Namestring`json:"name"`
- RepoCountint`json:"repo_count"`
- }
- typePData[]ProjectData
- //提供harbor地址获取project
- funcGetProject(urlstring)[]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)
- iferr!=nil{
- fmt.Println("excutefailed")
- fmt.Println(err)
- }
- //获取body
- body,_:=ioutil.ReadAll(response.Body)
- deferresponse.Body.Close()
- ret:=PData{}
- json.Unmarshal([]byte(string(body)),&ret)
- varps=[]map[string]string{}
- //获取返回的数据
- fori:=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)
- }
- returnps
- }
获取项目下的repo
- //定义要获取的数据结构
- typeReposiDatastruct{
- Idint`json:"id"`
- Namestring`json:"name"`
- ProjectIdint`json:"project_id"`
- PullCountint`json:"pull_count"`
- }
- typeRepoData[]ReposiData
- //通过提供harbor地址和对应的项目来获取项目下的repo
- funcGetRepoData(urlstring,projstring)[]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)
- iferr!=nil{
- fmt.Println("excutefailed")
- fmt.Println(err)
- }
- //获取body
- body,_:=ioutil.ReadAll(response.Body)
- deferresponse.Body.Close()
- ret:=RepoData{}
- json.Unmarshal([]byte(string(body)),&ret)
- varps=[]map[string]string{}
- //获取返回的数据
- fori:=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)
- }
- returnps
- }
镜像tag操作
- //定义要获取的tag数据结构
- typeTagstruct{
- ArtifactIdint`json:"artifact_id"`
- Idint`json:"id"`
- Namestring`json:"name"`
- RepositoryIdint`json:"repository_id"`
- PushTimtestring`json:"push_time"`
- }
- typeTag2struct{
- ArtifactIdstring`json:"artifact_id"`
- Idstring`json:"id"`
- Namestring`json:"name"`
- RepositoryIdstring`json:"repository_id"`
- PushTimtestring`json:"push_time"`
- }
- typeTag2s[]Tag2
- //deletetagbyspecifiedcount,这里通过count先获取要删除的tag列表
- funcDeleTagsByCount(tags[]map[string]string,countint)[]string{
- varre[]string
- tt:=tags[0]["tags"]
- ss:=Tag2s{}
- json.Unmarshal([]byte(tt),&ss)
- //haveasort
- fori:=0;i<len(ss);i++{
- forj:=i+1;j<len(ss);j++{
- //根据pushtime进行排序
- ifss[i].PushTimte>ss[j].PushTimte{
- ss[i],ss[j]=ss[j],ss[i]
- }
- }
- }
- //getalltags
- fori:=0;i<len(ss);i++{
- re=append(re,ss[i].Name)
- }
- //返回count个会被删除的tag,
- returnre[0:count]
- }
- //deletetagbyspecifiedtag删除指定的tag
- funcDelTags(urlstring,projectstring,repostring,tagstring)(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)
- deferresponse.Body.Close()
- varresultmap[string]interface{}
- bd,err:=ioutil.ReadAll(response.Body)
- iferr==nil{
- err=json.Unmarshal(bd,&result)
- }
- returnresponse.StatusCode,result
- }
- //定义要获取的tag数据结构
- typeArtiDatastruct{
- Idint`json:"id"`
- ProjectIdint`json:"project_id"`
- RepositoryIdint`json:"repository_id"`
- //Digeststring`json:"digest"`
- Tags[]Tag`json:"tags"`
- }
- typeAData[]ArtiData
- //根据harbor地址、project和repo获取tag数据
- funcGetTags(urlstring,projectstring,repostring)[]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)
- iferr!=nil{
- fmt.Println("excutefailed")
- fmt.Println(err)
- }
- body,_:=ioutil.ReadAll(response.Body)
- deferresponse.Body.Close()
- ret:=AData{}
- json.Unmarshal([]byte(string(body)),&ret)
- varps=[]map[string]string{}
- sum:=0
- RData:=make(map[string]string)
- RData["name"]=repo
- //获取返回的数据
- fori:=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
- vartdata=[]map[string]string{}
- sum=len((ret[i].Tags))
- //获取tag
- forj:=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)
- iferr!=nil{
- fmt.Println("failed")
- os.Exit(2)
- }
- RData["tags"]=string(ss)
- ps=append(ps,RData)
- }
- returnps
- }
获取用户命令行输入,列出harbor中所有的项目
- //定义获取harbor中project的相关命令操作
- varprojectCmd=&cobra.Command{
- Use:"project",
- Short:"tooperatorproject",
- Run:func(cmd*cobra.Command,args[]string){
- output,err:=ExecuteCommand("harbor","project",args...)
- iferr!=nil{
- Error(cmd,args,err)
- }
- fmt.Fprint(os.Stdout,output)
- },
- }
- //projectlist
- varprojectLsCmd=&cobra.Command{
- Use:"ls",
- Short:"listallproject",
- Run:func(cmd*cobra.Command,args[]string){
- url,_:=cmd.Flags().GetString("url")
- iflen(url)==0{
- fmt.Println("urlisnull,pleasespecifiedtheharborurlfirst!!!!")
- os.Exit(2)
- }
- //获取所有的project
- output:=harbor.GetProject(url)
- fmt.Println("项目名访问级别仓库数量")
- fori:=0;i<len(output);i++{
- fmt.Println(output[i]["name"],output[i]["public"],output[i]["repo_count"])
- }
- },
- }
- //init
- funcinit(){
- //./harborprojectls-uhttps://
- rootCmd.AddCommand(projectCmd)
- projectCmd.AddCommand(projectLsCmd)
- projectLsCmd.Flags().StringP("url","u","","defaults:[https://127.0.0.1]")
- }
获取repo列表
- //repocommand
- varrepoCmd=&cobra.Command{
- Use:"repo",
- Short:"tooperatorrepository",
- Run:func(cmd*cobra.Command,args[]string){
- output,err:=ExecuteCommand("harbor","repo",args...)
- iferr!=nil{
- Error(cmd,args,err)
- }
- fmt.Fprint(os.Stdout,output)
- },
- }
- //repolist
- varrepoLsCmd=&cobra.Command{
- Use:"ls",
- Short:"listproject'srepository",
- Run:func(cmd*cobra.Command,args[]string){
- url,_:=cmd.Flags().GetString("url")
- project,_:=cmd.Flags().GetString("project")
- iflen(project)==0{
- fmt.Println("sorry,youmustspecifiedtheprojectwhichyouwanttoshowrepository!!!")
- os.Exit(2)
- }
- //getallrepo
- output:=harbor.GetRepoData(url,project)
- //展示数据
- fmt.Println("仓库名----------拉取次数")
- fori:=0;i<len(output);i++{
- fmt.Println(output[i]["name"],output[i]["pullCount"])
- }
- },
- }
- funcinit(){
- //./harborrepols-uhttps://-pxxx
- rootCmd.AddCommand(repoCmd)
- repoCmd.AddCommand(repoLsCmd)
- repoLsCmd.Flags().StringP("url","u","","defaults:[https://127.0.0.1]")
- repoLsCmd.Flags().StringP("project","p","","theproject")
- }
tag操作
- //tagcommand
- vartagCmd=&cobra.Command{
- Use:"tag",
- Short:"tooperatorimage",
- Run:func(cmd*cobra.Command,args[]string){
- output,err:=ExecuteCommand("harbor","tag",args...)
- iferr!=nil{
- Error(cmd,args,err)
- }
- fmt.Fprint(os.Stdout,output)
- },
- }
- //tagls
- vartagLsCmd=&cobra.Command{
- Use:"ls",
- Short:"listalltagsoftherepositoryyouhavespecifiedwhichyoushouldspecifiedprojectatthesametime",
- Run:func(cmd*cobra.Command,args[]string){
- url,_:=cmd.Flags().GetString("url")
- project,_:=cmd.Flags().GetString("project")
- repo,_:=cmd.Flags().GetString("repo")
- //getalltags
- ss:=harbor.GetTags(url,project,repo)
- fori:=0;i<len(ss);i++{
- count,_:=strconv.Atoi((ss[i])["count"])
- fmt.Printf("therepo%shas%dimages\n",repo,count)
- }
- },
- }
- //tagdelbytagorthenumberofimageyouwanttosave
- vartagDelCmd=&cobra.Command{
- Use:"del",
- Short:"deletethetagsoftherepositoryyouhavespecifiedwhichyoushouldspecifiedprojectatthesametime",
- 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)
- iflen(tag)!=0&&ret!=0{
- fmt.Println("Youcan'tchoosebothbetweencountandtag")
- os.Exit(2)
- }elseiflen(tag)==0&&ret!=0{
- //getalltags
- retu:=harbor.GetTags(url,project,repo)
- //deletetagbyyouhsvespeciedthenumberoftheimagesyouwanttosave
- rTagCount,_:=strconv.Atoi((retu[0])["count"])
- //根据用户输入的count和实际tag数进行对比,决定是否去执行删除tag
- ifret==rTagCount{
- fmt.Printf("therepository%softheproject%sonlyhave%dtags,soyoucan'tdeletetagsandwewilldonothing!!\n",repo,project,ret)
- }elseifret>rTagCount{
- fmt.Printf("therepository%softheproject%sonlyhave%dtags,butyouwanttodelete%dtags,sowesuggestyoutohavearestandwewilldonothing!!\n",repo,project,rTagCount,ret)
- }else{
- //可以执行删除tag
- fmt.Printf("wewillsavethelatest%dtagsanddeleteother%dtags!!!\n",ret,(rTagCount-ret))
- tags:=harbor.GetTags(url,project,repo)
- retu:=harbor.DeleTagsByCount(tags,(rTagCount-ret))
- fori:=0;i<len(retu);i++{
- //todeletetag
- code,msg:=harbor.DelTags(url,project,repo,retu[i])
- fmt.Printf("thetag%sisdeleted,statuscodeis%d,msgis%s\n",retu[i],code,msg)
- }
- }
- }else{
- //deletetagbyyouspeciedtag
- code,msg:=harbor.DelTags(url,project,repo,tag)
- fmt.Println(code,msg["errors"])
- }
- },
- }
- funcinit(){
- //./harbortagls-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","","theproject")
- tagLsCmd.Flags().StringP("repo","r","","therepository")
- //./harbortagdel-u-p-r[-t|-c]
- tagCmd.AddCommand(tagDelCmd)
- tagDelCmd.Flags().StringP("url","u","","defaults:[https://127.0.0.1]")
- tagDelCmd.Flags().StringP("project","p","","theprojectwhichyoushouldspecifiedifyouwanttodeletethetagofanyrepository")
- tagDelCmd.Flags().StringP("repo","r","","therepositorywhichyoushouldspecifiedifyouwanttodeletethetag")
- tagDelCmd.Flags().StringP("tag","t","","thetag,Youcan'tchooseitwithtagtogether")
- tagDelCmd.Flags().StringP("count","c","","thetotalnumberyouwanttosave.forexample:youset--count=10,wewillsavethe10latesttagsbyusepush_timetosort,can'tchooseitwithtagtogether")
- }
测试
- //获取帮助
- harbor%./harbor-hhttps://harbor.zaizai.com
- Usage:
- harbor[flags]
- harbor[command]
- AvailableCommands:
- completiongeneratetheautocompletionscriptforthespecifiedshell
- helpHelpaboutanycommand
- projecttooperatorproject
- repotooperatorrepository
- tagtooperatorimage
- Flags:
- -h,--helphelpforharbor
- Use"harbor[command]--help"formoreinformationaboutacommand.
- //列出所有project
- harbor%./harborprojectls-uhttps://harbor.zaizai.com
- 项目名访问级别仓库数量
- goharborfalse3
- librarytrue0
- publictrue1
- //列出所有repo
- harbor%./harborrepols-uhttps://harbor.zaizai.com-pgoharbor
- 仓库名----------拉取次数
- goharbor/harbor-portal0
- goharbor/harbor-db1
- goharbor/prepare0
- //列出tagsharbor%./harbortagls-uhttps://harbor.zaizai.com-pgoharbor-rharbor-db
- therepoharbor-dbhas9images
- //通过保留最近20个镜像去删除tag
- harbor%./harbortagdel-uhttps://harbor.zaizai.com-pgoharbor-rharbor-db-c20
- therepositoryharbor-dboftheprojectgoharboronlyhave9tags,butyouwanttodelete20tags,sowesuggestyoutohavearestandwewilldonothing!!
- //通过保留最近10个镜像去删除tag
- harbor%./harbortagdel-uhttps://harbor.zaizai.com-pgoharbor-rharbor-db-c10
- therepositoryharbor-dboftheprojectgoharboronlyhave9tags,butyouwanttodelete10tags,sowesuggestyoutohavearestandwewilldonothing!!
- //通过保留最近5个镜像去删除tag
- harbor%./harbortagdel-uhttps://harbor.zaizai.com-pgoharbor-rharbor-db-c5
- wewillsavethelatest5tagsanddeleteother4tags!!!
- thetagv2.3.9isdeleted,statuscodeis200,msgismap[]
- thetagv2.3.10isdeleted,statuscodeis200,msgismap[]
- thetagv2.3.8isdeleted,statuscodeis200,msgismap[]
- thetagv2.3.7isdeleted,statuscodeis200,msgismap[]
- //指定tag进行删除
- caicloud@MacBook-Pro-2harbor%./harbortagdel-uhttps://harbor.zaizai.com-pgoharbor-rharbor-db-tv2.3.6
- 200<nil>
- !!!!最后需要手动去harborUI上进行垃圾回收!!!
参考
https://github.com/spf13/cobra
harbor swagger
原文链接:https://mp.weixin.qq.com/s/64D9omkJx7MJaksOwVnb8g








发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。