Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit a84e9a9

Browse files
kencochraneDavid Chung
authored andcommitted
Adding AWS ELB ingress implementation (#683)
Signed-off-by: Ken Cochrane <[email protected]>
1 parent 4082ccd commit a84e9a9

File tree

5 files changed

+322
-1
lines changed

5 files changed

+322
-1
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package aws
2+
3+
import (
4+
"github.com/aws/aws-sdk-go/aws/credentials"
5+
)
6+
7+
// Credential defines static authentication details (as opposed to environment-based) for the AWS API.
8+
type Credential struct {
9+
AccessKeyID string `yaml:"access_key_id" json:"access_key_id"`
10+
SecretAccessKey string `yaml:"secret_access_key" json:"secret_access_key"`
11+
SessionToken string `yaml:"session_token" json:"session_token"`
12+
}
13+
14+
// Retrieve implements the AWS credentials.Provider interface method
15+
func (a *Credential) Retrieve() (credentials.Value, error) {
16+
return credentials.Value{
17+
AccessKeyID: a.AccessKeyID,
18+
SecretAccessKey: a.SecretAccessKey,
19+
SessionToken: a.SessionToken,
20+
ProviderName: "Machete",
21+
}, nil
22+
}
23+
24+
// IsExpired implements the AWS credentials.Provider interface method. For static credentials this always returns false
25+
func (a *Credential) IsExpired() bool {
26+
return false
27+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
6+
log "github.com/Sirupsen/logrus"
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/aws/credentials"
9+
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
10+
"github.com/aws/aws-sdk-go/aws/ec2metadata"
11+
"github.com/aws/aws-sdk-go/aws/session"
12+
"github.com/aws/aws-sdk-go/service/elb"
13+
"github.com/aws/aws-sdk-go/service/elb/elbiface"
14+
"github.com/docker/infrakit/pkg/spi/instance"
15+
"github.com/docker/infrakit/pkg/spi/loadbalancer"
16+
)
17+
18+
// ELBOptions are the configuration parameters for the ELB provisioner.
19+
type ELBOptions struct {
20+
Region string
21+
Retries int
22+
}
23+
24+
type elbPlugin struct {
25+
client elbiface.ELBAPI
26+
name string
27+
}
28+
29+
// NewELBPlugin creates an AWS-based ELB provisioner.
30+
func NewELBPlugin(client elbiface.ELBAPI, name string) (loadbalancer.L4, error) {
31+
return &elbPlugin{
32+
client: client,
33+
name: name,
34+
}, nil
35+
}
36+
37+
// Credentials allocates a credential object that has the access key and secret id.
38+
func Credentials(cred *Credential) *credentials.Credentials {
39+
staticCred := new(Credential)
40+
if cred != nil {
41+
staticCred = cred
42+
}
43+
44+
return credentials.NewChainCredentials([]credentials.Provider{
45+
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
46+
&credentials.EnvProvider{},
47+
&credentials.SharedCredentialsProvider{},
48+
staticCred,
49+
})
50+
}
51+
52+
// CreateELBClient creates an AWS ELB API client.
53+
func CreateELBClient(awsCredentials *credentials.Credentials, opt ELBOptions) elbiface.ELBAPI {
54+
region := opt.Region
55+
if region == "" {
56+
region, _ = GetRegion()
57+
}
58+
59+
log.Infoln("ELB Client in region", region)
60+
61+
return elb.New(session.New(aws.NewConfig().
62+
WithRegion(region).
63+
WithCredentials(awsCredentials).
64+
WithLogger(getLogger()).
65+
WithLogLevel(aws.LogDebugWithHTTPBody).
66+
WithMaxRetries(opt.Retries)))
67+
}
68+
69+
func (p *elbPlugin) Name() string {
70+
return p.name
71+
}
72+
73+
// Routes lists all registered routes.
74+
func (p *elbPlugin) Routes() ([]loadbalancer.Route, error) {
75+
output, err := p.client.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{
76+
LoadBalancerNames: []*string{aws.String(p.name)},
77+
})
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
routes := []loadbalancer.Route{}
83+
84+
if len(output.LoadBalancerDescriptions) > 0 && output.LoadBalancerDescriptions[0].ListenerDescriptions != nil {
85+
for _, listener := range output.LoadBalancerDescriptions[0].ListenerDescriptions {
86+
routes = append(routes, loadbalancer.Route{
87+
Port: int(*listener.Listener.InstancePort),
88+
Protocol: loadbalancer.ProtocolFromString(*listener.Listener.Protocol),
89+
LoadBalancerPort: int(*listener.Listener.LoadBalancerPort),
90+
Certificate: listener.Listener.SSLCertificateId,
91+
})
92+
}
93+
}
94+
95+
return routes, nil
96+
}
97+
98+
func (p *elbPlugin) RegisterBackends(ids []instance.ID) (loadbalancer.Result, error) {
99+
100+
addInstances := []*elb.Instance{}
101+
for _, instanceID := range ids {
102+
addInstance := &elb.Instance{}
103+
strID := string(instanceID)
104+
addInstance.InstanceId = &strID
105+
addInstances = append(addInstances, addInstance)
106+
}
107+
108+
return p.client.RegisterInstancesWithLoadBalancer(&elb.RegisterInstancesWithLoadBalancerInput{
109+
Instances: addInstances,
110+
LoadBalancerName: aws.String(p.name),
111+
})
112+
}
113+
114+
func (p *elbPlugin) DeregisterBackends(ids []instance.ID) (loadbalancer.Result, error) {
115+
116+
rmInstances := []*elb.Instance{}
117+
for _, instanceID := range ids {
118+
rmInstance := &elb.Instance{}
119+
strID := string(instanceID)
120+
rmInstance.InstanceId = &strID
121+
rmInstances = append(rmInstances, rmInstance)
122+
}
123+
124+
return p.client.DeregisterInstancesFromLoadBalancer(&elb.DeregisterInstancesFromLoadBalancerInput{
125+
Instances: rmInstances,
126+
LoadBalancerName: aws.String(p.name),
127+
})
128+
}
129+
130+
func (p *elbPlugin) Backends() ([]instance.ID, error) {
131+
output, err := p.client.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{
132+
LoadBalancerNames: []*string{
133+
aws.String(p.name),
134+
},
135+
})
136+
if err != nil {
137+
return []instance.ID{}, err
138+
}
139+
140+
instanceIDs := []instance.ID{}
141+
142+
if len(output.LoadBalancerDescriptions) == 0 {
143+
return instanceIDs, nil
144+
}
145+
146+
for _, loadBalancerDescription := range output.LoadBalancerDescriptions {
147+
for _, lbInstance := range loadBalancerDescription.Instances {
148+
instanceIDs = append(instanceIDs, instance.ID(*lbInstance.InstanceId))
149+
}
150+
}
151+
return instanceIDs, nil
152+
}
153+
154+
func (p *elbPlugin) Publish(route loadbalancer.Route) (loadbalancer.Result, error) {
155+
156+
if route.Protocol == loadbalancer.Invalid {
157+
return nil, fmt.Errorf("Bad protocol")
158+
}
159+
instanceProtocol := aws.String(string(route.Protocol))
160+
if route.Protocol == loadbalancer.SSL {
161+
// SSL needs to point to TCP internally
162+
instanceProtocol = aws.String(string(loadbalancer.TCP))
163+
} else if route.Protocol == loadbalancer.HTTPS {
164+
// HTTPS has to point to HTTP internally
165+
instanceProtocol = aws.String(string(loadbalancer.HTTP))
166+
}
167+
168+
listener := &elb.Listener{
169+
InstancePort: aws.Int64(int64(route.Port)),
170+
LoadBalancerPort: aws.Int64(int64(route.LoadBalancerPort)),
171+
Protocol: aws.String(string(route.Protocol)),
172+
InstanceProtocol: instanceProtocol,
173+
SSLCertificateId: route.Certificate,
174+
}
175+
176+
return p.client.CreateLoadBalancerListeners(&elb.CreateLoadBalancerListenersInput{
177+
Listeners: []*elb.Listener{listener},
178+
LoadBalancerName: aws.String(p.name),
179+
})
180+
}
181+
182+
func (p *elbPlugin) Unpublish(extPort int) (loadbalancer.Result, error) {
183+
return p.client.DeleteLoadBalancerListeners(&elb.DeleteLoadBalancerListenersInput{
184+
LoadBalancerPorts: []*int64{aws.Int64(int64(extPort))},
185+
LoadBalancerName: aws.String(p.name),
186+
})
187+
}
188+
189+
func (p *elbPlugin) ConfigureHealthCheck(hc loadbalancer.HealthCheck) (loadbalancer.Result, error) {
190+
191+
return p.client.ConfigureHealthCheck(&elb.ConfigureHealthCheckInput{
192+
HealthCheck: &elb.HealthCheck{
193+
HealthyThreshold: aws.Int64(int64(hc.Healthy)),
194+
Interval: aws.Int64(int64(hc.Interval.Seconds())),
195+
Target: aws.String(fmt.Sprintf("TCP:%d", hc.BackendPort)),
196+
Timeout: aws.Int64(int64(hc.Timeout.Seconds())),
197+
UnhealthyThreshold: aws.Int64(int64(hc.Unhealthy)),
198+
},
199+
LoadBalancerName: aws.String(p.name),
200+
})
201+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package aws
2+
3+
import (
4+
"github.com/aws/aws-sdk-go/aws"
5+
"log"
6+
"os"
7+
)
8+
9+
type logger struct {
10+
logger *log.Logger
11+
}
12+
13+
func (l logger) Log(args ...interface{}) {
14+
l.logger.Println(args...)
15+
}
16+
17+
func getLogger() aws.Logger {
18+
return &logger{
19+
logger: log.New(os.Stderr, "", log.LstdFlags),
20+
}
21+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package aws
2+
3+
import (
4+
"io/ioutil"
5+
"net/http"
6+
)
7+
8+
// MetadataKey is the identifier for a metadata entry.
9+
type MetadataKey string
10+
11+
const (
12+
// MetadataAmiID - AMI ID
13+
MetadataAmiID = MetadataKey("http://169.254.169.254/latest/meta-data/ami-id")
14+
15+
// MetadataInstanceID - Instance ID
16+
MetadataInstanceID = MetadataKey("http://169.254.169.254/latest/meta-data/instance-id")
17+
18+
// MetadataInstanceType - Instance type
19+
MetadataInstanceType = MetadataKey("http://169.254.169.254/latest/meta-data/instance-type")
20+
21+
// MetadataHostname - Host name
22+
MetadataHostname = MetadataKey("http://169.254.169.254/latest/meta-data/hostname")
23+
24+
// MetadataLocalIPv4 - Local IPv4 address
25+
MetadataLocalIPv4 = MetadataKey("http://169.254.169.254/latest/meta-data/local-ipv4")
26+
27+
// MetadataPublicIPv4 - Public IPv4 address
28+
MetadataPublicIPv4 = MetadataKey("http://169.254.169.254/latest/meta-data/public-ipv4")
29+
30+
// MetadataAvailabilityZone - Availability zone
31+
MetadataAvailabilityZone = MetadataKey("http://169.254.169.254/latest/meta-data/placement/availability-zone")
32+
)
33+
34+
// GetMetadata returns the value of the metadata by key
35+
func GetMetadata(key MetadataKey) (string, error) {
36+
resp, err := http.Get(string(key))
37+
if err != nil {
38+
return "", err
39+
}
40+
buff, err := ioutil.ReadAll(resp.Body)
41+
resp.Body.Close()
42+
if err != nil {
43+
return "", err
44+
}
45+
return string(buff), nil
46+
}
47+
48+
// GetRegion returns the AWS region this instance is in.
49+
func GetRegion() (string, error) {
50+
az, err := GetMetadata(MetadataAvailabilityZone)
51+
if err != nil {
52+
return "", err
53+
}
54+
return az[0 : len(az)-1], nil
55+
}

pkg/run/v0/aws/aws.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,22 @@ import (
1515
logutil "github.com/docker/infrakit/pkg/log"
1616
"github.com/docker/infrakit/pkg/plugin"
1717
aws_instance "github.com/docker/infrakit/pkg/provider/aws/plugin/instance"
18+
aws_loadbalancer "github.com/docker/infrakit/pkg/provider/aws/plugin/loadbalancer"
1819
aws_metadata "github.com/docker/infrakit/pkg/provider/aws/plugin/metadata"
1920
"github.com/docker/infrakit/pkg/run"
21+
"github.com/docker/infrakit/pkg/run/local"
2022
"github.com/docker/infrakit/pkg/spi/event"
2123
"github.com/docker/infrakit/pkg/spi/instance"
24+
"github.com/docker/infrakit/pkg/spi/loadbalancer"
2225
"github.com/docker/infrakit/pkg/spi/metadata"
2326
"github.com/docker/infrakit/pkg/types"
2427
)
2528

2629
const (
2730
// Kind is the canonical name of the plugin for starting up, etc.
2831
Kind = "aws"
32+
// EnvELBName is the name of the ELB ENV variable name for the ELB plugin.
33+
EnvELBName = "INFRAKIT_AWS_ELB_NAME"
2934
)
3035

3136
var (
@@ -81,11 +86,18 @@ func Run(plugins func() discovery.Plugins, name plugin.Name,
8186
if err != nil {
8287
return
8388
}
89+
90+
var elbPlugin loadbalancer.L4
91+
elbClient := elb.New(builder.Config)
92+
elbPlugin, err = aws_loadbalancer.NewELBPlugin(elbClient, local.Getenv(EnvELBName, "default"))
93+
if err != nil {
94+
return
95+
}
96+
8497
autoscalingClient := autoscaling.New(builder.Config)
8598
cloudWatchLogsClient := cloudwatchlogs.New(builder.Config)
8699
dynamodbClient := dynamodb.New(builder.Config)
87100
ec2Client := ec2.New(builder.Config)
88-
elbClient := elb.New(builder.Config)
89101
iamClient := iam.New(builder.Config)
90102
sqsClient := sqs.New(builder.Config)
91103

@@ -95,6 +107,11 @@ func Run(plugins func() discovery.Plugins, name plugin.Name,
95107
"ec2-instance": (&aws_instance.Monitor{Plugin: instancePlugin}).Init(),
96108
},
97109
run.Metadata: metadataPlugin,
110+
run.L4: func() (map[string]loadbalancer.L4, error) {
111+
return map[string]loadbalancer.L4{
112+
local.Getenv(EnvELBName, "default"): elbPlugin,
113+
}, nil
114+
},
98115
run.Instance: map[string]instance.Plugin{
99116
"autoscaling-autoscalinggroup": aws_instance.NewAutoScalingGroupPlugin(autoscalingClient, options.Namespace),
100117
"autoscaling-launchconfiguration": aws_instance.NewLaunchConfigurationPlugin(autoscalingClient, options.Namespace),

0 commit comments

Comments
 (0)