Cleaned old code and added skeleton for Config object
parent
f45fb16a19
commit
59dcb7607b
|
@ -0,0 +1,5 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
1
go.mod
1
go.mod
|
@ -6,4 +6,5 @@ require (
|
|||
github.com/aws/aws-sdk-go v1.25.43
|
||||
github.com/stretchr/testify v1.4.0 // indirect
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
|
36
main.go
36
main.go
|
@ -25,37 +25,16 @@ var (
|
|||
referenceFile string
|
||||
outputFile string
|
||||
format string
|
||||
period PeriodValue
|
||||
period models.Interval
|
||||
tmpl *template.Template
|
||||
err error
|
||||
quit chan os.Signal
|
||||
// existingInstances []models.Instance
|
||||
currentInstances []models.Instance
|
||||
debug bool
|
||||
ticker *time.Ticker
|
||||
logger *log.Logger
|
||||
hook string
|
||||
debug bool
|
||||
ticker *time.Ticker
|
||||
logger *log.Logger
|
||||
)
|
||||
|
||||
// PeriodValue as flag
|
||||
type PeriodValue struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
// Set method to set value from string to this interface
|
||||
func (p *PeriodValue) Set(value string) error {
|
||||
duration, err := time.ParseDuration(value)
|
||||
p.Duration = duration
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String method to get string value from the interface
|
||||
func (p *PeriodValue) String() string {
|
||||
return p.Duration.String()
|
||||
}
|
||||
|
||||
func init() {
|
||||
logger = log.New(os.Stdout, "nginx-org-asg: ", log.Lshortfile|log.Ltime|log.Ldate)
|
||||
flag.StringVar(&profile, "profile", "default", "Profile to use for from ~/.aws/credentials")
|
||||
|
@ -64,6 +43,7 @@ func init() {
|
|||
flag.StringVar(&outputFile, "out", "~/output.txt", "Output file for extracting IP addresses")
|
||||
flag.StringVar(&referenceFile, "ref", "~/reference.json", "Output file for reference of IP addresses")
|
||||
flag.StringVar(&format, "format", "{{.PrivateIPAddress}}:8080;", "Format to print out with.")
|
||||
flag.StringVar(&hook, "hook", "echo", "Executable Hook path")
|
||||
flag.BoolVar(&debug, "debug", false, "Debug output.")
|
||||
flag.Var(&period, "period", "Period to repeat it at")
|
||||
flag.Parse()
|
||||
|
@ -109,7 +89,7 @@ func main() {
|
|||
func checkChange(existingInstances []models.Instance) []models.Instance {
|
||||
newInstances := fetchInstancesFromAWS()
|
||||
if !reflect.DeepEqual(existingInstances, newInstances) {
|
||||
err := utils.TruncateFileR(outputFile)
|
||||
err := utils.TruncateFile(outputFile)
|
||||
if err != nil {
|
||||
logger.Println("Problem with truncating output file", err)
|
||||
return existingInstances
|
||||
|
@ -129,7 +109,7 @@ func checkChange(existingInstances []models.Instance) []models.Instance {
|
|||
}
|
||||
|
||||
// Update referenceFile for consistency
|
||||
err = utils.TruncateFileR(referenceFile)
|
||||
err = utils.TruncateFile(referenceFile)
|
||||
if err != nil {
|
||||
logger.Println("Problem with truncating reference file", err)
|
||||
return existingInstances
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interval for parsing and using time.Duration
|
||||
type Interval struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
// Set method to set value from string to this interface
|
||||
func (i *Interval) Set(value string) error {
|
||||
duration, err := time.ParseDuration(value)
|
||||
i.Duration = duration
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String method to get string value from the interface
|
||||
func (i *Interval) String() string {
|
||||
return i.Duration.String()
|
||||
}
|
||||
|
||||
// MarshalJSON for marshalling Interval type
|
||||
func (i Interval) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON method to allow unmarshalling
|
||||
func (i *Interval) UnmarshalJSON(b []byte) error {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch value := v.(type) {
|
||||
case float64:
|
||||
i.Duration = time.Duration(value)
|
||||
return nil
|
||||
case string:
|
||||
var err error
|
||||
i.Duration, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid duration")
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalYAML to marshal Interval type
|
||||
func (i Interval) MarshalYAML() (interface{}, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML to unmarshal Interval type
|
||||
func (i *Interval) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var v interface{}
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch value := v.(type) {
|
||||
case float64:
|
||||
i.Duration = time.Duration(value)
|
||||
return nil
|
||||
case string:
|
||||
var err error
|
||||
i.Duration, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid duration")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var (
|
||||
sample = `
|
||||
refresh_interval: 5s
|
||||
|
||||
aws:
|
||||
profile: stage
|
||||
region: us-east-1
|
||||
|
||||
targets:
|
||||
- name: cookie-sync
|
||||
group_name: cookie-sync-asg
|
||||
reference_file: /tmp/reference.json
|
||||
output_file: /etc/nginx/snippets/autoscaling.conf
|
||||
format: "server {{.PrivateIPAddress}}:8080;"
|
||||
reload_hook:
|
||||
command: nginx
|
||||
arguments:
|
||||
- "-s"
|
||||
- reload
|
||||
health_check:
|
||||
interval: 10s
|
||||
response_timeout: 1s
|
||||
request_method: GET
|
||||
request_protocol: http
|
||||
request_path: /
|
||||
port: 8080
|
||||
response_code:
|
||||
- 200
|
||||
- 204
|
||||
- 404
|
||||
healthy_threshold: 2
|
||||
unhealthy_threshold: 5
|
||||
`
|
||||
logger *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
logger = log.New(os.Stdout, "models: ", log.Ltime|log.Lshortfile)
|
||||
}
|
||||
|
||||
// Config structure containing all the configuration about the run
|
||||
type Config struct {
|
||||
RefreshInterval Interval `yaml:"refresh_interval,omitempty"`
|
||||
AWS AWS `yaml:"aws,omitempty"`
|
||||
Targets []Target `yaml:"targets,omitempty"`
|
||||
}
|
||||
|
||||
// AWS structure for storing AWS configuration
|
||||
type AWS struct {
|
||||
Profile string `yaml:"profile,omitempty"`
|
||||
Region string `yaml:"region,omitempty"`
|
||||
}
|
||||
|
||||
// Target structure has all the details about the targets or autoscaling groups
|
||||
// It will also have the reload hook which will be executed in case if there's
|
||||
// any change in the group instances
|
||||
type Target struct {
|
||||
// Name is just for identifying
|
||||
Name string `yaml:"name"`
|
||||
// GroupName is matched while calling AWS API to get instances
|
||||
GroupName string `yaml:"group_name"`
|
||||
// ReferenceFile is temporary storage which is read at the first run
|
||||
ReferenceFile string `yaml:"reference_file"`
|
||||
// OutputFile is created by the template Format and used by other application
|
||||
OutputFile string `yaml:"output_file"`
|
||||
// Format is golang template format of Instance type.
|
||||
// e.g. "server {{.PrivateIPAddress}}:8080;"
|
||||
Format string `yaml:"format"`
|
||||
// ReloadHook is executed when there's change in Healthy instances
|
||||
ReloadHook Hook `yaml:"reload_hook,omitempty"`
|
||||
// HealthCheck is configuration about how the health check should be performed
|
||||
HealthCheck HealthCheck `yaml:"health_check"`
|
||||
}
|
||||
|
||||
// Hook structure has just two things Command and Arguments which help us
|
||||
// execute command with provided arguments.
|
||||
type Hook struct {
|
||||
// Command for specifying which command to be executed
|
||||
Command string `yaml:"command,omitempty"`
|
||||
// Arguments specify optional arguments to be passed along with command
|
||||
Arguments []string `yaml:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
// HealthCheck structure for storing configuration about HealthCheck
|
||||
type HealthCheck struct {
|
||||
httpClient *http.Client
|
||||
// tcpclient *net.TCPConn
|
||||
|
||||
// Interval is the time at which it should perform health check
|
||||
Interval Interval `yaml:"interval"`
|
||||
// RequestTimeout specifies max time to wait for response
|
||||
RequestTimeout Interval `yaml:"request_timeout"`
|
||||
// RequestMethod specifies which HTTP Method to use for request
|
||||
// e.g. HEAD/GET/POST/PUT/...
|
||||
RequestMethod string `yaml:"request_method"`
|
||||
// RequestProtocol specifies which Protocol to use for request http or https
|
||||
RequestProtocol string `yaml:"request_protocol"`
|
||||
// RequestPath specifies path of the URL
|
||||
RequestPath string `yaml:"request_path,omitempty"`
|
||||
// Port which is to be checked
|
||||
Port int `yaml:"port"`
|
||||
// ResponseCode to match the response with any of them
|
||||
ResponseCode []int `yaml:"response_code"`
|
||||
// HealthyThreshold is the threshold after which the instance should be
|
||||
// considered healthy
|
||||
HealthyThreshold int `yaml:"healthy_threshold"`
|
||||
// UnhealthyThreshold is the threshold after which the instance should be
|
||||
// considered unhealthy
|
||||
UnhealthyThreshold int `yaml:"unhealthy_threshold"`
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) Configure() {
|
||||
hc.httpClient = &http.Client{
|
||||
Timeout: hc.RequestTimeout.Duration,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check(host string) bool {
|
||||
u, err := url.Parse(
|
||||
fmt.Sprintf("%s://%s:%d%s",
|
||||
hc.RequestProtocol,
|
||||
host,
|
||||
hc.Port,
|
||||
hc.RequestPath,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Println(err)
|
||||
return false
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: hc.RequestMethod,
|
||||
Host: host,
|
||||
URL: u,
|
||||
// fmt.Sprintf("%s:%d", host, hc.Port),
|
||||
}
|
||||
resp, err := hc.httpClient.Do(req)
|
||||
if err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
// sort.Sort(&hc.ResponseCode)
|
||||
if hc.ResponseCode[sort.SearchInts(hc.ResponseCode, resp.StatusCode)] == resp.StatusCode {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
config = Config{
|
||||
AWS: AWS{
|
||||
Profile: "prod",
|
||||
Region: "us-east-1",
|
||||
},
|
||||
RefreshInterval: Interval{time.Second * 10},
|
||||
Targets: []Target{
|
||||
Target{
|
||||
Format: "server {{.PrivateIPAddress}}:8080;",
|
||||
GroupName: "cookie-sync-asg",
|
||||
HealthCheck: HealthCheck{
|
||||
Interval: Interval{time.Second * 10},
|
||||
HealthyThreshold: 2,
|
||||
UnhealthyThreshold: 5,
|
||||
Port: 8080,
|
||||
RequestMethod: "GET",
|
||||
RequestPath: "/",
|
||||
RequestProtocol: "http",
|
||||
ResponseCode: []int{200, 204, 404},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
configfile, err := os.OpenFile("config.yaml", os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
bts, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
_, err = configfile.Write(bts)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
t.FailNow()
|
||||
}
|
||||
var configt Config
|
||||
err = yaml.Unmarshal(bts, &configt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove("config.yaml")
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
hc := HealthCheck{
|
||||
Port: 8000,
|
||||
RequestMethod: "GET",
|
||||
RequestPath: "/",
|
||||
RequestProtocol: "http",
|
||||
ResponseCode: []int{200, 204},
|
||||
RequestTimeout: Interval{time.Second * 10},
|
||||
}
|
||||
hc.Configure()
|
||||
if !hc.check("localhost") {
|
||||
t.Fail()
|
||||
}
|
||||
hc.ResponseCode = []int{204}
|
||||
if hc.check("localhost") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
|
@ -1,10 +1,5 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Instance struct for storing instance details
|
||||
type Instance struct {
|
||||
InstanceID string `json:"instance_id"`
|
||||
|
@ -20,16 +15,16 @@ func (a ByIP) Len() int { return len(a) }
|
|||
func (a ByIP) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByIP) Less(i, j int) bool { return a[i].PrivateIPAddress < a[j].PrivateIPAddress }
|
||||
|
||||
func (i *Instance) Check(port int) {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", i.PrivateIPAddress, port))
|
||||
i.Healthy = true
|
||||
if err != nil {
|
||||
i.Healthy = false
|
||||
return
|
||||
}
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
i.Healthy = false
|
||||
return
|
||||
}
|
||||
}
|
||||
// func (i *Instance) Check(port int) {
|
||||
// conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", i.PrivateIPAddress, port))
|
||||
// i.Healthy = true
|
||||
// if err != nil {
|
||||
// i.Healthy = false
|
||||
// return
|
||||
// }
|
||||
// err = conn.Close()
|
||||
// if err != nil {
|
||||
// i.Healthy = false
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -37,8 +37,14 @@ func GetInstances(ref *os.File) ([]models.Instance, error) {
|
|||
}
|
||||
|
||||
// TruncateFile for truncating the file and going to (0, 0) position.
|
||||
func TruncateFile(fileptr *os.File) error {
|
||||
err := fileptr.Truncate(0)
|
||||
func TruncateFile(filename string) error {
|
||||
fileptr, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
log.Println("Error in TruncateFileR:1", err)
|
||||
return err
|
||||
}
|
||||
defer fileptr.Close()
|
||||
err = fileptr.Truncate(0)
|
||||
if err != nil {
|
||||
log.Println("Error in TruncateFile:1", err)
|
||||
return err
|
||||
|
@ -52,21 +58,6 @@ func TruncateFile(fileptr *os.File) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// TruncateFileR for truncating the file and going to (0, 0) position.
|
||||
func TruncateFileR(filename string) error {
|
||||
fileptr, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
log.Println("Error in TruncateFileR:1", err)
|
||||
return err
|
||||
}
|
||||
defer fileptr.Close()
|
||||
if err = TruncateFile(fileptr); err != nil {
|
||||
log.Println("Error in TruncateFileR:2", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteTemplate for executing the template
|
||||
func ExecuteTemplate(filename string, tmpl *template.Template, instances []models.Instance) error {
|
||||
of, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
|
|
Loading…
Reference in New Issue