Skip to content

Commit 70a1098

Browse files
chuckhak8s-ci-robot
authored andcommitted
Add security groups to new instances (openshift#154)
* Fetch the IP with the deployer (needs fixing) * Apply a startup script to control plane nodes Signed-off-by: Chuck Ha <[email protected]>
1 parent e835162 commit 70a1098

File tree

7 files changed

+159
-69
lines changed

7 files changed

+159
-69
lines changed

Gopkg.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/aws/actuators/machine/actuator.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ func (a *Actuator) Create(cluster *clusterv1.Cluster, machine *clusterv1.Machine
9696

9797
status.InstanceID = &i.ID
9898
status.InstanceState = aws.String(string(i.State))
99-
// TODO: Set the machine.Status.NodeRef after the node has initialized
99+
100+
if machine.Annotations == nil {
101+
machine.Annotations = map[string]string{}
102+
}
103+
machine.Annotations["cluster-api-provider-aws"] = "true"
104+
100105
return a.updateStatus(machine, status)
101106
}
102107

@@ -151,12 +156,7 @@ func (a *Actuator) Update(cluster *clusterv1.Cluster, machine *clusterv1.Machine
151156
return errors.Wrap(err, "failed to get machine status")
152157
}
153158

154-
err = a.updateStatus(machine, status)
155-
if err != nil {
156-
return errors.Wrap(err, "failed to update machine status")
157-
}
158-
159-
return nil
159+
return a.updateStatus(machine, status)
160160
}
161161

162162
// Exists test for the existence of a machine and is invoked by the Machine Controller

cloud/aws/actuators/machine/actuator_test.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/aws/aws-sdk-go/aws"
2121
"github.com/aws/aws-sdk-go/service/ec2"
2222
"github.com/golang/mock/gomock"
23+
"k8s.io/apimachinery/pkg/apis/meta/v1"
2324
"k8s.io/apimachinery/pkg/runtime"
2425
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
2526
clientv1 "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1"
@@ -50,6 +51,10 @@ func TestCreate(t *testing.T) {
5051
// clusterapi calls
5152
mg.mi.EXPECT().
5253
UpdateStatus(&clusterv1.Machine{
54+
ObjectMeta: v1.ObjectMeta{
55+
Labels: map[string]string{"set": "node"},
56+
Annotations: map[string]string{"cluster-api-provider-aws": "true"},
57+
},
5358
Status: clusterv1.MachineStatus{
5459
ProviderStatus: &runtime.RawExtension{
5560
Raw: []byte(`{"kind":"AWSMachineProviderStatus","apiVersion":"awsproviderconfig/v1alpha1","instanceID":"1234","instanceState":"running"}
@@ -70,11 +75,12 @@ func TestCreate(t *testing.T) {
7075
// ec2 calls
7176
me.EXPECT().
7277
RunInstances(&ec2.RunInstancesInput{
73-
ImageId: aws.String("aws-instance-id"),
74-
InstanceType: aws.String(""),
75-
MaxCount: aws.Int64(1),
76-
MinCount: aws.Int64(1),
77-
SubnetId: aws.String("subnet-1"),
78+
ImageId: aws.String("aws-instance-id"),
79+
InstanceType: aws.String(""),
80+
MaxCount: aws.Int64(1),
81+
MinCount: aws.Int64(1),
82+
SubnetId: aws.String("subnet-1"),
83+
SecurityGroupIds: aws.StringSlice([]string{"2"}),
7884
}).
7985
Return(&ec2.Reservation{
8086
Instances: []*ec2.Instance{
@@ -109,11 +115,16 @@ func TestCreate(t *testing.T) {
109115
if err := actuator.Create(&clusterv1.Cluster{
110116
Status: clusterv1.ClusterStatus{
111117
ProviderStatus: &runtime.RawExtension{
112-
Raw: []byte(`{"kind":"AWSClusterProviderStatus","apiVersion":"awsproviderconfig/v1alpha1","network":{"subnets":[{"id": "subnet-1", "public": false}]}}
118+
Raw: []byte(`{"kind":"AWSClusterProviderStatus","apiVersion":"awsproviderconfig/v1alpha1","network":{"subnets":[{"id": "subnet-1", "public": false}],"securityGroups":{"node":{"id":"2"}}}}
113119
`),
114120
},
115121
},
116122
}, &clusterv1.Machine{
123+
ObjectMeta: v1.ObjectMeta{
124+
Labels: map[string]string{
125+
"set": "node",
126+
},
127+
},
117128
Spec: clusterv1.MachineSpec{
118129
ProviderConfig: clusterv1.ProviderConfig{
119130
Value: &runtime.RawExtension{

cloud/aws/deployer/deployer.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ limitations under the License.
1717
package deployer
1818

1919
import (
20-
"errors"
20+
"fmt"
2121

22+
"github.com/aws/aws-sdk-go/aws/session"
23+
"github.com/aws/aws-sdk-go/service/ec2"
2224
clustercommon "sigs.k8s.io/cluster-api/pkg/apis/cluster/common"
2325
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
26+
27+
"github.com/pkg/errors"
28+
"sigs.k8s.io/cluster-api-provider-aws/cloud/aws/providerconfig/v1alpha1"
2429
)
2530

2631
// ProviderName is the name of the cloud provider
@@ -35,7 +40,34 @@ type AWSDeployer struct{}
3540

3641
// GetIP returns the IP of a machine, but this is going away.
3742
func (*AWSDeployer) GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) {
38-
return "", errors.New("Not implemented")
43+
codec, err := v1alpha1.NewCodec()
44+
if err != nil {
45+
return "", errors.Wrap(err, "Failed to create codec in deployer")
46+
}
47+
48+
status := &v1alpha1.AWSMachineProviderStatus{}
49+
if err := codec.DecodeProviderStatus(machine.Status.ProviderStatus, status); err != nil {
50+
return "", errors.Wrap(err, "failed to decode machine provider status in deployer")
51+
}
52+
53+
// will have credentials in environment
54+
sess := session.Must(session.NewSession())
55+
ec2client := ec2.New(sess)
56+
57+
dio, err := ec2client.DescribeInstances(&ec2.DescribeInstancesInput{
58+
InstanceIds: []*string{status.InstanceID},
59+
})
60+
if err != nil {
61+
return "", errors.Wrap(err, "failed to get instance description in deployer")
62+
}
63+
64+
if len(dio.Reservations) == 0 {
65+
return "", errors.New(fmt.Sprintf("no reservartions found for instance id %v", *status.InstanceID))
66+
}
67+
if len(dio.Reservations[0].Instances) == 0 {
68+
return "", errors.New(fmt.Sprintf("instance was not found in a reserver for instance id %v", *status.InstanceID))
69+
}
70+
return *dio.Reservations[0].Instances[0].PublicIpAddress, nil
3971
}
4072

4173
// GetKubeConfig returns the kubeconfig after the bootstrap process is complete.

cloud/aws/providerconfig/v1alpha1/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,10 @@ type Instance struct {
355355

356356
// Indicates whether the instance is optimized for Amazon EBS I/O.
357357
EBSOptimized *bool `json:"ebsOptimized"`
358+
359+
// UserData defines the Base64-encoded user data to make available to the instance.
360+
UserData *string
361+
362+
// SecurityGroupIDs are one or more security group IDs this instance belongs to.
363+
SecurityGroupIDs []*string
358364
}

cloud/aws/services/ec2/instances.go

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package ec2
1515

1616
import (
17+
"encoding/base64"
1718
"fmt"
1819

1920
"github.com/aws/aws-sdk-go/aws"
@@ -49,7 +50,8 @@ func (s *Service) InstanceIfExists(instanceID *string) (*v1alpha1.Instance, erro
4950
func (s *Service) CreateInstance(machine *clusterv1.Machine, config *v1alpha1.AWSMachineProviderConfig, clusterStatus *v1alpha1.AWSClusterProviderStatus) (*v1alpha1.Instance, error) {
5051

5152
input := &v1alpha1.Instance{
52-
Type: config.InstanceType,
53+
Type: config.InstanceType,
54+
SecurityGroupIDs: []*string{},
5355
}
5456

5557
// Pick image from the machine configuration, or use a default one.
@@ -70,6 +72,18 @@ func (s *Service) CreateInstance(machine *clusterv1.Machine, config *v1alpha1.AW
7072
input.SubnetID = sns[0].ID
7173
}
7274

75+
fmt.Println(machine.ObjectMeta)
76+
77+
// apply values based on the role of the machine
78+
if machine.ObjectMeta.Labels["set"] == "controlplane" {
79+
input.UserData = aws.String(initControlPlaneScript())
80+
input.SecurityGroupIDs = append(input.SecurityGroupIDs, aws.String(clusterStatus.Network.SecurityGroups[v1alpha1.SecurityGroupControlPlane].ID))
81+
}
82+
83+
if machine.ObjectMeta.Labels["set"] == "node" {
84+
input.SecurityGroupIDs = append(input.SecurityGroupIDs, aws.String(clusterStatus.Network.SecurityGroups[v1alpha1.SecurityGroupNode].ID))
85+
}
86+
7387
// Pick SSH key, if any.
7488
if config.KeyName != "" {
7589
input.KeyName = aws.String(config.KeyName)
@@ -123,13 +137,15 @@ func (s *Service) CreateOrGetMachine(machine *clusterv1.Machine, status *v1alpha
123137

124138
func (s *Service) runInstance(i *v1alpha1.Instance) (*v1alpha1.Instance, error) {
125139
input := &ec2.RunInstancesInput{
126-
InstanceType: aws.String(i.Type),
127-
SubnetId: aws.String(i.SubnetID),
128-
ImageId: aws.String(i.ImageID),
129-
KeyName: i.KeyName,
130-
EbsOptimized: i.EBSOptimized,
131-
MaxCount: aws.Int64(1),
132-
MinCount: aws.Int64(1),
140+
InstanceType: aws.String(i.Type),
141+
SubnetId: aws.String(i.SubnetID),
142+
ImageId: aws.String(i.ImageID),
143+
KeyName: i.KeyName,
144+
EbsOptimized: i.EBSOptimized,
145+
MaxCount: aws.Int64(1),
146+
MinCount: aws.Int64(1),
147+
UserData: i.UserData,
148+
SecurityGroupIds: i.SecurityGroupIDs,
133149
}
134150

135151
if i.IAMProfile != nil {
@@ -151,6 +167,10 @@ func (s *Service) runInstance(i *v1alpha1.Instance) (*v1alpha1.Instance, error)
151167
}
152168

153169
func fromSDKTypeToInstance(v *ec2.Instance) *v1alpha1.Instance {
170+
securityGroupIDs := make([]*string, len(v.SecurityGroups))
171+
for i, sg := range v.SecurityGroups {
172+
securityGroupIDs[i] = sg.GroupId
173+
}
154174
i := &v1alpha1.Instance{
155175
ID: *v.InstanceId,
156176
State: v1alpha1.InstanceState(*v.State.Name),
@@ -162,6 +182,8 @@ func fromSDKTypeToInstance(v *ec2.Instance) *v1alpha1.Instance {
162182
PublicIP: v.PublicIpAddress,
163183
ENASupport: v.EnaSupport,
164184
EBSOptimized: v.EbsOptimized,
185+
// UserData is not defined on an instance
186+
SecurityGroupIDs: securityGroupIDs,
165187
}
166188

167189
if v.IamInstanceProfile != nil && v.IamInstanceProfile.Arn != nil {
@@ -172,3 +194,24 @@ func fromSDKTypeToInstance(v *ec2.Instance) *v1alpha1.Instance {
172194

173195
return i
174196
}
197+
198+
// initControlPlaneScrip returns the b64 encoded script to run on start up.
199+
func initControlPlaneScript() string {
200+
// The script must start with #!. If it goes on the next line Dedent will start the script with a \n.
201+
script := []byte(`#!/usr/bin/env bash
202+
203+
cat >/tmp/kubeadm.yaml <<EOF
204+
apiVersion: kubeadm.k8s.io/v1alpha3
205+
kind: InitConfiguration
206+
nodeRegistration:
207+
criSocket: /var/run/containerd/containerd.sock
208+
EOF
209+
210+
kubeadm init --config /tmp/kubeadm.yaml
211+
212+
# Installation from https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/calico
213+
kubectl --kubeconfig /etc/kubernetes/admin.conf apply -f https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
214+
kubectl --kubeconfig /etc/kubernetes/admin.conf apply -f https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
215+
`)
216+
return base64.StdEncoding.EncodeToString(script)
217+
}

cloud/aws/services/ec2/instances_test.go

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"github.com/aws/aws-sdk-go/service/ec2"
2121
"github.com/golang/mock/gomock"
2222
"github.com/pkg/errors"
23-
"k8s.io/apimachinery/pkg/runtime"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2424
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
2525

2626
"sigs.k8s.io/cluster-api-provider-aws/cloud/aws/providerconfig/v1alpha1"
@@ -184,48 +184,57 @@ func TestTerminateInstance(t *testing.T) {
184184
}
185185

186186
func TestCreateInstance(t *testing.T) {
187-
mockCtrl := gomock.NewController(t)
188-
defer mockCtrl.Finish()
189-
190187
testcases := []struct {
191-
name string
192-
machine clusterv1.Machine
193-
expect func(m *mock_ec2iface.MockEC2API)
194-
check func(instance *v1alpha1.Instance, err error)
188+
name string
189+
machine clusterv1.Machine
190+
machineConfig *v1alpha1.AWSMachineProviderConfig
191+
clusterStatus *v1alpha1.AWSClusterProviderStatus
192+
expect func(m *mock_ec2iface.MockEC2API)
193+
check func(instance *v1alpha1.Instance, err error)
195194
}{
196195
{
197196
name: "simple",
198197
machine: clusterv1.Machine{
199-
Spec: clusterv1.MachineSpec{
200-
ProviderConfig: clusterv1.ProviderConfig{
201-
Value: &runtime.RawExtension{
202-
Raw: []byte(`apiVersion: "cluster.k8s.io/v1alpha1"
203-
kind: Machine
204-
metadata:
205-
generateName: aws-controlplane-
206-
labels:
207-
set: controlplane
208-
spec:
209-
versions:
210-
kubelet: v1.11.2
211-
controlPlane: v1.11.2
212-
providerConfig:
213-
value:
214-
apiVersion: awsproviderconfig/v1alpha1
215-
kind: AWSMachineProviderConfig
216-
instanceType: m5.large`),
198+
ObjectMeta: metav1.ObjectMeta{
199+
Labels: map[string]string{"set": "node"},
200+
},
201+
},
202+
machineConfig: &v1alpha1.AWSMachineProviderConfig{
203+
AMI: v1alpha1.AWSResourceReference{
204+
ID: aws.String("abc"),
205+
},
206+
InstanceType: "m5.large",
207+
},
208+
clusterStatus: &v1alpha1.AWSClusterProviderStatus{
209+
Network: v1alpha1.Network{
210+
Subnets: v1alpha1.Subnets{
211+
&v1alpha1.Subnet{
212+
ID: "subnet-1",
213+
IsPublic: false,
214+
},
215+
&v1alpha1.Subnet{
216+
IsPublic: false,
217+
},
218+
},
219+
SecurityGroups: map[v1alpha1.SecurityGroupRole]*v1alpha1.SecurityGroup{
220+
v1alpha1.SecurityGroupControlPlane: &v1alpha1.SecurityGroup{
221+
ID: "1",
222+
},
223+
v1alpha1.SecurityGroupNode: &v1alpha1.SecurityGroup{
224+
ID: "2",
217225
},
218226
},
219227
},
220228
},
221229
expect: func(m *mock_ec2iface.MockEC2API) {
222230
m.EXPECT().
223231
RunInstances(&ec2.RunInstancesInput{
224-
ImageId: aws.String("abc"),
225-
InstanceType: aws.String("m5.large"),
226-
MaxCount: aws.Int64(1),
227-
MinCount: aws.Int64(1),
228-
SubnetId: aws.String("subnet-1"),
232+
ImageId: aws.String("abc"),
233+
InstanceType: aws.String("m5.large"),
234+
MaxCount: aws.Int64(1),
235+
MinCount: aws.Int64(1),
236+
SubnetId: aws.String("subnet-1"),
237+
SecurityGroupIds: aws.StringSlice([]string{"2"}),
229238
}).
230239
Return(&ec2.Reservation{
231240
Instances: []*ec2.Instance{
@@ -251,24 +260,12 @@ spec:
251260

252261
for _, tc := range testcases {
253262
t.Run(tc.name, func(t *testing.T) {
263+
mockCtrl := gomock.NewController(t)
264+
defer mockCtrl.Finish()
254265
ec2Mock := mock_ec2iface.NewMockEC2API(mockCtrl)
255266
tc.expect(ec2Mock)
256267
s := ec2svc.NewService(ec2Mock)
257-
instance, err := s.CreateInstance(&tc.machine, &v1alpha1.AWSMachineProviderConfig{
258-
AMI: v1alpha1.AWSResourceReference{
259-
ID: aws.String("abc"),
260-
},
261-
InstanceType: "m5.large",
262-
}, &v1alpha1.AWSClusterProviderStatus{
263-
Network: v1alpha1.Network{
264-
Subnets: v1alpha1.Subnets{
265-
&v1alpha1.Subnet{
266-
ID: "subnet-1",
267-
IsPublic: false,
268-
},
269-
},
270-
},
271-
})
268+
instance, err := s.CreateInstance(&tc.machine, tc.machineConfig, tc.clusterStatus)
272269
tc.check(instance, err)
273270
})
274271
}

0 commit comments

Comments
 (0)