196 lines
4.2 KiB
Go
196 lines
4.2 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"coopcloud.tech/abra/pkg/web"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/client"
|
|
"github.com/hashicorp/go-retryablehttp"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type RawTag struct {
|
|
Layer string
|
|
Name string
|
|
}
|
|
|
|
type RawTags []RawTag
|
|
|
|
var registryURL = "https://registry.hub.docker.com/v1/repositories/%s/tags"
|
|
|
|
func GetRegistryTags(image string) (RawTags, error) {
|
|
var tags RawTags
|
|
|
|
tagsUrl := fmt.Sprintf(registryURL, image)
|
|
if err := web.ReadJSON(tagsUrl, &tags); err != nil {
|
|
return tags, err
|
|
}
|
|
|
|
return tags, nil
|
|
}
|
|
|
|
func basicAuth(username, password string) string {
|
|
auth := username + ":" + password
|
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
|
}
|
|
|
|
// getRegv2Token retrieves a registry v2 authentication token.
|
|
func getRegv2Token(cl *client.Client, image reference.Named) (string, error) {
|
|
img := reference.Path(image)
|
|
tokenURL := "https://auth.docker.io/token"
|
|
values := fmt.Sprintf("service=registry.docker.io&scope=repository:%s:pull", img)
|
|
|
|
fullURL := fmt.Sprintf("%s?%s", tokenURL, values)
|
|
req, err := retryablehttp.NewRequest("GET", fullURL, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
username, userOk := os.LookupEnv("DOCKER_USERNAME")
|
|
password, passOk := os.LookupEnv("DOCKER_PASSWORD")
|
|
if userOk && passOk {
|
|
logrus.Debugf("using docker log in credentials for registry token request")
|
|
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", basicAuth(username, password)))
|
|
}
|
|
|
|
client := web.NewHTTPRetryClient()
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
_, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
|
|
tokenRes := struct {
|
|
AccessToken string `json:"access_token"`
|
|
Expiry int `json:"expires_in"`
|
|
Issued string `json:"issued_at"`
|
|
Token string `json:"token"`
|
|
}{}
|
|
|
|
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return tokenRes.Token, nil
|
|
}
|
|
|
|
// GetTagDigest retrieves an image digest from a v2 registry
|
|
func GetTagDigest(cl *client.Client, image reference.Named) (string, error) {
|
|
img := reference.Path(image)
|
|
tag := image.(reference.NamedTagged).Tag()
|
|
manifestURL := fmt.Sprintf("https://index.docker.io/v2/%s/manifests/%s", img, tag)
|
|
|
|
req, err := retryablehttp.NewRequest("GET", manifestURL, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
token, err := getRegv2Token(cl, image)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if token == "" {
|
|
return "", fmt.Errorf("unable to retrieve registry token?")
|
|
}
|
|
|
|
req.Header = http.Header{
|
|
"Accept": []string{
|
|
"application/vnd.docker.distribution.manifest.v2+json",
|
|
"application/vnd.docker.distribution.manifest.list.v2+json",
|
|
},
|
|
"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
|
|
}
|
|
|
|
client := web.NewHTTPRetryClient()
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
_, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
registryResT1 := struct {
|
|
SchemaVersion int
|
|
MediaType string
|
|
Manifests []struct {
|
|
MediaType string
|
|
Size int
|
|
Digest string
|
|
Platform struct {
|
|
Architecture string
|
|
Os string
|
|
}
|
|
}
|
|
}{}
|
|
|
|
registryResT2 := struct {
|
|
SchemaVersion int
|
|
MediaType string
|
|
Config struct {
|
|
MediaType string
|
|
Size int
|
|
Digest string
|
|
}
|
|
Layers []struct {
|
|
MediaType string
|
|
Size int
|
|
Digest string
|
|
}
|
|
}{}
|
|
|
|
if err := json.Unmarshal(body, ®istryResT1); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var digest string
|
|
for _, manifest := range registryResT1.Manifests {
|
|
if string(manifest.Platform.Architecture) == "amd64" {
|
|
digest = strings.Split(manifest.Digest, ":")[1][:7]
|
|
}
|
|
}
|
|
|
|
if digest == "" {
|
|
if err := json.Unmarshal(body, ®istryResT2); err != nil {
|
|
return "", err
|
|
}
|
|
digest = strings.Split(registryResT2.Config.Digest, ":")[1][:7]
|
|
}
|
|
|
|
if digest == "" {
|
|
return "", fmt.Errorf("Unable to retrieve amd64 digest for %s", image)
|
|
}
|
|
|
|
return digest, nil
|
|
}
|