From ef9016865422975f3bc3a64d4f44a3d4e077accf Mon Sep 17 00:00:00 2001 From: moooofly Date: Sun, 30 Sep 2018 11:31:33 +0800 Subject: [PATCH 1/4] docs: update 'Garbage Collection.md' --- docs/Garbage Collection.md | 119 +++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/docs/Garbage Collection.md b/docs/Garbage Collection.md index 0fc9b53..17149b2 100644 --- a/docs/Garbage Collection.md +++ b/docs/Garbage Collection.md @@ -1,60 +1,62 @@ # [Garbage collection](https://docs.docker.com/registry/garbage-collection/) -> As of v2.4.0 a garbage collector command is included within the registry binary. -This document describes what this command does and how and why it should be used. +> As of v2.4.0 a **garbage collector** command is included within the registry binary. This document describes what this command does and how and why it should be used. -从 v2.4.0 版本开始,registry 可执行程序提供了 garbage collector 命令;本文主要说明该命令做了哪些事情,以及为何要使用该命令; +- 从 [v2.4.0](https://github.com/docker/distribution/tree/v2.4.0) 版本开始,registry 可执行程序提供了 garbage collector 命令; +- 本文主要说明该命令做了哪些事情,以及为何要使用该命令; + +> 项目 docker/docker-registry 更名为 docker/distribution ## About garbage collection -> In the context of the Docker registry, garbage collection is the process of -removing blobs from the filesystem when they are no longer referenced by a -manifest. Blobs can include both layers and manifests. +> In the context of the Docker registry, **garbage collection** is the process of removing **blobs** from the filesystem when they are no longer referenced by a **manifest**. +> **Blobs** can include both **layers and **manifests**. -对于 Docker registry 来说,garbage collection 对应从文件系统中移除 blobs 的处理行为,当且仅当这些 blobs 不在被 manifest 所引用时;Blobs 本身可以同时包含 layers 和 manifests ; +- 在 Docker registry 上下文下,garbage collection 对应从文件系统中移除 blobs 的处理过程(当且仅当 blobs 不在被 manifest 所引用时); +- Blobs 既可以是 layers ,也可以是 manifests ; -> Registry data can occupy considerable amounts of disk space. In addition, -garbage collection can be a security consideration, when it is desirable to ensure -that certain layers no longer exist on the filesystem. +> **Registry data** can occupy considerable amounts of disk space. +> In addition, garbage collection can be a security consideration, when it is desirable to ensure that certain layers no longer exist on the filesystem. -Registry 数据会占用大量的磁盘空间;除此之外,garbage collection 还可以作为一种安全考量,用于确定特定的 layers 在文件系统中是否已经不存在; +- Registry 数据会占用大量的磁盘空间; +- 除此之外,garbage collection 还可以作为一种安全考量,用于确保特定的 layers 在文件系统中确实不再存在; ## Garbage collection in practice -> Filesystem layers are stored by their content address in the Registry. This -has many advantages, one of which is that data is stored once and referred to by manifests. -See [here](compatibility.md#content-addressable-storage-cas) for more details. +> **Filesystem layers are stored by their content address in the Registry**. +> This has many advantages, one of which is that data is stored once and referred to by manifests. +> See [here](https://docs.docker.com/registry/compatibility/#content-addressable-storage-cas) for more details. -文件系统 layers 是按照其 content address 保存在 Registry 中的;这种方式有很多有点,其一就是数据只要存储一次,即可被所有 manifests 所引用; +- 文件系统 layers 是基于其 content address 保存在 Registry 中的; +- 这种方式有很多优点,其一就是 data 只需要保存一份,后续即可通过不同的 manifests 引用; -> Layers are therefore shared amongst manifests; each manifest maintains a reference -to the layer. As long as a layer is referenced by one manifest, it cannot be garbage -collected. +> **Layers are therefore shared amongst manifests**. **each manifest maintains a reference to the layer**. +> As long as a layer is referenced by one manifest, it cannot be garbage collected. -因此 Layers 是在 manifests 之间共享的;每个 manifest 都维护一个针对特定 layer 的引用;只要 layer 仍旧被一个 manifest 所引用,就不能被 GC ; +- Layers 是在不同 manifests 之间是共享的; +- 每个 manifest 都维护一个针对特定 layer 的引用; +- 只要一个 layer 仍旧被一个 manifest 所引用,该 layer 就不能被 GC ; -> Manifests and layers can be `deleted` with the registry API (refer to the API -documentation [here](spec/api.md#deleting-a-layer) and -[here](spec/api.md#deleting-an-image) for details). This API removes references -to the target and makes them eligible for garbage collection. It also makes them -unable to be read via the API. +> **Manifests and layers can be `deleted` with the registry API** (refer to the API documentation [here](https://docs.docker.com/registry/spec/api/#deleting-a-layer) +> and [here](https://docs.docker.com/registry/spec/api/#deleting-an-image) for details). +> This API removes references to the target and makes them eligible for garbage collection. It also makes them unable to be read via the API. -Manifests 和 layers 都可以通过 registry API 进行 `deleted` ;该 API 能够移除针对 target 的引用,使其能够被 GC ;该 API 同样会令 target 无法通过 API 被读取; +- Manifests 和 layers 都可以通过 registry API 进行 `deleted` ; +- 该 API 能够移除针对 target 的引用,使其能够被 GC ;该 API 同样会令 target 无法通过 API 被读取; -> If a layer is deleted it will be removed from the filesystem when garbage collection -is run. If a manifest is deleted the layers to which it refers will be removed from -the filesystem if no other manifests refers to them. +> **If a layer is deleted**, it will be removed from the filesystem when garbage collection is run. +> **If a manifest is deleted**, the layers to which it refers will be removed from the filesystem if no other manifests refers to them. -如果 layer 被删除了,则可以在 GC 运行的时候,将其从文件系统移除;如果 manifest 被删除了,则被其引用的 layers 在没有被其他 manifests 所引用的情况下,会被删除; +- 如果 layer 被删除了,则可以在 GC 运行的时候,将其从文件系统移除; +- 如果 manifest 被删除了,则被其引用的 layers 在没有被其他 manifests 所引用的情况下,会被删除; ### Example -> In this example manifest A references two layers: `a` and `b`. Manifest `B` references -layers `a` and `c`. In this state, nothing is eligible for garbage collection: +> In this example manifest A references two layers: `a` and `b`. Manifest `B` references layers `a` and `c`. +> In this state, nothing is eligible for garbage collection: -在这个例子中,manifest A 引用了两个 layer `a` 和 layer `b` ,manifest B -引用了两个 layer `a` 和 layer `c` ;在这种情况下,没有任何 target 能够被 GC ; +在这个例子中,manifest A 引用了 layer `a` 和 layer `b` ,manifest B 引用了 layer `a` 和 layer `c` ;在这种情况下,没有任何 blobs 能够被 GC ; ``` A -----> a <----- B @@ -72,16 +74,17 @@ A -----> a B c ``` -> In this state layer `c` no longer has a reference and is eligible for garbage -collection. Layer `a` had one reference removed but will not be garbage -collected as it is still referenced by manifest `A`. The blob representing -manifest `B` will also be eligible for garbage collection. +> In this state layer `c` no longer has a reference and is eligible for garbage collection. +> Layer `a` had one reference removed but will not be garbage collected as it is still referenced by manifest `A`. +> The blob representing manifest `B` will also be eligible for garbage collection. -在这种状态下,layer `c` 已经没有被引用,因此符合被 GC 的要求;而 Layer `a` 有一个引用被移除了,但仍旧存在 manifest `A` 的引用,因此无法被 GC ;另外,代表 manifest `B` 的 blob 同样符合被 GC 的要求; +- 在这种状态下,layer `c` 已经没有被引用,因此符合被 GC 的要求; +- 而 Layer `a` 有一个引用被移除了,但仍旧存在 manifest `A` 的引用,因此无法被 GC ; +- 另外,代表 manifest `B` 的 blob 同样符合被 GC 的要求; > After garbage collection has been run, manifest `A` and its blobs remain. -在 GC 运行过后,manifest `A` 和其 blobs 仍旧存在; +在 GC 运行过后,manifest `A` 和其引用的 blobs 仍旧存在; ``` A -----> a @@ -91,46 +94,44 @@ A -----> a ### More details about garbage collection -> Garbage collection runs in two phases. First, in the 'mark' phase, the process -scans all the manifests in the registry. From these manifests, it constructs a -set of content address digests. This set is the 'mark set' and denotes the set -of blobs to *not* delete. Secondly, in the 'sweep' phase, the process scans all -the blobs and if a blob's content address digest is not in the mark set, the -process will delete it. +> Garbage collection runs in two phases. +> +> - First, in the **'mark' phase**, the process **scans all the manifests in the registry**. From these manifests, it **constructs a set of content address digests**. +> This set is the 'mark set' and denotes the set of blobs to *not* delete. +> - Secondly, in the **'sweep' phase**, the process **scans all the blobs** and if a blob's content address digest is not in the mark set, the process will delete it. GC 运行分为两个阶段: -- 首先,为 'mark' 阶段,GC 进程会扫描 registry 中所有 manifests ,并构建出一组(set)相应的 content address digests ;该组实际为 'mark set' ,标识不能被删除的 blobs 组; +- 首先,为 'mark' 阶段,GC 进程会扫描 registry 中所有 manifests ,并构建出一组相应的 content address digests ;该组实际为 'mark set' ,标识不能被删除的 blobs 组; - 其次,为 'sweep' 阶段,GC 进程会扫描所有的 blobs ,若某个 blob 的 content address digest 不在 mark set 中,则将其删除; ->> **Note**: You should ensure that the registry is in read-only mode or not running at ->> all. If you were to upload an image while garbage collection is running, there is the ->> risk that the image's layers will be mistakenly deleted, leading to a corrupted image. +>> **Note**: **You should ensure that the registry is in read-only mode or not running at all**. +>> If you were to upload an image while garbage collection is running, there is the risk that the image's layers will be mistakenly deleted, leading to a corrupted image. -**注意**:你需要确保 registry 处于 read-only 模式,或者干脆没有运行;如果你你在 GC 运行过程中,上传了 image ,则有很大可能会导致 image 的 layers 被错误的删除,导致 image 损毁; +**注意**:你需要确保 registry 处于 read-only 模式,或者干脆处于未运行状态;如果你在 GC 运行过程中上传了 image ,则有很大可能会导致 image 的 layers 被错误的删除,导致 image 损毁; -> This type of garbage collection is known as stop-the-world garbage collection. In future -registry versions the intention is that garbage collection will be an automated background -action and this manual process will no longer apply. +> This type of garbage collection is known as stop-the-world garbage collection. +> In future registry versions the intention is that garbage collection will be an automated background action and this manual process will no longer apply. 该 GC 的类型为 stop-the-world GC ,在未来的 registry 版本中,GC 将会成为一种全自动的后台行为,故当前这种手动方式将不复存在; - ## Run garbage collection > Garbage collection can be run as follows 可以按照如下命令运行 GC -`bin/registry garbage-collect [--dry-run] /path/to/config.yml` +``` +bin/registry garbage-collect [--dry-run] /path/to/config.yml +``` -> The garbage-collect command accepts a `--dry-run` parameter, which will print the progress -of the mark and sweep phases without removing any data. Running with a log level of `info` -will give a clear indication of what will and will not be deleted. +> The `garbage-collect` command accepts a `--dry-run` parameter, which will print the progress of the mark and sweep phases without removing any data. +> Running with a log level of `info` will give a clear indication of what will and will not be deleted. -GC 命令接受一个 `--dry-run` 参数,能够输出 mark 和 sweep 阶段的处理过程,而不移除任何实际数据;若指定日志级别 `info` 运行上述命令,则能够清楚的看到哪些内容会被删除,哪些内容不会被删除; +- GC 命令接受一个 `--dry-run` 参数,能够输出 mark 和 sweep 阶段的处理过程,而不移除任何实际数据; +- 若指定日志级别 `info` 运行上述命令,则能够清楚的看到哪些内容会被删除,哪些内容不会被删除; > The config.yml file should be in the following format: From 9ad5ea945489f46925423915cf0fb742a4331097 Mon Sep 17 00:00:00 2001 From: moooofly Date: Sun, 30 Sep 2018 15:49:06 +0800 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20update=20'Harbor=20=E9=95=9C?= =?UTF-8?q?=E5=83=8F=E5=88=A0=E9=99=A4=E9=97=AE=E9=A2=98'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\217\345\210\240\351\231\244\351\227\256\351\242\230.md" | 6 ++++++ 1 file changed, 6 insertions(+) diff --git "a/docs/Harbor \351\225\234\345\203\217\345\210\240\351\231\244\351\227\256\351\242\230.md" "b/docs/Harbor \351\225\234\345\203\217\345\210\240\351\231\244\351\227\256\351\242\230.md" index 94e6b52..594a4fc 100644 --- "a/docs/Harbor \351\225\234\345\203\217\345\210\240\351\231\244\351\227\256\351\242\230.md" +++ "b/docs/Harbor \351\225\234\345\203\217\345\210\240\351\231\244\351\227\256\351\242\230.md" @@ -33,6 +33,12 @@ > > For more information about GC, please see GC. +简单来说,Repository 删除分两步: + +- 首先,通过 Harbor UI 或者 RESTful API 删除目标 repository 或者目标 repository 的某些 tags ;此时的删除被称作“soft deletion”;在 soft deletion 之后,repository 不再直接被 harbor 所管理,但 repository 对应的文件内容仍旧存在于 harbor 所使用的存储设备中; +- 其次,通过 repository 提供的 GC 命令删除 repository 相关的文件系统文件;在执行 GC 命令时,要求 harbor 处于只读状态,或者干脆不要运行;否则,可能发生 image 的某些 layers 被错误的删除的情况,进而导致 image 不可用(相关 issues :[goharbor/harbor/issues#4214](https://github.com/goharbor/harbor/issues/4214) [goharbor/harbor/issues#5167](https://github.com/goharbor/harbor/issues/5167)); + + 这里有一个问题需要弄清楚:**即删除镜像的过程中需要注意哪些问题**? 详见《[Garbage Collection](https://github.com/moooofly/harbor-go-client/blob/master/docs/Garbage%20Collection.md)》 From 603570c59250a47b48ec83163b8dca9ab443f9d4 Mon Sep 17 00:00:00 2001 From: moooofly Date: Wed, 17 Oct 2018 10:52:34 +0800 Subject: [PATCH 3/4] feat(api): add labels APIs --- api/labels.go | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 api/labels.go diff --git a/api/labels.go b/api/labels.go new file mode 100644 index 0000000..e564517 --- /dev/null +++ b/api/labels.go @@ -0,0 +1,315 @@ +package api + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/moooofly/harbor-go-client/utils" +) + +func init() { + utils.Parser.AddCommand("labels_list", + "List labels according to the query strings.", + "This endpoint let user list labels by name, scope and project_id", + &labelslist) + utils.Parser.AddCommand("label_create", + "Post creates a label", + "This endpoint let user creates a label.", + &labelcreate) + utils.Parser.AddCommand("label_del_by_id", + "Delete the label specified by ID.", + "Delete the label specified by ID.", + &labeldel) + utils.Parser.AddCommand("label_get_by_id", + "Get the label specified by ID.", + "This endpoint let user get the label by specific ID.", + &labelget) + utils.Parser.AddCommand("label_update", + "Update the label properties.", + "This endpoint let user update label properties.", + &labelupdate) +} + +type labelsList struct { + Name string `short:"n" long:"name" description:"The label name as filter." default:""` + Scope string `short:"s" long:"scope" description:"(REQUIRED) The label scope. Valid values are 'g' and 'p'. 'g' for global labels and 'p' for project labels." required:"yes"` + ProjectID int `short:"i" long:"project_id" description:"Relevant project ID, Required when scope is 'p'." default:"0"` + Page int `short:"p" long:"page" description:"The page nubmer, default is 1." default:"1"` + PageSize int `short:"z" long:"page_size" description:"The size of per page, default is 10, maximum is 100." default:"10"` +} + +var labelslist labelsList + +func (x *labelsList) Execute(args []string) error { + GetLabels(utils.URLGen("/api/labels")) + return nil +} + +// GetLabels let user list labels by name, scope and project_id +// +// params: +// name - The label name. +// scope - (REQUIRED) The label scope. Valid values are g and p. g for global labels and p for project labels. +// project_id - Relevant project ID, required when scope is p. +// page - The page nubmer, default is 1. +// page_size - The size of per page, default is 10, maximum is 100. +// +// operation format: +// GET /labels +// +// e.g. curl -X GET --header 'Accept: application/json' 'https://localhost/api/labels?scope=g&page=1&page_size=10' +// +func GetLabels(baseURL string) { + targetURL := baseURL + "?scope=" + labelslist.Scope + + "&name=" + labelslist.Name + + "&project_id=" + strconv.Itoa(labelslist.ProjectID) + + "&page=" + strconv.Itoa(labelslist.Page) + + "&page_size=" + strconv.Itoa(labelslist.PageSize) + + fmt.Println("==> GET", targetURL) + + // Read beegosessionID from .cookie.yaml + c, err := utils.CookieLoad() + if err != nil { + fmt.Println("Error:", err) + return + } + + utils.Request.Get(targetURL). + Set("Cookie", "harbor-lang=zh-cn; beegosessionID="+c.BeegosessionID). + End(utils.PrintStatus) +} + +type labelCreate struct { + ID int `short:"i" long:"id" description:"The ID of label. If not set, automatically generated by harbor." default:"0" json:"id"` + Name string `short:"n" long:"name" description:"(REQUIRED) The name of label." required:"yes" json:"name"` + Description string `short:"d" long:"description" description:"(REQUIRED) The description of label." required:"yes" json:"description"` + Color string `short:"c" long:"color" description:"The color code of label. (e.g. Format: #A9B6BE)" default:"#000000" json:"color"` + Scope string `short:"s" long:"scope" description:"The scope of label, 'g' for global labels and 'p' for project labels." default:"g" json:"scope"` + ProjectID int `short:"p" long:"project_id" description:"The project ID if the label is a project label. Required when scope is 'p'." default:"0" json:"project_id"` + CreationTime string `long:"creation_time" description:"The creation time of label. default time.Now()" default:"" json:"creation_time"` + UpdateTime string `long:"update_time" description:"The update time of label. default time.Now()" default:"" json:"update_time"` + Deleted bool `long:"deleted" description:"The label is deleted or not." json:"deleted"` +} + +var labelcreate labelCreate + +func (x *labelCreate) Execute(args []string) error { + PostLabelCreate(utils.URLGen("/api/labels")) + return nil +} + +// PostLabelCreate let user creates a label. +// +// params: +// id - The ID of label. +// name - (REQUIRED) The name of label. +// description - (REQUIRED) The description of label. +// color - The color code of label. (e.g. Format: #A9B6BE) +// scope - The scope of label. ('p' indicates project scope, 'g' indicates global scope) +// project_id - Which project id this label belongs to when created. ('0' indicates global label, others indicate specific project) +// creation_time - The creation time of label. default time.Now() +// update_time - The update time of label. default time.Now() +// deleted - not sure +// +// format: +// POST /labels +/* +curl -X POST --header 'Content-Type: application/json' --header 'Accept: text/plain' -d '{ \ + "id": 100, \ + "name": "label-name-100", \ + "description": "label-description-100", \ + "color": "#000000", \ + "scope": "g", \ + "project_id": 0, \ + "deleted": true \ + }' 'https://localhost/api/labels' +*/ +func PostLabelCreate(baseURL string) { + if labelcreate.CreationTime == "" || labelcreate.UpdateTime == "" { + now := time.Now().Format("2006-01-02T15:04:05Z") + labelcreate.CreationTime = now + labelcreate.UpdateTime = now + } + + targetURL := baseURL + fmt.Println("==> POST", targetURL) + + // Read beegosessionID from .cookie.yaml + c, err := utils.CookieLoad() + if err != nil { + fmt.Println("Error:", err) + return + } + + t, err := json.Marshal(&labelcreate) + if err != nil { + fmt.Println("error:", err) + return + } + + fmt.Println("==> label add:", string(t)) + + utils.Request.Post(targetURL). + Set("Cookie", "harbor-lang=zh-cn; beegosessionID="+c.BeegosessionID). + Send(string(t)). + End(utils.PrintStatus) +} + +type labelDel struct { + ID int `short:"i" long:"id" description:"(REQUIRED) Label ID." required:"yes"` +} + +var labeldel labelDel + +func (x *labelDel) Execute(args []string) error { + DeleteLabel(utils.URLGen("/api/labels")) + return nil +} + +// DeleteLabel deletes the label specified by ID. +// +// params: +// id - Label ID. +// +// operation format: +// DELETE /labels/{id} +// +// e.g. curl -X DELETE --header 'Accept: text/plain' 'https://localhost/api/labels/100' +// +func DeleteLabel(baseURL string) { + targetURL := baseURL + "/" + strconv.Itoa(labeldel.ID) + + fmt.Println("==> DELETE", targetURL) + + // Read beegosessionID from .cookie.yaml + c, err := utils.CookieLoad() + if err != nil { + fmt.Println("Error:", err) + return + } + + utils.Request.Delete(targetURL). + Set("Cookie", "harbor-lang=zh-cn; beegosessionID="+c.BeegosessionID). + End(utils.PrintStatus) +} + +type labelGet struct { + ID int `short:"i" long:"id" description:"(REQUIRED) Label ID." required:"yes"` +} + +var labelget labelGet + +func (x *labelGet) Execute(args []string) error { + GetLabel(utils.URLGen("/api/labels")) + return nil +} + +// GetLabel gets the label specified by ID. +// +// params: +// id - Label ID. +// +// operation format: +// GET /labels/{id} +// +// e.g. curl -X GET --header 'Accept: text/plain' 'https://localhost/api/labels/100' +// +func GetLabel(baseURL string) { + targetURL := baseURL + "/" + strconv.Itoa(labelget.ID) + + fmt.Println("==> GET", targetURL) + + // Read beegosessionID from .cookie.yaml + c, err := utils.CookieLoad() + if err != nil { + fmt.Println("Error:", err) + return + } + + utils.Request.Get(targetURL). + Set("Cookie", "harbor-lang=zh-cn; beegosessionID="+c.BeegosessionID). + End(utils.PrintStatus) +} + +type labelUpdate struct { + ID int `short:"i" long:"id" description:"(REQUIRED) Label ID." required:"yes" json:"id"` + Name string `short:"n" long:"name" description:"(REQUIRED) The name of label." required:"yes" json:"name"` + Description string `short:"d" long:"description" description:"(REQUIRED) The description of label." required:"yes" json:"description"` + Color string `short:"c" long:"color" description:"The color code of label. (e.g. Format: #A9B6BE)" default:"#000000" json:"color"` + Scope string `short:"s" long:"scope" description:"The scope of label, 'g' for global labels and 'p' for project labels." default:"g" json:"scope"` + ProjectID int `short:"p" long:"project_id" description:"The project ID if the label is a project label. Required when scope is 'p'." default:"0" json:"project_id"` + //CreationTime string `long:"creation_time" description:"The creation time of label. default time.Now()" default:"" json:"creation_time"` + //UpdateTime string `long:"update_time" description:"The update time of label. default time.Now()" default:"" json:"update_time"` + Deleted bool `long:"deleted" description:"The label is deleted or not." json:"deleted"` +} + +var labelupdate labelUpdate + +func (x *labelUpdate) Execute(args []string) error { + PutLabelUpdate(utils.URLGen("/api/labels")) + return nil +} + +// PutLabelUpdate let user update label properties. +// +// params: +// id - The ID of label. +// name - (REQUIRED) The name of label. +// description - (REQUIRED) The description of label. +// color - The color code of label. (e.g. Format: #A9B6BE) +// scope - The scope of label. ('p' indicates project scope, 'g' indicates global scope) +// project_id - Which project id this label belongs to when created. ('0' indicates global label, others indicate specific project) +// creation_time - The creation time of label. default time.Now() +// update_time - The update time of label. default time.Now() +// deleted - not sure +// +// operation format: +// PUT /labels/{id} +/* +curl -X PUT --header 'Content-Type: application/json' --header 'Accept: text/plain' -d '{ \ + "id": 0, \ + "name": "label-name-100", \ + "description": "label-description-100", \ + "color": "#000000", \ + "scope": "g", \ + "project_id": 0, \ + "deleted": true \ + }' 'https://localhost/api/labels/100' +*/ +func PutLabelUpdate(baseURL string) { + // NOTE: + // Though as swagger shows, both creation_time and creation_time can be updated, but actually not + /* + if labelupdate.UpdateTime == "" { + now := time.Now().Format("2006-01-02T15:04:05Z") + labelupdate.UpdateTime = now + } + */ + + targetURL := baseURL + "/" + strconv.Itoa(labelupdate.ID) + fmt.Println("==> PUT", targetURL) + + // Read beegosessionID from .cookie.yaml + c, err := utils.CookieLoad() + if err != nil { + fmt.Println("Error:", err) + return + } + + t, err := json.Marshal(&labelupdate) + if err != nil { + fmt.Println("error:", err) + return + } + + fmt.Println("==> label add:", string(t)) + + utils.Request.Put(targetURL). + Set("Cookie", "harbor-lang=zh-cn; beegosessionID="+c.BeegosessionID). + Set("Content-Type", "application/json"). + Send(string(t)). + End(utils.PrintStatus) +} From 90b100afba7c9e06a9b5fb16bfeb10554664c663 Mon Sep 17 00:00:00 2001 From: moooofly Date: Wed, 17 Oct 2018 11:02:23 +0800 Subject: [PATCH 4/4] chore: bump version number, update CHANGELOG.md and README.md --- CHANGELOG.md | 10 ++++++++++ README.md | 10 +++++----- VERSION | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0da336..c8d6de5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +# [0.9.6](https://github.com/moooofly/harbor-go-client/compare/v0.9.5...v0.9.6) (2018-10-17) + + +### Features + +* **api:** add labels APIs ([603570c](https://github.com/moooofly/harbor-go-client/commit/603570c)) + + + # [0.9.5](https://github.com/moooofly/harbor-go-client/compare/v0.9.4...v0.9.5) (2018-09-29) diff --git a/README.md b/README.md index ebaf9c3..9a5d2d1 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,11 @@ Current Harbor API support status: - [x] GET /api/policies/replication/{id} - [x] PUT /api/policies/replication/{id} - labels - - [ ] GET /api/labels - - [ ] POST /api/labels - - [ ] DELETE /api/labels/{id} - - [ ] GET /api/labels/{id} - - [ ] PUT /api/labels/{id} + - [x] GET /api/labels + - [x] POST /api/labels + - [x] DELETE /api/labels/{id} + - [x] GET /api/labels/{id} + - [x] PUT /api/labels/{id} - replications - [ ] POST /api/replications - targets diff --git a/VERSION b/VERSION index 06769c1..c7ceed8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.9.5 +v0.9.6