Add tokens API CRUD to hub client

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
This commit is contained in:
Silvin Lubecki 2020-10-16 14:14:08 +02:00
Родитель c9ddc75f88
Коммит 70be35782e
4 изменённых файлов: 233 добавлений и 1 удалений

1
go.mod
Просмотреть файл

@ -18,6 +18,7 @@ require (
github.com/docker/go-units v0.4.0
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.8.0 // indirect
github.com/jinzhu/gorm v1.9.16 // indirect
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a

Просмотреть файл

@ -159,6 +159,7 @@ func (c *Client) login(hubBaseURL string, hubAuthConfig types.AuthConfig) (strin
func (c *Client) doRequest(req *http.Request, reqOps ...RequestOp) ([]byte, error) {
req.Header["Accept"] = []string{"application/json"}
req.Header["Content-Type"] = []string{"application/json"}
req.Header["User-Agent"] = []string{fmt.Sprintf("hub-tool/%s", internal.Version)}
for _, op := range reqOps {
if err := op(req); err != nil {

Просмотреть файл

@ -80,7 +80,7 @@ func (c *Client) GetTags(repository string, reqOps ...RequestOp) ([]Tag, error)
}
if c.fetchAllElements {
for next != "" {
pageTags, n, err := c.getTagsPage(next, repository)
pageTags, n, err := c.getTagsPage(next, repository, reqOps...)
if err != nil {
return nil, err
}

230
internal/hub/tokens.go Normal file
Просмотреть файл

@ -0,0 +1,230 @@
/*
Copyright 2020 Docker Hub Tool authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hub
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/google/uuid"
)
const (
// TokensURL path to the Hub API listing the Personal Access Tokens
TokensURL = "/v2/api_tokens"
// TokenURL path to the Hub API Personal Access Token
TokenURL = "/v2/api_tokens/%s"
)
//Token is a personal access token. The token field will only be filled at creation and can never been accessed again.
type Token struct {
UUID uuid.UUID
ClientID string
CreatorIP string
CreatorUA string
CreatedAt time.Time
LastUsed time.Time
GeneratedBy string
IsActive bool
Token string
Description string
}
// CreateToken creates a Personal Access Token and returns the token field only once
func (c *Client) CreateToken(description string) (*Token, error) {
data, err := json.Marshal(hubTokenRequest{Description: description})
if err != nil {
return nil, err
}
body := bytes.NewBuffer(data)
req, err := http.NewRequest("POST", c.domain+TokensURL, body)
if err != nil {
return nil, err
}
response, err := c.doRequest(req, WithHubToken(c.token))
if err != nil {
return nil, err
}
var tokenResponse hubTokenResult
if err := json.Unmarshal(response, &tokenResponse); err != nil {
return nil, err
}
token, err := convertToken(tokenResponse)
if err != nil {
return nil, err
}
return &token, nil
}
//GetTokens calls the hub repo API and returns all the information on all tokens
func (c *Client) GetTokens() ([]Token, error) {
u, err := url.Parse(c.domain + TokensURL)
if err != nil {
return nil, err
}
q := url.Values{}
q.Add("page_size", fmt.Sprintf("%v", itemsPerPage))
q.Add("page", "1")
u.RawQuery = q.Encode()
tokens, next, err := c.getTokensPage(u.String())
if err != nil {
return nil, err
}
if c.fetchAllElements {
for next != "" {
pageTokens, n, err := c.getTokensPage(next)
if err != nil {
return nil, err
}
next = n
tokens = append(tokens, pageTokens...)
}
}
return tokens, nil
}
//GetToken calls the hub repo API and returns the information on one token
func (c *Client) GetToken(tokenUUID string) (*Token, error) {
req, err := http.NewRequest("GET", c.domain+fmt.Sprintf(TokenURL, tokenUUID), nil)
if err != nil {
return nil, err
}
response, err := c.doRequest(req, WithHubToken(c.token))
if err != nil {
return nil, err
}
var tokenResponse hubTokenResult
if err := json.Unmarshal(response, &tokenResponse); err != nil {
return nil, err
}
token, err := convertToken(tokenResponse)
if err != nil {
return nil, err
}
return &token, nil
}
// UpdateToken updates a token's description and activeness
func (c *Client) UpdateToken(tokenUUID, description string, isActive bool) (*Token, error) {
data, err := json.Marshal(hubTokenRequest{Description: description, IsActive: isActive})
if err != nil {
return nil, err
}
body := bytes.NewBuffer(data)
req, err := http.NewRequest("PATCH", c.domain+fmt.Sprintf(TokenURL, tokenUUID), body)
if err != nil {
return nil, err
}
response, err := c.doRequest(req, WithHubToken(c.token))
if err != nil {
return nil, err
}
var tokenResponse hubTokenResult
if err := json.Unmarshal(response, &tokenResponse); err != nil {
return nil, err
}
token, err := convertToken(tokenResponse)
if err != nil {
return nil, err
}
return &token, nil
}
//RevokeToken revoke a token from personal access token
func (c *Client) RevokeToken(tokenUUID string) error {
//DELETE https://hub.docker.com/v2/api_tokens/8208674e-d08a-426f-b6f4-e3aba7058459 => 202
req, err := http.NewRequest("DELETE", c.domain+fmt.Sprintf(TokenURL, tokenUUID), nil)
if err != nil {
return err
}
_, err = c.doRequest(req, WithHubToken(c.token))
return err
}
func (c *Client) getTokensPage(url string) ([]Token, string, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, "", err
}
response, err := c.doRequest(req, WithHubToken(c.token))
if err != nil {
return nil, "", err
}
var hubResponse hubTokenResponse
if err := json.Unmarshal(response, &hubResponse); err != nil {
return nil, "", err
}
var tokens []Token
for _, result := range hubResponse.Results {
token, err := convertToken(result)
if err != nil {
return nil, "", err
}
tokens = append(tokens, token)
}
return tokens, hubResponse.Next, nil
}
type hubTokenRequest struct {
Description string `json:"token_label,omitempty"`
IsActive bool `json:"is_active,omitempty"`
}
type hubTokenResponse struct {
Count int `json:"count"`
Next string `json:"next,omitempty"`
Previous string `json:"previous,omitempty"`
Results []hubTokenResult `json:"results,omitempty"`
}
type hubTokenResult struct {
UUID string `json:"uuid"`
ClientID string `json:"client_id"`
CreatorIP string `json:"creator_ip"`
CreatorUA string `json:"creator_ua"`
CreatedAt time.Time `json:"created_at"`
LastUsed time.Time `json:"last_used,omitempty"`
GeneratedBy string `json:"generated_by"`
IsActive bool `json:"is_active"`
Token string `json:"token"`
TokenLabel string `json:"token_label"`
}
func convertToken(response hubTokenResult) (Token, error) {
u, err := uuid.Parse(response.UUID)
if err != nil {
return Token{}, err
}
return Token{
UUID: u,
ClientID: response.ClientID,
CreatorIP: response.CreatorIP,
CreatorUA: response.CreatorUA,
CreatedAt: response.CreatedAt,
LastUsed: response.LastUsed,
GeneratedBy: response.GeneratedBy,
IsActive: response.IsActive,
Token: response.Token,
Description: response.TokenLabel,
}, nil
}