微信公众号:运维开发故事,作者:华仔
github地址:https://github.com/sunsharing-note/harbor
最近在巡检过程中,发现harbor存储空间使用率已经达到了80%。于是,去看了一下各项目下的镜像标签数。发现有个别项目下的镜像标签数竟然有好几百个。细问之下得知,该项目目前处于调试阶段,每天调试很多次。既然存储空间不多了,那就去harbor上删除掉之前的镜像标签,保留最近的几个就好了。在手动删除的过程中,发现几百个,每页才展示十个。我得先按照推送时间排序,然后一页一页的删除。心想着这种情况经历一次就好了,不要再有下一次。
后来,仔细想想,这个也是不好控制的,每次巡检发现了就得手动删除太麻烦。所以就打算写一个脚本,每次通过脚本去删除镜像的标签,保留最近的几个就好了。刚好最近在学习golang,就用它来写就好了。
比较尴尬的是,我脚本写完了,测试没问题后,发现新版本harbor已经可以在UI上设置保留策略了。自我安慰一下,就当作是一种练习、尝试好了!
//根据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
}
// 定义要获取的数据结构
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数据结构
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中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 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 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上进行垃圾回收!!!