cli: image info (v2)

This commit is contained in:
Malte Poll 2023-05-23 09:17:27 +02:00 committed by Malte Poll
parent cd7b116794
commit d0e53cbb59
37 changed files with 429 additions and 461 deletions

View File

@ -17,7 +17,6 @@ go_library(
deps = [
"//cli/internal/clusterid",
"//cli/internal/iamid",
"//cli/internal/image",
"//cli/internal/libvirt",
"//cli/internal/terraform",
"//internal/atls",
@ -27,6 +26,7 @@ go_library(
"//internal/cloud/gcpshared",
"//internal/config",
"//internal/constants",
"//internal/imagefetcher",
"//internal/variant",
"@com_github_azure_azure_sdk_for_go//profiles/latest/attestation/attestation",
"@com_github_azure_azure_sdk_for_go_sdk_azcore//policy",
@ -54,6 +54,7 @@ go_test(
"//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared",
"//internal/config",
"//internal/variant",
"@com_github_hashicorp_terraform_json//:terraform-json",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",

View File

@ -12,13 +12,16 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
tfjson "github.com/hashicorp/terraform-json"
)
// imageFetcher gets an image reference from the versionsapi.
type imageFetcher interface {
FetchReference(ctx context.Context, config *config.Config) (string, error)
FetchReference(ctx context.Context,
provider cloudprovider.Provider, attestationVariant variant.Variant,
image, region string,
) (string, error)
}
type terraformClient interface {

View File

@ -13,7 +13,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
tfjson "github.com/hashicorp/terraform-json"
"go.uber.org/goleak"
@ -103,7 +103,10 @@ type stubImageFetcher struct {
fetchReferenceErr error
}
func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) {
func (f *stubImageFetcher) FetchReference(_ context.Context,
_ cloudprovider.Provider, _ variant.Variant,
_, _ string,
) (string, error) {
return f.reference, f.fetchReferenceErr
}

View File

@ -25,12 +25,12 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/image"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/internal/variant"
)
@ -48,7 +48,7 @@ type Creator struct {
func NewCreator(out io.Writer) *Creator {
return &Creator{
out: out,
image: image.New(),
image: imagefetcher.New(),
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
return terraform.New(ctx, constants.TerraformWorkingDir)
},
@ -56,7 +56,7 @@ func NewCreator(out io.Writer) *Creator {
return libvirt.New()
},
newRawDownloader: func() rawDownloader {
return image.NewDownloader()
return imagefetcher.NewDownloader()
},
policyPatcher: policyPatcher{},
}
@ -75,7 +75,10 @@ type CreateOptions struct {
// Create creates the handed amount of instances and all the needed resources.
func (c *Creator) Create(ctx context.Context, opts CreateOptions) (clusterid.File, error) {
image, err := c.image.FetchReference(ctx, opts.Config)
provider := opts.Config.GetProvider()
attestationVariant := opts.Config.GetAttestationConfig().GetVariant()
region := opts.Config.GetRegion()
image, err := c.image.FetchReference(ctx, provider, attestationVariant, opts.Config.Image, region)
if err != nil {
return clusterid.File{}, fmt.Errorf("fetching image reference: %w", err)
}

View File

@ -41,7 +41,6 @@ go_library(
"//cli/internal/clusterid",
"//cli/internal/helm",
"//cli/internal/iamid",
"//cli/internal/image",
"//cli/internal/kubernetes",
"//cli/internal/libvirt",
"//cli/internal/terraform",
@ -62,6 +61,7 @@ go_library(
"//internal/file",
"//internal/grpc/dialer",
"//internal/grpc/retry",
"//internal/imagefetcher",
"//internal/kms/uri",
"//internal/kubernetes/kubectl",
"//internal/license",

View File

@ -16,7 +16,6 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/image"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/cli/internal/upgrade"
@ -25,6 +24,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero"
"github.com/spf13/cobra"
@ -65,7 +65,7 @@ func runUpgradeApply(cmd *cobra.Command, _ []string) error {
return err
}
fetcher := image.New()
fetcher := imagefetcher.New()
applyCmd := upgradeApplyCmd{upgrader: upgrader, log: log, fetcher: fetcher}
return applyCmd.upgradeApply(cmd, fileHandler)
@ -194,7 +194,10 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, file file.Handler
func (u *upgradeApplyCmd) parseUpgradeVars(cmd *cobra.Command, conf *config.Config, fetcher imageFetcher) ([]string, terraform.Variables, error) {
// Fetch variables to execute Terraform script with
imageRef, err := fetcher.FetchReference(cmd.Context(), conf)
provider := conf.GetProvider()
attestationVariant := conf.GetAttestationConfig().GetVariant()
region := conf.GetRegion()
imageRef, err := fetcher.FetchReference(cmd.Context(), provider, attestationVariant, conf.Image, region)
if err != nil {
return nil, nil, fmt.Errorf("fetching image reference: %w", err)
}
@ -264,7 +267,10 @@ func (u *upgradeApplyCmd) parseUpgradeVars(cmd *cobra.Command, conf *config.Conf
}
type imageFetcher interface {
FetchReference(ctx context.Context, conf *config.Config) (string, error)
FetchReference(ctx context.Context,
provider cloudprovider.Provider, attestationVariant variant.Variant,
image, region string,
) (string, error)
}
// upgradeAttestConfigIfDiff checks if the locally configured measurements are different from the cluster's measurements.

View File

@ -200,6 +200,9 @@ type stubImageFetcher struct {
fetchReferenceErr error
}
func (s stubImageFetcher) FetchReference(context.Context, *config.Config) (string, error) {
return "", s.fetchReferenceErr
func (f stubImageFetcher) FetchReference(_ context.Context,
_ cloudprovider.Provider, _ variant.Variant,
_, _ string,
) (string, error) {
return "", f.fetchReferenceErr
}

View File

@ -12,14 +12,15 @@ go_library(
visibility = ["//cli:__subpackages__"],
deps = [
"//cli/internal/helm",
"//cli/internal/image",
"//cli/internal/terraform",
"//cli/internal/upgrade",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/compatibility",
"//internal/config",
"//internal/constants",
"//internal/file",
"//internal/imagefetcher",
"//internal/kubernetes",
"//internal/kubernetes/kubectl",
"//internal/variant",
@ -45,10 +46,12 @@ go_test(
embed = [":kubernetes"],
deps = [
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/compatibility",
"//internal/config",
"//internal/constants",
"//internal/logger",
"//internal/variant",
"//internal/versions",
"//internal/versions/components",
"//operators/constellation-node-operator/api/v1alpha1",

View File

@ -16,14 +16,15 @@ import (
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/image"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/cli/internal/upgrade"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
"github.com/edgelesssys/constellation/v2/internal/variant"
@ -122,7 +123,7 @@ func NewUpgrader(ctx context.Context, outWriter io.Writer, log debugLog) (*Upgra
stableInterface: &stableClient{client: kubeClient},
dynamicInterface: &NodeVersionClient{client: unstructuredClient},
helmClient: helmClient,
imageFetcher: image.New(),
imageFetcher: imagefetcher.New(),
outWriter: outWriter,
tfUpgrader: tfUpgrader,
log: log,
@ -164,7 +165,10 @@ func (u *Upgrader) UpgradeHelmServices(ctx context.Context, config *config.Confi
// UpgradeNodeVersion upgrades the cluster's NodeVersion object and in turn triggers image & k8s version upgrades.
// The versions set in the config are validated against the versions running in the cluster.
func (u *Upgrader) UpgradeNodeVersion(ctx context.Context, conf *config.Config) error {
imageReference, err := u.imageFetcher.FetchReference(ctx, conf)
provider := conf.GetProvider()
attestationVariant := conf.GetAttestationConfig().GetVariant()
region := conf.GetRegion()
imageReference, err := u.imageFetcher.FetchReference(ctx, provider, attestationVariant, conf.Image, region)
if err != nil {
return fmt.Errorf("fetching image reference: %w", err)
}
@ -526,5 +530,8 @@ type debugLog interface {
// imageFetcher gets an image reference from the versionsapi.
type imageFetcher interface {
FetchReference(ctx context.Context, config *config.Config) (string, error)
FetchReference(ctx context.Context,
provider cloudprovider.Provider, attestationVariant variant.Variant,
image, region string,
) (string, error)
}

View File

@ -14,10 +14,12 @@ import (
"testing"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/edgelesssys/constellation/v2/internal/versions/components"
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1"
@ -550,6 +552,9 @@ type stubImageFetcher struct {
fetchReferenceErr error
}
func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) {
func (f *stubImageFetcher) FetchReference(_ context.Context,
_ cloudprovider.Provider, _ variant.Variant,
_, _ string,
) (string, error) {
return f.reference, f.fetchReferenceErr
}

View File

@ -14,10 +14,10 @@ go_library(
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/constants",
"//internal/imagefetcher",
"//internal/logger",
"//internal/variant",
"//internal/versionsapi",
"//internal/versionsapi/fetcher",
"@sh_helm_helm_v3//pkg/action",
"@sh_helm_helm_v3//pkg/cli",
],

View File

@ -10,14 +10,13 @@ package upgrade
import (
"context"
"errors"
"net/http"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher"
)
type upgradeInfo struct {
@ -27,13 +26,12 @@ type upgradeInfo struct {
}
func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider,
attestationVariant variant.Variant, toImage string,
attestationVariant variant.Variant, toImage, region string,
) (upgradeInfo, error) {
info := upgradeInfo{
measurements: make(measurements.M),
shortPath: toImage,
}
versionsClient := fetcher.NewFetcher()
ver, err := versionsapi.NewVersionFromShortPath(toImage, versionsapi.VersionKindImage)
if err != nil {
@ -55,11 +53,8 @@ func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider,
}
info.measurements = fetchedMeasurements
imageRef, err := fetchImageRef(ctx, versionsClient, csp, versionsapi.ImageInfo{
Ref: ver.Ref,
Stream: ver.Stream,
Version: ver.Version,
})
fetcher := imagefetcher.New()
imageRef, err := fetcher.FetchReference(ctx, csp, attestationVariant, toImage, region)
if err != nil {
return upgradeInfo{}, err
}
@ -67,21 +62,3 @@ func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider,
return info, nil
}
func fetchImageRef(ctx context.Context, client *fetcher.Fetcher, csp cloudprovider.Provider, imageInfo versionsapi.ImageInfo) (string, error) {
imageInfo, err := client.FetchImageInfo(ctx, imageInfo)
if err != nil {
return "", err
}
switch csp {
case cloudprovider.GCP:
return imageInfo.GCP["sev-es"], nil
case cloudprovider.Azure:
return imageInfo.Azure["cvm"], nil
case cloudprovider.AWS:
return imageInfo.AWS["eu-central-1"], nil
default:
return "", errors.New("finding wanted image")
}
}

View File

@ -245,6 +245,7 @@ func writeUpgradeConfig(require *require.Assertions, image string, kubernetes st
cfg.GetProvider(),
cfg.GetAttestationConfig().GetVariant(),
image,
cfg.GetRegion(),
)
require.NoError(err)

View File

@ -16,10 +16,10 @@ import (
type archivist interface {
Archive(ctx context.Context,
version versionsapi.Version, csp, variant string, img io.Reader,
version versionsapi.Version, csp, attestationVariant string, img io.Reader,
) (string, error)
}
type uploader interface {
Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error)
Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error)
}

View File

@ -84,14 +84,14 @@ func runAWS(cmd *cobra.Command, _ []string) error {
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
Provider: flags.provider,
Version: flags.version,
AttestationVariant: flags.attestationVariant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -85,14 +85,14 @@ func runAzure(cmd *cobra.Command, _ []string) error {
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
Provider: flags.provider,
Version: flags.version,
AttestationVariant: flags.attestationVariant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -18,16 +18,16 @@ import (
)
type commonFlags struct {
rawImage string
pki string
provider cloudprovider.Provider
variant string
version versionsapi.Version
timestamp time.Time
region string
bucket string
out string
logLevel zapcore.Level
rawImage string
pki string
provider cloudprovider.Provider
attestationVariant string
version versionsapi.Version
timestamp time.Time
region string
bucket string
out string
logLevel zapcore.Level
}
func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) {
@ -43,7 +43,7 @@ func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) {
if pki == "" {
pki = filepath.Join(workspaceDir, "image/pki")
}
variant, err := cmd.Flags().GetString("variant")
attestationVariant, err := cmd.Flags().GetString("attestation-variant")
if err != nil {
return commonFlags{}, err
}
@ -88,15 +88,15 @@ func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) {
}
return commonFlags{
rawImage: rawImage,
pki: pki,
variant: variant,
version: ver,
timestamp: timestmp,
region: region,
bucket: bucket,
out: out,
logLevel: logLevel,
rawImage: rawImage,
pki: pki,
attestationVariant: attestationVariant,
version: ver,
timestamp: timestmp,
region: region,
bucket: bucket,
out: out,
logLevel: logLevel,
}, nil
}

View File

@ -85,14 +85,14 @@ func runGCP(cmd *cobra.Command, _ []string) error {
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
Provider: flags.provider,
Version: flags.version,
AttestationVariant: flags.attestationVariant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -68,14 +68,14 @@ func runNOP(cmd *cobra.Command, provider cloudprovider.Provider, _ []string) err
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
Provider: flags.provider,
Version: flags.version,
AttestationVariant: flags.attestationVariant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -13,14 +13,13 @@ import (
"io"
"strings"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req *osimage.UploadRequest, out io.Writer) error {
// upload to S3 archive
archiveURL, err := archiveC.Archive(ctx, req.Version, strings.ToLower(req.Provider.String()), req.Variant, req.Image)
archiveURL, err := archiveC.Archive(ctx, req.Version, strings.ToLower(req.Provider.String()), req.AttestationVariant, req.Image)
if err != nil {
return err
}
@ -34,8 +33,12 @@ func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req
return err
}
if len(imageReferences) == 0 {
imageReferences = map[string]string{
req.Variant: archiveURL,
imageReferences = []versionsapi.ImageInfoEntry{
{
CSP: req.Provider.String(),
AttestationVariant: req.AttestationVariant,
Reference: archiveURL,
},
}
}
@ -43,20 +46,7 @@ func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req
Ref: req.Version.Ref,
Stream: req.Version.Stream,
Version: req.Version.Version,
}
switch req.Provider {
case cloudprovider.AWS:
imageInfo.AWS = imageReferences
case cloudprovider.Azure:
imageInfo.Azure = imageReferences
case cloudprovider.GCP:
imageInfo.GCP = imageReferences
case cloudprovider.OpenStack:
imageInfo.OpenStack = imageReferences
case cloudprovider.QEMU:
imageInfo.QEMU = imageReferences
default:
return fmt.Errorf("uploading image: cloud provider %s is not yet supported", req.Provider.String())
List: imageReferences,
}
if err := json.NewEncoder(out).Encode(imageInfo); err != nil {

View File

@ -42,7 +42,7 @@ func newRootCmd() *cobra.Command {
rootCmd.PersistentFlags().String("raw-image", "", "Path to os image in CSP specific format that should be uploaded.")
rootCmd.PersistentFlags().String("pki", "", "Base path to the PKI (secure boot signing) files.")
rootCmd.PersistentFlags().String("variant", "", "Variant of the image being uploaded.")
rootCmd.PersistentFlags().String("attestation-variant", "", "Attestation variant of the image being uploaded.")
rootCmd.PersistentFlags().String("version", "", "Shortname of the os image version.")
rootCmd.PersistentFlags().String("timestamp", "", "Optional timestamp to use for resource names. Uses format 2006-01-02T15:04:05Z07:00.")
rootCmd.PersistentFlags().String("region", "eu-central-1", "AWS region of the archive S3 bucket")
@ -50,7 +50,7 @@ func newRootCmd() *cobra.Command {
rootCmd.PersistentFlags().String("out", "", "Optional path to write the upload result to. If not set, the result is written to stdout.")
rootCmd.PersistentFlags().Bool("verbose", false, "Enable verbose output")
must(rootCmd.MarkPersistentFlagRequired("raw-image"))
must(rootCmd.MarkPersistentFlagRequired("variant"))
must(rootCmd.MarkPersistentFlagRequired("attestation-variant"))
must(rootCmd.MarkPersistentFlagRequired("version"))
rootCmd.AddCommand(cmd.NewAWSCmd())

View File

@ -546,6 +546,23 @@ func (c *Config) GetAttestationConfig() AttestationCfg {
return &DummyCfg{}
}
// GetRegion returns the configured region.
func (c *Config) GetRegion() string {
switch c.GetProvider() {
case cloudprovider.AWS:
return c.Provider.AWS.Region
case cloudprovider.Azure:
return c.Provider.Azure.Location
case cloudprovider.GCP:
return c.Provider.GCP.Region
case cloudprovider.OpenStack:
return c.Provider.OpenStack.RegionName
case cloudprovider.QEMU:
return ""
}
return ""
}
// UpdateMAAURL updates the MAA URL in the config.
func (c *Config) UpdateMAAURL(maaURL string) {
if c.Attestation.AzureSEVSNP != nil {

View File

@ -2,16 +2,15 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "image",
name = "imagefetcher",
srcs = [
"image.go",
"imagegfetcher.go",
"raw.go",
],
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/image",
importpath = "github.com/edgelesssys/constellation/v2/internal/imagefetcher",
visibility = ["//cli:__subpackages__"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/config",
"//internal/variant",
"//internal/versionsapi",
"//internal/versionsapi/fetcher",
@ -21,16 +20,16 @@ go_library(
)
go_test(
name = "image_test",
name = "imagefetcher_test",
srcs = [
"image_test.go",
"imagefetcher_test.go",
"raw_test.go",
],
embed = [":image"],
embed = [":imagefetcher"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/config",
"//internal/file",
"//internal/variant",
"//internal/versionsapi",
"@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert",

View File

@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
*/
/*
Package image provides helping wrappers around a versionsapi fetcher.
Package imagefetcher provides helping wrappers around a versionsapi fetcher.
It also enables local image overrides and download of raw images.
*/
package image
package imagefetcher
import (
"context"
@ -20,7 +20,6 @@ import (
"regexp"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher"
@ -42,14 +41,11 @@ func New() *Fetcher {
}
// FetchReference fetches the image reference for a given image version uid, CSP and image variant.
func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (string, error) {
provider := config.GetProvider()
variant, err := imageVariant(provider, config)
if err != nil {
return "", fmt.Errorf("determining variant: %w", err)
}
ver, err := versionsapi.NewVersionFromShortPath(config.Image, versionsapi.VersionKindImage)
func (f *Fetcher) FetchReference(ctx context.Context,
provider cloudprovider.Provider, attestationVariant variant.Variant,
image, region string,
) (string, error) {
ver, err := versionsapi.NewVersionFromShortPath(image, versionsapi.VersionKindImage)
if err != nil {
return "", fmt.Errorf("parsing config image short path: %w", err)
}
@ -83,29 +79,18 @@ func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (st
return "", fmt.Errorf("validating image info file: %w", err)
}
return getReferenceFromImageInfo(provider, variant, imgInfo)
return getReferenceFromImageInfo(provider, attestationVariant.String(), imgInfo, filters(provider, region)...)
}
// imageVariant returns the image variant for a given CSP and configuration.
func imageVariant(provider cloudprovider.Provider, config *config.Config) (string, error) {
func filters(provider cloudprovider.Provider, region string) []filter {
var filters []filter
switch provider {
case cloudprovider.AWS:
return config.Provider.AWS.Region, nil
case cloudprovider.Azure:
if config.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
return "trustedlaunch", nil
}
return "cvm", nil
case cloudprovider.GCP:
return "sev-es", nil
case cloudprovider.OpenStack:
return "sev", nil
case cloudprovider.QEMU:
return "default", nil
default:
return "", fmt.Errorf("unsupported provider: %s", provider)
filters = append(filters, func(i versionsapi.ImageInfoEntry) bool {
return i.Region == region
})
}
return filters
}
func getFromFile(fs *afero.Afero, imgInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) {
@ -132,36 +117,36 @@ func imageInfoFilename(imgInfo versionsapi.ImageInfo) string {
}
// getReferenceFromImageInfo returns the image reference for a given CSP and image variant.
func getReferenceFromImageInfo(provider cloudprovider.Provider, variant string, imgInfo versionsapi.ImageInfo,
func getReferenceFromImageInfo(provider cloudprovider.Provider,
attestationVariant string, imgInfo versionsapi.ImageInfo,
filt ...filter,
) (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.OpenStack:
providerList = imgInfo.OpenStack
case cloudprovider.QEMU:
providerList = imgInfo.QEMU
default:
return "", fmt.Errorf("image not available in image info for CSP %q", provider.String())
var correctVariant versionsapi.ImageInfoEntry
var found bool
variantLoop:
for _, variant := range imgInfo.List {
gotCSP := cloudprovider.FromString(variant.CSP)
if gotCSP != provider || variant.AttestationVariant != attestationVariant {
continue
}
for _, f := range filt {
if !f(variant) {
continue variantLoop
}
}
correctVariant = variant
found = true
break
}
if !found {
return "", fmt.Errorf("image not available in image info for CSP %q, variant %q and other filters", provider.String(), attestationVariant)
}
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
return correctVariant.Reference, nil
}
type versionsAPIImageInfoFetcher interface {
FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error)
}
type filter func(versionsapi.ImageInfoEntry) bool

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package image
package imagefetcher
import (
"context"
@ -14,8 +14,8 @@ import (
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
@ -32,49 +32,88 @@ func TestGetReference(t *testing.T) {
info versionsapi.ImageInfo
provider cloudprovider.Provider
variant string
filter filter
wantReference string
wantErr bool
}{
"reference exists with filter": {
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someReference"},
{CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someOtherReference", Region: "someRegion"},
},
},
provider: cloudprovider.AWS,
variant: "aws-nitro-tpm",
filter: func(entry versionsapi.ImageInfoEntry) bool {
return entry.Region == "someRegion"
},
wantReference: "someOtherReference",
},
"reference exists aws": {
info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someReference"},
},
},
provider: cloudprovider.AWS,
variant: "someVariant",
variant: "aws-nitro-tpm",
wantReference: "someReference",
},
"reference exists azure": {
info: versionsapi.ImageInfo{Azure: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "azure", AttestationVariant: "azure-sev-snp", Reference: "someReference"},
},
},
provider: cloudprovider.Azure,
variant: "someVariant",
variant: "azure-sev-snp",
wantReference: "someReference",
},
"reference exists gcp": {
info: versionsapi.ImageInfo{GCP: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "gcp", AttestationVariant: "gcp-sev-es", Reference: "someReference"},
},
},
provider: cloudprovider.GCP,
variant: "someVariant",
variant: "gcp-sev-es",
wantReference: "someReference",
},
"reference exists openstack": {
info: versionsapi.ImageInfo{OpenStack: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "openstack", AttestationVariant: "qemu-vtpm", Reference: "someReference"},
},
},
provider: cloudprovider.OpenStack,
variant: "someVariant",
variant: "qemu-vtpm",
wantReference: "someReference",
},
"reference exists qemu": {
info: versionsapi.ImageInfo{QEMU: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "qemu", AttestationVariant: "qemu-vtpm", Reference: "someReference"},
},
},
provider: cloudprovider.QEMU,
variant: "someVariant",
variant: "qemu-vtpm",
wantReference: "someReference",
},
"csp does not exist": {
info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{List: []versionsapi.ImageInfoEntry{}},
provider: cloudprovider.Unknown,
variant: "someVariant",
wantErr: true,
},
"variant does not exist": {
info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}},
info: versionsapi.ImageInfo{
List: []versionsapi.ImageInfoEntry{
{CSP: "aws", AttestationVariant: "dummy", Reference: "someReference"},
},
},
provider: cloudprovider.AWS,
variant: "nonExistingVariant",
variant: "aws-nitro-tpm",
wantErr: true,
},
"info is empty": {
@ -83,12 +122,6 @@ func TestGetReference(t *testing.T) {
variant: "someVariant",
wantErr: true,
},
"csp is nil": {
info: versionsapi.ImageInfo{AWS: nil},
provider: cloudprovider.AWS,
variant: "someVariant",
wantErr: true,
},
}
for name, tc := range testCases {
@ -96,7 +129,11 @@ func TestGetReference(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
reference, err := getReferenceFromImageInfo(tc.provider, tc.variant, tc.info)
var filters []filter
if tc.filter != nil {
filters = []filter{tc.filter}
}
reference, err := getReferenceFromImageInfo(tc.provider, tc.variant, tc.info, filters...)
if tc.wantErr {
assert.Error(err)
@ -108,79 +145,6 @@ func TestGetReference(t *testing.T) {
}
}
func TestImageVariant(t *testing.T) {
testCases := map[string]struct {
csp cloudprovider.Provider
config *config.Config
wantVariant string
wantErr bool
}{
"AWS region": {
csp: cloudprovider.AWS,
config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{
AWS: &config.AWSConfig{Region: "someRegion"},
}},
wantVariant: "someRegion",
},
"Azure cvm": {
csp: cloudprovider.Azure,
config: &config.Config{
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{}},
},
wantVariant: "cvm",
},
"Azure trustedlaunch": {
csp: cloudprovider.Azure,
config: &config.Config{
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
Attestation: config.AttestationConfig{AzureTrustedLaunch: &config.AzureTrustedLaunch{}},
},
wantVariant: "trustedlaunch",
},
"GCP": {
csp: cloudprovider.GCP,
config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{
GCP: &config.GCPConfig{},
}},
wantVariant: "sev-es",
},
"OpenStack": {
csp: cloudprovider.OpenStack,
config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{
OpenStack: &config.OpenStackConfig{},
}},
wantVariant: "sev",
},
"QEMU": {
csp: cloudprovider.QEMU,
config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{},
}},
wantVariant: "default",
},
"invalid": {
csp: cloudprovider.Provider(9999),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
vari, err := imageVariant(tc.csp, tc.config)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantVariant, vari)
})
}
}
func TestFetchReference(t *testing.T) {
img := "ref/abc/stream/nightly/v1.2.3"
newImgInfo := func() versionsapi.ImageInfo {
@ -188,49 +152,47 @@ func TestFetchReference(t *testing.T) {
Ref: "abc",
Stream: "nightly",
Version: "v1.2.3",
QEMU: map[string]string{"default": "someReference"},
AWS: map[string]string{"foo": "bar"},
Azure: map[string]string{"foo": "bar"},
GCP: map[string]string{"foo": "bar"},
List: []versionsapi.ImageInfoEntry{
{
CSP: "qemu",
AttestationVariant: "dummy",
Reference: "someReference",
},
},
}
}
imgInfoPath := imageInfoFilename(newImgInfo())
testCases := map[string]struct {
config *config.Config
provider cloudprovider.Provider
image string
imageInfoFetcher versionsAPIImageInfoFetcher
localFile []byte
wantReference string
wantErr bool
}{
"reference fetched remotely": {
config: &config.Config{
Image: img,
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
provider: cloudprovider.QEMU,
image: img,
imageInfoFetcher: &stubVersionsAPIImageFetcher{
fetchImageInfoInfo: newImgInfo(),
},
wantReference: "someReference",
},
"reference fetched remotely fails": {
config: &config.Config{
Image: img,
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
provider: cloudprovider.QEMU,
image: img,
imageInfoFetcher: &stubVersionsAPIImageFetcher{
fetchImageInfoErr: errors.New("failed"),
},
wantErr: true,
},
"reference fetched locally": {
config: &config.Config{
Image: img,
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
provider: cloudprovider.QEMU,
image: img,
localFile: func() []byte {
info := newImgInfo()
info.QEMU["default"] = "localOverrideReference"
info.List[0].Reference = "localOverrideReference"
file, err := json.Marshal(info)
require.NoError(t, err)
return file
@ -238,16 +200,14 @@ func TestFetchReference(t *testing.T) {
wantReference: "localOverrideReference",
},
"local file first": {
config: &config.Config{
Image: img,
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
provider: cloudprovider.QEMU,
image: img,
imageInfoFetcher: &stubVersionsAPIImageFetcher{
fetchImageInfoInfo: newImgInfo(),
},
localFile: func() []byte {
info := newImgInfo()
info.QEMU["default"] = "localOverrideReference"
info.List[0].Reference = "localOverrideReference"
file, err := json.Marshal(info)
require.NoError(t, err)
return file
@ -255,18 +215,14 @@ func TestFetchReference(t *testing.T) {
wantReference: "localOverrideReference",
},
"local file is invalid": {
config: &config.Config{
Image: img,
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
provider: cloudprovider.QEMU,
image: img,
localFile: []byte("invalid"),
wantErr: true,
},
"local file has invalid image info": {
config: &config.Config{
Image: img,
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
provider: cloudprovider.QEMU,
image: img,
localFile: func() []byte {
info := newImgInfo()
info.Ref = ""
@ -277,11 +233,9 @@ func TestFetchReference(t *testing.T) {
wantErr: true,
},
"image version does not exist": {
config: &config.Config{
Image: "nonExistingImageName",
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
},
wantErr: true,
provider: cloudprovider.QEMU,
image: "nonExistingImageName",
wantErr: true,
},
}
@ -302,7 +256,7 @@ func TestFetchReference(t *testing.T) {
fs: af,
}
reference, err := fetcher.FetchReference(context.Background(), tc.config)
reference, err := fetcher.FetchReference(context.Background(), tc.provider, variant.Dummy{}, tc.image, "someRegion")
if tc.wantErr {
assert.Error(err)

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package image
package imagefetcher
import (
"context"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package image
package imagefetcher
import (
"bytes"

View File

@ -51,7 +51,7 @@ func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, cs
if err != nil {
return "", err
}
a.log.Debugf("Archiving OS image %s %s %v to s3://%v/%v", csp, variant, version.ShortPath(), a.bucket, key)
a.log.Debugf("Archiving OS image %s %s %v to s3://%v/%v", csp, attestationVariant, version.ShortPath(), a.bucket, key)
_, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
Bucket: &a.bucket,
Key: &key,

View File

@ -72,7 +72,7 @@ func New(region, bucketName string, log *logger.Logger) (*Uploader, error) {
}
// Upload uploads an OS image to AWS.
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) {
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) {
blobName := fmt.Sprintf("image-%s-%s-%d.raw", req.Version.Stream, req.Version.Version, req.Timestamp.Unix())
imageName := imageName(req.Version, req.Timestamp)
allRegions := []string{u.region}
@ -129,6 +129,7 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[
}
// wait for replication, tag, publish
var imageInfo []versionsapi.ImageInfoEntry
for _, region := range allRegions {
if err := u.waitForImage(ctx, amiIDs[region], region); err != nil {
return nil, fmt.Errorf("waiting for image to become available in region %s: %w", region, err)
@ -142,8 +143,15 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[
if err := u.publishImage(ctx, amiIDs[region], region); err != nil {
return nil, fmt.Errorf("publishing image in region %s: %w", region, err)
}
imageInfo = append(imageInfo, versionsapi.ImageInfoEntry{
CSP: "aws",
AttestationVariant: req.AttestationVariant,
Reference: amiIDs[region],
Region: region,
})
}
return amiIDs, nil
return imageInfo, nil
}
func (u *Uploader) ensureBucket(ctx context.Context) error {

View File

@ -93,9 +93,9 @@ func New(subscription, location, resourceGroup string, log *logger.Logger) (*Upl
}
// Upload uploads an OS image to Azure.
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) {
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) {
formattedTime := req.Timestamp.Format(timestampFormat)
diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream, formattedTime, req.Variant)
diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream, formattedTime, req.AttestationVariant)
var sigName string
switch req.Version.Stream {
case "stable":
@ -140,7 +140,7 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[
if err := u.ensureSIG(ctx, sigName); err != nil {
return nil, fmt.Errorf("ensuring sig exists: %w", err)
}
if err := u.ensureImageDefinition(ctx, sigName, definitionName, req.Version, req.Variant); err != nil {
if err := u.ensureImageDefinition(ctx, sigName, definitionName, req.Version, req.AttestationVariant); err != nil {
return nil, fmt.Errorf("ensuring image definition exists: %w", err)
}
@ -154,8 +154,12 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[
return nil, fmt.Errorf("getting image reference: %w", err)
}
return map[string]string{
req.Variant: imageReference,
return []versionsapi.ImageInfoEntry{
{
CSP: "azure",
AttestationVariant: req.AttestationVariant,
Reference: imageReference,
},
}, nil
}
@ -337,7 +341,7 @@ func (u *Uploader) ensureSIG(ctx context.Context, sigName string) error {
}
// ensureImageDefinition creates an image definition (component of a SIG) if it does not exist yet.
func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitionName string, version versionsapi.Version, variant string) error {
func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitionName string, version versionsapi.Version, attestationVariant string) error {
_, err := u.image.Get(ctx, u.resourceGroup, sigName, definitionName, &armcomputev4.GalleryImagesClientGetOptions{})
if err == nil {
u.log.Debugf("Image definition %s/%s in %s exists", sigName, definitionName, u.resourceGroup)
@ -349,10 +353,10 @@ func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitio
// based on wether a VMGS was provided or not.
// VMGS provided: ConfidentialVM
// No VMGS provided: ConfidentialVMSupported
switch strings.ToLower(variant) {
case "cvm":
switch strings.ToLower(attestationVariant) {
case "azure-sev-snp":
securityType = string("ConfidentialVMSupported")
case "trustedlaunch":
case "azure-trustedlaunch":
securityType = string(armcomputev4.SecurityTypesTrustedLaunch)
}
offer := imageOffer(version)

View File

@ -61,8 +61,8 @@ func New(ctx context.Context, project, location, bucketName string, log *logger.
}
// Upload uploads an OS image to GCP.
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) {
imageName := u.imageName(req.Version, req.Variant)
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) {
imageName := u.imageName(req.Version, req.AttestationVariant)
blobName := imageName + ".tar.gz"
if err := u.ensureBucket(ctx); err != nil {
return nil, fmt.Errorf("setup: ensuring bucket exists: %w", err)
@ -86,8 +86,12 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[
if err != nil {
return nil, fmt.Errorf("creating image: %w", err)
}
return map[string]string{
req.Variant: imageRef,
return []versionsapi.ImageInfoEntry{
{
CSP: "gcp",
AttestationVariant: req.AttestationVariant,
Reference: imageRef,
},
}, nil
}
@ -220,8 +224,8 @@ func (u *Uploader) blobURL(blobName string) string {
}).String()
}
func (u *Uploader) imageName(version versionsapi.Version, variant string) string {
return strings.ReplaceAll(version.Version, ".", "-") + "-" + variant + "-" + version.Stream
func (u *Uploader) imageName(version versionsapi.Version, attestationVariant string) string {
return strings.ReplaceAll(version.Version, ".", "-") + "-" + attestationVariant + "-" + version.Stream
}
func (u *Uploader) imageFamily(version versionsapi.Version) string {

View File

@ -8,5 +8,6 @@ go_library(
deps = [
"//internal/logger",
"//internal/osimage",
"//internal/versionsapi",
],
)

View File

@ -12,6 +12,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
// Uploader is a no-op uploader.
@ -25,7 +26,7 @@ func New(log *logger.Logger) *Uploader {
}
// Upload pretends to upload images to a csp.
func (u *Uploader) Upload(_ context.Context, req *osimage.UploadRequest) (map[string]string, error) {
func (u *Uploader) Upload(_ context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) {
u.log.Debugf("Skipping image upload of %s since this CSP does not require images to be uploaded in advance.", req.Version.ShortPath())
return nil, nil
}

View File

@ -18,12 +18,12 @@ import (
// UploadRequest is a request to upload an os image.
type UploadRequest struct {
Provider cloudprovider.Provider
Version versionsapi.Version
Variant string
SBDatabase secureboot.Database
UEFIVarStore secureboot.UEFIVarStore
Size int64
Timestamp time.Time
Image io.ReadSeeker
Provider cloudprovider.Provider
Version versionsapi.Version
AttestationVariant string
SBDatabase secureboot.Database
UEFIVarStore secureboot.UEFIVarStore
Size int64
Timestamp time.Time
Image io.ReadSeeker
}

View File

@ -211,24 +211,23 @@ func deleteImage(ctx context.Context, clients rmImageClients, ver versionsapi.Ve
return fmt.Errorf("fetching image info: %w", err)
}
log.Infof("Deleting AWS images from %s", imageInfo.JSONPath())
for awsRegion, awsImage := range imageInfo.AWS {
if err := clients.aws.deleteImage(ctx, awsImage, awsRegion, dryrun, log); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting AWS image %s: %w", awsImage, err))
}
}
log.Infof("Deleting GCP images from %s", imageInfo.JSONPath())
for _, gcpImage := range imageInfo.GCP {
if err := clients.gcp.deleteImage(ctx, gcpImage, dryrun, log); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting GCP image %s: %w", gcpImage, err))
}
}
log.Infof("Deleting Azure images from %s", imageInfo.JSONPath())
for _, azImage := range imageInfo.Azure {
if err := clients.az.deleteImage(ctx, azImage, dryrun, log); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting Azure image %s: %w", azImage, err))
for _, entry := range imageInfo.List {
switch entry.CSP {
case "aws":
log.Infof("Deleting AWS images from %s", imageInfo.JSONPath())
if err := clients.aws.deleteImage(ctx, entry.Reference, entry.Region, dryrun, log); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting AWS image %s: %w", entry.Reference, err))
}
case "gcp":
log.Infof("Deleting GCP images from %s", imageInfo.JSONPath())
if err := clients.gcp.deleteImage(ctx, entry.Reference, dryrun, log); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting GCP image %s: %w", entry.Reference, err))
}
case "azure":
log.Infof("Deleting Azure images from %s", imageInfo.JSONPath())
if err := clients.az.deleteImage(ctx, entry.Reference, dryrun, log); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting Azure image %s: %w", entry.Reference, err))
}
}
}

View File

@ -24,22 +24,22 @@ type ImageInfo struct {
Stream string `json:"stream,omitempty"`
// Version is the version of the image.
Version string `json:"version,omitempty"`
// AWS is a map of AWS regions to AMI IDs.
AWS map[string]string `json:"aws,omitempty"`
// Azure is a map of image types to Azure image IDs.
Azure map[string]string `json:"azure,omitempty"`
// GCP is a map of image types to GCP image IDs.
GCP map[string]string `json:"gcp,omitempty"`
// OpenStack is a map of image types to OpenStack image IDs.
OpenStack map[string]string `json:"openstack,omitempty"`
// QEMU is a map of image types to QEMU image URLs.
QEMU map[string]string `json:"qemu,omitempty"`
// List contains the image variants for this version.
List []ImageInfoEntry `json:"list,omitempty"`
}
// ImageInfoEntry contains information about a single image variant.
type ImageInfoEntry struct {
CSP string `json:"csp"`
AttestationVariant string `json:"attestationVariant"`
Reference string `json:"reference"`
Region string `json:"region,omitempty"`
}
// JSONPath returns the S3 JSON path for this object.
func (i ImageInfo) JSONPath() string {
return path.Join(
constants.CDNAPIPrefix,
constants.CDNAPIPrefixV2,
"ref", i.Ref,
"stream", i.Stream,
i.Version,
@ -71,20 +71,8 @@ func (i ImageInfo) ValidateRequest() error {
if !semver.IsValid(i.Version) {
retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version))
}
if len(i.AWS) != 0 {
retErr = errors.Join(retErr, errors.New("AWS map must be empty for request"))
}
if len(i.Azure) != 0 {
retErr = errors.Join(retErr, errors.New("Azure map must be empty for request"))
}
if len(i.GCP) != 0 {
retErr = errors.Join(retErr, errors.New("GCP map must be empty for request"))
}
if len(i.OpenStack) != 0 {
retErr = errors.Join(retErr, errors.New("OpenStack map must be empty for request"))
}
if len(i.QEMU) != 0 {
retErr = errors.Join(retErr, errors.New("QEMU map must be empty for request"))
if len(i.List) != 0 {
retErr = errors.Join(retErr, errors.New("list must be empty for request"))
}
return retErr
@ -102,14 +90,8 @@ func (i ImageInfo) Validate() error {
if !semver.IsValid(i.Version) {
retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version))
}
var providers int
providers += len(i.AWS)
providers += len(i.Azure)
providers += len(i.GCP)
providers += len(i.OpenStack)
providers += len(i.QEMU)
if providers == 0 {
retErr = errors.Join(retErr, errors.New("one or more providers must be specified"))
if len(i.List) == 0 {
retErr = errors.Join(retErr, errors.New("one or more image variants must be specified in the list"))
}
return retErr

View File

@ -24,7 +24,7 @@ func TestImageInfoJSONPath(t *testing.T) {
Stream: "nightly",
Version: "v1.0.0",
},
wantPath: constants.CDNAPIPrefix + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json",
wantPath: constants.CDNAPIPrefixV2 + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json",
},
"image info release": {
info: ImageInfo{
@ -32,7 +32,7 @@ func TestImageInfoJSONPath(t *testing.T) {
Stream: "stable",
Version: "v1.0.0",
},
wantPath: constants.CDNAPIPrefix + "/ref/-/stream/stable/v1.0.0/image/info.json",
wantPath: constants.CDNAPIPrefixV2 + "/ref/-/stream/stable/v1.0.0/image/info.json",
},
}
@ -55,7 +55,7 @@ func TestImageInfoURL(t *testing.T) {
Stream: "nightly",
Version: "v1.0.0",
},
wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json",
wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json",
},
"image info release": {
info: ImageInfo{
@ -63,7 +63,7 @@ func TestImageInfoURL(t *testing.T) {
Stream: "stable",
Version: "v1.0.0",
},
wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/-/stream/stable/v1.0.0/image/info.json",
wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/-/stream/stable/v1.0.0/image/info.json",
},
}
@ -87,10 +87,35 @@ func TestImageInfoValidate(t *testing.T) {
Ref: "test-ref",
Stream: "nightly",
Version: "v1.0.0",
AWS: map[string]string{"key": "value", "key2": "value2"},
GCP: map[string]string{"key": "value", "key2": "value2"},
Azure: map[string]string{"key": "value", "key2": "value2"},
QEMU: map[string]string{"key": "value", "key2": "value2"},
List: []ImageInfoEntry{
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-1",
},
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-2",
},
{
CSP: "gcp",
AttestationVariant: "gcp-sev-es",
Reference: "gcp-123",
},
{
CSP: "azure",
AttestationVariant: "azure-sev-snp",
Reference: "azure-123",
},
{
CSP: "qemu",
AttestationVariant: "qemu-vtpm",
Reference: "https://example.com/qemu-123/image.raw",
},
},
},
},
"invalid ref": {
@ -98,10 +123,14 @@ func TestImageInfoValidate(t *testing.T) {
Ref: "",
Stream: "nightly",
Version: "v1.0.0",
AWS: map[string]string{"key": "value", "key2": "value2"},
GCP: map[string]string{"key": "value", "key2": "value2"},
Azure: map[string]string{"key": "value", "key2": "value2"},
QEMU: map[string]string{"key": "value", "key2": "value2"},
List: []ImageInfoEntry{
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-1",
},
},
},
wantErr: true,
},
@ -110,10 +139,14 @@ func TestImageInfoValidate(t *testing.T) {
Ref: "test-ref",
Stream: "",
Version: "v1.0.0",
AWS: map[string]string{"key": "value", "key2": "value2"},
GCP: map[string]string{"key": "value", "key2": "value2"},
Azure: map[string]string{"key": "value", "key2": "value2"},
QEMU: map[string]string{"key": "value", "key2": "value2"},
List: []ImageInfoEntry{
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-1",
},
},
},
wantErr: true,
},
@ -122,14 +155,18 @@ func TestImageInfoValidate(t *testing.T) {
Ref: "test-ref",
Stream: "nightly",
Version: "",
AWS: map[string]string{"key": "value", "key2": "value2"},
GCP: map[string]string{"key": "value", "key2": "value2"},
Azure: map[string]string{"key": "value", "key2": "value2"},
QEMU: map[string]string{"key": "value", "key2": "value2"},
List: []ImageInfoEntry{
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-1",
},
},
},
wantErr: true,
},
"no provider": {
"no entries in list": {
info: ImageInfo{
Ref: "test-ref",
Stream: "nightly",
@ -197,48 +234,19 @@ func TestImageInfoValidateRequest(t *testing.T) {
},
wantErr: true,
},
"invalid gcp": {
"request contains entries": {
info: ImageInfo{
Ref: "test-ref",
Stream: "nightly",
Version: "v1.0.0",
GCP: map[string]string{"key": "value", "key2": "value2"},
},
wantErr: true,
},
"invalid azure": {
info: ImageInfo{
Ref: "test-ref",
Stream: "nightly",
Version: "v1.0.0",
Azure: map[string]string{"key": "value", "key2": "value2"},
},
wantErr: true,
},
"invalid aws": {
info: ImageInfo{
Ref: "test-ref",
Stream: "nightly",
Version: "v1.0.0",
AWS: map[string]string{"key": "value", "key2": "value2"},
},
wantErr: true,
},
"invalid qemu": {
info: ImageInfo{
Ref: "test-ref",
Stream: "nightly",
Version: "v1.0.0",
QEMU: map[string]string{"key": "value", "key2": "value2"},
},
wantErr: true,
},
"invalid openstack": {
info: ImageInfo{
Ref: "test-ref",
Stream: "nightly",
Version: "v1.0.0",
OpenStack: map[string]string{"key": "value", "key2": "value2"},
List: []ImageInfoEntry{
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-1",
},
},
},
wantErr: true,
},
@ -247,10 +255,14 @@ func TestImageInfoValidateRequest(t *testing.T) {
Ref: "",
Stream: "",
Version: "",
AWS: map[string]string{"key": "value", "key2": "value2"},
GCP: map[string]string{"key": "value", "key2": "value2"},
Azure: map[string]string{"key": "value", "key2": "value2"},
QEMU: map[string]string{"key": "value", "key2": "value2"},
List: []ImageInfoEntry{
{
CSP: "aws",
AttestationVariant: "aws-nitro-tpm",
Reference: "ami-123",
Region: "us-east-1",
},
},
},
wantErr: true,
},