mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-12-11 06:02:41 -05:00
cli: add basic support for constellation create on OpenStack (#1283)
* image: support OpenStack image build / upload * cli: add OpenStack terraform template * config: add OpenStack as CSP * versionsapi: add OpenStack as CSP * cli: add OpenStack as provider for `config generate` and `create` * disk-mapper: add basic support for boot on OpenStack * debugd: add placeholder for OpenStack * image: fix config file sourcing for image upload
This commit is contained in:
parent
b013a7ab32
commit
b79f7d0c8c
37 changed files with 954 additions and 79 deletions
|
|
@ -25,6 +25,8 @@ const (
|
|||
Azure
|
||||
// GCP is Google Compute Platform.
|
||||
GCP
|
||||
// OpenStack is an open standard cloud computing platform.
|
||||
OpenStack
|
||||
// QEMU for a local emulated installation.
|
||||
QEMU
|
||||
)
|
||||
|
|
@ -69,6 +71,8 @@ func FromString(s string) Provider {
|
|||
return Azure
|
||||
case "gcp":
|
||||
return GCP
|
||||
case "openstack":
|
||||
return OpenStack
|
||||
case "qemu":
|
||||
return QEMU
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ func TestMarshalJSON(t *testing.T) {
|
|||
input: GCP,
|
||||
want: []byte("\"GCP\""),
|
||||
},
|
||||
"openstack": {
|
||||
input: OpenStack,
|
||||
want: []byte("\"OpenStack\""),
|
||||
},
|
||||
"qemu": {
|
||||
input: QEMU,
|
||||
want: []byte("\"QEMU\""),
|
||||
|
|
@ -79,6 +83,10 @@ func TestUnmarshalJSON(t *testing.T) {
|
|||
input: []byte("\"gcp\""),
|
||||
want: GCP,
|
||||
},
|
||||
"openstack": {
|
||||
input: []byte("\"openstack\""),
|
||||
want: OpenStack,
|
||||
},
|
||||
"qemu": {
|
||||
input: []byte("\"qemu\""),
|
||||
want: QEMU,
|
||||
|
|
@ -123,6 +131,10 @@ func TestMarshalYAML(t *testing.T) {
|
|||
input: GCP,
|
||||
want: []byte("GCP\n"),
|
||||
},
|
||||
"openstack": {
|
||||
input: OpenStack,
|
||||
want: []byte("OpenStack\n"),
|
||||
},
|
||||
"qemu": {
|
||||
input: QEMU,
|
||||
want: []byte("QEMU\n"),
|
||||
|
|
@ -167,6 +179,10 @@ func TestUnmarshalYAML(t *testing.T) {
|
|||
input: []byte("gcp\n"),
|
||||
want: GCP,
|
||||
},
|
||||
"openstack": {
|
||||
input: []byte("openstack\n"),
|
||||
want: OpenStack,
|
||||
},
|
||||
"qemu": {
|
||||
input: []byte("qemu\n"),
|
||||
want: QEMU,
|
||||
|
|
@ -215,6 +231,10 @@ func TestFromString(t *testing.T) {
|
|||
input: "gcp",
|
||||
want: GCP,
|
||||
},
|
||||
"openstack": {
|
||||
input: "openstack",
|
||||
want: OpenStack,
|
||||
},
|
||||
"qemu": {
|
||||
input: "qemu",
|
||||
want: QEMU,
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ func _() {
|
|||
_ = x[AWS-1]
|
||||
_ = x[Azure-2]
|
||||
_ = x[GCP-3]
|
||||
_ = x[QEMU-4]
|
||||
_ = x[OpenStack-4]
|
||||
_ = x[QEMU-5]
|
||||
}
|
||||
|
||||
const _Provider_name = "UnknownAWSAzureGCPQEMU"
|
||||
const _Provider_name = "UnknownAWSAzureGCPOpenStackQEMU"
|
||||
|
||||
var _Provider_index = [...]uint8{0, 7, 10, 15, 18, 22}
|
||||
var _Provider_index = [...]uint8{0, 7, 10, 15, 18, 27, 31}
|
||||
|
||||
func (i Provider) String() string {
|
||||
if i >= Provider(len(_Provider_index)-1) {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ type ProviderConfig struct {
|
|||
// Configuration for Google Cloud as provider.
|
||||
GCP *GCPConfig `yaml:"gcp,omitempty" validate:"omitempty,dive"`
|
||||
// description: |
|
||||
// Configuration for OpenStack as provider.
|
||||
OpenStack *OpenStackConfig `yaml:"openstack,omitempty" validate:"omitempty,dive"`
|
||||
// description: |
|
||||
// Configuration for QEMU as provider.
|
||||
QEMU *QEMUConfig `yaml:"qemu,omitempty" validate:"omitempty,dive"`
|
||||
}
|
||||
|
|
@ -220,6 +223,25 @@ type GCPConfig struct {
|
|||
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
|
||||
}
|
||||
|
||||
// OpenStackConfig holds config information for OpenStack based Constellation deployments.
|
||||
type OpenStackConfig struct {
|
||||
// description: |
|
||||
// OpenStack cloud name to select from "clouds.yaml". Only required if config file for OpenStack is used. Fallback authentication uses environment variables. For details see: https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html.
|
||||
Cloud string `yaml:"cloud"`
|
||||
// description: |
|
||||
// Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html
|
||||
AvailabilityZone string `yaml:"availabilityZone" validate:"required"`
|
||||
// description: |
|
||||
// Flavor ID (machine type) to use for the VMs. For details see: https://docs.openstack.org/nova/latest/admin/flavors.html
|
||||
FlavorID string `yaml:"flavorID" validate:"required"`
|
||||
// description: |
|
||||
// Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html
|
||||
FloatingIPPoolID string `yaml:"floatingIPPoolID" validate:"required"`
|
||||
// description: |
|
||||
// If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack.
|
||||
DirectDownload *bool `yaml:"directDownload" validate:"required"`
|
||||
}
|
||||
|
||||
// QEMUConfig holds config information for QEMU based Constellation deployments.
|
||||
type QEMUConfig struct {
|
||||
// description: |
|
||||
|
|
@ -295,6 +317,9 @@ func Default() *Config {
|
|||
DeployCSIDriver: func() *bool { b := true; return &b }(),
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.GCP),
|
||||
},
|
||||
OpenStack: &OpenStackConfig{
|
||||
DirectDownload: func() *bool { b := true; return &b }(),
|
||||
},
|
||||
QEMU: &QEMUConfig{
|
||||
ImageFormat: "raw",
|
||||
VCPUs: 2,
|
||||
|
|
@ -393,6 +418,8 @@ func (c *Config) RemoveProviderExcept(provider cloudprovider.Provider) {
|
|||
c.Provider.Azure = currentProviderConfigs.Azure
|
||||
case cloudprovider.GCP:
|
||||
c.Provider.GCP = currentProviderConfigs.GCP
|
||||
case cloudprovider.OpenStack:
|
||||
c.Provider.OpenStack = currentProviderConfigs.OpenStack
|
||||
case cloudprovider.QEMU:
|
||||
c.Provider.QEMU = currentProviderConfigs.QEMU
|
||||
default:
|
||||
|
|
@ -429,6 +456,9 @@ func (c *Config) GetProvider() cloudprovider.Provider {
|
|||
if c.Provider.GCP != nil {
|
||||
return cloudprovider.GCP
|
||||
}
|
||||
if c.Provider.OpenStack != nil {
|
||||
return cloudprovider.OpenStack
|
||||
}
|
||||
if c.Provider.QEMU != nil {
|
||||
return cloudprovider.QEMU
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ConfigDoc encoder.Doc
|
||||
UpgradeConfigDoc encoder.Doc
|
||||
ProviderConfigDoc encoder.Doc
|
||||
AWSConfigDoc encoder.Doc
|
||||
AzureConfigDoc encoder.Doc
|
||||
GCPConfigDoc encoder.Doc
|
||||
QEMUConfigDoc encoder.Doc
|
||||
ConfigDoc encoder.Doc
|
||||
UpgradeConfigDoc encoder.Doc
|
||||
ProviderConfigDoc encoder.Doc
|
||||
AWSConfigDoc encoder.Doc
|
||||
AzureConfigDoc encoder.Doc
|
||||
GCPConfigDoc encoder.Doc
|
||||
OpenStackConfigDoc encoder.Doc
|
||||
QEMUConfigDoc encoder.Doc
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -110,7 +111,7 @@ func init() {
|
|||
FieldName: "provider",
|
||||
},
|
||||
}
|
||||
ProviderConfigDoc.Fields = make([]encoder.Doc, 4)
|
||||
ProviderConfigDoc.Fields = make([]encoder.Doc, 5)
|
||||
ProviderConfigDoc.Fields[0].Name = "aws"
|
||||
ProviderConfigDoc.Fields[0].Type = "AWSConfig"
|
||||
ProviderConfigDoc.Fields[0].Note = ""
|
||||
|
|
@ -126,11 +127,16 @@ func init() {
|
|||
ProviderConfigDoc.Fields[2].Note = ""
|
||||
ProviderConfigDoc.Fields[2].Description = "Configuration for Google Cloud as provider."
|
||||
ProviderConfigDoc.Fields[2].Comments[encoder.LineComment] = "Configuration for Google Cloud as provider."
|
||||
ProviderConfigDoc.Fields[3].Name = "qemu"
|
||||
ProviderConfigDoc.Fields[3].Type = "QEMUConfig"
|
||||
ProviderConfigDoc.Fields[3].Name = "openstack"
|
||||
ProviderConfigDoc.Fields[3].Type = "OpenStackConfig"
|
||||
ProviderConfigDoc.Fields[3].Note = ""
|
||||
ProviderConfigDoc.Fields[3].Description = "Configuration for QEMU as provider."
|
||||
ProviderConfigDoc.Fields[3].Comments[encoder.LineComment] = "Configuration for QEMU as provider."
|
||||
ProviderConfigDoc.Fields[3].Description = "Configuration for OpenStack as provider."
|
||||
ProviderConfigDoc.Fields[3].Comments[encoder.LineComment] = "Configuration for OpenStack as provider."
|
||||
ProviderConfigDoc.Fields[4].Name = "qemu"
|
||||
ProviderConfigDoc.Fields[4].Type = "QEMUConfig"
|
||||
ProviderConfigDoc.Fields[4].Note = ""
|
||||
ProviderConfigDoc.Fields[4].Description = "Configuration for QEMU as provider."
|
||||
ProviderConfigDoc.Fields[4].Comments[encoder.LineComment] = "Configuration for QEMU as provider."
|
||||
|
||||
AWSConfigDoc.Type = "AWSConfig"
|
||||
AWSConfigDoc.Comments[encoder.LineComment] = "AWSConfig are AWS specific configuration values used by the CLI."
|
||||
|
|
@ -315,6 +321,42 @@ func init() {
|
|||
GCPConfigDoc.Fields[7].Description = "Expected confidential VM measurements."
|
||||
GCPConfigDoc.Fields[7].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
||||
|
||||
OpenStackConfigDoc.Type = "OpenStackConfig"
|
||||
OpenStackConfigDoc.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments."
|
||||
OpenStackConfigDoc.Description = "OpenStackConfig holds config information for OpenStack based Constellation deployments."
|
||||
OpenStackConfigDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "ProviderConfig",
|
||||
FieldName: "openstack",
|
||||
},
|
||||
}
|
||||
OpenStackConfigDoc.Fields = make([]encoder.Doc, 5)
|
||||
OpenStackConfigDoc.Fields[0].Name = "cloud"
|
||||
OpenStackConfigDoc.Fields[0].Type = "string"
|
||||
OpenStackConfigDoc.Fields[0].Note = ""
|
||||
OpenStackConfigDoc.Fields[0].Description = "OpenStack cloud name to select from \"clouds.yaml\". Only required if config file for OpenStack is used. Fallback authentication uses environment variables. For details see: https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html."
|
||||
OpenStackConfigDoc.Fields[0].Comments[encoder.LineComment] = "OpenStack cloud name to select from \"clouds.yaml\". Only required if config file for OpenStack is used. Fallback authentication uses environment variables. For details see: https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html."
|
||||
OpenStackConfigDoc.Fields[1].Name = "availabilityZone"
|
||||
OpenStackConfigDoc.Fields[1].Type = "string"
|
||||
OpenStackConfigDoc.Fields[1].Note = ""
|
||||
OpenStackConfigDoc.Fields[1].Description = "Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html"
|
||||
OpenStackConfigDoc.Fields[1].Comments[encoder.LineComment] = "Availability zone to place the VMs in. For details see: https://docs.openstack.org/nova/latest/admin/availability-zones.html"
|
||||
OpenStackConfigDoc.Fields[2].Name = "flavorID"
|
||||
OpenStackConfigDoc.Fields[2].Type = "string"
|
||||
OpenStackConfigDoc.Fields[2].Note = ""
|
||||
OpenStackConfigDoc.Fields[2].Description = "Flavor ID (machine type) to use for the VMs. For details see: https://docs.openstack.org/nova/latest/admin/flavors.html"
|
||||
OpenStackConfigDoc.Fields[2].Comments[encoder.LineComment] = "Flavor ID (machine type) to use for the VMs. For details see: https://docs.openstack.org/nova/latest/admin/flavors.html"
|
||||
OpenStackConfigDoc.Fields[3].Name = "floatingIPPoolID"
|
||||
OpenStackConfigDoc.Fields[3].Type = "string"
|
||||
OpenStackConfigDoc.Fields[3].Note = ""
|
||||
OpenStackConfigDoc.Fields[3].Description = "Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html"
|
||||
OpenStackConfigDoc.Fields[3].Comments[encoder.LineComment] = "Floating IP pool to use for the VMs. For details see: https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html"
|
||||
OpenStackConfigDoc.Fields[4].Name = "directDownload"
|
||||
OpenStackConfigDoc.Fields[4].Type = "bool"
|
||||
OpenStackConfigDoc.Fields[4].Note = ""
|
||||
OpenStackConfigDoc.Fields[4].Description = "If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack."
|
||||
OpenStackConfigDoc.Fields[4].Comments[encoder.LineComment] = "If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack."
|
||||
|
||||
QEMUConfigDoc.Type = "QEMUConfig"
|
||||
QEMUConfigDoc.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments."
|
||||
QEMUConfigDoc.Description = "QEMUConfig holds config information for QEMU based Constellation deployments."
|
||||
|
|
@ -396,6 +438,10 @@ func (_ GCPConfig) Doc() *encoder.Doc {
|
|||
return &GCPConfigDoc
|
||||
}
|
||||
|
||||
func (_ OpenStackConfig) Doc() *encoder.Doc {
|
||||
return &OpenStackConfigDoc
|
||||
}
|
||||
|
||||
func (_ QEMUConfig) Doc() *encoder.Doc {
|
||||
return &QEMUConfigDoc
|
||||
}
|
||||
|
|
@ -412,6 +458,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
|
|||
&AWSConfigDoc,
|
||||
&AzureConfigDoc,
|
||||
&GCPConfigDoc,
|
||||
&OpenStackConfigDoc,
|
||||
&QEMUConfigDoc,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
const defaultErrCount = 21 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
|
||||
const defaultErrCount = 24 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
|
||||
const azErrCount = 9
|
||||
const gcpErrCount = 6
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,9 @@ func validateProvider(sl validator.StructLevel) {
|
|||
if provider.GCP != nil {
|
||||
providerCount++
|
||||
}
|
||||
if provider.OpenStack != nil {
|
||||
providerCount++
|
||||
}
|
||||
if provider.QEMU != nil {
|
||||
providerCount++
|
||||
}
|
||||
|
|
@ -163,7 +166,7 @@ func translateGCPInstanceTypeError(ut ut.Translator, fe validator.FieldError) st
|
|||
|
||||
// Validation translation functions for Provider errors.
|
||||
func registerNoProviderError(ut ut.Translator) error {
|
||||
return ut.Add("no_provider", "{0}: No provider has been defined (requires either Azure, GCP or QEMU)", true)
|
||||
return ut.Add("no_provider", "{0}: No provider has been defined (requires either Azure, GCP, OpenStack or QEMU)", true)
|
||||
}
|
||||
|
||||
func translateNoProviderError(ut ut.Translator, fe validator.FieldError) string {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ type ImageInfo struct {
|
|||
Azure map[string]string `json:"azure,omitempty"`
|
||||
// GCP is a map of image types to GCP image IDs.
|
||||
GCP map[string]string `json:"gcp,omitempty"`
|
||||
// OpenStack is a map of image types to OpenStack image IDs.
|
||||
OpenStack map[string]string `json:"openstack,omitempty"`
|
||||
// QEMU is a map of image types to QEMU image URLs.
|
||||
QEMU map[string]string `json:"qemu,omitempty"`
|
||||
}
|
||||
|
|
@ -78,6 +80,9 @@ func (i ImageInfo) ValidateRequest() error {
|
|||
if len(i.GCP) != 0 {
|
||||
retErr = errors.Join(retErr, errors.New("GCP map must be empty for request"))
|
||||
}
|
||||
if len(i.OpenStack) != 0 {
|
||||
retErr = errors.Join(retErr, errors.New("OpenStack map must be empty for request"))
|
||||
}
|
||||
if len(i.QEMU) != 0 {
|
||||
retErr = errors.Join(retErr, errors.New("QEMU map must be empty for request"))
|
||||
}
|
||||
|
|
@ -97,17 +102,14 @@ func (i ImageInfo) Validate() error {
|
|||
if !semver.IsValid(i.Version) {
|
||||
retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version))
|
||||
}
|
||||
if len(i.AWS) == 0 {
|
||||
retErr = errors.Join(retErr, errors.New("AWS map must not be empty"))
|
||||
}
|
||||
if len(i.Azure) == 0 {
|
||||
retErr = errors.Join(retErr, errors.New("Azure map must not be empty"))
|
||||
}
|
||||
if len(i.GCP) == 0 {
|
||||
retErr = errors.Join(retErr, errors.New("GCP map must not be empty"))
|
||||
}
|
||||
if len(i.QEMU) == 0 {
|
||||
retErr = errors.Join(retErr, errors.New("QEMU map must not be empty"))
|
||||
var providers int
|
||||
providers += len(i.AWS)
|
||||
providers += len(i.Azure)
|
||||
providers += len(i.GCP)
|
||||
providers += len(i.OpenStack)
|
||||
providers += len(i.QEMU)
|
||||
if providers == 0 {
|
||||
retErr = errors.Join(retErr, errors.New("one or more providers must be specified"))
|
||||
}
|
||||
|
||||
return retErr
|
||||
|
|
|
|||
|
|
@ -129,47 +129,11 @@ func TestImageInfoValidate(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid aws": {
|
||||
"no provider": {
|
||||
info: ImageInfo{
|
||||
Ref: "test-ref",
|
||||
Stream: "nightly",
|
||||
Version: "v1.0.0",
|
||||
GCP: map[string]string{"key": "value", "key2": "value2"},
|
||||
Azure: map[string]string{"key": "value", "key2": "value2"},
|
||||
QEMU: map[string]string{"key": "value", "key2": "value2"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid gcp": {
|
||||
info: ImageInfo{
|
||||
Ref: "test-ref",
|
||||
Stream: "nightly",
|
||||
Version: "v1.0.0",
|
||||
AWS: map[string]string{"key": "value", "key2": "value2"},
|
||||
Azure: map[string]string{"key": "value", "key2": "value2"},
|
||||
QEMU: map[string]string{"key": "value", "key2": "value2"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid azure": {
|
||||
info: ImageInfo{
|
||||
Ref: "test-ref",
|
||||
Stream: "nightly",
|
||||
Version: "v1.0.0",
|
||||
AWS: map[string]string{"key": "value", "key2": "value2"},
|
||||
GCP: map[string]string{"key": "value", "key2": "value2"},
|
||||
QEMU: map[string]string{"key": "value", "key2": "value2"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid qemu": {
|
||||
info: ImageInfo{
|
||||
Ref: "test-ref",
|
||||
Stream: "nightly",
|
||||
Version: "v1.0.0",
|
||||
AWS: map[string]string{"key": "value", "key2": "value2"},
|
||||
GCP: map[string]string{"key": "value", "key2": "value2"},
|
||||
Azure: map[string]string{"key": "value", "key2": "value2"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
|
|
@ -269,6 +233,15 @@ func TestImageInfoValidateRequest(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid openstack": {
|
||||
info: ImageInfo{
|
||||
Ref: "test-ref",
|
||||
Stream: "nightly",
|
||||
Version: "v1.0.0",
|
||||
OpenStack: map[string]string{"key": "value", "key2": "value2"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"multiple errors": {
|
||||
info: ImageInfo{
|
||||
Ref: "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue