Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/v1beta1/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
dst.Status.Bastion.NetworkInterfaceType = restored.Status.Bastion.NetworkInterfaceType
dst.Status.Bastion.CapacityReservationID = restored.Status.Bastion.CapacityReservationID
dst.Status.Bastion.MarketType = restored.Status.Bastion.MarketType
dst.Status.Bastion.HostAffinity = restored.Status.Bastion.HostAffinity
dst.Status.Bastion.HostID = restored.Status.Bastion.HostID
dst.Status.Bastion.HostResourceGroupArn = restored.Status.Bastion.HostResourceGroupArn
dst.Status.Bastion.CapacityReservationPreference = restored.Status.Bastion.CapacityReservationPreference
dst.Status.Bastion.CPUOptions = restored.Status.Bastion.CPUOptions
if restored.Status.Bastion.DynamicHostAllocation != nil {
dst.Status.Bastion.DynamicHostAllocation = restored.Status.Bastion.DynamicHostAllocation
}
}
dst.Spec.Partition = restored.Spec.Partition

Expand Down
4 changes: 4 additions & 0 deletions api/v1beta1/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides
dst.Spec.CapacityReservationID = restored.Spec.CapacityReservationID
dst.Spec.MarketType = restored.Spec.MarketType
dst.Spec.HostResourceGroupArn = restored.Spec.HostResourceGroupArn
dst.Spec.LicenseConfigurationArns = restored.Spec.LicenseConfigurationArns
dst.Spec.NetworkInterfaceType = restored.Spec.NetworkInterfaceType
if restored.Spec.ElasticIPPool != nil {
if dst.Spec.ElasticIPPool == nil {
Expand Down Expand Up @@ -107,6 +109,8 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides
dst.Spec.Template.Spec.CapacityReservationID = restored.Spec.Template.Spec.CapacityReservationID
dst.Spec.Template.Spec.MarketType = restored.Spec.Template.Spec.MarketType
dst.Spec.Template.Spec.HostResourceGroupArn = restored.Spec.Template.Spec.HostResourceGroupArn
dst.Spec.Template.Spec.LicenseConfigurationArns = restored.Spec.Template.Spec.LicenseConfigurationArns
dst.Spec.Template.Spec.NetworkInterfaceType = restored.Spec.Template.Spec.NetworkInterfaceType
if restored.Spec.Template.Spec.ElasticIPPool != nil {
if dst.Spec.Template.Spec.ElasticIPPool == nil {
Expand Down
9 changes: 9 additions & 0 deletions api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,28 @@ type AWSMachineSpec struct {
// If marketType is not specified and spotMarketOptions is provided, the marketType defaults to "Spot".
// +optional
MarketType MarketType `json:"marketType,omitempty"`

// HostResourceGroupArn specifies the Dedicated Host Resource Group ARN on which the instance must be started.
// This field is mutually exclusive with DynamicHostAllocation and HostID.
// Note: The instance's AMI licenses must match the licenses associated with the host resource group.
// If the host resource group has no associated licenses, ensure the AMI also has no special licensing requirements.
// +kubebuilder:validation:Pattern=`^arn:aws[a-z\-]*:resource-groups:[a-z0-9\-]+:[0-9]{12}:group/[a-zA-Z0-9\-_]+$`
// +optional
HostResourceGroupArn *string `json:"hostResourceGroupArn,omitempty"`

// LicenseConfigurationArns specifies the License Configuration ARNs to associate with the instance.
// This field is required when HostResourceGroupArn is specified to ensure proper license compliance.
// +kubebuilder:validation:MaxItems=10
// +optional
LicenseConfigurationArns []string `json:"licenseConfigurationArns,omitempty"`
}

// DynamicHostAllocationSpec defines the configuration for dynamic dedicated host allocation.
// This specification always allocates exactly one dedicated host per machine.
type DynamicHostAllocationSpec struct {
// Tags to apply to the allocated dedicated host.
// +optional
Tags map[string]string `json:"tags,omitempty"`
}

// CloudInit defines options related to the bootstrapping systems where
Expand Down
25 changes: 25 additions & 0 deletions api/v1beta2/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,31 @@ func (r *AWSMachine) validateAdditionalSecurityGroups() field.ErrorList {
return allErrs
}

func (r *AWSMachine) validateHostAllocation() field.ErrorList {
var allErrs field.ErrorList

// Check if multiple host allocation options are specified
hasHostResourceGroupArn := r.Spec.HostResourceGroupArn != nil && len(*r.Spec.HostResourceGroupArn) > 0

count := 0
if hasHostResourceGroupArn {
count++
}

if count > 1 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "hostID, hostResourceGroupArn, and dynamicHostAllocation are mutually exclusive"))
}

// Validate licenseConfigurationArns is required when hostResourceGroupArn is specified
if hasHostResourceGroupArn {
if len(r.Spec.LicenseConfigurationArns) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec", "licenseConfigurationArns"), "licenseConfigurationArns is required when hostResourceGroupArn is specified"))
}
}

return allErrs
}

func (r *AWSMachine) validateSSHKeyName() field.ErrorList {
return validateSSHKeyName(r.Spec.SSHKeyName)
}
112 changes: 112 additions & 0 deletions api/v1beta2/awsmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,118 @@ func TestAWSMachineCreate(t *testing.T) {
},
wantErr: true,
},
{
name: "hostID and dynamicHostAllocation are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: true,
},
{
name: "hostID alone is valid",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
},
},
wantErr: false,
},
{
name: "dynamicHostAllocation alone is valid",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: false,
},
{
name: "hostResourceGroupArn alone is valid",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
wantErr: false,
},
{
name: "hostID and hostResourceGroupArn are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
wantErr: true,
},
{
name: "hostResourceGroupArn and dynamicHostAllocation are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: true,
},
{
name: "all three host allocation options are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: true,
},
{
name: "hostResourceGroupArn without licenseConfigurationArns should fail",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
wantErr: true,
},
{
name: "hostResourceGroupArn with licenseConfigurationArns should succeed",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
LicenseConfigurationArns: []string{"arn:aws:license-manager:us-west-2:259732043995:license-configuration:lic-4acd3f7c117b9e314cce36e46084d071"},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
28 changes: 28 additions & 0 deletions api/v1beta2/awsmachinetemplate_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,34 @@ func (r *AWSMachineTemplate) validateIgnitionAndCloudInit() field.ErrorList {

return allErrs
}

func (r *AWSMachineTemplate) validateHostAllocation() field.ErrorList {
var allErrs field.ErrorList

spec := r.Spec.Template.Spec

// Check if multiple host allocation options are specified
hasHostResourceGroupArn := spec.HostResourceGroupArn != nil && len(*spec.HostResourceGroupArn) > 0

count := 0
if hasHostResourceGroupArn {
count++
}

if count > 1 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.template.spec"), "hostID, hostResourceGroupArn, and dynamicHostAllocation are mutually exclusive"))
}

// Validate licenseConfigurationArns is required when hostResourceGroupArn is specified
if hasHostResourceGroupArn {
if len(spec.LicenseConfigurationArns) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec", "template", "spec", "licenseConfigurationArns"), "licenseConfigurationArns is required when hostResourceGroupArn is specified"))
}
}

return allErrs
}

func (r *AWSMachineTemplate) validateSSHKeyName() field.ErrorList {
return validateSSHKeyName(r.Spec.Template.Spec.SSHKeyName)
}
Expand Down
102 changes: 102 additions & 0 deletions api/v1beta2/awsmachinetemplate_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,108 @@ func TestAWSMachineTemplateValidateCreate(t *testing.T) {
},
wantError: false,
},
{
name: "hostID and dynamicHostAllocation are mutually exclusive",
inputTemplate: &AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{},
Spec: AWSMachineTemplateSpec{
Template: AWSMachineTemplateResource{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
},
},
wantError: true,
},
{
name: "hostResourceGroupArn alone is valid",
inputTemplate: &AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{},
Spec: AWSMachineTemplateSpec{
Template: AWSMachineTemplateResource{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
},
},
wantError: false,
},
{
name: "hostID and hostResourceGroupArn are mutually exclusive",
inputTemplate: &AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{},
Spec: AWSMachineTemplateSpec{
Template: AWSMachineTemplateResource{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
},
},
wantError: true,
},
{
name: "hostResourceGroupArn and dynamicHostAllocation are mutually exclusive",
inputTemplate: &AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{},
Spec: AWSMachineTemplateSpec{
Template: AWSMachineTemplateResource{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
},
},
wantError: true,
},
{
name: "hostResourceGroupArn without licenseConfigurationArns should fail",
inputTemplate: &AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{},
Spec: AWSMachineTemplateSpec{
Template: AWSMachineTemplateResource{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
},
},
wantError: true,
},
{
name: "hostResourceGroupArn with licenseConfigurationArns should succeed",
inputTemplate: &AWSMachineTemplate{
ObjectMeta: metav1.ObjectMeta{},
Spec: AWSMachineTemplateSpec{
Template: AWSMachineTemplateResource{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
LicenseConfigurationArns: []string{"arn:aws:license-manager:us-west-2:259732043995:license-configuration:lic-4acd3f7c117b9e314cce36e46084d071"},
},
},
},
},
wantError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading
Loading