mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
cli: use versionsapi fetcher to fetch image ref
Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com>
This commit is contained in:
parent
e1a0a01ac3
commit
f19a90527b
@ -9,133 +9,29 @@ package image
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/fs"
|
||||||
"net/http"
|
"regexp"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/shortname"
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
// imageName is a struct that describes a Constellation OS imageName name.
|
|
||||||
type imageName struct {
|
|
||||||
Ref string
|
|
||||||
Stream string
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newImageName(name string) (*imageName, error) {
|
|
||||||
ref, stream, version, err := shortname.ToParts(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &imageName{
|
|
||||||
Ref: ref,
|
|
||||||
Stream: stream,
|
|
||||||
Version: version,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *imageName) infoPath() string {
|
|
||||||
return path.Join(constants.CDNAPIPrefix, "ref", i.Ref, "stream", i.Stream, "image", i.Version, "info.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *imageName) shortname() string {
|
|
||||||
return shortname.FromParts(i.Ref, i.Stream, i.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// filename is the override file name for the image info file.
|
|
||||||
func (i *imageName) filename() string {
|
|
||||||
name := i.shortname()
|
|
||||||
// replace all non-alphanumeric characters with an underscore
|
|
||||||
name = strings.Map(func(r rune) rune {
|
|
||||||
if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '.' {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return '_'
|
|
||||||
}, name)
|
|
||||||
return name + ".json"
|
|
||||||
}
|
|
||||||
|
|
||||||
// imageInfo is a lookup table for image references.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "aws": {
|
|
||||||
// "us-west-2": "ami-0123456789abcdef0"
|
|
||||||
// },
|
|
||||||
// "azure": {
|
|
||||||
// "cvm": "cvm-image-id"
|
|
||||||
// },
|
|
||||||
// "gcp": {
|
|
||||||
// "sev-es": "projects/<project>/global/images/<image>"
|
|
||||||
// },
|
|
||||||
// "qemu": {
|
|
||||||
// "default": "https://cdn.example.com/image.raw"
|
|
||||||
// },
|
|
||||||
// "version": "1.0.0",
|
|
||||||
// "debug": false
|
|
||||||
// }
|
|
||||||
type imageInfo struct {
|
|
||||||
AWS map[string]string `json:"aws,omitempty"`
|
|
||||||
Azure map[string]string `json:"azure,omitempty"`
|
|
||||||
GCP map[string]string `json:"gcp,omitempty"`
|
|
||||||
QEMU map[string]string `json:"qemu,omitempty"`
|
|
||||||
Debug bool `json:"debug,omitempty"`
|
|
||||||
Version string `json:"version,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// getReference returns the image reference for a given CSP and image variant.
|
|
||||||
func (l *imageInfo) getReference(csp, variant string) (string, error) {
|
|
||||||
if l == nil {
|
|
||||||
return "", fmt.Errorf("image info is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cspList map[string]string
|
|
||||||
switch cloudprovider.FromString(csp) {
|
|
||||||
case cloudprovider.AWS:
|
|
||||||
cspList = l.AWS
|
|
||||||
case cloudprovider.Azure:
|
|
||||||
cspList = l.Azure
|
|
||||||
case cloudprovider.GCP:
|
|
||||||
cspList = l.GCP
|
|
||||||
case cloudprovider.QEMU:
|
|
||||||
cspList = l.QEMU
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("image not available for CSP %q", csp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cspList == nil {
|
|
||||||
return "", fmt.Errorf("image not available for CSP %q", csp)
|
|
||||||
}
|
|
||||||
|
|
||||||
ref, ok := cspList[variant]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("image not available for variant %q", variant)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ref, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetcher fetches image references using a lookup table.
|
// Fetcher fetches image references using a lookup table.
|
||||||
type Fetcher struct {
|
type Fetcher struct {
|
||||||
httpc httpc
|
fetcher versionsAPIImageInfoFetcher
|
||||||
fs *afero.Afero
|
fs *afero.Afero
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new image fetcher.
|
// New returns a new image fetcher.
|
||||||
func New() *Fetcher {
|
func New() *Fetcher {
|
||||||
return &Fetcher{
|
return &Fetcher{
|
||||||
httpc: http.DefaultClient,
|
fetcher: fetcher.NewFetcher(),
|
||||||
fs: &afero.Afero{Fs: afero.NewOsFs()},
|
fs: &afero.Afero{Fs: afero.NewOsFs()},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,27 +42,31 @@ func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
image, err := newImageName(config.Image)
|
|
||||||
|
ver, err := versionsapi.NewVersionFromShortPath(config.Image, versionsapi.VersionKindImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return f.fetch(ctx, provider, image, variant)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch fetches the image reference for a given image name, uid, CSP and image variant.
|
imgInfoReq := versionsapi.ImageInfo{
|
||||||
func (f *Fetcher) fetch(ctx context.Context, csp cloudprovider.Provider, img *imageName, variant string) (string, error) {
|
Ref: ver.Ref,
|
||||||
raw, err := getFromFile(f.fs, img)
|
Stream: ver.Stream,
|
||||||
if err != nil && os.IsNotExist(err) {
|
Version: ver.Version,
|
||||||
raw, err = getFromURL(ctx, f.httpc, img)
|
}
|
||||||
|
|
||||||
|
imgInfo, err := getFromFile(f.fs, imgInfoReq)
|
||||||
|
if err != nil && errors.Is(err, fs.ErrNotExist) {
|
||||||
|
imgInfo, err = f.fetcher.FetchImageInfo(ctx, imgInfoReq)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("fetching image reference: %w", err)
|
return "", err
|
||||||
}
|
}
|
||||||
var info imageInfo
|
|
||||||
if err := json.Unmarshal(raw, &info); err != nil {
|
if err := imgInfo.Validate(); err != nil {
|
||||||
return "", fmt.Errorf("decoding image reference: %w", err)
|
return "", fmt.Errorf("validating image info file: %w", err)
|
||||||
}
|
}
|
||||||
return info.getReference(strings.ToLower(csp.String()), variant)
|
|
||||||
|
return getReferenceFromImageInfo(provider, variant, imgInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// variant returns the image variant for a given CSP and configuration.
|
// variant returns the image variant for a given CSP and configuration.
|
||||||
@ -189,42 +89,58 @@ func variant(provider cloudprovider.Provider, config *config.Config) (string, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFromFile(fs *afero.Afero, img *imageName) ([]byte, error) {
|
func getFromFile(fs *afero.Afero, imgInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) {
|
||||||
return fs.ReadFile(img.filename())
|
fileName := imageInfoFilename(imgInfo)
|
||||||
}
|
|
||||||
|
|
||||||
// getFromURL fetches the image lookup table from a URL.
|
raw, err := fs.ReadFile(fileName)
|
||||||
func getFromURL(ctx context.Context, client httpc, img *imageName) ([]byte, error) {
|
|
||||||
url, err := url.Parse(constants.CDNRepositoryURL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing image version repository URL: %w", err)
|
return versionsapi.ImageInfo{}, err
|
||||||
}
|
|
||||||
url.Path = img.infoPath()
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), http.NoBody)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
var newInfo versionsapi.ImageInfo
|
||||||
if err != nil {
|
if err := json.Unmarshal(raw, &newInfo); err != nil {
|
||||||
return nil, err
|
return versionsapi.ImageInfo{}, fmt.Errorf("decoding image info file: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
return newInfo, nil
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusNotFound:
|
|
||||||
return nil, fmt.Errorf("image %q does not exist", img.shortname())
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return content, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpc interface {
|
var filenameReplaceRegexp = regexp.MustCompile(`([^a-zA-Z0-9.-])`)
|
||||||
Do(req *http.Request) (*http.Response, error)
|
|
||||||
|
func imageInfoFilename(imgInfo versionsapi.ImageInfo) string {
|
||||||
|
path := imgInfo.JSONPath()
|
||||||
|
return filenameReplaceRegexp.ReplaceAllString(path, "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getReferenceFromImageInfo returns the image reference for a given CSP and image variant.
|
||||||
|
func getReferenceFromImageInfo(provider cloudprovider.Provider, variant string, imgInfo versionsapi.ImageInfo,
|
||||||
|
) (string, error) {
|
||||||
|
var providerList map[string]string
|
||||||
|
switch provider {
|
||||||
|
case cloudprovider.AWS:
|
||||||
|
providerList = imgInfo.AWS
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
providerList = imgInfo.Azure
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
providerList = imgInfo.GCP
|
||||||
|
case cloudprovider.QEMU:
|
||||||
|
providerList = imgInfo.QEMU
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("image not available in image info for CSP %q", provider.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if providerList == nil {
|
||||||
|
return "", fmt.Errorf("image not available in image info for CSP %q", provider.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, ok := providerList[variant]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("image not available in image info for variant %q", variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionsAPIImageInfoFetcher interface {
|
||||||
|
FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error)
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -29,40 +29,59 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestGetReference(t *testing.T) {
|
func TestGetReference(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
info *imageInfo
|
info versionsapi.ImageInfo
|
||||||
csp, variant string
|
provider cloudprovider.Provider
|
||||||
|
variant string
|
||||||
wantReference string
|
wantReference string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"reference exists": {
|
"reference exists aws": {
|
||||||
info: &imageInfo{AWS: map[string]string{"someVariant": "someReference"}},
|
info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}},
|
||||||
csp: "aws",
|
provider: cloudprovider.AWS,
|
||||||
|
variant: "someVariant",
|
||||||
|
wantReference: "someReference",
|
||||||
|
},
|
||||||
|
"reference exists azure": {
|
||||||
|
info: versionsapi.ImageInfo{Azure: map[string]string{"someVariant": "someReference"}},
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
variant: "someVariant",
|
||||||
|
wantReference: "someReference",
|
||||||
|
},
|
||||||
|
"reference exists gcp": {
|
||||||
|
info: versionsapi.ImageInfo{GCP: map[string]string{"someVariant": "someReference"}},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
variant: "someVariant",
|
||||||
|
wantReference: "someReference",
|
||||||
|
},
|
||||||
|
"reference exists qemu": {
|
||||||
|
info: versionsapi.ImageInfo{QEMU: map[string]string{"someVariant": "someReference"}},
|
||||||
|
provider: cloudprovider.QEMU,
|
||||||
variant: "someVariant",
|
variant: "someVariant",
|
||||||
wantReference: "someReference",
|
wantReference: "someReference",
|
||||||
},
|
},
|
||||||
"csp does not exist": {
|
"csp does not exist": {
|
||||||
info: &imageInfo{AWS: map[string]string{"someVariant": "someReference"}},
|
info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}},
|
||||||
csp: "nonExistingCSP",
|
provider: cloudprovider.Unknown,
|
||||||
variant: "someVariant",
|
variant: "someVariant",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"variant does not exist": {
|
"variant does not exist": {
|
||||||
info: &imageInfo{AWS: map[string]string{"someVariant": "someReference"}},
|
info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}},
|
||||||
csp: "aws",
|
provider: cloudprovider.AWS,
|
||||||
variant: "nonExistingVariant",
|
variant: "nonExistingVariant",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"info is nil": {
|
"info is empty": {
|
||||||
info: nil,
|
info: versionsapi.ImageInfo{},
|
||||||
csp: "aws",
|
provider: cloudprovider.AWS,
|
||||||
variant: "someVariant",
|
variant: "someVariant",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"csp is nil": {
|
"csp is nil": {
|
||||||
info: &imageInfo{AWS: nil},
|
info: versionsapi.ImageInfo{AWS: nil},
|
||||||
csp: "aws",
|
provider: cloudprovider.AWS,
|
||||||
variant: "someVariant",
|
variant: "someVariant",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +90,7 @@ func TestGetReference(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
reference, err := tc.info.getReference(tc.csp, tc.variant)
|
reference, err := getReferenceFromImageInfo(tc.provider, tc.variant, tc.info)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
@ -83,14 +102,6 @@ func TestGetReference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetReferenceOnNil(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
var lut *imageInfo
|
|
||||||
_, err := lut.getReference("someCSP", "someVariant")
|
|
||||||
assert.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariant(t *testing.T) {
|
func TestVariant(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
csp cloudprovider.Provider
|
csp cloudprovider.Provider
|
||||||
@ -161,55 +172,104 @@ func TestVariant(t *testing.T) {
|
|||||||
|
|
||||||
func TestFetchReference(t *testing.T) {
|
func TestFetchReference(t *testing.T) {
|
||||||
img := "ref/abc/stream/nightly/v1.2.3"
|
img := "ref/abc/stream/nightly/v1.2.3"
|
||||||
client := newTestClient(func(req *http.Request) *http.Response {
|
newImgInfo := func() versionsapi.ImageInfo {
|
||||||
if strings.HasSuffix(req.URL.String(), "/constellation/v1/ref/abc/stream/nightly/image/v1.2.3/info.json") {
|
return versionsapi.ImageInfo{
|
||||||
return &http.Response{
|
Ref: "abc",
|
||||||
StatusCode: http.StatusOK,
|
Stream: "nightly",
|
||||||
Body: io.NopCloser(bytes.NewBufferString(lut)),
|
Version: "v1.2.3",
|
||||||
Header: make(http.Header),
|
QEMU: map[string]string{"default": "someReference"},
|
||||||
}
|
AWS: map[string]string{"foo": "bar"},
|
||||||
|
Azure: map[string]string{"foo": "bar"},
|
||||||
|
GCP: map[string]string{"foo": "bar"},
|
||||||
}
|
}
|
||||||
return &http.Response{
|
}
|
||||||
StatusCode: http.StatusNotFound,
|
imgInfoPath := imageInfoFilename(newImgInfo())
|
||||||
Body: io.NopCloser(bytes.NewBufferString("Not found.")),
|
|
||||||
Header: make(http.Header),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
overrideFile string
|
imageInfoFetcher versionsAPIImageInfoFetcher
|
||||||
wantReference string
|
localFile []byte
|
||||||
wantErr bool
|
wantReference string
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"reference fetched remotely": {
|
"reference fetched remotely": {
|
||||||
config: &config.Config{Image: img, Provider: config.ProviderConfig{
|
config: &config.Config{
|
||||||
QEMU: &config.QEMUConfig{},
|
Image: img,
|
||||||
}},
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
|
},
|
||||||
|
imageInfoFetcher: &stubVersionsAPIImageFetcher{
|
||||||
|
fetchImageInfoInfo: newImgInfo(),
|
||||||
|
},
|
||||||
wantReference: "someReference",
|
wantReference: "someReference",
|
||||||
},
|
},
|
||||||
"reference fetched locally": {
|
"reference fetched remotely fails": {
|
||||||
config: &config.Config{Image: img, Provider: config.ProviderConfig{
|
config: &config.Config{
|
||||||
QEMU: &config.QEMUConfig{},
|
Image: img,
|
||||||
}},
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
overrideFile: `{"qemu":{"default":"localOverrideReference"}}`,
|
},
|
||||||
wantReference: "localOverrideReference",
|
imageInfoFetcher: &stubVersionsAPIImageFetcher{
|
||||||
},
|
fetchImageInfoErr: errors.New("failed"),
|
||||||
"lut is invalid": {
|
},
|
||||||
config: &config.Config{Image: img, Provider: config.ProviderConfig{
|
|
||||||
QEMU: &config.QEMUConfig{},
|
|
||||||
}},
|
|
||||||
overrideFile: `{`,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"image version does not exist": {
|
|
||||||
config: &config.Config{Image: "nonExistingImageName", Provider: config.ProviderConfig{
|
|
||||||
QEMU: &config.QEMUConfig{},
|
|
||||||
}},
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"invalid config": {
|
"reference fetched locally": {
|
||||||
config: &config.Config{},
|
config: &config.Config{
|
||||||
|
Image: img,
|
||||||
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
|
},
|
||||||
|
localFile: func() []byte {
|
||||||
|
info := newImgInfo()
|
||||||
|
info.QEMU["default"] = "localOverrideReference"
|
||||||
|
file, err := json.Marshal(info)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return file
|
||||||
|
}(),
|
||||||
|
wantReference: "localOverrideReference",
|
||||||
|
},
|
||||||
|
"local file first": {
|
||||||
|
config: &config.Config{
|
||||||
|
Image: img,
|
||||||
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
|
},
|
||||||
|
imageInfoFetcher: &stubVersionsAPIImageFetcher{
|
||||||
|
fetchImageInfoInfo: newImgInfo(),
|
||||||
|
},
|
||||||
|
localFile: func() []byte {
|
||||||
|
info := newImgInfo()
|
||||||
|
info.QEMU["default"] = "localOverrideReference"
|
||||||
|
file, err := json.Marshal(info)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return file
|
||||||
|
}(),
|
||||||
|
wantReference: "localOverrideReference",
|
||||||
|
},
|
||||||
|
"local file is invalid": {
|
||||||
|
config: &config.Config{
|
||||||
|
Image: img,
|
||||||
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
|
},
|
||||||
|
localFile: []byte("invalid"),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"local file has invalid image info": {
|
||||||
|
config: &config.Config{
|
||||||
|
Image: img,
|
||||||
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
|
},
|
||||||
|
localFile: func() []byte {
|
||||||
|
info := newImgInfo()
|
||||||
|
info.Ref = ""
|
||||||
|
file, err := json.Marshal(info)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return file
|
||||||
|
}(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"image version does not exist": {
|
||||||
|
config: &config.Config{
|
||||||
|
Image: "nonExistingImageName",
|
||||||
|
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||||
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -219,11 +279,20 @@ func TestFetchReference(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
fetcher := &Fetcher{
|
fs := afero.NewMemMapFs()
|
||||||
httpc: client,
|
af := &afero.Afero{Fs: fs}
|
||||||
fs: newImageVersionStubFs(t, img, tc.overrideFile),
|
if tc.localFile != nil {
|
||||||
|
fh := file.NewHandler(af)
|
||||||
|
require.NoError(fh.Write(imgInfoPath, tc.localFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetcher := &Fetcher{
|
||||||
|
fetcher: tc.imageInfoFetcher,
|
||||||
|
fs: af,
|
||||||
|
}
|
||||||
|
|
||||||
reference, err := fetcher.FetchReference(context.Background(), tc.config)
|
reference, err := fetcher.FetchReference(context.Background(), tc.config)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
@ -234,6 +303,17 @@ func TestFetchReference(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubVersionsAPIImageFetcher struct {
|
||||||
|
fetchImageInfoInfo versionsapi.ImageInfo
|
||||||
|
fetchImageInfoErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubVersionsAPIImageFetcher) FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (
|
||||||
|
versionsapi.ImageInfo, error,
|
||||||
|
) {
|
||||||
|
return f.fetchImageInfoInfo, f.fetchImageInfoErr
|
||||||
|
}
|
||||||
|
|
||||||
func must(t *testing.T, err error) {
|
func must(t *testing.T, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -255,15 +335,3 @@ func newTestClient(fn roundTripFunc) *http.Client {
|
|||||||
Transport: fn,
|
Transport: fn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newImageVersionStubFs(t *testing.T, image string, overrideFile string) *afero.Afero {
|
|
||||||
fs := afero.NewMemMapFs()
|
|
||||||
img, err := newImageName(image)
|
|
||||||
must(t, err)
|
|
||||||
if overrideFile != "" {
|
|
||||||
must(t, afero.WriteFile(fs, img.filename(), []byte(overrideFile), os.ModePerm))
|
|
||||||
}
|
|
||||||
return &afero.Afero{Fs: fs}
|
|
||||||
}
|
|
||||||
|
|
||||||
const lut = `{"qemu":{"default":"someReference"}}`
|
|
||||||
|
@ -137,3 +137,7 @@ func (*nopWriteCloser) Write(p []byte) (int, error) {
|
|||||||
func (*nopWriteCloser) Close() error {
|
func (*nopWriteCloser) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpc interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user