如何快速删除harbor镜像

微信公众号:运维开发故事,作者:华仔
github地址:https://github.com/sunsharing-note/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上进行垃圾回收!!!

参考

  1. https://github.com/spf13/cobra
  2. harbor swagger
上一篇:JMeter 配置元件之随机变量(RandomVariable)介绍


下一篇:Luogu4782 【模板】2-SAT 问题(2-SAT)