Skip to content

Commit 1575648

Browse files
committed
Make agent code cloud agnostic
1 parent 8acd921 commit 1575648

File tree

8 files changed

+328
-174
lines changed

8 files changed

+328
-174
lines changed

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,19 @@ Below you will find documentation on how to use nginx-asg-sync.
2121
**Note:** the documentation for **the latest stable release** is available via a link in the description of the release. See the [releases page](https://github.com/nginxinc/nginx-asg-sync/releases).
2222

2323
**Contents:**
24-
- [Supported Operating Systems](#supported-operating-systems)
25-
- [Setting up Access to the AWS API](#setting-up-access-to-the-aws-api)
26-
- [Installation](#installation)
27-
- [Configuration](#configuration)
28-
- [NGINX Plus Configuration](#nginx-plus-configuration)
29-
- [nginx-asg-sync Configuration](#nginx-asg-sync-configuration)
30-
- [Usage](#usage)
31-
- [Troubleshooting](#troubleshooting)
32-
- [Building a Software Package](#building-a-software-package)
33-
- [Support](#support)
24+
- [NGINX Plus Integration with AWS Auto Scaling groups -- nginx-asg-sync](#nginx-plus-integration-with-aws-auto-scaling-groups----nginx-asg-sync)
25+
- [How It Works](#how-it-works)
26+
- [Documentation](#documentation)
27+
- [Supported Operating Systems](#supported-operating-systems)
28+
- [Setting up Access to the AWS API](#setting-up-access-to-the-aws-api)
29+
- [Installation](#installation)
30+
- [Configuration](#configuration)
31+
- [NGINX Plus Configuration](#nginx-plus-configuration)
32+
- [nginx-asg-sync Configuration](#nginx-asg-sync-configuration)
33+
- [Usage](#usage)
34+
- [Troubleshooting](#troubleshooting)
35+
- [Building a Software Package](#building-a-software-package)
36+
- [Support](#support)
3437

3538
## Supported Operating Systems
3639

@@ -122,6 +125,7 @@ nginx-asg-sync is configured in **/etc/nginx/aws.yaml**. For our example, we def
122125
region: us-west-2
123126
api_endpoint: http://127.0.0.1:8080/api
124127
sync_interval_in_seconds: 5
128+
cloud_provider: AWS
125129
upstreams:
126130
- name: backend-one
127131
autoscaling_group: backend-one-group
@@ -136,6 +140,7 @@ upstreams:
136140
* The `region` key defines the AWS region where we deploy NGINX Plus and the Auto Scaling groups.
137141
* The `api_endpoint` key defines the NGINX Plus API endpoint.
138142
* The `sync_interval_in_seconds` key defines the synchronization interval: nginx-asg-sync checks for scaling updates every 5 seconds.
143+
* The `cloud_provider` key defines a cloud provider that will be used. The default is `AWS`. This means the key can be empty if using AWS.
139144
* The `upstreams` key defines the list of upstream groups. For each upstream group we specify:
140145
* `name` – The name we specified for the upstream block in the NGINX Plus configuration.
141146
* `autoscaling_group` – The name of the corresponding Auto Scaling group.

cmd/sync/aws.go

Lines changed: 126 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,114 @@ package main
22

33
import (
44
"fmt"
5+
"net/http"
6+
"time"
57

68
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/aws/session"
710
"github.com/aws/aws-sdk-go/service/autoscaling"
811
"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
912
"github.com/aws/aws-sdk-go/service/ec2"
1013
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
14+
yaml "gopkg.in/yaml.v2"
1115
)
1216

13-
// AWSClient allows you to get the list of IP addresses of instanes of an Auto Scaling group
17+
const upstreamNameErrorMsg = "The mandatory field name is either empty or missing for an upstream in the config file"
18+
const upstreamErrorMsgFormat = "The mandatory field %v is either empty or missing for the upstream %v in the config file"
19+
const upstreamPortErrorMsgFormat = "The mandatory field port is either zero or missing for the upstream %v in the config file"
20+
const upstreamKindErrorMsgFormat = "The mandatory field kind is either not equal to http or tcp or missing for the upstream %v in the config file"
21+
22+
// AWSClient allows you to get the list of IP addresses of instanes of an Auto Scaling group. It implements the CloudProvider interface
1423
type AWSClient struct {
1524
svcEC2 ec2iface.EC2API
1625
svcAutoscaling autoscalingiface.AutoScalingAPI
26+
config *awsConfig
27+
}
28+
29+
// NewAWSClient creates and configures an AWSClient
30+
func NewAWSClient(data []byte) (*AWSClient, error) {
31+
awsClient := &AWSClient{}
32+
cfg, err := parseAWSConfig(data)
33+
if err != nil {
34+
return nil, fmt.Errorf("error validating config: %v", err)
35+
}
36+
37+
awsClient.config = cfg
38+
39+
err = awsClient.configure()
40+
if err != nil {
41+
return nil, fmt.Errorf("error configuring AWS Client: %v", err)
42+
}
43+
44+
return awsClient, nil
45+
}
46+
47+
// GetUpstreams returns the Upstreams list
48+
func (client *AWSClient) GetUpstreams() []Upstream {
49+
var upstreams []Upstream
50+
for _, awsU := range client.config.Upstreams {
51+
u := Upstream{
52+
Name: awsU.Name,
53+
Port: awsU.Port,
54+
Kind: awsU.Kind,
55+
ScalingGroup: awsU.AutoscalingGroup,
56+
}
57+
upstreams = append(upstreams, u)
58+
}
59+
return upstreams
60+
}
61+
62+
// configure configures the AWSClient with necessary parameters
63+
func (client *AWSClient) configure() error {
64+
httpClient := &http.Client{Timeout: connTimeoutInSecs * time.Second}
65+
cfg := &aws.Config{Region: aws.String(client.config.Region), HTTPClient: httpClient}
66+
67+
session, err := session.NewSession(cfg)
68+
if err != nil {
69+
return err
70+
}
71+
72+
svcAutoscaling := autoscaling.New(session)
73+
svcEC2 := ec2.New(session)
74+
client.svcEC2 = svcEC2
75+
client.svcAutoscaling = svcAutoscaling
76+
return nil
1777
}
1878

19-
// NewAWSClient creates an AWSClient
20-
func NewAWSClient(svcEC2 ec2iface.EC2API, svcAutoscaling autoscalingiface.AutoScalingAPI) *AWSClient {
21-
return &AWSClient{svcEC2, svcAutoscaling}
79+
// parseAWSConfig parses and validates AWSClient config
80+
func parseAWSConfig(data []byte) (*awsConfig, error) {
81+
cfg := &awsConfig{}
82+
err := yaml.Unmarshal(data, cfg)
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
err = validateAWSConfig(cfg)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
return cfg, nil
2293
}
2394

24-
// CheckIfAutoscalingGroupExists checks if the Auto Scaling group exists
25-
func (client *AWSClient) CheckIfAutoscalingGroupExists(name string) (bool, error) {
95+
// CheckIfScalingGroupExists checks if the Auto Scaling group exists
96+
func (client *AWSClient) CheckIfScalingGroupExists(name string) (bool, error) {
2697
_, exists, err := client.getAutoscalingGroup(name)
27-
return exists, err
98+
if err != nil {
99+
return exists, fmt.Errorf("couldn't check if an AutoScaling group exists: %v", err)
100+
}
101+
return exists, nil
28102
}
29103

30-
// GetPrivateIPsOfInstancesOfAutoscalingGroup returns the list of IP addresses of instanes of the Auto Scaling group
31-
func (client *AWSClient) GetPrivateIPsOfInstancesOfAutoscalingGroup(name string) ([]string, error) {
104+
// GetPrivateIPsForScalingGroup returns the list of IP addresses of instanes of the Auto Scaling group
105+
func (client *AWSClient) GetPrivateIPsForScalingGroup(name string) ([]string, error) {
32106
group, exists, err := client.getAutoscalingGroup(name)
33107
if err != nil {
34108
return nil, err
35109
}
110+
36111
if !exists {
37-
return nil, fmt.Errorf("autoscaling group %v doesn't exists", name)
112+
return nil, fmt.Errorf("autoscaling group %v doesn't exist", name)
38113
}
39114

40115
instances, err := client.getInstancesOfAutoscalingGroup(group)
@@ -96,3 +171,44 @@ func (client *AWSClient) getInstancesOfAutoscalingGroup(group *autoscaling.Group
96171

97172
return result, nil
98173
}
174+
175+
// Configuration for AWS Cloud Provider
176+
177+
type awsConfig struct {
178+
Region string
179+
Upstreams []awsUpstream
180+
}
181+
182+
type awsUpstream struct {
183+
Name string
184+
AutoscalingGroup string `yaml:"autoscaling_group"`
185+
Port int
186+
Kind string
187+
}
188+
189+
func validateAWSConfig(cfg *awsConfig) error {
190+
if cfg.Region == "" {
191+
return fmt.Errorf(errorMsgFormat, "region")
192+
}
193+
194+
if len(cfg.Upstreams) == 0 {
195+
return fmt.Errorf("There are no upstreams found in the config file")
196+
}
197+
198+
for _, ups := range cfg.Upstreams {
199+
if ups.Name == "" {
200+
return fmt.Errorf(upstreamNameErrorMsg)
201+
}
202+
if ups.AutoscalingGroup == "" {
203+
return fmt.Errorf(upstreamErrorMsgFormat, "autoscaling_group", ups.Name)
204+
}
205+
if ups.Port == 0 {
206+
return fmt.Errorf(upstreamPortErrorMsgFormat, ups.Name)
207+
}
208+
if ups.Kind == "" || !(ups.Kind == "http" || ups.Kind == "stream") {
209+
return fmt.Errorf(upstreamKindErrorMsgFormat, ups.Name)
210+
}
211+
}
212+
213+
return nil
214+
}

cmd/sync/aws_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import "testing"
4+
5+
type testInputAWS struct {
6+
cfg *awsConfig
7+
msg string
8+
}
9+
10+
func getValidAWSConfig() *awsConfig {
11+
upstreams := []awsUpstream{
12+
{
13+
Name: "backend1",
14+
AutoscalingGroup: "backend-group",
15+
Port: 80,
16+
Kind: "http",
17+
},
18+
}
19+
cfg := awsConfig{
20+
Region: "us-west-2",
21+
Upstreams: upstreams,
22+
}
23+
24+
return &cfg
25+
}
26+
27+
func getInvalidAWSConfigInput() []*testInputAWS {
28+
var input []*testInputAWS
29+
30+
invalidRegionCfg := getValidAWSConfig()
31+
invalidRegionCfg.Region = ""
32+
input = append(input, &testInputAWS{invalidRegionCfg, "invalid region"})
33+
34+
invalidMissingUpstreamsCfg := getValidAWSConfig()
35+
invalidMissingUpstreamsCfg.Upstreams = nil
36+
input = append(input, &testInputAWS{invalidMissingUpstreamsCfg, "no upstreams"})
37+
38+
invalidUpstreamNameCfg := getValidAWSConfig()
39+
invalidUpstreamNameCfg.Upstreams[0].Name = ""
40+
input = append(input, &testInputAWS{invalidUpstreamNameCfg, "invalid name of the upstream"})
41+
42+
invalidUpstreamAutoscalingGroupCfg := getValidAWSConfig()
43+
invalidUpstreamAutoscalingGroupCfg.Upstreams[0].AutoscalingGroup = ""
44+
input = append(input, &testInputAWS{invalidUpstreamAutoscalingGroupCfg, "invalid autoscaling_group of the upstream"})
45+
46+
invalidUpstreamPortCfg := getValidAWSConfig()
47+
invalidUpstreamPortCfg.Upstreams[0].Port = 0
48+
input = append(input, &testInputAWS{invalidUpstreamPortCfg, "invalid port of the upstream"})
49+
50+
invalidUpstreamKindCfg := getValidAWSConfig()
51+
invalidUpstreamKindCfg.Upstreams[0].Kind = ""
52+
input = append(input, &testInputAWS{invalidUpstreamKindCfg, "invalid kind of the upstream"})
53+
54+
return input
55+
}
56+
57+
func TestValidateAWSConfigNotValid(t *testing.T) {
58+
input := getInvalidAWSConfigInput()
59+
60+
for _, item := range input {
61+
err := validateAWSConfig(item.cfg)
62+
if err == nil {
63+
t.Errorf("validateAWSConfig() didn't fail for the invalid config file with %v", item.msg)
64+
}
65+
}
66+
}
67+
68+
func TestValidateAWSConfigValid(t *testing.T) {
69+
cfg := getValidAWSConfig()
70+
71+
err := validateAWSConfig(cfg)
72+
if err != nil {
73+
t.Errorf("validateAWSConfig() failed for the valid config: %v", err)
74+
}
75+
}

0 commit comments

Comments
 (0)