package client import ( "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 } // 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) 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") values = fmt.Sprintf("%s&grant_type=password&client_id=coopcloud.tech&username=%s&password=%s", values, username, password) } fullURL := fmt.Sprintf("%s?%s", tokenURL, values) req, err := retryablehttp.NewRequest("GET", fullURL, nil) if err != nil { return "", err } 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 }