mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
CLI: use global image version field
- Restructure config by removing CSP-specific image references - Add global image field - Download image lookup table on create - Download QEMU image on QEMU create
This commit is contained in:
parent
9222468d3b
commit
575b6e93f6
@ -8,9 +8,11 @@ package cloudcmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type terraformClient interface {
|
type terraformClient interface {
|
||||||
@ -25,3 +27,11 @@ type libvirtRunner interface {
|
|||||||
Start(ctx context.Context, containerName, imageName string) error
|
Start(ctx context.Context, containerName, imageName string) error
|
||||||
Stop(ctx context.Context) error
|
Stop(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type imageFetcher interface {
|
||||||
|
FetchReference(ctx context.Context, config *config.Config) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawDownloader interface {
|
||||||
|
Download(ctx context.Context, errWriter io.Writer, isTTY bool, source, version string) (string, error)
|
||||||
|
}
|
||||||
|
@ -8,10 +8,12 @@ package cloudcmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
|
||||||
"go.uber.org/goleak"
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
@ -72,3 +74,21 @@ func (r *stubLibvirtRunner) Stop(context.Context) error {
|
|||||||
r.stopCalled = true
|
r.stopCalled = true
|
||||||
return r.stopErr
|
return r.stopErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubImageFetcher struct {
|
||||||
|
reference string
|
||||||
|
fetchReferenceErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) {
|
||||||
|
return f.reference, f.fetchReferenceErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubRawDownloader struct {
|
||||||
|
destination string
|
||||||
|
downloadErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stubRawDownloader) Download(_ context.Context, _ io.Writer, _ bool, _ string, _ string) (string, error) {
|
||||||
|
return d.destination, d.downloadErr
|
||||||
|
}
|
||||||
|
@ -22,31 +22,43 @@ import (
|
|||||||
"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/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Creator creates cloud resources.
|
// Creator creates cloud resources.
|
||||||
type Creator struct {
|
type Creator struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
|
image imageFetcher
|
||||||
newTerraformClient func(ctx context.Context) (terraformClient, error)
|
newTerraformClient func(ctx context.Context) (terraformClient, error)
|
||||||
newLibvirtRunner func() libvirtRunner
|
newLibvirtRunner func() libvirtRunner
|
||||||
|
newRawDownloader func() rawDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCreator creates a new creator.
|
// NewCreator creates a new creator.
|
||||||
func NewCreator(out io.Writer) *Creator {
|
func NewCreator(out io.Writer) *Creator {
|
||||||
return &Creator{
|
return &Creator{
|
||||||
out: out,
|
out: out,
|
||||||
|
image: image.New(),
|
||||||
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
|
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
|
||||||
return terraform.New(ctx, constants.TerraformWorkingDir)
|
return terraform.New(ctx, constants.TerraformWorkingDir)
|
||||||
},
|
},
|
||||||
newLibvirtRunner: func() libvirtRunner {
|
newLibvirtRunner: func() libvirtRunner {
|
||||||
return libvirt.New()
|
return libvirt.New()
|
||||||
},
|
},
|
||||||
|
newRawDownloader: func() rawDownloader {
|
||||||
|
return image.NewDownloader()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates the handed amount of instances and all the needed resources.
|
// Create creates the handed amount of instances and all the needed resources.
|
||||||
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int,
|
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int,
|
||||||
) (clusterid.File, error) {
|
) (clusterid.File, error) {
|
||||||
|
image, err := c.image.FetchReference(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
return clusterid.File{}, fmt.Errorf("fetching image reference: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
switch provider {
|
switch provider {
|
||||||
case cloudprovider.AWS:
|
case cloudprovider.AWS:
|
||||||
cl, err := c.newTerraformClient(ctx)
|
cl, err := c.newTerraformClient(ctx)
|
||||||
@ -54,21 +66,21 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
|
|||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
defer cl.RemoveInstaller()
|
defer cl.RemoveInstaller()
|
||||||
return c.createAWS(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
|
return c.createAWS(ctx, cl, config, name, insType, controlPlaneCount, workerCount, image)
|
||||||
case cloudprovider.GCP:
|
case cloudprovider.GCP:
|
||||||
cl, err := c.newTerraformClient(ctx)
|
cl, err := c.newTerraformClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
defer cl.RemoveInstaller()
|
defer cl.RemoveInstaller()
|
||||||
return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
|
return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount, image)
|
||||||
case cloudprovider.Azure:
|
case cloudprovider.Azure:
|
||||||
cl, err := c.newTerraformClient(ctx)
|
cl, err := c.newTerraformClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
defer cl.RemoveInstaller()
|
defer cl.RemoveInstaller()
|
||||||
return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
|
return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount, image)
|
||||||
case cloudprovider.QEMU:
|
case cloudprovider.QEMU:
|
||||||
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
|
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
|
||||||
return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
|
return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
@ -79,14 +91,14 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
|
|||||||
}
|
}
|
||||||
defer cl.RemoveInstaller()
|
defer cl.RemoveInstaller()
|
||||||
lv := c.newLibvirtRunner()
|
lv := c.newLibvirtRunner()
|
||||||
return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount)
|
return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount, image)
|
||||||
default:
|
default:
|
||||||
return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
|
return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *config.Config,
|
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *config.Config,
|
||||||
name, insType string, controlPlaneCount, workerCount int,
|
name, insType string, controlPlaneCount, workerCount int, image string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
vars := terraform.AWSVariables{
|
vars := terraform.AWSVariables{
|
||||||
CommonVariables: terraform.CommonVariables{
|
CommonVariables: terraform.CommonVariables{
|
||||||
@ -99,7 +111,7 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
|
|||||||
Region: config.Provider.AWS.Region,
|
Region: config.Provider.AWS.Region,
|
||||||
Zone: config.Provider.AWS.Zone,
|
Zone: config.Provider.AWS.Zone,
|
||||||
InstanceType: insType,
|
InstanceType: insType,
|
||||||
AMIImageID: config.Provider.AWS.Image,
|
AMIImageID: image,
|
||||||
IAMProfileControlPlane: config.Provider.AWS.IAMProfileControlPlane,
|
IAMProfileControlPlane: config.Provider.AWS.IAMProfileControlPlane,
|
||||||
IAMProfileWorkerNodes: config.Provider.AWS.IAMProfileWorkerNodes,
|
IAMProfileWorkerNodes: config.Provider.AWS.IAMProfileWorkerNodes,
|
||||||
Debug: config.IsDebugCluster(),
|
Debug: config.IsDebugCluster(),
|
||||||
@ -122,7 +134,7 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
|
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
|
||||||
name, insType string, controlPlaneCount, workerCount int,
|
name, insType string, controlPlaneCount, workerCount int, image string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
vars := terraform.GCPVariables{
|
vars := terraform.GCPVariables{
|
||||||
CommonVariables: terraform.CommonVariables{
|
CommonVariables: terraform.CommonVariables{
|
||||||
@ -137,7 +149,7 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
|
|||||||
CredentialsFile: config.Provider.GCP.ServiceAccountKeyPath,
|
CredentialsFile: config.Provider.GCP.ServiceAccountKeyPath,
|
||||||
InstanceType: insType,
|
InstanceType: insType,
|
||||||
StateDiskType: config.Provider.GCP.StateDiskType,
|
StateDiskType: config.Provider.GCP.StateDiskType,
|
||||||
ImageID: config.Provider.GCP.Image,
|
ImageID: image,
|
||||||
Debug: config.IsDebugCluster(),
|
Debug: config.IsDebugCluster(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +170,7 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
|
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
|
||||||
name, insType string, controlPlaneCount, workerCount int,
|
name, insType string, controlPlaneCount, workerCount int, image string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
vars := terraform.AzureVariables{
|
vars := terraform.AzureVariables{
|
||||||
CommonVariables: terraform.CommonVariables{
|
CommonVariables: terraform.CommonVariables{
|
||||||
@ -172,7 +184,7 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
|
|||||||
UserAssignedIdentity: config.Provider.Azure.UserAssignedIdentity,
|
UserAssignedIdentity: config.Provider.Azure.UserAssignedIdentity,
|
||||||
InstanceType: insType,
|
InstanceType: insType,
|
||||||
StateDiskType: config.Provider.Azure.StateDiskType,
|
StateDiskType: config.Provider.Azure.StateDiskType,
|
||||||
ImageID: config.Provider.Azure.Image,
|
ImageID: image,
|
||||||
ConfidentialVM: *config.Provider.Azure.ConfidentialVM,
|
ConfidentialVM: *config.Provider.Azure.ConfidentialVM,
|
||||||
SecureBoot: *config.Provider.Azure.SecureBoot,
|
SecureBoot: *config.Provider.Azure.SecureBoot,
|
||||||
Debug: config.IsDebugCluster(),
|
Debug: config.IsDebugCluster(),
|
||||||
@ -223,11 +235,18 @@ func normalizeAzureURIs(vars terraform.AzureVariables) terraform.AzureVariables
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, name string, config *config.Config,
|
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, name string, config *config.Config,
|
||||||
controlPlaneCount, workerCount int,
|
controlPlaneCount, workerCount int, source string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
qemuRollbacker := &rollbackerQEMU{client: cl, libvirt: lv, createdWorkspace: false}
|
qemuRollbacker := &rollbackerQEMU{client: cl, libvirt: lv, createdWorkspace: false}
|
||||||
defer rollbackOnError(context.Background(), c.out, &retErr, qemuRollbacker)
|
defer rollbackOnError(context.Background(), c.out, &retErr, qemuRollbacker)
|
||||||
|
|
||||||
|
// TODO: render progress bar
|
||||||
|
downloader := c.newRawDownloader()
|
||||||
|
imagePath, err := downloader.Download(ctx, c.out, false, source, config.Image)
|
||||||
|
if err != nil {
|
||||||
|
return clusterid.File{}, fmt.Errorf("download raw image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
libvirtURI := config.Provider.QEMU.LibvirtURI
|
libvirtURI := config.Provider.QEMU.LibvirtURI
|
||||||
libvirtSocketPath := "."
|
libvirtSocketPath := "."
|
||||||
|
|
||||||
@ -273,7 +292,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
|
|||||||
},
|
},
|
||||||
LibvirtURI: libvirtURI,
|
LibvirtURI: libvirtURI,
|
||||||
LibvirtSocketPath: libvirtSocketPath,
|
LibvirtSocketPath: libvirtSocketPath,
|
||||||
ImagePath: config.Provider.QEMU.Image,
|
ImagePath: imagePath,
|
||||||
ImageFormat: config.Provider.QEMU.ImageFormat,
|
ImageFormat: config.Provider.QEMU.ImageFormat,
|
||||||
CPUCount: config.Provider.QEMU.VCPUs,
|
CPUCount: config.Provider.QEMU.VCPUs,
|
||||||
MemorySizeMiB: config.Provider.QEMU.Memory,
|
MemorySizeMiB: config.Provider.QEMU.Memory,
|
||||||
|
@ -98,12 +98,20 @@ func TestCreator(t *testing.T) {
|
|||||||
|
|
||||||
creator := &Creator{
|
creator := &Creator{
|
||||||
out: &bytes.Buffer{},
|
out: &bytes.Buffer{},
|
||||||
|
image: &stubImageFetcher{
|
||||||
|
reference: "some-image",
|
||||||
|
},
|
||||||
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
|
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
|
||||||
return tc.tfClient, tc.newTfClientErr
|
return tc.tfClient, tc.newTfClientErr
|
||||||
},
|
},
|
||||||
newLibvirtRunner: func() libvirtRunner {
|
newLibvirtRunner: func() libvirtRunner {
|
||||||
return tc.libvirt
|
return tc.libvirt
|
||||||
},
|
},
|
||||||
|
newRawDownloader: func() rawDownloader {
|
||||||
|
return &stubRawDownloader{
|
||||||
|
destination: "some-destination",
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
idFile, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3)
|
idFile, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3)
|
||||||
|
@ -11,13 +11,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
"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/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/image"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -49,10 +49,10 @@ func runConfigFetchMeasurements(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("constructing Rekor client: %w", err)
|
return fmt.Errorf("constructing Rekor client: %w", err)
|
||||||
}
|
}
|
||||||
return configFetchMeasurements(cmd, rekor, fileHandler, http.DefaultClient)
|
return configFetchMeasurements(cmd, rekor, fileHandler, http.DefaultClient, image.New())
|
||||||
}
|
}
|
||||||
|
|
||||||
func configFetchMeasurements(cmd *cobra.Command, verifier rekorVerifier, fileHandler file.Handler, client *http.Client) error {
|
func configFetchMeasurements(cmd *cobra.Command, verifier rekorVerifier, fileHandler file.Handler, client *http.Client, img imageFetcher) error {
|
||||||
flags, err := parseFetchMeasurementsFlags(cmd)
|
flags, err := parseFetchMeasurementsFlags(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -63,16 +63,17 @@ func configFetchMeasurements(cmd *cobra.Command, verifier rekorVerifier, fileHan
|
|||||||
return displayConfigValidationErrors(cmd.ErrOrStderr(), err)
|
return displayConfigValidationErrors(cmd.ErrOrStderr(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.IsDebugImage() {
|
if !conf.IsReleaseImage() {
|
||||||
cmd.PrintErrln("Configured image doesn't look like a released production image. Double check image before deploying to production.")
|
cmd.PrintErrln("Configured image doesn't look like a released production image. Double check image before deploying to production.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := flags.updateURLs(conf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
if err := flags.updateURLs(ctx, conf, img); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var fetchedMeasurements measurements.M
|
var fetchedMeasurements measurements.M
|
||||||
hash, err := fetchedMeasurements.FetchAndVerify(ctx, client, flags.measurementsURL, flags.signatureURL, []byte(constants.CosignPublicKey))
|
hash, err := fetchedMeasurements.FetchAndVerify(ctx, client, flags.measurementsURL, flags.signatureURL, []byte(constants.CosignPublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -128,9 +129,15 @@ func parseFetchMeasurementsFlags(cmd *cobra.Command) (*fetchMeasurementsFlags, e
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetchMeasurementsFlags) updateURLs(conf *config.Config) error {
|
func (f *fetchMeasurementsFlags) updateURLs(ctx context.Context, conf *config.Config, img imageFetcher) error {
|
||||||
|
imageRef, err := img.FetchReference(ctx, conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if f.measurementsURL == nil {
|
if f.measurementsURL == nil {
|
||||||
parsedURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(conf.Image()) + "/measurements.yaml")
|
// TODO(AB#2644): resolve image version to reference
|
||||||
|
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.yaml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -138,7 +145,7 @@ func (f *fetchMeasurementsFlags) updateURLs(conf *config.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if f.signatureURL == nil {
|
if f.signatureURL == nil {
|
||||||
parsedURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(conf.Image()) + "/measurements.yaml.sig")
|
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.yaml.sig")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -146,3 +153,7 @@ func (f *fetchMeasurementsFlags) updateURLs(conf *config.Config) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type imageFetcher interface {
|
||||||
|
FetchReference(ctx context.Context, config *config.Config) (string, error)
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -102,10 +103,9 @@ func TestUpdateURLs(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"both values nil": {
|
"both values nil": {
|
||||||
conf: &config.Config{
|
conf: &config.Config{
|
||||||
|
Image: "someImageVersion",
|
||||||
Provider: config.ProviderConfig{
|
Provider: config.ProviderConfig{
|
||||||
GCP: &config.GCPConfig{
|
GCP: &config.GCPConfig{},
|
||||||
Image: "some/image/path/image-123456",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
flags: &fetchMeasurementsFlags{},
|
flags: &fetchMeasurementsFlags{},
|
||||||
@ -127,7 +127,9 @@ func TestUpdateURLs(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
err := tc.flags.updateURLs(tc.conf)
|
err := tc.flags.updateURLs(context.Background(), tc.conf, &stubImageFetcher{
|
||||||
|
reference: "some/image/path/image-123456",
|
||||||
|
})
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(tc.wantMeasurementsURL, tc.flags.measurementsURL.String())
|
assert.Equal(tc.wantMeasurementsURL, tc.flags.measurementsURL.String())
|
||||||
})
|
})
|
||||||
@ -162,14 +164,14 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
|||||||
signature := "MEUCIFdJ5dH6HDywxQWTUh9Bw77wMrq0mNCUjMQGYP+6QsVmAiEAmazj/L7rFGA4/Gz8y+kI5h5E5cDgc3brihvXBKF6qZA="
|
signature := "MEUCIFdJ5dH6HDywxQWTUh9Bw77wMrq0mNCUjMQGYP+6QsVmAiEAmazj/L7rFGA4/Gz8y+kI5h5E5cDgc3brihvXBKF6qZA="
|
||||||
|
|
||||||
client := newTestClient(func(req *http.Request) *http.Response {
|
client := newTestClient(func(req *http.Request) *http.Response {
|
||||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/projects/constellation-images/global/images/constellation-coreos-1658216163/measurements.yaml" {
|
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.yaml" {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewBufferString(measurements)),
|
Body: io.NopCloser(bytes.NewBufferString(measurements)),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/projects/constellation-images/global/images/constellation-coreos-1658216163/measurements.yaml.sig" {
|
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.yaml.sig" {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewBufferString(signature)),
|
Body: io.NopCloser(bytes.NewBufferString(signature)),
|
||||||
@ -213,12 +215,23 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
|||||||
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
||||||
|
|
||||||
gcpConfig := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP)
|
gcpConfig := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP)
|
||||||
gcpConfig.Provider.GCP.Image = "projects/constellation-images/global/images/constellation-coreos-1658216163"
|
gcpConfig.Image = "someImage"
|
||||||
|
|
||||||
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
assert.NoError(configFetchMeasurements(cmd, tc.verifier, fileHandler, client))
|
assert.NoError(configFetchMeasurements(cmd, tc.verifier, fileHandler, client, &stubImageFetcher{
|
||||||
|
reference: "someImage",
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubImageFetcher struct {
|
||||||
|
reference string
|
||||||
|
fetchReferenceErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) {
|
||||||
|
return f.reference, f.fetchReferenceErr
|
||||||
|
}
|
||||||
|
@ -67,7 +67,7 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var printedAWarning bool
|
var printedAWarning bool
|
||||||
if conf.IsDebugImage() {
|
if !conf.IsReleaseImage() {
|
||||||
cmd.PrintErrln("Configured image doesn't look like a released production image. Double check image before deploying to production.")
|
cmd.PrintErrln("Configured image doesn't look like a released production image. Double check image before deploying to production.")
|
||||||
printedAWarning = true
|
printedAWarning = true
|
||||||
}
|
}
|
||||||
|
@ -388,8 +388,8 @@ func TestAttestation(t *testing.T) {
|
|||||||
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, existingIDFile, file.OptNone))
|
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, existingIDFile, file.OptNone))
|
||||||
|
|
||||||
cfg := config.Default()
|
cfg := config.Default()
|
||||||
|
cfg.Image = "image"
|
||||||
cfg.RemoveProviderExcept(cloudprovider.QEMU)
|
cfg.RemoveProviderExcept(cloudprovider.QEMU)
|
||||||
cfg.Provider.QEMU.Image = "some/image/location"
|
|
||||||
cfg.Provider.QEMU.Measurements[0] = measurements.PCRWithAllBytes(0x00)
|
cfg.Provider.QEMU.Measurements[0] = measurements.PCRWithAllBytes(0x00)
|
||||||
cfg.Provider.QEMU.Measurements[1] = measurements.PCRWithAllBytes(0x11)
|
cfg.Provider.QEMU.Measurements[1] = measurements.PCRWithAllBytes(0x11)
|
||||||
cfg.Provider.QEMU.Measurements[2] = measurements.PCRWithAllBytes(0x22)
|
cfg.Provider.QEMU.Measurements[2] = measurements.PCRWithAllBytes(0x22)
|
||||||
@ -463,13 +463,14 @@ func (s *stubInitServer) Init(ctx context.Context, req *initproto.InitRequest) (
|
|||||||
func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, csp cloudprovider.Provider) *config.Config {
|
func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, csp cloudprovider.Provider) *config.Config {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
conf.Image = "image"
|
||||||
|
|
||||||
switch csp {
|
switch csp {
|
||||||
case cloudprovider.Azure:
|
case cloudprovider.Azure:
|
||||||
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
||||||
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
||||||
conf.Provider.Azure.Location = "test-location"
|
conf.Provider.Azure.Location = "test-location"
|
||||||
conf.Provider.Azure.UserAssignedIdentity = "test-identity"
|
conf.Provider.Azure.UserAssignedIdentity = "test-identity"
|
||||||
conf.Provider.Azure.Image = "some/image/location"
|
|
||||||
conf.Provider.Azure.ResourceGroup = "test-resource-group"
|
conf.Provider.Azure.ResourceGroup = "test-resource-group"
|
||||||
conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab"
|
conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab"
|
||||||
conf.Provider.Azure.ClientSecretValue = "test-client-secret"
|
conf.Provider.Azure.ClientSecretValue = "test-client-secret"
|
||||||
@ -479,14 +480,12 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
|
|||||||
case cloudprovider.GCP:
|
case cloudprovider.GCP:
|
||||||
conf.Provider.GCP.Region = "test-region"
|
conf.Provider.GCP.Region = "test-region"
|
||||||
conf.Provider.GCP.Project = "test-project"
|
conf.Provider.GCP.Project = "test-project"
|
||||||
conf.Provider.GCP.Image = "some/image/location"
|
|
||||||
conf.Provider.GCP.Zone = "test-zone"
|
conf.Provider.GCP.Zone = "test-zone"
|
||||||
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
|
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
|
||||||
conf.Provider.GCP.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
conf.Provider.GCP.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
||||||
conf.Provider.GCP.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
conf.Provider.GCP.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
||||||
conf.Provider.GCP.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
conf.Provider.GCP.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
||||||
case cloudprovider.QEMU:
|
case cloudprovider.QEMU:
|
||||||
conf.Provider.QEMU.Image = "some/image/location"
|
|
||||||
conf.Provider.QEMU.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
conf.Provider.QEMU.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
||||||
conf.Provider.QEMU.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
conf.Provider.QEMU.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
||||||
conf.Provider.QEMU.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
conf.Provider.QEMU.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@ -26,8 +25,6 @@ import (
|
|||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
|
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/license"
|
"github.com/edgelesssys/constellation/v2/internal/license"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
|
||||||
"github.com/schollz/progressbar/v3"
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -188,23 +185,9 @@ func prepareConfig(cmd *cobra.Command, fileHandler file.Handler) (*config.Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// download image to current directory if it doesn't exist
|
|
||||||
const imagePath = "./constellation.raw"
|
|
||||||
if _, err := os.Stat(imagePath); err == nil {
|
|
||||||
cmd.Printf("Using existing image at %s\n\n", imagePath)
|
|
||||||
} else if errors.Is(err, os.ErrNotExist) {
|
|
||||||
cmd.Printf("Downloading image to %s\n", imagePath)
|
|
||||||
if err := installImage(cmd.Context(), cmd.ErrOrStderr(), versions.ConstellationQEMUImageURL, imagePath); err != nil {
|
|
||||||
return nil, fmt.Errorf("downloading image to %s: %w", imagePath, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("checking if image exists: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := config.Default()
|
config := config.Default()
|
||||||
config.RemoveProviderExcept(cloudprovider.QEMU)
|
config.RemoveProviderExcept(cloudprovider.QEMU)
|
||||||
config.StateDiskSizeGB = 8
|
config.StateDiskSizeGB = 8
|
||||||
config.Provider.QEMU.Image = imagePath
|
|
||||||
|
|
||||||
return config, fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptOverwrite)
|
return config, fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptOverwrite)
|
||||||
}
|
}
|
||||||
@ -245,51 +228,3 @@ func initializeMiniCluster(cmd *cobra.Command, fileHandler file.Handler, spinner
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// installImage downloads the image from sourceURL to the destination.
|
|
||||||
func installImage(ctx context.Context, errWriter io.Writer, sourceURL, destination string) error {
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("downloading image: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("downloading image: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(destination, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
bar := progressbar.NewOptions64(
|
|
||||||
resp.ContentLength,
|
|
||||||
progressbar.OptionSetWriter(errWriter),
|
|
||||||
progressbar.OptionShowBytes(true),
|
|
||||||
progressbar.OptionSetPredictTime(true),
|
|
||||||
progressbar.OptionFullWidth(),
|
|
||||||
progressbar.OptionSetTheme(progressbar.Theme{
|
|
||||||
Saucer: "=",
|
|
||||||
SaucerHead: ">",
|
|
||||||
SaucerPadding: " ",
|
|
||||||
BarStart: "[",
|
|
||||||
BarEnd: "]",
|
|
||||||
}),
|
|
||||||
progressbar.OptionClearOnFinish(),
|
|
||||||
progressbar.OptionOnCompletion(func() { fmt.Fprintf(errWriter, "Done.\n\n") }),
|
|
||||||
)
|
|
||||||
defer bar.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -61,7 +61,7 @@ func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *c
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !constellationConfig.IsDebugImage() {
|
if constellationConfig.IsReleaseImage() {
|
||||||
log.Println("WARNING: Constellation image does not look like a debug image. Are you using a debug image?")
|
log.Println("WARNING: Constellation image does not look like a debug image. Are you using a debug image?")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"strings"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
@ -38,17 +38,15 @@ const (
|
|||||||
Version1 = "v1"
|
Version1 = "v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
azureReleaseImageRegex = regexp.MustCompile(`^(?i)\/CommunityGalleries\/ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df\/Images\/constellation\/Versions\/[\d]+.[\d]+.[\d]+$`)
|
|
||||||
gcpReleaseImageRegex = regexp.MustCompile(`^projects\/constellation-images\/global\/images\/constellation-v[\d]+-[\d]+-[\d]+$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config defines configuration used by CLI.
|
// Config defines configuration used by CLI.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// description: |
|
// description: |
|
||||||
// Schema version of this configuration file.
|
// Schema version of this configuration file.
|
||||||
Version string `yaml:"version" validate:"eq=v1"`
|
Version string `yaml:"version" validate:"eq=v1"`
|
||||||
// description: |
|
// description: |
|
||||||
|
// Machine image used to create Constellation nodes.
|
||||||
|
Image string `yaml:"image" validate:"required,safe_image"`
|
||||||
|
// description: |
|
||||||
// Size (in GB) of a node's disk to store the non-volatile state.
|
// Size (in GB) of a node's disk to store the non-volatile state.
|
||||||
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
|
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
|
||||||
// description: |
|
// description: |
|
||||||
@ -75,7 +73,7 @@ type Config struct {
|
|||||||
// UpgradeConfig defines configuration used during constellation upgrade.
|
// UpgradeConfig defines configuration used during constellation upgrade.
|
||||||
type UpgradeConfig struct {
|
type UpgradeConfig struct {
|
||||||
// description: |
|
// description: |
|
||||||
// Updated machine image to install on all nodes.
|
// Updated Constellation machine image to install on all nodes.
|
||||||
Image string `yaml:"image"`
|
Image string `yaml:"image"`
|
||||||
// description: |
|
// description: |
|
||||||
// Measurements of the updated image.
|
// Measurements of the updated image.
|
||||||
@ -127,9 +125,6 @@ type AWSConfig struct {
|
|||||||
// AWS data center zone name in defined region. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones
|
// AWS data center zone name in defined region. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones
|
||||||
Zone string `yaml:"zone" validate:"required"`
|
Zone string `yaml:"zone" validate:"required"`
|
||||||
// description: |
|
// description: |
|
||||||
// AMI ID of the machine image used to create Constellation nodes.
|
|
||||||
Image string `yaml:"image" validate:"required"`
|
|
||||||
// description: |
|
|
||||||
// VM instance type to use for Constellation nodes. Needs to support NitroTPM. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enable-nitrotpm-prerequisites.html
|
// VM instance type to use for Constellation nodes. Needs to support NitroTPM. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enable-nitrotpm-prerequisites.html
|
||||||
InstanceType string `yaml:"instanceType" validate:"lowercase,aws_instance_type"`
|
InstanceType string `yaml:"instanceType" validate:"lowercase,aws_instance_type"`
|
||||||
// description: |
|
// description: |
|
||||||
@ -173,9 +168,6 @@ type AzureConfig struct {
|
|||||||
// Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable.
|
// Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable.
|
||||||
ClientSecretValue string `yaml:"clientSecretValue" validate:"required"`
|
ClientSecretValue string `yaml:"clientSecretValue" validate:"required"`
|
||||||
// description: |
|
// description: |
|
||||||
// Machine image used to create Constellation nodes.
|
|
||||||
Image string `yaml:"image" validate:"required"`
|
|
||||||
// description: |
|
|
||||||
// VM instance type to use for Constellation nodes.
|
// VM instance type to use for Constellation nodes.
|
||||||
InstanceType string `yaml:"instanceType" validate:"azure_instance_type"`
|
InstanceType string `yaml:"instanceType" validate:"azure_instance_type"`
|
||||||
// description: |
|
// description: |
|
||||||
@ -219,9 +211,6 @@ type GCPConfig struct {
|
|||||||
// Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization
|
// Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization
|
||||||
ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"`
|
ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"`
|
||||||
// description: |
|
// description: |
|
||||||
// Machine image used to create Constellation nodes.
|
|
||||||
Image string `yaml:"image" validate:"required"`
|
|
||||||
// description: |
|
|
||||||
// VM instance type to use for Constellation nodes.
|
// VM instance type to use for Constellation nodes.
|
||||||
InstanceType string `yaml:"instanceType" validate:"gcp_instance_type"`
|
InstanceType string `yaml:"instanceType" validate:"gcp_instance_type"`
|
||||||
// description: |
|
// description: |
|
||||||
@ -240,9 +229,6 @@ type GCPConfig struct {
|
|||||||
|
|
||||||
// QEMUConfig holds config information for QEMU based Constellation deployments.
|
// QEMUConfig holds config information for QEMU based Constellation deployments.
|
||||||
type QEMUConfig struct {
|
type QEMUConfig struct {
|
||||||
// description: |
|
|
||||||
// Path to the image to use for the VMs.
|
|
||||||
Image string `yaml:"image" validate:"required"`
|
|
||||||
// description: |
|
// description: |
|
||||||
// Format of the image to use for the VMs. Should be either qcow2 or raw.
|
// Format of the image to use for the VMs. Should be either qcow2 or raw.
|
||||||
ImageFormat string `yaml:"imageFormat" validate:"oneof=qcow2 raw"`
|
ImageFormat string `yaml:"imageFormat" validate:"oneof=qcow2 raw"`
|
||||||
@ -279,12 +265,12 @@ type QEMUConfig struct {
|
|||||||
func Default() *Config {
|
func Default() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Version: Version1,
|
Version: Version1,
|
||||||
|
Image: defaultImage,
|
||||||
StateDiskSizeGB: 30,
|
StateDiskSizeGB: 30,
|
||||||
DebugCluster: func() *bool { b := false; return &b }(),
|
DebugCluster: func() *bool { b := false; return &b }(),
|
||||||
Provider: ProviderConfig{
|
Provider: ProviderConfig{
|
||||||
AWS: &AWSConfig{
|
AWS: &AWSConfig{
|
||||||
Region: "",
|
Region: "",
|
||||||
Image: "",
|
|
||||||
InstanceType: "m6a.xlarge",
|
InstanceType: "m6a.xlarge",
|
||||||
StateDiskType: "gp3",
|
StateDiskType: "gp3",
|
||||||
IAMProfileControlPlane: "",
|
IAMProfileControlPlane: "",
|
||||||
@ -298,7 +284,6 @@ func Default() *Config {
|
|||||||
Location: "",
|
Location: "",
|
||||||
UserAssignedIdentity: "",
|
UserAssignedIdentity: "",
|
||||||
ResourceGroup: "",
|
ResourceGroup: "",
|
||||||
Image: DefaultImageAzure,
|
|
||||||
InstanceType: "Standard_DC4as_v5",
|
InstanceType: "Standard_DC4as_v5",
|
||||||
StateDiskType: "Premium_LRS",
|
StateDiskType: "Premium_LRS",
|
||||||
DeployCSIDriver: func() *bool { b := true; return &b }(),
|
DeployCSIDriver: func() *bool { b := true; return &b }(),
|
||||||
@ -314,7 +299,6 @@ func Default() *Config {
|
|||||||
Region: "",
|
Region: "",
|
||||||
Zone: "",
|
Zone: "",
|
||||||
ServiceAccountKeyPath: "",
|
ServiceAccountKeyPath: "",
|
||||||
Image: DefaultImageGCP,
|
|
||||||
InstanceType: "n2d-standard-4",
|
InstanceType: "n2d-standard-4",
|
||||||
StateDiskType: "pd-ssd",
|
StateDiskType: "pd-ssd",
|
||||||
DeployCSIDriver: func() *bool { b := true; return &b }(),
|
DeployCSIDriver: func() *bool { b := true; return &b }(),
|
||||||
@ -386,22 +370,6 @@ func (c *Config) HasProvider(provider cloudprovider.Provider) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image returns OS image for the configured cloud provider.
|
|
||||||
// If multiple cloud providers are configured (which is not supported)
|
|
||||||
// only a single image is returned.
|
|
||||||
func (c *Config) Image() string {
|
|
||||||
if c.HasProvider(cloudprovider.AWS) {
|
|
||||||
return c.Provider.AWS.Image
|
|
||||||
}
|
|
||||||
if c.HasProvider(cloudprovider.Azure) {
|
|
||||||
return c.Provider.Azure.Image
|
|
||||||
}
|
|
||||||
if c.HasProvider(cloudprovider.GCP) {
|
|
||||||
return c.Provider.GCP.Image
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMeasurements overwrites measurements in config with the provided ones.
|
// UpdateMeasurements overwrites measurements in config with the provided ones.
|
||||||
func (c *Config) UpdateMeasurements(newMeasurements Measurements) {
|
func (c *Config) UpdateMeasurements(newMeasurements Measurements) {
|
||||||
if c.Provider.AWS != nil {
|
if c.Provider.AWS != nil {
|
||||||
@ -451,21 +419,9 @@ func (c *Config) IsDebugCluster() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDebugImage checks whether image name looks like a release image, if not it is
|
// IsReleaseImage checks whether image name looks like a release image.
|
||||||
// probably a debug image. In the end we do not if bootstrapper or debugd
|
func (c *Config) IsReleaseImage() bool {
|
||||||
// was put inside an image just by looking at its name.
|
return strings.HasPrefix(c.Image, "v")
|
||||||
func (c *Config) IsDebugImage() bool {
|
|
||||||
switch {
|
|
||||||
case c.Provider.AWS != nil:
|
|
||||||
// TODO: Add proper image name validation for AWS as part of rfc/image-discoverability.md
|
|
||||||
return false
|
|
||||||
case c.Provider.Azure != nil:
|
|
||||||
return !azureReleaseImageRegex.MatchString(c.Provider.Azure.Image)
|
|
||||||
case c.Provider.GCP != nil:
|
|
||||||
return !gcpReleaseImageRegex.MatchString(c.Provider.GCP.Image)
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProvider returns the configured cloud provider.
|
// GetProvider returns the configured cloud provider.
|
||||||
@ -543,6 +499,10 @@ func (c *Config) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validate.RegisterValidation("safe_image", validateImage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// register custom validator with label supported_k8s_version to validate version based on available versionConfigs.
|
// register custom validator with label supported_k8s_version to validate version based on available versionConfigs.
|
||||||
if err := validate.RegisterValidation("supported_k8s_version", validateK8sVersion); err != nil {
|
if err := validate.RegisterValidation("supported_k8s_version", validateK8sVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -25,46 +25,51 @@ func init() {
|
|||||||
ConfigDoc.Type = "Config"
|
ConfigDoc.Type = "Config"
|
||||||
ConfigDoc.Comments[encoder.LineComment] = "Config defines configuration used by CLI."
|
ConfigDoc.Comments[encoder.LineComment] = "Config defines configuration used by CLI."
|
||||||
ConfigDoc.Description = "Config defines configuration used by CLI."
|
ConfigDoc.Description = "Config defines configuration used by CLI."
|
||||||
ConfigDoc.Fields = make([]encoder.Doc, 7)
|
ConfigDoc.Fields = make([]encoder.Doc, 8)
|
||||||
ConfigDoc.Fields[0].Name = "version"
|
ConfigDoc.Fields[0].Name = "version"
|
||||||
ConfigDoc.Fields[0].Type = "string"
|
ConfigDoc.Fields[0].Type = "string"
|
||||||
ConfigDoc.Fields[0].Note = ""
|
ConfigDoc.Fields[0].Note = ""
|
||||||
ConfigDoc.Fields[0].Description = "Schema version of this configuration file."
|
ConfigDoc.Fields[0].Description = "Schema version of this configuration file."
|
||||||
ConfigDoc.Fields[0].Comments[encoder.LineComment] = "Schema version of this configuration file."
|
ConfigDoc.Fields[0].Comments[encoder.LineComment] = "Schema version of this configuration file."
|
||||||
ConfigDoc.Fields[1].Name = "stateDiskSizeGB"
|
ConfigDoc.Fields[1].Name = "image"
|
||||||
ConfigDoc.Fields[1].Type = "int"
|
ConfigDoc.Fields[1].Type = "string"
|
||||||
ConfigDoc.Fields[1].Note = ""
|
ConfigDoc.Fields[1].Note = ""
|
||||||
ConfigDoc.Fields[1].Description = "Size (in GB) of a node's disk to store the non-volatile state."
|
ConfigDoc.Fields[1].Description = "Machine image used to create Constellation nodes."
|
||||||
ConfigDoc.Fields[1].Comments[encoder.LineComment] = "Size (in GB) of a node's disk to store the non-volatile state."
|
ConfigDoc.Fields[1].Comments[encoder.LineComment] = "Machine image used to create Constellation nodes."
|
||||||
ConfigDoc.Fields[2].Name = "kubernetesVersion"
|
ConfigDoc.Fields[2].Name = "stateDiskSizeGB"
|
||||||
ConfigDoc.Fields[2].Type = "string"
|
ConfigDoc.Fields[2].Type = "int"
|
||||||
ConfigDoc.Fields[2].Note = ""
|
ConfigDoc.Fields[2].Note = ""
|
||||||
ConfigDoc.Fields[2].Description = "Kubernetes version to be installed in the cluster."
|
ConfigDoc.Fields[2].Description = "Size (in GB) of a node's disk to store the non-volatile state."
|
||||||
ConfigDoc.Fields[2].Comments[encoder.LineComment] = "Kubernetes version to be installed in the cluster."
|
ConfigDoc.Fields[2].Comments[encoder.LineComment] = "Size (in GB) of a node's disk to store the non-volatile state."
|
||||||
ConfigDoc.Fields[3].Name = "debugCluster"
|
ConfigDoc.Fields[3].Name = "kubernetesVersion"
|
||||||
ConfigDoc.Fields[3].Type = "bool"
|
ConfigDoc.Fields[3].Type = "string"
|
||||||
ConfigDoc.Fields[3].Note = ""
|
ConfigDoc.Fields[3].Note = ""
|
||||||
ConfigDoc.Fields[3].Description = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
|
ConfigDoc.Fields[3].Description = "Kubernetes version to be installed in the cluster."
|
||||||
ConfigDoc.Fields[3].Comments[encoder.LineComment] = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
|
ConfigDoc.Fields[3].Comments[encoder.LineComment] = "Kubernetes version to be installed in the cluster."
|
||||||
ConfigDoc.Fields[4].Name = "provider"
|
ConfigDoc.Fields[4].Name = "debugCluster"
|
||||||
ConfigDoc.Fields[4].Type = "ProviderConfig"
|
ConfigDoc.Fields[4].Type = "bool"
|
||||||
ConfigDoc.Fields[4].Note = ""
|
ConfigDoc.Fields[4].Note = ""
|
||||||
ConfigDoc.Fields[4].Description = "Supported cloud providers and their specific configurations."
|
ConfigDoc.Fields[4].Description = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
|
||||||
ConfigDoc.Fields[4].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations."
|
ConfigDoc.Fields[4].Comments[encoder.LineComment] = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
|
||||||
ConfigDoc.Fields[5].Name = "sshUsers"
|
ConfigDoc.Fields[5].Name = "provider"
|
||||||
ConfigDoc.Fields[5].Type = "[]UserKey"
|
ConfigDoc.Fields[5].Type = "ProviderConfig"
|
||||||
ConfigDoc.Fields[5].Note = ""
|
ConfigDoc.Fields[5].Note = ""
|
||||||
ConfigDoc.Fields[5].Description = "Deprecated: Does nothing! To get node SSH access, see: https://constellation-docs.edgeless.systems/constellation/workflows/troubleshooting#connect-to-nodes-via-ssh"
|
ConfigDoc.Fields[5].Description = "Supported cloud providers and their specific configurations."
|
||||||
ConfigDoc.Fields[5].Comments[encoder.LineComment] = "Deprecated: Does nothing! To get node SSH access, see: https://constellation-docs.edgeless.systems/constellation/workflows/troubleshooting#connect-to-nodes-via-ssh"
|
ConfigDoc.Fields[5].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations."
|
||||||
|
ConfigDoc.Fields[6].Name = "sshUsers"
|
||||||
ConfigDoc.Fields[5].AddExample("", []UserKey{{Username: "Alice", PublicKey: "ssh-rsa AAAAB3NzaC...5QXHKW1rufgtJeSeJ8= alice@domain.com"}})
|
ConfigDoc.Fields[6].Type = "[]UserKey"
|
||||||
ConfigDoc.Fields[6].Name = "upgrade"
|
|
||||||
ConfigDoc.Fields[6].Type = "UpgradeConfig"
|
|
||||||
ConfigDoc.Fields[6].Note = ""
|
ConfigDoc.Fields[6].Note = ""
|
||||||
ConfigDoc.Fields[6].Description = "Configuration to apply during constellation upgrade."
|
ConfigDoc.Fields[6].Description = "Deprecated: Does nothing! To get node SSH access, see: https://constellation-docs.edgeless.systems/constellation/workflows/troubleshooting#connect-to-nodes-via-ssh"
|
||||||
ConfigDoc.Fields[6].Comments[encoder.LineComment] = "Configuration to apply during constellation upgrade."
|
ConfigDoc.Fields[6].Comments[encoder.LineComment] = "Deprecated: Does nothing! To get node SSH access, see: https://constellation-docs.edgeless.systems/constellation/workflows/troubleshooting#connect-to-nodes-via-ssh"
|
||||||
|
|
||||||
ConfigDoc.Fields[6].AddExample("", UpgradeConfig{Image: "", Measurements: Measurements{}})
|
ConfigDoc.Fields[6].AddExample("", []UserKey{{Username: "Alice", PublicKey: "ssh-rsa AAAAB3NzaC...5QXHKW1rufgtJeSeJ8= alice@domain.com"}})
|
||||||
|
ConfigDoc.Fields[7].Name = "upgrade"
|
||||||
|
ConfigDoc.Fields[7].Type = "UpgradeConfig"
|
||||||
|
ConfigDoc.Fields[7].Note = ""
|
||||||
|
ConfigDoc.Fields[7].Description = "Configuration to apply during constellation upgrade."
|
||||||
|
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Configuration to apply during constellation upgrade."
|
||||||
|
|
||||||
|
ConfigDoc.Fields[7].AddExample("", UpgradeConfig{Image: "", Measurements: Measurements{}})
|
||||||
|
|
||||||
UpgradeConfigDoc.Type = "UpgradeConfig"
|
UpgradeConfigDoc.Type = "UpgradeConfig"
|
||||||
UpgradeConfigDoc.Comments[encoder.LineComment] = "UpgradeConfig defines configuration used during constellation upgrade."
|
UpgradeConfigDoc.Comments[encoder.LineComment] = "UpgradeConfig defines configuration used during constellation upgrade."
|
||||||
@ -81,8 +86,8 @@ func init() {
|
|||||||
UpgradeConfigDoc.Fields[0].Name = "image"
|
UpgradeConfigDoc.Fields[0].Name = "image"
|
||||||
UpgradeConfigDoc.Fields[0].Type = "string"
|
UpgradeConfigDoc.Fields[0].Type = "string"
|
||||||
UpgradeConfigDoc.Fields[0].Note = ""
|
UpgradeConfigDoc.Fields[0].Note = ""
|
||||||
UpgradeConfigDoc.Fields[0].Description = "Updated machine image to install on all nodes."
|
UpgradeConfigDoc.Fields[0].Description = "Updated Constellation machine image to install on all nodes."
|
||||||
UpgradeConfigDoc.Fields[0].Comments[encoder.LineComment] = "Updated machine image to install on all nodes."
|
UpgradeConfigDoc.Fields[0].Comments[encoder.LineComment] = "Updated Constellation machine image to install on all nodes."
|
||||||
UpgradeConfigDoc.Fields[1].Name = "measurements"
|
UpgradeConfigDoc.Fields[1].Name = "measurements"
|
||||||
UpgradeConfigDoc.Fields[1].Type = "Measurements"
|
UpgradeConfigDoc.Fields[1].Type = "Measurements"
|
||||||
UpgradeConfigDoc.Fields[1].Note = ""
|
UpgradeConfigDoc.Fields[1].Note = ""
|
||||||
@ -152,7 +157,7 @@ func init() {
|
|||||||
FieldName: "aws",
|
FieldName: "aws",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
AWSConfigDoc.Fields = make([]encoder.Doc, 9)
|
AWSConfigDoc.Fields = make([]encoder.Doc, 8)
|
||||||
AWSConfigDoc.Fields[0].Name = "region"
|
AWSConfigDoc.Fields[0].Name = "region"
|
||||||
AWSConfigDoc.Fields[0].Type = "string"
|
AWSConfigDoc.Fields[0].Type = "string"
|
||||||
AWSConfigDoc.Fields[0].Note = ""
|
AWSConfigDoc.Fields[0].Note = ""
|
||||||
@ -163,41 +168,36 @@ func init() {
|
|||||||
AWSConfigDoc.Fields[1].Note = ""
|
AWSConfigDoc.Fields[1].Note = ""
|
||||||
AWSConfigDoc.Fields[1].Description = "AWS data center zone name in defined region. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones"
|
AWSConfigDoc.Fields[1].Description = "AWS data center zone name in defined region. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones"
|
||||||
AWSConfigDoc.Fields[1].Comments[encoder.LineComment] = "AWS data center zone name in defined region. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones"
|
AWSConfigDoc.Fields[1].Comments[encoder.LineComment] = "AWS data center zone name in defined region. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones"
|
||||||
AWSConfigDoc.Fields[2].Name = "image"
|
AWSConfigDoc.Fields[2].Name = "instanceType"
|
||||||
AWSConfigDoc.Fields[2].Type = "string"
|
AWSConfigDoc.Fields[2].Type = "string"
|
||||||
AWSConfigDoc.Fields[2].Note = ""
|
AWSConfigDoc.Fields[2].Note = ""
|
||||||
AWSConfigDoc.Fields[2].Description = "AMI ID of the machine image used to create Constellation nodes."
|
AWSConfigDoc.Fields[2].Description = "VM instance type to use for Constellation nodes. Needs to support NitroTPM. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enable-nitrotpm-prerequisites.html"
|
||||||
AWSConfigDoc.Fields[2].Comments[encoder.LineComment] = "AMI ID of the machine image used to create Constellation nodes."
|
AWSConfigDoc.Fields[2].Comments[encoder.LineComment] = "VM instance type to use for Constellation nodes. Needs to support NitroTPM. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enable-nitrotpm-prerequisites.html"
|
||||||
AWSConfigDoc.Fields[3].Name = "instanceType"
|
AWSConfigDoc.Fields[3].Name = "stateDiskType"
|
||||||
AWSConfigDoc.Fields[3].Type = "string"
|
AWSConfigDoc.Fields[3].Type = "string"
|
||||||
AWSConfigDoc.Fields[3].Note = ""
|
AWSConfigDoc.Fields[3].Note = ""
|
||||||
AWSConfigDoc.Fields[3].Description = "VM instance type to use for Constellation nodes. Needs to support NitroTPM. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enable-nitrotpm-prerequisites.html"
|
AWSConfigDoc.Fields[3].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html"
|
||||||
AWSConfigDoc.Fields[3].Comments[encoder.LineComment] = "VM instance type to use for Constellation nodes. Needs to support NitroTPM. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enable-nitrotpm-prerequisites.html"
|
AWSConfigDoc.Fields[3].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html"
|
||||||
AWSConfigDoc.Fields[4].Name = "stateDiskType"
|
AWSConfigDoc.Fields[4].Name = "iamProfileControlPlane"
|
||||||
AWSConfigDoc.Fields[4].Type = "string"
|
AWSConfigDoc.Fields[4].Type = "string"
|
||||||
AWSConfigDoc.Fields[4].Note = ""
|
AWSConfigDoc.Fields[4].Note = ""
|
||||||
AWSConfigDoc.Fields[4].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html"
|
AWSConfigDoc.Fields[4].Description = "Name of the IAM profile to use for the control plane nodes."
|
||||||
AWSConfigDoc.Fields[4].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html"
|
AWSConfigDoc.Fields[4].Comments[encoder.LineComment] = "Name of the IAM profile to use for the control plane nodes."
|
||||||
AWSConfigDoc.Fields[5].Name = "iamProfileControlPlane"
|
AWSConfigDoc.Fields[5].Name = "iamProfileWorkerNodes"
|
||||||
AWSConfigDoc.Fields[5].Type = "string"
|
AWSConfigDoc.Fields[5].Type = "string"
|
||||||
AWSConfigDoc.Fields[5].Note = ""
|
AWSConfigDoc.Fields[5].Note = ""
|
||||||
AWSConfigDoc.Fields[5].Description = "Name of the IAM profile to use for the control plane nodes."
|
AWSConfigDoc.Fields[5].Description = "Name of the IAM profile to use for the worker nodes."
|
||||||
AWSConfigDoc.Fields[5].Comments[encoder.LineComment] = "Name of the IAM profile to use for the control plane nodes."
|
AWSConfigDoc.Fields[5].Comments[encoder.LineComment] = "Name of the IAM profile to use for the worker nodes."
|
||||||
AWSConfigDoc.Fields[6].Name = "iamProfileWorkerNodes"
|
AWSConfigDoc.Fields[6].Name = "measurements"
|
||||||
AWSConfigDoc.Fields[6].Type = "string"
|
AWSConfigDoc.Fields[6].Type = "Measurements"
|
||||||
AWSConfigDoc.Fields[6].Note = ""
|
AWSConfigDoc.Fields[6].Note = ""
|
||||||
AWSConfigDoc.Fields[6].Description = "Name of the IAM profile to use for the worker nodes."
|
AWSConfigDoc.Fields[6].Description = "Expected VM measurements."
|
||||||
AWSConfigDoc.Fields[6].Comments[encoder.LineComment] = "Name of the IAM profile to use for the worker nodes."
|
AWSConfigDoc.Fields[6].Comments[encoder.LineComment] = "Expected VM measurements."
|
||||||
AWSConfigDoc.Fields[7].Name = "measurements"
|
AWSConfigDoc.Fields[7].Name = "enforcedMeasurements"
|
||||||
AWSConfigDoc.Fields[7].Type = "Measurements"
|
AWSConfigDoc.Fields[7].Type = "[]uint32"
|
||||||
AWSConfigDoc.Fields[7].Note = ""
|
AWSConfigDoc.Fields[7].Note = ""
|
||||||
AWSConfigDoc.Fields[7].Description = "Expected VM measurements."
|
AWSConfigDoc.Fields[7].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
AWSConfigDoc.Fields[7].Comments[encoder.LineComment] = "Expected VM measurements."
|
AWSConfigDoc.Fields[7].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
AWSConfigDoc.Fields[8].Name = "enforcedMeasurements"
|
|
||||||
AWSConfigDoc.Fields[8].Type = "[]uint32"
|
|
||||||
AWSConfigDoc.Fields[8].Note = ""
|
|
||||||
AWSConfigDoc.Fields[8].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
AWSConfigDoc.Fields[8].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
|
|
||||||
AzureConfigDoc.Type = "AzureConfig"
|
AzureConfigDoc.Type = "AzureConfig"
|
||||||
AzureConfigDoc.Comments[encoder.LineComment] = "AzureConfig are Azure specific configuration values used by the CLI."
|
AzureConfigDoc.Comments[encoder.LineComment] = "AzureConfig are Azure specific configuration values used by the CLI."
|
||||||
@ -208,7 +208,7 @@ func init() {
|
|||||||
FieldName: "azure",
|
FieldName: "azure",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
AzureConfigDoc.Fields = make([]encoder.Doc, 17)
|
AzureConfigDoc.Fields = make([]encoder.Doc, 16)
|
||||||
AzureConfigDoc.Fields[0].Name = "subscription"
|
AzureConfigDoc.Fields[0].Name = "subscription"
|
||||||
AzureConfigDoc.Fields[0].Type = "string"
|
AzureConfigDoc.Fields[0].Type = "string"
|
||||||
AzureConfigDoc.Fields[0].Note = ""
|
AzureConfigDoc.Fields[0].Note = ""
|
||||||
@ -244,56 +244,51 @@ func init() {
|
|||||||
AzureConfigDoc.Fields[6].Note = ""
|
AzureConfigDoc.Fields[6].Note = ""
|
||||||
AzureConfigDoc.Fields[6].Description = "Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable."
|
AzureConfigDoc.Fields[6].Description = "Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable."
|
||||||
AzureConfigDoc.Fields[6].Comments[encoder.LineComment] = "Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable."
|
AzureConfigDoc.Fields[6].Comments[encoder.LineComment] = "Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable."
|
||||||
AzureConfigDoc.Fields[7].Name = "image"
|
AzureConfigDoc.Fields[7].Name = "instanceType"
|
||||||
AzureConfigDoc.Fields[7].Type = "string"
|
AzureConfigDoc.Fields[7].Type = "string"
|
||||||
AzureConfigDoc.Fields[7].Note = ""
|
AzureConfigDoc.Fields[7].Note = ""
|
||||||
AzureConfigDoc.Fields[7].Description = "Machine image used to create Constellation nodes."
|
AzureConfigDoc.Fields[7].Description = "VM instance type to use for Constellation nodes."
|
||||||
AzureConfigDoc.Fields[7].Comments[encoder.LineComment] = "Machine image used to create Constellation nodes."
|
AzureConfigDoc.Fields[7].Comments[encoder.LineComment] = "VM instance type to use for Constellation nodes."
|
||||||
AzureConfigDoc.Fields[8].Name = "instanceType"
|
AzureConfigDoc.Fields[8].Name = "stateDiskType"
|
||||||
AzureConfigDoc.Fields[8].Type = "string"
|
AzureConfigDoc.Fields[8].Type = "string"
|
||||||
AzureConfigDoc.Fields[8].Note = ""
|
AzureConfigDoc.Fields[8].Note = ""
|
||||||
AzureConfigDoc.Fields[8].Description = "VM instance type to use for Constellation nodes."
|
AzureConfigDoc.Fields[8].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.microsoft.com/en-us/azure/virtual-machines/disks-types#disk-type-comparison"
|
||||||
AzureConfigDoc.Fields[8].Comments[encoder.LineComment] = "VM instance type to use for Constellation nodes."
|
AzureConfigDoc.Fields[8].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.microsoft.com/en-us/azure/virtual-machines/disks-types#disk-type-comparison"
|
||||||
AzureConfigDoc.Fields[9].Name = "stateDiskType"
|
AzureConfigDoc.Fields[9].Name = "deployCSIDriver"
|
||||||
AzureConfigDoc.Fields[9].Type = "string"
|
AzureConfigDoc.Fields[9].Type = "bool"
|
||||||
AzureConfigDoc.Fields[9].Note = ""
|
AzureConfigDoc.Fields[9].Note = ""
|
||||||
AzureConfigDoc.Fields[9].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.microsoft.com/en-us/azure/virtual-machines/disks-types#disk-type-comparison"
|
AzureConfigDoc.Fields[9].Description = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
||||||
AzureConfigDoc.Fields[9].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://docs.microsoft.com/en-us/azure/virtual-machines/disks-types#disk-type-comparison"
|
AzureConfigDoc.Fields[9].Comments[encoder.LineComment] = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
||||||
AzureConfigDoc.Fields[10].Name = "deployCSIDriver"
|
AzureConfigDoc.Fields[10].Name = "confidentialVM"
|
||||||
AzureConfigDoc.Fields[10].Type = "bool"
|
AzureConfigDoc.Fields[10].Type = "bool"
|
||||||
AzureConfigDoc.Fields[10].Note = ""
|
AzureConfigDoc.Fields[10].Note = ""
|
||||||
AzureConfigDoc.Fields[10].Description = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
AzureConfigDoc.Fields[10].Description = "Use Confidential VMs. If set to false, Trusted Launch VMs are used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
|
||||||
AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "Use Confidential VMs. If set to false, Trusted Launch VMs are used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
|
||||||
AzureConfigDoc.Fields[11].Name = "confidentialVM"
|
AzureConfigDoc.Fields[11].Name = "secureBoot"
|
||||||
AzureConfigDoc.Fields[11].Type = "bool"
|
AzureConfigDoc.Fields[11].Type = "bool"
|
||||||
AzureConfigDoc.Fields[11].Note = ""
|
AzureConfigDoc.Fields[11].Note = ""
|
||||||
AzureConfigDoc.Fields[11].Description = "Use Confidential VMs. If set to false, Trusted Launch VMs are used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
|
AzureConfigDoc.Fields[11].Description = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
|
||||||
AzureConfigDoc.Fields[11].Comments[encoder.LineComment] = "Use Confidential VMs. If set to false, Trusted Launch VMs are used instead. See: https://docs.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview"
|
AzureConfigDoc.Fields[11].Comments[encoder.LineComment] = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
|
||||||
AzureConfigDoc.Fields[12].Name = "secureBoot"
|
AzureConfigDoc.Fields[12].Name = "idKeyDigest"
|
||||||
AzureConfigDoc.Fields[12].Type = "bool"
|
AzureConfigDoc.Fields[12].Type = "string"
|
||||||
AzureConfigDoc.Fields[12].Note = ""
|
AzureConfigDoc.Fields[12].Note = ""
|
||||||
AzureConfigDoc.Fields[12].Description = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
|
AzureConfigDoc.Fields[12].Description = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
|
||||||
AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
|
AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
|
||||||
AzureConfigDoc.Fields[13].Name = "idKeyDigest"
|
AzureConfigDoc.Fields[13].Name = "enforceIdKeyDigest"
|
||||||
AzureConfigDoc.Fields[13].Type = "string"
|
AzureConfigDoc.Fields[13].Type = "bool"
|
||||||
AzureConfigDoc.Fields[13].Note = ""
|
AzureConfigDoc.Fields[13].Note = ""
|
||||||
AzureConfigDoc.Fields[13].Description = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
|
AzureConfigDoc.Fields[13].Description = "Enforce the specified idKeyDigest value during remote attestation."
|
||||||
AzureConfigDoc.Fields[13].Comments[encoder.LineComment] = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
|
AzureConfigDoc.Fields[13].Comments[encoder.LineComment] = "Enforce the specified idKeyDigest value during remote attestation."
|
||||||
AzureConfigDoc.Fields[14].Name = "enforceIdKeyDigest"
|
AzureConfigDoc.Fields[14].Name = "measurements"
|
||||||
AzureConfigDoc.Fields[14].Type = "bool"
|
AzureConfigDoc.Fields[14].Type = "Measurements"
|
||||||
AzureConfigDoc.Fields[14].Note = ""
|
AzureConfigDoc.Fields[14].Note = ""
|
||||||
AzureConfigDoc.Fields[14].Description = "Enforce the specified idKeyDigest value during remote attestation."
|
AzureConfigDoc.Fields[14].Description = "Expected confidential VM measurements."
|
||||||
AzureConfigDoc.Fields[14].Comments[encoder.LineComment] = "Enforce the specified idKeyDigest value during remote attestation."
|
AzureConfigDoc.Fields[14].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
||||||
AzureConfigDoc.Fields[15].Name = "measurements"
|
AzureConfigDoc.Fields[15].Name = "enforcedMeasurements"
|
||||||
AzureConfigDoc.Fields[15].Type = "Measurements"
|
AzureConfigDoc.Fields[15].Type = "[]uint32"
|
||||||
AzureConfigDoc.Fields[15].Note = ""
|
AzureConfigDoc.Fields[15].Note = ""
|
||||||
AzureConfigDoc.Fields[15].Description = "Expected confidential VM measurements."
|
AzureConfigDoc.Fields[15].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
AzureConfigDoc.Fields[15].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
AzureConfigDoc.Fields[15].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
AzureConfigDoc.Fields[16].Name = "enforcedMeasurements"
|
|
||||||
AzureConfigDoc.Fields[16].Type = "[]uint32"
|
|
||||||
AzureConfigDoc.Fields[16].Note = ""
|
|
||||||
AzureConfigDoc.Fields[16].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
AzureConfigDoc.Fields[16].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
|
|
||||||
GCPConfigDoc.Type = "GCPConfig"
|
GCPConfigDoc.Type = "GCPConfig"
|
||||||
GCPConfigDoc.Comments[encoder.LineComment] = "GCPConfig are GCP specific configuration values used by the CLI."
|
GCPConfigDoc.Comments[encoder.LineComment] = "GCPConfig are GCP specific configuration values used by the CLI."
|
||||||
@ -304,7 +299,7 @@ func init() {
|
|||||||
FieldName: "gcp",
|
FieldName: "gcp",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
GCPConfigDoc.Fields = make([]encoder.Doc, 10)
|
GCPConfigDoc.Fields = make([]encoder.Doc, 9)
|
||||||
GCPConfigDoc.Fields[0].Name = "project"
|
GCPConfigDoc.Fields[0].Name = "project"
|
||||||
GCPConfigDoc.Fields[0].Type = "string"
|
GCPConfigDoc.Fields[0].Type = "string"
|
||||||
GCPConfigDoc.Fields[0].Note = ""
|
GCPConfigDoc.Fields[0].Note = ""
|
||||||
@ -325,36 +320,31 @@ func init() {
|
|||||||
GCPConfigDoc.Fields[3].Note = ""
|
GCPConfigDoc.Fields[3].Note = ""
|
||||||
GCPConfigDoc.Fields[3].Description = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization"
|
GCPConfigDoc.Fields[3].Description = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization"
|
||||||
GCPConfigDoc.Fields[3].Comments[encoder.LineComment] = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization"
|
GCPConfigDoc.Fields[3].Comments[encoder.LineComment] = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization"
|
||||||
GCPConfigDoc.Fields[4].Name = "image"
|
GCPConfigDoc.Fields[4].Name = "instanceType"
|
||||||
GCPConfigDoc.Fields[4].Type = "string"
|
GCPConfigDoc.Fields[4].Type = "string"
|
||||||
GCPConfigDoc.Fields[4].Note = ""
|
GCPConfigDoc.Fields[4].Note = ""
|
||||||
GCPConfigDoc.Fields[4].Description = "Machine image used to create Constellation nodes."
|
GCPConfigDoc.Fields[4].Description = "VM instance type to use for Constellation nodes."
|
||||||
GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "Machine image used to create Constellation nodes."
|
GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "VM instance type to use for Constellation nodes."
|
||||||
GCPConfigDoc.Fields[5].Name = "instanceType"
|
GCPConfigDoc.Fields[5].Name = "stateDiskType"
|
||||||
GCPConfigDoc.Fields[5].Type = "string"
|
GCPConfigDoc.Fields[5].Type = "string"
|
||||||
GCPConfigDoc.Fields[5].Note = ""
|
GCPConfigDoc.Fields[5].Note = ""
|
||||||
GCPConfigDoc.Fields[5].Description = "VM instance type to use for Constellation nodes."
|
GCPConfigDoc.Fields[5].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types"
|
||||||
GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "VM instance type to use for Constellation nodes."
|
GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types"
|
||||||
GCPConfigDoc.Fields[6].Name = "stateDiskType"
|
GCPConfigDoc.Fields[6].Name = "deployCSIDriver"
|
||||||
GCPConfigDoc.Fields[6].Type = "string"
|
GCPConfigDoc.Fields[6].Type = "bool"
|
||||||
GCPConfigDoc.Fields[6].Note = ""
|
GCPConfigDoc.Fields[6].Note = ""
|
||||||
GCPConfigDoc.Fields[6].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types"
|
GCPConfigDoc.Fields[6].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
||||||
GCPConfigDoc.Fields[6].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types"
|
GCPConfigDoc.Fields[6].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
||||||
GCPConfigDoc.Fields[7].Name = "deployCSIDriver"
|
GCPConfigDoc.Fields[7].Name = "measurements"
|
||||||
GCPConfigDoc.Fields[7].Type = "bool"
|
GCPConfigDoc.Fields[7].Type = "Measurements"
|
||||||
GCPConfigDoc.Fields[7].Note = ""
|
GCPConfigDoc.Fields[7].Note = ""
|
||||||
GCPConfigDoc.Fields[7].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
GCPConfigDoc.Fields[7].Description = "Expected confidential VM measurements."
|
||||||
GCPConfigDoc.Fields[7].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
|
GCPConfigDoc.Fields[7].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
||||||
GCPConfigDoc.Fields[8].Name = "measurements"
|
GCPConfigDoc.Fields[8].Name = "enforcedMeasurements"
|
||||||
GCPConfigDoc.Fields[8].Type = "Measurements"
|
GCPConfigDoc.Fields[8].Type = "[]uint32"
|
||||||
GCPConfigDoc.Fields[8].Note = ""
|
GCPConfigDoc.Fields[8].Note = ""
|
||||||
GCPConfigDoc.Fields[8].Description = "Expected confidential VM measurements."
|
GCPConfigDoc.Fields[8].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
GCPConfigDoc.Fields[8].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
GCPConfigDoc.Fields[8].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
GCPConfigDoc.Fields[9].Name = "enforcedMeasurements"
|
|
||||||
GCPConfigDoc.Fields[9].Type = "[]uint32"
|
|
||||||
GCPConfigDoc.Fields[9].Note = ""
|
|
||||||
GCPConfigDoc.Fields[9].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
GCPConfigDoc.Fields[9].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
|
|
||||||
QEMUConfigDoc.Type = "QEMUConfig"
|
QEMUConfigDoc.Type = "QEMUConfig"
|
||||||
QEMUConfigDoc.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments."
|
QEMUConfigDoc.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments."
|
||||||
@ -365,62 +355,57 @@ func init() {
|
|||||||
FieldName: "qemu",
|
FieldName: "qemu",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
QEMUConfigDoc.Fields = make([]encoder.Doc, 11)
|
QEMUConfigDoc.Fields = make([]encoder.Doc, 10)
|
||||||
QEMUConfigDoc.Fields[0].Name = "image"
|
QEMUConfigDoc.Fields[0].Name = "imageFormat"
|
||||||
QEMUConfigDoc.Fields[0].Type = "string"
|
QEMUConfigDoc.Fields[0].Type = "string"
|
||||||
QEMUConfigDoc.Fields[0].Note = ""
|
QEMUConfigDoc.Fields[0].Note = ""
|
||||||
QEMUConfigDoc.Fields[0].Description = "Path to the image to use for the VMs."
|
QEMUConfigDoc.Fields[0].Description = "Format of the image to use for the VMs. Should be either qcow2 or raw."
|
||||||
QEMUConfigDoc.Fields[0].Comments[encoder.LineComment] = "Path to the image to use for the VMs."
|
QEMUConfigDoc.Fields[0].Comments[encoder.LineComment] = "Format of the image to use for the VMs. Should be either qcow2 or raw."
|
||||||
QEMUConfigDoc.Fields[1].Name = "imageFormat"
|
QEMUConfigDoc.Fields[1].Name = "vcpus"
|
||||||
QEMUConfigDoc.Fields[1].Type = "string"
|
QEMUConfigDoc.Fields[1].Type = "int"
|
||||||
QEMUConfigDoc.Fields[1].Note = ""
|
QEMUConfigDoc.Fields[1].Note = ""
|
||||||
QEMUConfigDoc.Fields[1].Description = "Format of the image to use for the VMs. Should be either qcow2 or raw."
|
QEMUConfigDoc.Fields[1].Description = "vCPU count for the VMs."
|
||||||
QEMUConfigDoc.Fields[1].Comments[encoder.LineComment] = "Format of the image to use for the VMs. Should be either qcow2 or raw."
|
QEMUConfigDoc.Fields[1].Comments[encoder.LineComment] = "vCPU count for the VMs."
|
||||||
QEMUConfigDoc.Fields[2].Name = "vcpus"
|
QEMUConfigDoc.Fields[2].Name = "memory"
|
||||||
QEMUConfigDoc.Fields[2].Type = "int"
|
QEMUConfigDoc.Fields[2].Type = "int"
|
||||||
QEMUConfigDoc.Fields[2].Note = ""
|
QEMUConfigDoc.Fields[2].Note = ""
|
||||||
QEMUConfigDoc.Fields[2].Description = "vCPU count for the VMs."
|
QEMUConfigDoc.Fields[2].Description = "Amount of memory per instance (MiB)."
|
||||||
QEMUConfigDoc.Fields[2].Comments[encoder.LineComment] = "vCPU count for the VMs."
|
QEMUConfigDoc.Fields[2].Comments[encoder.LineComment] = "Amount of memory per instance (MiB)."
|
||||||
QEMUConfigDoc.Fields[3].Name = "memory"
|
QEMUConfigDoc.Fields[3].Name = "metadataAPIServer"
|
||||||
QEMUConfigDoc.Fields[3].Type = "int"
|
QEMUConfigDoc.Fields[3].Type = "string"
|
||||||
QEMUConfigDoc.Fields[3].Note = ""
|
QEMUConfigDoc.Fields[3].Note = ""
|
||||||
QEMUConfigDoc.Fields[3].Description = "Amount of memory per instance (MiB)."
|
QEMUConfigDoc.Fields[3].Description = "Container image to use for the QEMU metadata server."
|
||||||
QEMUConfigDoc.Fields[3].Comments[encoder.LineComment] = "Amount of memory per instance (MiB)."
|
QEMUConfigDoc.Fields[3].Comments[encoder.LineComment] = "Container image to use for the QEMU metadata server."
|
||||||
QEMUConfigDoc.Fields[4].Name = "metadataAPIServer"
|
QEMUConfigDoc.Fields[4].Name = "libvirtSocket"
|
||||||
QEMUConfigDoc.Fields[4].Type = "string"
|
QEMUConfigDoc.Fields[4].Type = "string"
|
||||||
QEMUConfigDoc.Fields[4].Note = ""
|
QEMUConfigDoc.Fields[4].Note = ""
|
||||||
QEMUConfigDoc.Fields[4].Description = "Container image to use for the QEMU metadata server."
|
QEMUConfigDoc.Fields[4].Description = "Libvirt connection URI. Leave empty to start a libvirt instance in Docker."
|
||||||
QEMUConfigDoc.Fields[4].Comments[encoder.LineComment] = "Container image to use for the QEMU metadata server."
|
QEMUConfigDoc.Fields[4].Comments[encoder.LineComment] = "Libvirt connection URI. Leave empty to start a libvirt instance in Docker."
|
||||||
QEMUConfigDoc.Fields[5].Name = "libvirtSocket"
|
QEMUConfigDoc.Fields[5].Name = "libvirtContainerImage"
|
||||||
QEMUConfigDoc.Fields[5].Type = "string"
|
QEMUConfigDoc.Fields[5].Type = "string"
|
||||||
QEMUConfigDoc.Fields[5].Note = ""
|
QEMUConfigDoc.Fields[5].Note = ""
|
||||||
QEMUConfigDoc.Fields[5].Description = "Libvirt connection URI. Leave empty to start a libvirt instance in Docker."
|
QEMUConfigDoc.Fields[5].Description = "Container image to use for launching a containerized libvirt daemon. Only relevant if `libvirtSocket = \"\"`."
|
||||||
QEMUConfigDoc.Fields[5].Comments[encoder.LineComment] = "Libvirt connection URI. Leave empty to start a libvirt instance in Docker."
|
QEMUConfigDoc.Fields[5].Comments[encoder.LineComment] = "Container image to use for launching a containerized libvirt daemon. Only relevant if `libvirtSocket = \"\"`."
|
||||||
QEMUConfigDoc.Fields[6].Name = "libvirtContainerImage"
|
QEMUConfigDoc.Fields[6].Name = "nvram"
|
||||||
QEMUConfigDoc.Fields[6].Type = "string"
|
QEMUConfigDoc.Fields[6].Type = "string"
|
||||||
QEMUConfigDoc.Fields[6].Note = ""
|
QEMUConfigDoc.Fields[6].Note = ""
|
||||||
QEMUConfigDoc.Fields[6].Description = "Container image to use for launching a containerized libvirt daemon. Only relevant if `libvirtSocket = \"\"`."
|
QEMUConfigDoc.Fields[6].Description = "NVRAM template to be used for secure boot. Can be sentinel value \"production\", \"testing\" or a path to a custom NVRAM template"
|
||||||
QEMUConfigDoc.Fields[6].Comments[encoder.LineComment] = "Container image to use for launching a containerized libvirt daemon. Only relevant if `libvirtSocket = \"\"`."
|
QEMUConfigDoc.Fields[6].Comments[encoder.LineComment] = "NVRAM template to be used for secure boot. Can be sentinel value \"production\", \"testing\" or a path to a custom NVRAM template"
|
||||||
QEMUConfigDoc.Fields[7].Name = "nvram"
|
QEMUConfigDoc.Fields[7].Name = "firmware"
|
||||||
QEMUConfigDoc.Fields[7].Type = "string"
|
QEMUConfigDoc.Fields[7].Type = "string"
|
||||||
QEMUConfigDoc.Fields[7].Note = ""
|
QEMUConfigDoc.Fields[7].Note = ""
|
||||||
QEMUConfigDoc.Fields[7].Description = "NVRAM template to be used for secure boot. Can be sentinel value \"production\", \"testing\" or a path to a custom NVRAM template"
|
QEMUConfigDoc.Fields[7].Description = "Path to the OVMF firmware. Leave empty for auto selection."
|
||||||
QEMUConfigDoc.Fields[7].Comments[encoder.LineComment] = "NVRAM template to be used for secure boot. Can be sentinel value \"production\", \"testing\" or a path to a custom NVRAM template"
|
QEMUConfigDoc.Fields[7].Comments[encoder.LineComment] = "Path to the OVMF firmware. Leave empty for auto selection."
|
||||||
QEMUConfigDoc.Fields[8].Name = "firmware"
|
QEMUConfigDoc.Fields[8].Name = "measurements"
|
||||||
QEMUConfigDoc.Fields[8].Type = "string"
|
QEMUConfigDoc.Fields[8].Type = "Measurements"
|
||||||
QEMUConfigDoc.Fields[8].Note = ""
|
QEMUConfigDoc.Fields[8].Note = ""
|
||||||
QEMUConfigDoc.Fields[8].Description = "Path to the OVMF firmware. Leave empty for auto selection."
|
QEMUConfigDoc.Fields[8].Description = "Measurement used to enable measured boot."
|
||||||
QEMUConfigDoc.Fields[8].Comments[encoder.LineComment] = "Path to the OVMF firmware. Leave empty for auto selection."
|
QEMUConfigDoc.Fields[8].Comments[encoder.LineComment] = "Measurement used to enable measured boot."
|
||||||
QEMUConfigDoc.Fields[9].Name = "measurements"
|
QEMUConfigDoc.Fields[9].Name = "enforcedMeasurements"
|
||||||
QEMUConfigDoc.Fields[9].Type = "Measurements"
|
QEMUConfigDoc.Fields[9].Type = "[]uint32"
|
||||||
QEMUConfigDoc.Fields[9].Note = ""
|
QEMUConfigDoc.Fields[9].Note = ""
|
||||||
QEMUConfigDoc.Fields[9].Description = "Measurement used to enable measured boot."
|
QEMUConfigDoc.Fields[9].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
QEMUConfigDoc.Fields[9].Comments[encoder.LineComment] = "Measurement used to enable measured boot."
|
QEMUConfigDoc.Fields[9].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
||||||
QEMUConfigDoc.Fields[10].Name = "enforcedMeasurements"
|
|
||||||
QEMUConfigDoc.Fields[10].Type = "[]uint32"
|
|
||||||
QEMUConfigDoc.Fields[10].Note = ""
|
|
||||||
QEMUConfigDoc.Fields[10].Description = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
QEMUConfigDoc.Fields[10].Comments[encoder.LineComment] = "List of values that should be enforced to be equal to the ones from the measurement list. Any non-equal values not in this list will only result in a warning."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ Config) Doc() *encoder.Doc {
|
func (_ Config) Doc() *encoder.Doc {
|
||||||
|
@ -121,13 +121,13 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
|||||||
confToWrite: func() *Config { // valid config with all, but clientSecretValue
|
confToWrite: func() *Config { // valid config with all, but clientSecretValue
|
||||||
c := Default()
|
c := Default()
|
||||||
c.RemoveProviderExcept(cloudprovider.Azure)
|
c.RemoveProviderExcept(cloudprovider.Azure)
|
||||||
|
c.Image = "v0.0.0"
|
||||||
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
|
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
|
||||||
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
|
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
|
||||||
c.Provider.Azure.Location = "westus"
|
c.Provider.Azure.Location = "westus"
|
||||||
c.Provider.Azure.ResourceGroup = "test"
|
c.Provider.Azure.ResourceGroup = "test"
|
||||||
c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity"
|
c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity"
|
||||||
c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e"
|
c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e"
|
||||||
c.Provider.Azure.Image = "/communityGalleries/ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.2.0"
|
|
||||||
return c
|
return c
|
||||||
}(),
|
}(),
|
||||||
envToSet: map[string]string{
|
envToSet: map[string]string{
|
||||||
@ -139,6 +139,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
|||||||
confToWrite: func() *Config {
|
confToWrite: func() *Config {
|
||||||
c := Default()
|
c := Default()
|
||||||
c.RemoveProviderExcept(cloudprovider.Azure)
|
c.RemoveProviderExcept(cloudprovider.Azure)
|
||||||
|
c.Image = "v0.0.0"
|
||||||
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
|
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
|
||||||
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
|
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
|
||||||
c.Provider.Azure.Location = "westus"
|
c.Provider.Azure.Location = "westus"
|
||||||
@ -146,7 +147,6 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
|||||||
c.Provider.Azure.ClientSecretValue = "other-value" // < Note secret set in config, as well.
|
c.Provider.Azure.ClientSecretValue = "other-value" // < Note secret set in config, as well.
|
||||||
c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity"
|
c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity"
|
||||||
c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e"
|
c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e"
|
||||||
c.Provider.Azure.Image = "/communityGalleries/ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.2.0"
|
|
||||||
return c
|
return c
|
||||||
}(),
|
}(),
|
||||||
envToSet: map[string]string{
|
envToSet: map[string]string{
|
||||||
@ -182,7 +182,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
const defaultErrCount = 20 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
|
const defaultErrCount = 17 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
|
||||||
const azErrCount = 8
|
const azErrCount = 8
|
||||||
const gcpErrCount = 5
|
const gcpErrCount = 5
|
||||||
|
|
||||||
@ -229,12 +229,12 @@ func TestValidate(t *testing.T) {
|
|||||||
"Azure config with all required fields is valid": {
|
"Azure config with all required fields is valid": {
|
||||||
cnf: func() *Config {
|
cnf: func() *Config {
|
||||||
cnf := Default()
|
cnf := Default()
|
||||||
|
cnf.Image = "v0.0.0"
|
||||||
az := cnf.Provider.Azure
|
az := cnf.Provider.Azure
|
||||||
az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
||||||
az.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
az.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
||||||
az.Location = "test-location"
|
az.Location = "test-location"
|
||||||
az.UserAssignedIdentity = "test-identity"
|
az.UserAssignedIdentity = "test-identity"
|
||||||
az.Image = "some/image/location"
|
|
||||||
az.ResourceGroup = "test-resource-group"
|
az.ResourceGroup = "test-resource-group"
|
||||||
az.AppClientID = "01234567-0123-0123-0123-0123456789ab"
|
az.AppClientID = "01234567-0123-0123-0123-0123456789ab"
|
||||||
az.ClientSecretValue = "test-client-secret"
|
az.ClientSecretValue = "test-client-secret"
|
||||||
@ -257,10 +257,10 @@ func TestValidate(t *testing.T) {
|
|||||||
"GCP config with all required fields is valid": {
|
"GCP config with all required fields is valid": {
|
||||||
cnf: func() *Config {
|
cnf: func() *Config {
|
||||||
cnf := Default()
|
cnf := Default()
|
||||||
|
cnf.Image = "v0.0.0"
|
||||||
gcp := cnf.Provider.GCP
|
gcp := cnf.Provider.GCP
|
||||||
gcp.Region = "test-region"
|
gcp.Region = "test-region"
|
||||||
gcp.Project = "test-project"
|
gcp.Project = "test-project"
|
||||||
gcp.Image = "some/image/location"
|
|
||||||
gcp.Zone = "test-zone"
|
gcp.Zone = "test-zone"
|
||||||
gcp.ServiceAccountKeyPath = "test-key-path"
|
gcp.ServiceAccountKeyPath = "test-key-path"
|
||||||
cnf.Provider = ProviderConfig{}
|
cnf.Provider = ProviderConfig{}
|
||||||
@ -300,39 +300,6 @@ func TestHasProvider(t *testing.T) {
|
|||||||
assert.False(cnfWithAzure.HasProvider(cloudprovider.GCP))
|
assert.False(cnfWithAzure.HasProvider(cloudprovider.GCP))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImage(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
cfg *Config
|
|
||||||
wantImage string
|
|
||||||
}{
|
|
||||||
"default aws": {
|
|
||||||
cfg: func() *Config { c := Default(); c.RemoveProviderExcept(cloudprovider.AWS); return c }(),
|
|
||||||
wantImage: Default().Provider.AWS.Image,
|
|
||||||
},
|
|
||||||
"default azure": {
|
|
||||||
cfg: func() *Config { c := Default(); c.RemoveProviderExcept(cloudprovider.Azure); return c }(),
|
|
||||||
wantImage: Default().Provider.Azure.Image,
|
|
||||||
},
|
|
||||||
"default gcp": {
|
|
||||||
cfg: func() *Config { c := Default(); c.RemoveProviderExcept(cloudprovider.GCP); return c }(),
|
|
||||||
wantImage: Default().Provider.GCP.Image,
|
|
||||||
},
|
|
||||||
"default qemu": {
|
|
||||||
cfg: func() *Config { c := Default(); c.RemoveProviderExcept(cloudprovider.QEMU); return c }(),
|
|
||||||
wantImage: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
image := tc.cfg.Image()
|
|
||||||
assert.Equal(tc.wantImage, image)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigRemoveProviderExcept(t *testing.T) {
|
func TestConfigRemoveProviderExcept(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
removeExcept cloudprovider.Provider
|
removeExcept cloudprovider.Provider
|
||||||
@ -388,6 +355,7 @@ func TestConfigGeneratedDocsFresh(t *testing.T) {
|
|||||||
assert.Len(ConfigDoc.Fields, reflect.ValueOf(Config{}).NumField(), updateMsg)
|
assert.Len(ConfigDoc.Fields, reflect.ValueOf(Config{}).NumField(), updateMsg)
|
||||||
assert.Len(UpgradeConfigDoc.Fields, reflect.ValueOf(UpgradeConfig{}).NumField(), updateMsg)
|
assert.Len(UpgradeConfigDoc.Fields, reflect.ValueOf(UpgradeConfig{}).NumField(), updateMsg)
|
||||||
assert.Len(ProviderConfigDoc.Fields, reflect.ValueOf(ProviderConfig{}).NumField(), updateMsg)
|
assert.Len(ProviderConfigDoc.Fields, reflect.ValueOf(ProviderConfig{}).NumField(), updateMsg)
|
||||||
|
assert.Len(AWSConfigDoc.Fields, reflect.ValueOf(AWSConfig{}).NumField(), updateMsg)
|
||||||
assert.Len(AzureConfigDoc.Fields, reflect.ValueOf(AzureConfig{}).NumField(), updateMsg)
|
assert.Len(AzureConfigDoc.Fields, reflect.ValueOf(AzureConfig{}).NumField(), updateMsg)
|
||||||
assert.Len(GCPConfigDoc.Fields, reflect.ValueOf(GCPConfig{}).NumField(), updateMsg)
|
assert.Len(GCPConfigDoc.Fields, reflect.ValueOf(GCPConfig{}).NumField(), updateMsg)
|
||||||
assert.Len(QEMUConfigDoc.Fields, reflect.ValueOf(QEMUConfig{}).NumField(), updateMsg)
|
assert.Len(QEMUConfigDoc.Fields, reflect.ValueOf(QEMUConfig{}).NumField(), updateMsg)
|
||||||
@ -439,47 +407,34 @@ func TestConfig_UpdateMeasurements(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_IsImageDebug(t *testing.T) {
|
func TestConfig_IsReleaseImage(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
conf *Config
|
conf *Config
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
// TODO: Add AWS when we know the format of published images & debug images
|
"release image v0.0.0": {
|
||||||
"gcp release": {
|
|
||||||
conf: func() *Config {
|
conf: func() *Config {
|
||||||
conf := Default()
|
conf := Default()
|
||||||
conf.RemoveProviderExcept(cloudprovider.GCP)
|
conf.Image = "v0.0.0"
|
||||||
conf.Provider.GCP.Image = "projects/constellation-images/global/images/constellation-v1-3-0"
|
|
||||||
return conf
|
|
||||||
}(),
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
"gcp debug": {
|
|
||||||
conf: func() *Config {
|
|
||||||
conf := Default()
|
|
||||||
conf.RemoveProviderExcept(cloudprovider.GCP)
|
|
||||||
conf.Provider.GCP.Image = "projects/constellation-images/global/images/constellation-20220812102023"
|
|
||||||
return conf
|
return conf
|
||||||
}(),
|
}(),
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
"azure release": {
|
"branch image": {
|
||||||
conf: func() *Config {
|
conf: func() *Config {
|
||||||
conf := Default()
|
conf := Default()
|
||||||
conf.RemoveProviderExcept(cloudprovider.Azure)
|
conf.Image = "feat-x-vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef"
|
||||||
conf.Provider.Azure.Image = "/CommunityGalleries/ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df/Images/constellation/Versions/0.0.1"
|
|
||||||
return conf
|
return conf
|
||||||
}(),
|
}(),
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
"azure debug": {
|
"debug image": {
|
||||||
conf: func() *Config {
|
conf: func() *Config {
|
||||||
conf := Default()
|
conf := Default()
|
||||||
conf.RemoveProviderExcept(cloudprovider.Azure)
|
conf.Image = "debug-vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef"
|
||||||
conf.Provider.Azure.Image = "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/constellation-images/providers/Microsoft.Compute/galleries/Constellation_Debug/images/v1.4.0/versions/2022.0805.151600"
|
|
||||||
return conf
|
return conf
|
||||||
}(),
|
}(),
|
||||||
want: true,
|
want: false,
|
||||||
},
|
},
|
||||||
"empty config": {
|
"empty config": {
|
||||||
conf: &Config{},
|
conf: &Config{},
|
||||||
@ -490,7 +445,7 @@ func TestConfig_IsImageDebug(t *testing.T) {
|
|||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
assert.Equal(tc.want, tc.conf.IsDebugImage())
|
assert.Equal(tc.want, tc.conf.IsReleaseImage())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultImageAzure = "/communityGalleries/ConstellationCVM-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.2.2"
|
// defaultImage is the default image for the enterprise build.
|
||||||
DefaultImageGCP = "projects/constellation-images/global/images/constellation-v2-2-2"
|
defaultImage = "v2.3.0"
|
||||||
)
|
)
|
||||||
|
@ -9,8 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultImageAzure is not set for OSS build.
|
// defaultImage is not set for OSS build.
|
||||||
DefaultImageAzure = ""
|
defaultImage = ""
|
||||||
// DefaultImageGCP is not set for OSS build.
|
|
||||||
DefaultImageGCP = ""
|
|
||||||
)
|
)
|
||||||
|
@ -17,6 +17,19 @@ import (
|
|||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func validateImage(fl validator.FieldLevel) bool {
|
||||||
|
image := fl.Field().String()
|
||||||
|
switch {
|
||||||
|
case image == "":
|
||||||
|
return false
|
||||||
|
case image == "..":
|
||||||
|
return false
|
||||||
|
case strings.Contains(image, "/"):
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func validateK8sVersion(fl validator.FieldLevel) bool {
|
func validateK8sVersion(fl validator.FieldLevel) bool {
|
||||||
return versions.IsSupportedK8sVersion(fl.Field().String())
|
return versions.IsSupportedK8sVersion(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,10 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf8F1hpmwE+YCFXzjGtaQcrL6XZVT
|
|||||||
JmEe5iSLvG1SyQSAew7WdMKF6o9t8e2TFuCkzlOhhlws2OHWbiFZnFWCFw==
|
JmEe5iSLvG1SyQSAew7WdMKF6o9t8e2TFuCkzlOhhlws2OHWbiFZnFWCFw==
|
||||||
-----END PUBLIC KEY-----
|
-----END PUBLIC KEY-----
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// ImageVersionRepositoryURL is the base URL of the repository containing
|
||||||
|
// image version information.
|
||||||
|
ImageVersionRepositoryURL = "https://cdn.confidential.cloud"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VersionInfo is the version of a binary. Left as a separate variable to allow override during build.
|
// VersionInfo is the version of a binary. Left as a separate variable to allow override during build.
|
||||||
|
161
internal/image/image.go
Normal file
161
internal/image/image.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// imageLookupTable 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"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
type imageLookupTable map[string]map[string]string
|
||||||
|
|
||||||
|
// getReference returns the image reference for a given CSP and image variant.
|
||||||
|
func (l *imageLookupTable) getReference(csp, variant string) (string, error) {
|
||||||
|
if l == nil {
|
||||||
|
return "", fmt.Errorf("image lookup table is nil")
|
||||||
|
}
|
||||||
|
if _, ok := (*l)[csp]; !ok {
|
||||||
|
return "", fmt.Errorf("image not available for CSP %q", csp)
|
||||||
|
}
|
||||||
|
if _, ok := (*l)[csp][variant]; !ok {
|
||||||
|
return "", fmt.Errorf("image not available for variant %q", variant)
|
||||||
|
}
|
||||||
|
return (*l)[csp][variant], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetcher fetches image references using a lookup table.
|
||||||
|
type Fetcher struct {
|
||||||
|
httpc httpc
|
||||||
|
fs *afero.Afero
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new image fetcher.
|
||||||
|
func New() *Fetcher {
|
||||||
|
return &Fetcher{
|
||||||
|
httpc: http.DefaultClient,
|
||||||
|
fs: &afero.Afero{Fs: afero.NewOsFs()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := variant(provider, config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return f.fetch(ctx, provider, config.Image, variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch fetches the image reference for a given image version uid, CSP and image variant.
|
||||||
|
func (f *Fetcher) fetch(ctx context.Context, csp cloudprovider.Provider, version, variant string) (string, error) {
|
||||||
|
raw, err := getFromFile(f.fs, version)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
raw, err = getFromURL(ctx, f.httpc, version)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("fetching image reference: %w", err)
|
||||||
|
}
|
||||||
|
lut := make(imageLookupTable)
|
||||||
|
if err := json.Unmarshal(raw, &lut); err != nil {
|
||||||
|
return "", fmt.Errorf("decoding image reference: %w", err)
|
||||||
|
}
|
||||||
|
return lut.getReference(strings.ToLower(csp.String()), variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
// variant returns the image variant for a given CSP and configuration.
|
||||||
|
func variant(provider cloudprovider.Provider, config *config.Config) (string, error) {
|
||||||
|
switch provider {
|
||||||
|
case cloudprovider.AWS:
|
||||||
|
return config.Provider.AWS.Region, nil
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
if *config.Provider.Azure.ConfidentialVM {
|
||||||
|
return "cvm", nil
|
||||||
|
}
|
||||||
|
return "trustedlaunch", nil
|
||||||
|
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
return "sev-es", nil
|
||||||
|
case cloudprovider.QEMU:
|
||||||
|
return "default", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported provider: %s", provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFromFile(fs *afero.Afero, version string) ([]byte, error) {
|
||||||
|
version = filepath.Base(version)
|
||||||
|
return fs.ReadFile(version + ".json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFromURL fetches the image lookup table from a URL.
|
||||||
|
func getFromURL(ctx context.Context, client httpc, version string) ([]byte, error) {
|
||||||
|
url, err := url.Parse(constants.ImageVersionRepositoryURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing image version repository URL: %w", err)
|
||||||
|
}
|
||||||
|
versionFilename := path.Base(version) + ".json"
|
||||||
|
url.Path = path.Join("constellation/v1/images", versionFilename)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return nil, fmt.Errorf("image %q does not exist", version)
|
||||||
|
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 {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
254
internal/image/image_test.go
Normal file
254
internal/image/image_test.go
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/goleak"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
goleak.VerifyTestMain(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetReference(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
csp, variant string
|
||||||
|
wantReference string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"reference exists": {
|
||||||
|
csp: "someCSP",
|
||||||
|
variant: "someVariant",
|
||||||
|
wantReference: "someReference",
|
||||||
|
},
|
||||||
|
"csp does not exist": {
|
||||||
|
csp: "nonExistingCSP",
|
||||||
|
variant: "someVariant",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"variant does not exist": {
|
||||||
|
csp: "someCSP",
|
||||||
|
variant: "nonExistingVariant",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
lut := &imageLookupTable{
|
||||||
|
"someCSP": {
|
||||||
|
"someVariant": "someReference",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reference, err := lut.getReference(tc.csp, tc.variant)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantReference, reference)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetReferenceOnNil(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
var lut *imageLookupTable
|
||||||
|
_, err := lut.getReference("someCSP", "someVariant")
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariant(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{ConfidentialVM: func() *bool { b := true; return &b }()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantVariant: "cvm",
|
||||||
|
},
|
||||||
|
"Azure trustedlaunch": {
|
||||||
|
csp: cloudprovider.Azure,
|
||||||
|
config: &config.Config{
|
||||||
|
Image: "someImage", Provider: config.ProviderConfig{
|
||||||
|
Azure: &config.AzureConfig{ConfidentialVM: func() *bool { b := false; return &b }()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantVariant: "trustedlaunch",
|
||||||
|
},
|
||||||
|
"GCP": {
|
||||||
|
csp: cloudprovider.GCP,
|
||||||
|
config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{
|
||||||
|
GCP: &config.GCPConfig{},
|
||||||
|
}},
|
||||||
|
wantVariant: "sev-es",
|
||||||
|
},
|
||||||
|
"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 := variant(tc.csp, tc.config)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantVariant, vari)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchReference(t *testing.T) {
|
||||||
|
imageVersionUID := "someImageVersionUID"
|
||||||
|
client := newTestClient(func(req *http.Request) *http.Response {
|
||||||
|
if req.URL.String() == "https://cdn.confidential.cloud/constellation/v1/images/someImageVersionUID.json" {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString(lut)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("Not found.")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
config *config.Config
|
||||||
|
overrideFile string
|
||||||
|
wantReference string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"reference fetched remotely": {
|
||||||
|
config: &config.Config{Image: imageVersionUID, Provider: config.ProviderConfig{
|
||||||
|
QEMU: &config.QEMUConfig{},
|
||||||
|
}},
|
||||||
|
wantReference: "someReference",
|
||||||
|
},
|
||||||
|
"reference fetched locally": {
|
||||||
|
config: &config.Config{Image: imageVersionUID, Provider: config.ProviderConfig{
|
||||||
|
QEMU: &config.QEMUConfig{},
|
||||||
|
}},
|
||||||
|
overrideFile: `{"qemu":{"default":"localOverrideReference"}}`,
|
||||||
|
wantReference: "localOverrideReference",
|
||||||
|
},
|
||||||
|
"lut is invalid": {
|
||||||
|
config: &config.Config{Image: imageVersionUID, Provider: config.ProviderConfig{
|
||||||
|
QEMU: &config.QEMUConfig{},
|
||||||
|
}},
|
||||||
|
overrideFile: `{`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"image version does not exist": {
|
||||||
|
config: &config.Config{Image: "nonExistingImageVersionUID", Provider: config.ProviderConfig{
|
||||||
|
QEMU: &config.QEMUConfig{},
|
||||||
|
}},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid config": {
|
||||||
|
config: &config.Config{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
fetcher := &Fetcher{
|
||||||
|
httpc: client,
|
||||||
|
fs: newImageVersionStubFs(t, imageVersionUID, tc.overrideFile),
|
||||||
|
}
|
||||||
|
reference, err := fetcher.FetchReference(context.Background(), tc.config)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantReference, reference)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundTripFunc .
|
||||||
|
type roundTripFunc func(req *http.Request) *http.Response
|
||||||
|
|
||||||
|
// RoundTrip .
|
||||||
|
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTestClient returns *http.Client with Transport replaced to avoid making real calls.
|
||||||
|
func newTestClient(fn roundTripFunc) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newImageVersionStubFs(t *testing.T, imageVersionUID string, overrideFile string) *afero.Afero {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
if overrideFile != "" {
|
||||||
|
must(t, afero.WriteFile(fs, imageVersionUID+".json", []byte(overrideFile), os.ModePerm))
|
||||||
|
}
|
||||||
|
return &afero.Afero{Fs: fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lut = `{"qemu":{"default":"someReference"}}`
|
139
internal/image/raw.go
Normal file
139
internal/image/raw.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/schollz/progressbar/v3"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Downloader downloads raw images.
|
||||||
|
type Downloader struct {
|
||||||
|
httpc httpc
|
||||||
|
fs *afero.Afero
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDownloader creates a new Downloader.
|
||||||
|
func NewDownloader() *Downloader {
|
||||||
|
return &Downloader{
|
||||||
|
httpc: http.DefaultClient,
|
||||||
|
fs: &afero.Afero{Fs: afero.NewOsFs()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download downloads the raw image from source.
|
||||||
|
func (d *Downloader) Download(ctx context.Context, errWriter io.Writer, showBar bool, source, version string) (string, error) {
|
||||||
|
url, err := url.Parse(source)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parsing image source URL: %w", err)
|
||||||
|
}
|
||||||
|
version = filepath.Base(version)
|
||||||
|
var partfile, destination string
|
||||||
|
switch url.Scheme {
|
||||||
|
case "http", "https":
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting current working directory: %w", err)
|
||||||
|
}
|
||||||
|
partfile = filepath.Join(cwd, version+".raw.part")
|
||||||
|
destination = filepath.Join(cwd, version+".raw")
|
||||||
|
case "file":
|
||||||
|
return url.Path, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported image source URL scheme: %s", url.Scheme)
|
||||||
|
}
|
||||||
|
if !d.shouldDownload(destination) {
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
if err := d.downloadWithProgress(ctx, errWriter, showBar, source, partfile); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return destination, d.fs.Rename(partfile, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldDownload checks if the image should be downloaded.
|
||||||
|
func (d *Downloader) shouldDownload(destination string) bool {
|
||||||
|
_, err := d.fs.Stat(destination)
|
||||||
|
return errors.Is(err, fs.ErrNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadWithProgress downloads the raw image from source to the destination.
|
||||||
|
func (d *Downloader) downloadWithProgress(ctx context.Context, errWriter io.Writer, showBar bool, source, destination string) error {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, source, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.httpc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("doing request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("downloading from %q: %s", source, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := d.fs.OpenFile(destination, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var bar io.WriteCloser
|
||||||
|
if showBar {
|
||||||
|
bar = prepareBar(errWriter, resp.ContentLength)
|
||||||
|
} else {
|
||||||
|
bar = &nopWriteCloser{}
|
||||||
|
}
|
||||||
|
defer bar.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareBar(writer io.Writer, total int64) io.WriteCloser {
|
||||||
|
return progressbar.NewOptions64(
|
||||||
|
total,
|
||||||
|
progressbar.OptionSetWriter(writer),
|
||||||
|
progressbar.OptionShowBytes(true),
|
||||||
|
progressbar.OptionSetPredictTime(true),
|
||||||
|
progressbar.OptionFullWidth(),
|
||||||
|
progressbar.OptionSetTheme(progressbar.Theme{
|
||||||
|
Saucer: "=",
|
||||||
|
SaucerHead: ">",
|
||||||
|
SaucerPadding: " ",
|
||||||
|
BarStart: "[",
|
||||||
|
BarEnd: "]",
|
||||||
|
}),
|
||||||
|
progressbar.OptionClearOnFinish(),
|
||||||
|
progressbar.OptionOnCompletion(func() { fmt.Fprintf(writer, "Done.\n\n") }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopWriteCloser struct{}
|
||||||
|
|
||||||
|
func (*nopWriteCloser) Write(p []byte) (int, error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*nopWriteCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
204
internal/image/raw_test.go
Normal file
204
internal/image/raw_test.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldDownload(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
partfile, destination string
|
||||||
|
wantDownload bool
|
||||||
|
}{
|
||||||
|
"no files exist yet": {
|
||||||
|
wantDownload: true,
|
||||||
|
},
|
||||||
|
"partial download": {
|
||||||
|
partfile: "some data",
|
||||||
|
wantDownload: true,
|
||||||
|
},
|
||||||
|
"download succeeded": {
|
||||||
|
destination: "all of the data",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
downloader := &Downloader{
|
||||||
|
fs: newDownloaderStubFs(t, "someVersion", tc.partfile, tc.destination),
|
||||||
|
}
|
||||||
|
gotDownload := downloader.shouldDownload("someVersion.raw")
|
||||||
|
assert.Equal(tc.wantDownload, gotDownload)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadWithProgress(t *testing.T) {
|
||||||
|
rawImage := "raw image"
|
||||||
|
client := newTestClient(func(req *http.Request) *http.Response {
|
||||||
|
if req.URL.String() == "https://cdn.example.com/image.raw" {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString(rawImage)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("Not found.")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
source string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"correct file requested": {
|
||||||
|
source: "https://cdn.example.com/image.raw",
|
||||||
|
},
|
||||||
|
"incorrect file requested": {
|
||||||
|
source: "https://cdn.example.com/incorrect.raw",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid scheme": {
|
||||||
|
source: "xyz://",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
fs := newDownloaderStubFs(t, "someVersion", "", "")
|
||||||
|
downloader := &Downloader{
|
||||||
|
httpc: client,
|
||||||
|
fs: fs,
|
||||||
|
}
|
||||||
|
var outBuffer bytes.Buffer
|
||||||
|
err := downloader.downloadWithProgress(context.Background(), &outBuffer, false, tc.source, "someVersion.raw")
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
out, err := fs.ReadFile("someVersion.raw")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(rawImage, string(out))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownload(t *testing.T) {
|
||||||
|
rawImage := "raw image"
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
wantDestination := path.Join(cwd, "someVersion.raw")
|
||||||
|
client := newTestClient(func(req *http.Request) *http.Response {
|
||||||
|
if req.URL.String() == "https://cdn.example.com/image.raw" {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString(rawImage)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("Not found.")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
source string
|
||||||
|
destination string
|
||||||
|
overrideFile string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"correct file requested": {
|
||||||
|
source: "https://cdn.example.com/image.raw",
|
||||||
|
},
|
||||||
|
"file url": {
|
||||||
|
source: "file:///override.raw",
|
||||||
|
overrideFile: "override image",
|
||||||
|
},
|
||||||
|
"file exists": {
|
||||||
|
source: "https://cdn.example.com/image.raw",
|
||||||
|
destination: "already exists",
|
||||||
|
},
|
||||||
|
"incorrect file requested": {
|
||||||
|
source: "https://cdn.example.com/incorrect.raw",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid scheme": {
|
||||||
|
source: "xyz://",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid URL": {
|
||||||
|
source: "\x00",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
fs := newDownloaderStubFs(t, cwd+"/someVersion", "", tc.destination)
|
||||||
|
if tc.overrideFile != "" {
|
||||||
|
must(t, fs.WriteFile("/override.raw", []byte(tc.overrideFile), os.ModePerm))
|
||||||
|
}
|
||||||
|
downloader := &Downloader{
|
||||||
|
httpc: client,
|
||||||
|
fs: fs,
|
||||||
|
}
|
||||||
|
var outBuffer bytes.Buffer
|
||||||
|
gotDestination, err := downloader.Download(context.Background(), &outBuffer, false, tc.source, "someVersion")
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
if tc.overrideFile == "" {
|
||||||
|
assert.Equal(wantDestination, gotDestination)
|
||||||
|
} else {
|
||||||
|
assert.Equal("/override.raw", gotDestination)
|
||||||
|
}
|
||||||
|
out, err := fs.ReadFile(gotDestination)
|
||||||
|
assert.NoError(err)
|
||||||
|
switch {
|
||||||
|
case tc.overrideFile != "":
|
||||||
|
assert.Equal(tc.overrideFile, string(out))
|
||||||
|
case tc.destination != "":
|
||||||
|
assert.Equal(tc.destination, string(out))
|
||||||
|
default:
|
||||||
|
assert.Equal(rawImage, string(out))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDownloaderStubFs(t *testing.T, version, partfile, destination string) *afero.Afero {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
if partfile != "" {
|
||||||
|
must(t, afero.WriteFile(fs, version+".raw.part", []byte(partfile), os.ModePerm))
|
||||||
|
}
|
||||||
|
if destination != "" {
|
||||||
|
must(t, afero.WriteFile(fs, version+".raw", []byte(destination), os.ModePerm))
|
||||||
|
}
|
||||||
|
return &afero.Afero{Fs: fs}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user