Cleaned old code and added skeleton for Config object

master
Darshil Chanpura 2019-12-01 20:04:08 +05:30
parent f45fb16a19
commit 59dcb7607b
8 changed files with 356 additions and 63 deletions

5
cmd/nginx-asg/main.go Normal file
View File

@ -0,0 +1,5 @@
package main
func main() {
}

1
go.mod
View File

@ -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
View File

@ -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

81
models/common.go Normal file
View File

@ -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")
}
}

158
models/config.go Normal file
View File

@ -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
}

82
models/config_test.go Normal file
View File

@ -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()
}
}

View File

@ -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
// }
// }

View File

@ -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)