如何快速刪除 Harbor 鏡像
本文轉(zhuǎn)載自微信公眾號(hào)「運(yùn)維開(kāi)發(fā)故事」,作者華仔。轉(zhuǎn)載本文請(qǐng)聯(lián)系運(yùn)維開(kāi)發(fā)故事公眾號(hào)。
背景
最近在巡檢過(guò)程中,發(fā)現(xiàn)harbor存儲(chǔ)空間使用率已經(jīng)達(dá)到了80%。于是,去看了一下各項(xiàng)目下的鏡像標(biāo)簽數(shù)。發(fā)現(xiàn)有個(gè)別項(xiàng)目下的鏡像標(biāo)簽數(shù)竟然有好幾百個(gè)。細(xì)問(wèn)之下得知,該項(xiàng)目目前處于調(diào)試階段,每天調(diào)試很多次。既然存儲(chǔ)空間不多了,那就去harbor上刪除掉之前的鏡像標(biāo)簽,保留最近的幾個(gè)就好了。在手動(dòng)刪除的過(guò)程中,發(fā)現(xiàn)幾百個(gè),每頁(yè)才展示十個(gè)。我得先按照推送時(shí)間排序,然后一頁(yè)一頁(yè)的刪除。心想著這種情況經(jīng)歷一次就好了,不要再有下一次。后來(lái),仔細(xì)想想,這個(gè)也是不好控制的,每次巡檢發(fā)現(xiàn)了就得手動(dòng)刪除太麻煩。所以就打算寫(xiě)一個(gè)腳本,每次通過(guò)腳本去刪除鏡像的標(biāo)簽,保留最近的幾個(gè)就好了。剛好最近在學(xué)習(xí)golang,就用它來(lái)寫(xiě)就好了。比較尷尬的是,我腳本寫(xiě)完了,測(cè)試沒(méi)問(wèn)題后,發(fā)現(xiàn)新版本harbor已經(jīng)可以在UI上設(shè)置保留策略了。自我安慰一下,就當(dāng)作是一種練習(xí)、嘗試好了!
目標(biāo)
- 通過(guò)命令行能夠查詢當(dāng)前所有的項(xiàng)目、無(wú)論是否公開(kāi)、倉(cāng)庫(kù)數(shù)量
- 通過(guò)命令行能夠查詢項(xiàng)目下的倉(cāng)庫(kù)名和鏡像名、拉取次數(shù)
- 在命令行能夠指定標(biāo)簽和保留個(gè)數(shù)進(jìn)行刪除鏡像標(biāo)簽
- 能夠獲取鏡像的標(biāo)簽數(shù)
- 刪除后,不支持立刻垃圾清理,請(qǐng)手動(dòng)進(jìn)行垃圾清理(考慮清理過(guò)程中無(wú)法推拉鏡像)
聲明
該腳本純屬個(gè)人練習(xí)所寫(xiě),不構(gòu)成任何建議
初學(xué)golang,僅僅是為了實(shí)現(xiàn)目標(biāo),代碼質(zhì)量極差,請(qǐng)諒解
本次使用的harbor是v2.3.1
全部代碼請(qǐng)移步至github
實(shí)現(xiàn)
獲取harbor中所有的項(xiàng)目,API可通過(guò)harbor的 swagger獲取
- //根據(jù)harbor swagger測(cè)試出來(lái)的結(jié)果定義要獲取的數(shù)據(jù)結(jié)構(gòu)
- 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"
- // 構(gòu)造請(qǐng)求
- request, _ := http.NewRequest(http.MethodGet, url,nil)
- //取消驗(yàn)證
- 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")
- //設(shè)置用戶和密碼
- 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{}
- // 獲取返回的數(shù)據(jù)
- 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
- }
獲取項(xiàng)目下的repo
- // 定義要獲取的數(shù)據(jù)結(jié)構(gòu)
- type ReposiData struct {
- Id int `json:"id"`
- Name string `json:"name"`
- ProjectId int `json:"project_id"`
- PullCount int `json:"pull_count"`
- }
- type RepoData []ReposiData
- //通過(guò)提供harbor地址和對(duì)應(yīng)的項(xiàng)目來(lái)獲取項(xiàng)目下的repo
- func GetRepoData(url string, proj string) []map[string]string {
- // /api/v2.0/projects/goharbor/repositories
- url = url + "/api/v2.0/projects/" + proj + "/repositories"
- //構(gòu)造請(qǐng)求
- request, _ := http.NewRequest(http.MethodGet, url,nil)
- //忽略認(rèn)證
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- }
- client := &http.Client{Timeout: 10 * time.Second, Transport: tr}
- request.Header.Set("accept", "application/json")
- //設(shè)置用戶名和密碼
- 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{}
- // 獲取返回的數(shù)據(jù)
- 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數(shù)據(jù)結(jié)構(gòu)
- 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,這里通過(guò)count先獲取要?jiǎng)h除的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++ {
- //根據(jù)pushtime進(jìn)行排序
- 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個(gè)會(huì)被刪除的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")
- // 執(zhí)行刪除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數(shù)據(jù)結(jié)構(gòu)
- 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
- // 根據(jù)harbor地址、project和repo獲取tag數(shù)據(jù)
- 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
- // 獲取返回的數(shù)據(jù)
- 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中所有的項(xiàng)目
- // 定義獲取harbor中project的相關(guān)命令操作
- 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("項(xiàng)目名 訪問(wèn)級(jí)別 倉(cāng)庫(kù)數(shù)量")
- 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)
- // 展示數(shù)據(jù)
- fmt.Println("倉(cāng)庫(kù)名----------拉取次數(shù)")
- 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) {
- // 獲取用戶輸入并轉(zhuǎn)格式
- 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"])
- // 根據(jù)用戶輸入的count和實(shí)際tag數(shù)進(jìn)行對(duì)比,決定是否去執(zhí)行刪除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 {
- // 可以執(zhí)行刪除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")
- }
測(cè)試
- //獲取幫助
- 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
- 項(xiàng)目名 訪問(wèn)級(jí)別 倉(cāng)庫(kù)數(shù)量
- goharbor false 3
- library true 0
- public true 1
- //列出所有repo
- harbor % ./harbor repo ls -u https://harbor.zaizai.com -p goharbor
- 倉(cāng)庫(kù)名----------拉取次數(shù)
- 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
- // 通過(guò)保留最近20個(gè)鏡像去刪除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!!
- // 通過(guò)保留最近10個(gè)鏡像去刪除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!!
- // 通過(guò)保留最近5個(gè)鏡像去刪除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進(jìn)行刪除
- caicloud@MacBook-Pro-2 harbor % ./harbor tag del -u https://harbor.zaizai.com -p goharbor -r harbor-db -t v2.3.6
- 200 <nil>
- ?。。?! 最后需要手動(dòng)去harbor UI上進(jìn)行垃圾回收?。?!
參考
https://github.com/spf13/cobra
harbor swagger