Skip to content

Commit 9f88e23

Browse files
committed
Added tag listing directlry from dockerhub
1 parent 6ed533d commit 9f88e23

File tree

7 files changed

+316
-9
lines changed

7 files changed

+316
-9
lines changed

crypto/password_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package crypto
1616

1717
import (
18+
"fmt"
1819
"testing"
1920
)
2021

@@ -29,6 +30,11 @@ func TestPasswordHash(t *testing.T) {
2930
}
3031
}
3132

33+
func TestGenSecretKey(t *testing.T) {
34+
sk, _ := GenerateSecretKey(50)
35+
fmt.Printf("Secret key: %v\n", sk)
36+
}
37+
3238
func BenchmarkBcrypt(b *testing.B) {
3339
b.StopTimer()
3440
pass, _ := HashPassword("this is test")

docker/docker_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ var (
2727
apiVersion = "1.39"
2828
containerID = ""
2929

30-
// cacert, _ = ioutil.ReadFile("/media/igor/ubuntu/Nextcloud/Documents/Cocooncam/conffiles/cocoon-operator/ca.pem")
31-
// certKey, _ = ioutil.ReadFile("/media/igor/ubuntu/Nextcloud/Documents/Cocooncam/conffiles/cocoon-operator/client-key.pem")
32-
// cert, _ = ioutil.ReadFile("/media/igor/ubuntu/Nextcloud/Documents/Cocooncam/conffiles/cocoon-operator/client-cert.pem")
3330
cacert, _ = ioutil.ReadFile("/media/igor/ubuntu/Nextcloud/Documents/Cocooncam/conffiles/development/docker-keys/docker-ca.pem")
3431
certKey, _ = ioutil.ReadFile("/media/igor/ubuntu/Nextcloud/Documents/Cocooncam/conffiles/development/docker-keys/docker-client-key.pem")
3532
cert, _ = ioutil.ReadFile("/media/igor/ubuntu/Nextcloud/Documents/Cocooncam/conffiles/development/docker-keys/docker-client-cert.pem")

dockerhub/dockerhub.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package dockerhub
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"strings"
7+
"sync"
8+
"unicode/utf8"
9+
10+
mclog "github.com/chryscloud/go-microkit-plugins/log"
11+
"github.com/go-resty/resty/v2"
12+
)
13+
14+
var (
15+
authURL = "https://auth.docker.io/token"
16+
serviceURL = "registry.docker.io"
17+
registryURL = "https://registry-1.docker.io"
18+
)
19+
20+
// Options for digital ocean
21+
type Options struct {
22+
Log mclog.Logger
23+
Host string
24+
username string
25+
password string
26+
}
27+
28+
// Option a single option
29+
type Option func(*Options)
30+
31+
// Log - recommended but optional
32+
func Log(log mclog.Logger) Option {
33+
return func(args *Options) {
34+
args.Log = log
35+
}
36+
}
37+
38+
// Host - remote host, options, default = registry-1.docker.io
39+
func Host(remoteHost string) Option {
40+
return func(args *Options) {
41+
args.Host = remoteHost
42+
}
43+
}
44+
45+
// Credentials - optionsl
46+
func Credentials(username, password string) Option {
47+
return func(args *Options) {
48+
args.username = username
49+
args.password = password
50+
}
51+
}
52+
53+
// Client - dockerhub abstraction
54+
type Client struct {
55+
host string
56+
log mclog.Logger
57+
httpClient *resty.Client
58+
username string
59+
password string
60+
token string
61+
mutex *sync.Mutex
62+
}
63+
64+
type authResponse struct {
65+
Token string `json:"token"`
66+
ExpiresIn int `json:"expires_in"`
67+
IssuedAt string `json:"issued_at"`
68+
}
69+
70+
type tagsResponse struct {
71+
Name string `json:"name"`
72+
Tags []string `json:"tags"`
73+
}
74+
75+
// NewClient new DockerHub client to interactions with private or public docker hub (currently only Tags method supported)
76+
func NewClient(opts ...Option) DockerHub {
77+
args := &Options{}
78+
for _, op := range opts {
79+
if op != nil {
80+
op(args)
81+
}
82+
}
83+
if args.Host == "" {
84+
args.Host = registryURL
85+
}
86+
cl := resty.New().SetHeader("Content-Type", "application/json").SetHeader("Docker-Distribution-Api-Version", "registry/2.0")
87+
cl.Debug = false
88+
89+
outClient := &Client{
90+
host: args.Host,
91+
log: args.Log,
92+
httpClient: cl,
93+
mutex: &sync.Mutex{},
94+
}
95+
if args.username != "" {
96+
outClient.username = args.username
97+
}
98+
if args.password != "" {
99+
outClient.password = args.password
100+
}
101+
return outClient
102+
}
103+
104+
// Tags - returns the list of tags from the dockerhub repository
105+
func (client *Client) Tags(repository string) ([]string, error) {
106+
// remove first slash if exists in repository
107+
if strings.HasPrefix(repository, "/") {
108+
_, i := utf8.DecodeRuneInString(repository)
109+
repository = repository[i:]
110+
}
111+
url := client.host + "/v2/" + repository + "/tags/list"
112+
113+
var tagsResponse tagsResponse
114+
var tagsErr error
115+
var tagsGetResp *resty.Response
116+
117+
var token authResponse
118+
if client.token == "" {
119+
t, err := client.retrieveAuthToken(repository)
120+
if err != nil {
121+
return nil, err
122+
}
123+
token = *t
124+
client.mutex.Lock()
125+
client.token = token.Token
126+
client.mutex.Unlock()
127+
}
128+
129+
tagsGetResp, tagsErr = client.httpClient.R().SetHeader("Authorization", "Bearer "+client.token).SetResult(&tagsResponse).Get(url)
130+
if tagsGetResp.StatusCode() == http.StatusUnauthorized {
131+
t, err := client.retrieveAuthToken(repository)
132+
if err != nil {
133+
return nil, err
134+
}
135+
token = *t
136+
client.mutex.Lock()
137+
client.token = token.Token
138+
client.mutex.Unlock()
139+
140+
tagsGetResp, tagsErr = client.httpClient.R().SetResult(&tagsResponse).SetHeader("Authorization", "Bearer "+client.token).Get(url)
141+
142+
}
143+
if tagsErr != nil {
144+
if client.log != nil {
145+
client.log.Error("failed to retrieve tags", tagsErr, tagsGetResp)
146+
}
147+
return nil, tagsErr
148+
}
149+
if tagsGetResp.StatusCode() != http.StatusOK {
150+
if client.log != nil {
151+
client.log.Error("unexpected http code returned", tagsGetResp.StatusCode(), string(tagsGetResp.Body()))
152+
}
153+
return nil, errors.New("unexpected http code returned")
154+
}
155+
156+
return tagsResponse.Tags, nil
157+
}
158+
159+
func (client *Client) retrieveAuthToken(repository string) (*authResponse, error) {
160+
scope := getScope(repository)
161+
tokenURL := authURL + "?service=" + serviceURL + "&" + "scope=" + scope + "&offline_token=1&client_id=microkit-plugins-1.0"
162+
var authResponse authResponse
163+
request := client.httpClient.R()
164+
if client.username != "" && client.password != "" {
165+
request = request.SetBasicAuth(client.username, client.password)
166+
}
167+
request = request.SetResult(&authResponse)
168+
tokenResp, tokenErr := request.Get(tokenURL)
169+
if tokenErr != nil {
170+
if client.log != nil {
171+
client.log.Error("failed to get authentication token", tokenErr)
172+
return nil, errors.New("Unauthirized")
173+
}
174+
}
175+
if tokenResp.StatusCode() != http.StatusOK {
176+
if client.log != nil {
177+
client.log.Error("failed to retrieve auth token", tokenResp)
178+
return nil, errors.New("failed to retrieve auth token")
179+
}
180+
}
181+
return &authResponse, nil
182+
}
183+
184+
// e.g. cocooncam/cc
185+
func getScope(repository string) string {
186+
return "repository:" + repository + ":pull"
187+
}
188+
189+
func slashFirstSlash(repository string) string {
190+
// remove first slash if exists in repository
191+
if strings.HasPrefix(repository, "/") {
192+
_, i := utf8.DecodeRuneInString(repository)
193+
repository = repository[i:]
194+
}
195+
return repository
196+
}

dockerhub/dockerhub_interfaces.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dockerhub
2+
3+
// DockerHub registry API basic operations
4+
type DockerHub interface {
5+
Tags(repostiory string) ([]string, error)
6+
}

dockerhub/dockerhub_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dockerhub
2+
3+
import (
4+
"testing"
5+
6+
mclog "github.com/chryscloud/go-microkit-plugins/log"
7+
)
8+
9+
var (
10+
zl, _ = mclog.NewZapLogger("info")
11+
)
12+
13+
func TestInit(t *testing.T) {
14+
var option Option
15+
cl := NewClient(option, Log(zl))
16+
tags, err := cl.Tags("chryscloud/chrysedgeproxy")
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
if len(tags) <= 0 {
21+
t.Fatalf("expected more than 0 repositories, got %v", len(tags))
22+
}
23+
}

go.mod

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,16 @@ module github.com/chryscloud/go-microkit-plugins
33
go 1.13
44

55
require (
6-
github.com/Microsoft/go-winio v0.4.14 // indirect
76
github.com/blendle/zapdriver v1.3.1
7+
github.com/chryscloud/microkit-plugins v0.0.0-20200824191810-84e7409ad039
88
github.com/dgrijalva/jwt-go v3.2.0+incompatible
9-
github.com/docker/distribution v2.7.1+incompatible // indirect
109
github.com/docker/docker v1.13.1
11-
github.com/docker/go-connections v0.4.0 // indirect
12-
github.com/docker/go-units v0.4.0 // indirect
1310
github.com/gin-gonic/gin v1.6.3
1411
github.com/go-openapi/spec v0.19.9 // indirect
1512
github.com/go-openapi/swag v0.19.9 // indirect
16-
github.com/golang/protobuf v1.3.5 // indirect
13+
github.com/go-resty/resty/v2 v2.3.0
1714
github.com/mailru/easyjson v0.7.2 // indirect
1815
github.com/opencontainers/go-digest v1.0.0 // indirect
19-
github.com/pkg/errors v0.9.1 // indirect
2016
github.com/swaggo/gin-swagger v1.2.0
2117
github.com/swaggo/swag v1.6.7 // indirect
2218
go.uber.org/zap v1.15.0

0 commit comments

Comments
 (0)