Skip to content
Open
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
1 change: 1 addition & 0 deletions api/v1beta1/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
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 {
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 @@ -45,6 +45,8 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.CapacityReservationID = restored.Spec.CapacityReservationID
dst.Spec.MarketType = restored.Spec.MarketType
dst.Spec.HostID = restored.Spec.HostID
dst.Spec.HostResourceGroupArn = restored.Spec.HostResourceGroupArn
dst.Spec.LicenseConfigurationArns = restored.Spec.LicenseConfigurationArns
dst.Spec.HostAffinity = restored.Spec.HostAffinity
dst.Spec.CapacityReservationPreference = restored.Spec.CapacityReservationPreference
dst.Spec.NetworkInterfaceType = restored.Spec.NetworkInterfaceType
Expand Down Expand Up @@ -117,6 +119,8 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.CapacityReservationID = restored.Spec.Template.Spec.CapacityReservationID
dst.Spec.Template.Spec.MarketType = restored.Spec.Template.Spec.MarketType
dst.Spec.Template.Spec.HostID = restored.Spec.Template.Spec.HostID
dst.Spec.Template.Spec.HostResourceGroupArn = restored.Spec.Template.Spec.HostResourceGroupArn
dst.Spec.Template.Spec.LicenseConfigurationArns = restored.Spec.Template.Spec.LicenseConfigurationArns
dst.Spec.Template.Spec.HostAffinity = restored.Spec.Template.Spec.HostAffinity
dst.Spec.Template.Spec.CapacityReservationPreference = restored.Spec.Template.Spec.CapacityReservationPreference
dst.Spec.Template.Spec.NetworkInterfaceType = restored.Spec.Template.Spec.NetworkInterfaceType
Expand Down
4 changes: 4 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.

14 changes: 14 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ type AWSMachineSpec struct {
// +optional
HostID *string `json:"hostID,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"`

// HostAffinity specifies the dedicated host affinity setting for the instance.
// When HostAffinity is set to host, an instance started onto a specific host always restarts on the same host if stopped.
// When HostAffinity is set to default, and you stop and restart the instance, it can be restarted on any available host.
Expand Down
25 changes: 22 additions & 3 deletions api/v1beta2/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,31 @@ func (r *AWSMachine) validateAdditionalSecurityGroups() field.ErrorList {
func (r *AWSMachine) validateHostAllocation() field.ErrorList {
var allErrs field.ErrorList

// Check if both hostID and dynamicHostAllocation are specified
// Check if multiple host allocation options are specified
hasHostID := r.Spec.HostID != nil && len(*r.Spec.HostID) > 0
hasHostResourceGroupArn := r.Spec.HostResourceGroupArn != nil && len(*r.Spec.HostResourceGroupArn) > 0
hasDynamicHostAllocation := r.Spec.DynamicHostAllocation != nil

if hasHostID && hasDynamicHostAllocation {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.hostID"), "hostID and dynamicHostAllocation are mutually exclusive"), field.Forbidden(field.NewPath("spec.dynamicHostAllocation"), "hostID and dynamicHostAllocation are mutually exclusive"))
count := 0
if hasHostID {
count++
}
if hasHostResourceGroupArn {
count++
}
if hasDynamicHostAllocation {
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
Expand Down
73 changes: 73 additions & 0 deletions api/v1beta2/awsmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,79 @@ func TestAWSMachineCreate(t *testing.T) {
},
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
25 changes: 22 additions & 3 deletions api/v1beta2/awsmachinetemplate_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,31 @@ func (r *AWSMachineTemplate) validateHostAllocation() field.ErrorList {

spec := r.Spec.Template.Spec

// Check if both hostID and dynamicHostAllocation are specified
// Check if multiple host allocation options are specified
hasHostID := spec.HostID != nil && len(*spec.HostID) > 0
hasHostResourceGroupArn := spec.HostResourceGroupArn != nil && len(*spec.HostResourceGroupArn) > 0
hasDynamicHostAllocation := spec.DynamicHostAllocation != nil

if hasHostID && hasDynamicHostAllocation {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.template.spec.hostID"), "hostID and dynamicHostAllocation are mutually exclusive"), field.Forbidden(field.NewPath("spec.template.spec.dynamicHostAllocation"), "hostID and dynamicHostAllocation are mutually exclusive"))
count := 0
if hasHostID {
count++
}
if hasHostResourceGroupArn {
count++
}
if hasDynamicHostAllocation {
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
Expand Down
82 changes: 82 additions & 0 deletions api/v1beta2/awsmachinetemplate_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,88 @@ func TestAWSMachineTemplateValidateCreate(t *testing.T) {
},
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
10 changes: 10 additions & 0 deletions api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ type Instance struct {
// +optional
HostID *string `json:"hostID,omitempty"`

// HostResourceGroupArn specifies the Dedicated Host Resource Group ARN on which the instance should be started.
// Note: The instance's AMI licenses must match the licenses associated with the host resource group.
// +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.
// +optional
LicenseConfigurationArns []string `json:"licenseConfigurationArns,omitempty"`

// DynamicHostAllocation enables automatic allocation of dedicated hosts.
// This field is mutually exclusive with HostID.
// +optional
Expand Down
20 changes: 20 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

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

Loading