diff --git a/api/v1alpha1/instancegroup_types.go b/api/v1alpha1/instancegroup_types.go index bf03559d..69c4d5e6 100644 --- a/api/v1alpha1/instancegroup_types.go +++ b/api/v1alpha1/instancegroup_types.go @@ -187,14 +187,18 @@ type EKSConfiguration struct { type UserDataStage struct { Name string `json:"name,omitempty"` - Stage string `json:"stage,omitempty"` - Data string `json:"data,omitempty"` + Stage string `json:"stage"` + Data string `json:"data"` } type NodeVolume struct { - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Size int64 `json:"size,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Size int64 `json:"size"` + Iops int64 `json:"iops,omitempty"` + DeleteOnTermination *bool `json:"deleteOnTermination,omitempty"` + Encrypted *bool `json:"encrypted,omitempty"` + SnapshotID string `json:"snapshotId,omitempty"` } type EKSFargateSpec struct { ClusterName string `json:"clusterName"` @@ -313,6 +317,21 @@ func (c *EKSConfiguration) Validate() error { if common.StringEmpty(c.KeyPairName) { return errors.Errorf("validation failed, 'keyPair' is a required parameter") } + + for _, v := range c.Volumes { + if !common.ContainsEqualFold(awsprovider.AllowedVolumeTypes, v.Type) { + return errors.Errorf("validation failed, volume type '%v' is unsuppoeted", v.Type) + } + if v.SnapshotID != "" { + if v.Size > 0 { + return errors.Errorf("validation failed, 'volume.snapshotId' and 'volume.size' are mutually exclusive") + } + } + if v.Iops != 0 && v.Iops < 100 { + return errors.Errorf("validation failed, volume IOPS must be min 100") + } + } + if len(c.Volumes) == 0 { c.Volumes = []NodeVolume{ { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7cd70088..dd009eb0 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -76,7 +76,9 @@ func (in *EKSConfiguration) DeepCopyInto(out *EKSConfiguration) { if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes *out = make([]NodeVolume, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.Subnets != nil { in, out := &in.Subnets, &out.Subnets @@ -417,6 +419,16 @@ func (in *InstanceGroupStatus) DeepCopy() *InstanceGroupStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeVolume) DeepCopyInto(out *NodeVolume) { *out = *in + if in.DeleteOnTermination != nil { + in, out := &in.DeleteOnTermination, &out.DeleteOnTermination + *out = new(bool) + **out = **in + } + if in.Encrypted != nil { + in, out := &in.Encrypted, &out.Encrypted + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeVolume. diff --git a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml index 4fa6750e..f44888a3 100644 --- a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml +++ b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml @@ -158,18 +158,34 @@ spec: type: string stage: type: string + required: + - data + - stage type: object type: array volumes: items: properties: + deleteOnTermination: + type: boolean + encrypted: + type: boolean + iops: + format: int64 + type: integer name: type: string size: format: int64 type: integer + snapshotId: + type: string type: type: string + required: + - name + - size + - type type: object type: array type: object diff --git a/controllers/providers/aws/aws.go b/controllers/providers/aws/aws.go index acdb281b..e241fbfc 100644 --- a/controllers/providers/aws/aws.go +++ b/controllers/providers/aws/aws.go @@ -101,6 +101,8 @@ var ( "GroupTotalInstances", "GroupTotalCapacity", } + + AllowedVolumeTypes = []string{"gp2", "io1", "sc1", "st1"} ) const ( @@ -156,15 +158,32 @@ func (w *AwsWorker) InstanceProfileExist(name string) (*iam.InstanceProfile, boo return out.InstanceProfile, true } -func (w *AwsWorker) GetBasicBlockDevice(name, volType string, volSize int64) *autoscaling.BlockDeviceMapping { - return &autoscaling.BlockDeviceMapping{ +func (w *AwsWorker) GetBasicBlockDevice(name, volType, snapshot string, volSize, iops int64, delete, encrypt *bool) *autoscaling.BlockDeviceMapping { + device := &autoscaling.BlockDeviceMapping{ DeviceName: aws.String(name), Ebs: &autoscaling.Ebs{ - VolumeSize: aws.Int64(volSize), - VolumeType: aws.String(volType), - DeleteOnTermination: aws.Bool(true), + VolumeType: aws.String(volType), }, } + if delete != nil { + device.Ebs.DeleteOnTermination = delete + } else { + device.Ebs.DeleteOnTermination = aws.Bool(true) + } + if encrypt != nil { + device.Ebs.Encrypted = encrypt + } + if iops != 0 { + device.Ebs.Iops = aws.Int64(iops) + } + if volSize != 0 { + device.Ebs.VolumeSize = aws.Int64(volSize) + } + if !common.StringEmpty(snapshot) { + device.Ebs.SnapshotId = aws.String(snapshot) + } + + return device } func (w *AwsWorker) CreateLaunchConfig(input *autoscaling.CreateLaunchConfigurationInput) error { diff --git a/controllers/provisioners/eks/helpers.go b/controllers/provisioners/eks/helpers.go index 34a7150a..140c3841 100644 --- a/controllers/provisioners/eks/helpers.go +++ b/controllers/provisioners/eks/helpers.go @@ -237,7 +237,7 @@ func (ctx *EksInstanceGroupContext) GetBlockDeviceList() []*autoscaling.BlockDev customVolumes := configuration.GetVolumes() for _, v := range customVolumes { - devices = append(devices, ctx.AwsWorker.GetBasicBlockDevice(v.Name, v.Type, v.Size)) + devices = append(devices, ctx.AwsWorker.GetBasicBlockDevice(v.Name, v.Type, v.SnapshotID, v.Size, v.Iops, v.DeleteOnTermination, v.Encrypted)) } return devices diff --git a/controllers/provisioners/eks/update_test.go b/controllers/provisioners/eks/update_test.go index 7a0cdddb..45e2882a 100644 --- a/controllers/provisioners/eks/update_test.go +++ b/controllers/provisioners/eks/update_test.go @@ -325,7 +325,7 @@ func TestLaunchConfigurationDrifted(t *testing.T) { keyDrift.KeyName = aws.String("some-key") usrDrift.UserData = aws.String("some-userdata") devDrift.BlockDeviceMappings = []*autoscaling.BlockDeviceMapping{ - w.GetBasicBlockDevice("some-device", "some-type", 0), + w.GetBasicBlockDevice("some-device", "some-type", "", 32, 0, nil, nil), } tests := []struct { diff --git a/docs/EKS.md b/docs/EKS.md index 4ff20a28..01a2e737 100644 --- a/docs/EKS.md +++ b/docs/EKS.md @@ -98,8 +98,8 @@ spec: configuration: userData: - name: : name of the stage - stage: : represents the stage of the script, allowed values are PreBootstrap, PostBootstrap - data: : represents the script payload to inject + stage: : represents the stage of the script, allowed values are PreBootstrap, PostBootstrap (required) + data: : represents the script payload to inject (required) ``` ### NodeVolume @@ -112,9 +112,13 @@ spec: eks: configuration: volumes: - - name: : represents the device name, e.g. /dev/xvda - type: : represents the type of volume, must be one of supported types "standard", "io1", "gp2", "st1", "sc1". - size: : represents a volume size in gigabytes + - name: : represents the device name, e.g. /dev/xvda (required) + type: : represents the type of volume, must be one of supported types "standard", "io1", "gp2", "st1", "sc1" (required) + size: : represents a volume size in gigabytes, cannot be used with snapshotId + snapshotId : : represents a snapshot ID to use, cannot be used with size + iops: : represents number of IOPS to provision volume with (min 100) + deleteOnTermination : : delete the EBS volume when the instance is terminated (defaults to true) + encrypted: : encrypt the EBS volume with a KMS key ``` ### Taint