diff --git a/cli/catalogue/catalogue.go b/cli/catalogue/catalogue.go index cb5bfa8f..b56e4ed8 100644 --- a/cli/catalogue/catalogue.go +++ b/cli/catalogue/catalogue.go @@ -66,8 +66,6 @@ var catalogueGenerateCommand = cli.Command{ internal.PublishFlag, internal.DryFlag, internal.SkipUpdatesFlag, - internal.RegistryUsernameFlag, - internal.RegistryPasswordFlag, }, Before: internal.SubCommandBefore, Description: ` @@ -132,11 +130,7 @@ keys configured on your account. continue } - versions, err := recipe.GetRecipeVersions( - recipeMeta.Name, - internal.RegistryUsername, - internal.RegistryPassword, - ) + versions, err := recipe.GetRecipeVersions(recipeMeta.Name) if err != nil { logrus.Warn(err) } diff --git a/cli/internal/cli.go b/cli/internal/cli.go index 19df5273..b50a594d 100644 --- a/cli/internal/cli.go +++ b/cli/internal/cli.go @@ -336,24 +336,6 @@ var SkipUpdatesFlag = &cli.BoolFlag{ Destination: &SkipUpdates, } -var RegistryUsername string -var RegistryUsernameFlag = &cli.StringFlag{ - Name: "username, user", - Value: "", - Usage: "Registry username", - EnvVar: "REGISTRY_USERNAME", - Destination: &RegistryUsername, -} - -var RegistryPassword string -var RegistryPasswordFlag = &cli.StringFlag{ - Name: "password, pass", - Value: "", - Usage: "Registry password", - EnvVar: "REGISTRY_PASSWORD", - Destination: &RegistryUsername, -} - var AllTags bool var AllTagsFlag = &cli.BoolFlag{ Name: "all-tags, a", diff --git a/cli/recipe/upgrade.go b/cli/recipe/upgrade.go index d3be2206..58c4bc9e 100644 --- a/cli/recipe/upgrade.go +++ b/cli/recipe/upgrade.go @@ -113,13 +113,13 @@ You may invoke this command in "wizard" mode and be prompted for input: logrus.Fatal(err) } - image := reference.Path(img) - regVersions, err := client.GetRegistryTags(image) + regVersions, err := client.GetRegistryTags(img) if err != nil { logrus.Fatal(err) } - logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image) + image := reference.Path(img) + logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image) image = formatter.StripTagMeta(image) switch img.(type) { @@ -142,7 +142,7 @@ You may invoke this command in "wizard" mode and be prompted for input: var compatible []tagcmp.Tag for _, regVersion := range regVersions { - other, err := tagcmp.Parse(regVersion.Name) + other, err := tagcmp.Parse(regVersion) if err != nil { continue // skip tags that cannot be parsed } @@ -232,7 +232,7 @@ You may invoke this command in "wizard" mode and be prompted for input: msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) compatibleStrings = []string{"skip"} for _, regVersion := range regVersions { - compatibleStrings = append(compatibleStrings, regVersion.Name) + compatibleStrings = append(compatibleStrings, regVersion) } } diff --git a/pkg/client/registry.go b/pkg/client/registry.go index 2a57039c..d33ded84 100644 --- a/pkg/client/registry.go +++ b/pkg/client/registry.go @@ -1,193 +1,57 @@ package client import ( - "encoding/base64" - "encoding/json" + "context" "fmt" - "io/ioutil" - "net/http" "strings" - "coopcloud.tech/abra/pkg/web" + "github.com/containers/image/docker" + "github.com/containers/image/types" "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 -} +// GetRegistryTags retrieves all tags of an image from a container registry. +func GetRegistryTags(img reference.Named) ([]string, error) { + var tags []string -type RawTags []RawTag + ref, err := docker.ParseReference(fmt.Sprintf("//%s", img)) + if err != nil { + return tags, fmt.Errorf("failed to parse image %s, saw: %s", img, err.Error()) + } -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 { + ctx := context.Background() + tags, err = docker.GetRepositoryTags(ctx, &types.SystemContext{}, ref) + if err != nil { return tags, err } return tags, nil } -func basicAuth(username, password string) string { - auth := username + ":" + password - return base64.StdEncoding.EncodeToString([]byte(auth)) -} +// GetTagDigest retrieves an image digest from a container registry. +func GetTagDigest(cl *client.Client, image reference.Named) (string, error) { + target := fmt.Sprintf("//%s", reference.Path(image)) -// getRegv2Token retrieves a registry v2 authentication token. -func getRegv2Token(cl *client.Client, image reference.Named, registryUsername, registryPassword string) (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) + ref, err := docker.ParseReference(target) if err != nil { - return "", err + return "", fmt.Errorf("failed to parse image %s, saw: %s", image, err.Error()) } - if registryUsername != "" && registryPassword != "" { - logrus.Debugf("using registry log in credentials for token request") - auth := basicAuth(registryUsername, registryPassword) - req.Header.Add("Authorization", fmt.Sprintf("Basic %s", auth)) - } - - client := web.NewHTTPRetryClient() - res, err := client.Do(req) + ctx := context.Background() + img, err := ref.NewImage(ctx, nil) if err != nil { - return "", err + logrus.Debugf("failed to query remote registry for %s, saw: %s", image, err.Error()) + return "", fmt.Errorf("unable to read digest for %s", image) } - defer res.Body.Close() + defer img.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, registryUsername, registryPassword string) (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, registryUsername, registryPassword) - 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] - } - } + digest := img.ConfigInfo().Digest.String() if digest == "" { - if err := json.Unmarshal(body, ®istryResT2); err != nil { - return "", err - } - digest = strings.Split(registryResT2.Config.Digest, ":")[1][:7] + return digest, fmt.Errorf("unable to read digest for %s", image) } - if digest == "" { - return "", fmt.Errorf("Unable to retrieve amd64 digest for %s", image) - } - - return digest, nil + return strings.Split(digest, ":")[1][:7], nil } diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index 01e09955..0f8f5423 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -232,7 +232,11 @@ func Get(recipeName string) (Recipe, error) { meta, err := GetRecipeMeta(recipeName) if err != nil { - return Recipe{}, err + if strings.Contains(err.Error(), "does not exist") { + meta = RecipeMeta{} + } else { + return Recipe{}, err + } } return Recipe{ @@ -799,8 +803,7 @@ func GetRecipeMeta(recipeName string) (RecipeMeta, error) { recipeMeta, ok := catl[recipeName] if !ok { - err := fmt.Errorf("recipe %s does not exist?", recipeName) - return RecipeMeta{}, err + return RecipeMeta{}, fmt.Errorf("recipe %s does not exist?", recipeName) } if err := EnsureExists(recipeName); err != nil { @@ -925,7 +928,7 @@ func ReadReposMetadata() (RepoCatalogue, error) { } // GetRecipeVersions retrieves all recipe versions. -func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (RecipeVersions, error) { +func GetRecipeVersions(recipeName string) (RecipeVersions, error) { versions := RecipeVersions{} recipeDir := path.Join(config.RECIPES_DIR, recipeName) @@ -969,7 +972,7 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R return err } - cl, err := client.New("default") // only required for docker.io registry calls + cl, err := client.New("default") // only required for container registry calls if err != nil { return err } @@ -999,18 +1002,19 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R var exists bool var digest string if digest, exists = queryCache[img]; !exists { - logrus.Debugf("looking up image: %s from %s", img, path) + logrus.Debugf("cache miss: querying for image: %s, tag: %s", path, tag) + var err error - digest, err = client.GetTagDigest(cl, img, registryUsername, registryPassword) + digest, err = client.GetTagDigest(cl, img) if err != nil { logrus.Warn(err) - continue + digest = "unknown" } - logrus.Debugf("queried for image: %s, tag: %s, digest: %s", path, tag, digest) + queryCache[img] = digest - logrus.Debugf("cached image: %s, tag: %s, digest: %s", path, tag, digest) + logrus.Debugf("cached insert: %s, tag: %s, digest: %s", path, tag, digest) } else { - logrus.Debugf("reading image: %s, tag: %s, digest: %s from cache", path, tag, digest) + logrus.Debugf("cache hit: image: %s, tag: %s, digest: %s", path, tag, digest) } versionMeta[service.Name] = ServiceMeta{