Skip to content

Commit 05fcbb5

Browse files
ddragosdselfxp
authored andcommitted
Initial version (#1)
* - initial prototype * Docker builds a minimalistic image of 28MB * added the main file which was missed from the initial commit * updated README * added aws-cli to the container for convenience * if the gateway fails to reload b/c a configuration file has an error, don't exit the sync job as it may get fixed later * implemented code reviews and added a simple unit test * added another unit test to check that sync cmd executes * health-check prints more information about the sync activity : ``` { status: "OK", lastSync: "2016-02-24T23:10:36.148512004-08:00", lastChangeDetected: "2016-02-24T23:07:36.036041758-08:00", lastReload: "2016-02-24T23:07:41.041648492-08:00" } ``` * `go fmt` updated source files * updated Dockerfile to execute the unit tests during build
1 parent 6244a03 commit 05fcbb5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+6593
-5
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin
2+
/pkg

Dockerfile

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# api-gateway-config-supervisor
2+
#
3+
# VERSION 1.9.3.1
4+
#
5+
# From https://hub.docker.com/_/alpine/
6+
#
7+
FROM alpine:latest
8+
9+
ENV GOPATH /usr/lib/go/bin
10+
ENV GOBIN /usr/lib/go/bin
11+
ENV PATH $PATH:/usr/lib/go/bin
12+
13+
14+
RUN mkdir -p /tmp/go
15+
ADD . /tmp/go
16+
RUN echo "http://dl-4.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
17+
&& apk update \
18+
&& apk add make git go \
19+
20+
&& echo " building local project ... " \
21+
&& cd /tmp/go \
22+
&& make setup \
23+
&& mkdir -p /tmp/go/Godeps/_workspace \
24+
&& ln -s /tmp/go/vendor /tmp/go/Godeps/_workspace/src \
25+
&& mkdir -p /tmp/go-src/src/github.com/adobe-apiplatform \
26+
&& ln -s /tmp/go /tmp/go-src/src/github.com/adobe-apiplatform/api-gateway-config-supervisor \
27+
&& GOPATH=/tmp/go/vendor:/tmp/go-src CGO_ENABLED=0 GOOS=linux /usr/lib/go/bin/godep go test \
28+
&& GOPATH=/tmp/go/vendor:/tmp/go-src CGO_ENABLED=0 GOOS=linux /usr/lib/go/bin/godep go build -ldflags "-s" -a -installsuffix cgo -o api-gateway-config-supervisor ./ \
29+
&& cp /tmp/go/api-gateway-config-supervisor /usr/lib/go/bin \
30+
31+
&& echo "installing rclone sync ... " \
32+
&& go get github.com/ncw/rclone \
33+
34+
&& echo " cleaning up ... " \
35+
&& rm -rf /usr/lib/go/bin/src \
36+
&& rm -rf /tmp/go \
37+
&& rm -rf /tmp/api-gateway-config-supervisor* \
38+
&& rm -rf /tmp/go-src \
39+
&& rm -rf /usr/lib/go/bin/pkg/ \
40+
&& rm -rf /usr/lib/go/bin/godep \
41+
&& apk del make git go \
42+
&& rm -rf /var/cache/apk/*
43+
44+
RUN echo " installing aws-cli ..." \
45+
&& apk update \
46+
&& apk add python \
47+
&& apk add py-pip \
48+
&& pip install --upgrade pip \
49+
&& pip install awscli
50+
51+
ENTRYPOINT ["api-gateway-config-supervisor"]

Godeps/Godeps.json

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

Godeps/Readme

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

Makefile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
GOPATH ?= `pwd`
2+
GOBIN ?= `pwd`/bin
3+
GOOS ?= $(`uname -a | awk '{print tolower($1)}'`)
4+
5+
setup:
6+
go get github.com/tools/godep
7+
8+
install:
9+
@go version
10+
export GO15VENDOREXPERIMENT=1
11+
# GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install -v ./...
12+
GOPATH=$(GOPATH) GOBIN=$(GOBIN) $(GOPATH)/bin/godep go install -v ./...
13+
14+
test:
15+
go test
16+
17+
format:
18+
gofmt -e -w ./
19+
20+
static:
21+
# CGO_ENABLED=0 GOOS=linux go build -ldflags "-s" -a -installsuffix cgo -o $(GOBIN)/api-gateway-config-supervisor-static ./
22+
CGO_ENABLED=0 go build -ldflags "-s" -a -installsuffix cgo -o $(GOBIN)/api-gateway-config-supervisor-static ./
23+
24+
docker:
25+
docker build -t adobeapiplatform/api-gateway-config-supervisor .
26+
27+
.PHONY: docker-ssh
28+
docker-ssh:
29+
docker run -ti --entrypoint='/bin/sh' adobeapiplatform/api-gateway-config-supervisor:latest

README.md

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# api-gateway-config-supervisor
2-
Syncs config files from Amazon S3, Google Drive, Dropbox, Amazon Cloud Drive reloading the gateway with the updates
2+
Syncs config files from Amazon S3, Google Drive, Dropbox, Amazon Cloud Drive reloading the gateway when there are any changes.
33

44
Table of Contents
55
=================
@@ -11,20 +11,91 @@ Table of Contents
1111

1212
Status
1313
======
14-
This module is in design phase at the moment.
14+
This module is considered production ready.
1515

1616
Quick Start
1717
============
18+
1819
This module should be executed alongside the gateway in order to keep track of the configuration files and reload the gateway when there is a change.
1920

20-
It should expose a simple REST API for the API Gateway to check during health-checks so that in case this modules fails, the gateway may also appear unhealthy.
21+
```
22+
api-gateway-config-supervisor \
23+
--reload-cmd="api-gateway -s reload" \
24+
--sync-folder=/etc/api-gateway \
25+
--sync-interval=10s \
26+
--sync-cmd="rclone sync s3-gw-config:api-gateway-config/ /etc/api-gateway -q" \
27+
--http-addr=127.0.0.1:8888
28+
```
29+
30+
`sync-cmd` is executed each `sync-interval`. If there are changes to the files in `sync-folder` `reload-cmd` is executed.
31+
`sync-folder` needs to exist before executing the command otherwise it exits with an error.
32+
A web server is also started at `http-addr`; the gateway should check `http://<http-addr>/health-check` as part of its own regular `health-check`
33+
so that in the unlikely event that this process dies the gateway appears unhealthy too.
34+
35+
In the initial design `rclone` was embedded into the program but b/c it wasn't straight forward to integrate it `sync-cmd` is used instead.
36+
Using an external command for syncing is not that bad actually as it allows other cloud specific tools to come into play ( i.e `aws cli` )
37+
38+
### Using AWS-CLI
39+
After installing `aws cli` the only change required to use this tool is to edit `sync-cmd` to something like:
40+
```
41+
--sync-cmd="aws s3 sync s3://api-gateway-config /etc/api-gateway"
42+
```
43+
44+
```
45+
api-gateway-config-supervisor \
46+
--reload-cmd="api-gateway -s reload" \
47+
--sync-folder=/etc/api-gateway \
48+
--sync-interval=10s \
49+
--sync-cmd="aws s3 sync s3://api-gateway-config /etc/api-gateway" \
50+
--http-addr=127.0.0.1:8888
51+
```
2152

2253
Dependencies
2354
============
24-
To be reviewed:
55+
56+
* https://github.com/tools/godep
2557
* https://gowalker.org/github.com/ncw/rclone
58+
* gopkg.in/fsnotify.v1
2659
* TBD
2760

2861
Developer guide
2962
===============
30-
TBD
63+
64+
Make sure you have go 1.5.1+ installed. On a Mac you can execute:
65+
```
66+
brew install go
67+
```
68+
69+
The go dependencies have been already added using `godeps` in the existing GitRepo in order to achieve repeatable builds and isolated envs.
70+
To build the project you can run:
71+
72+
```
73+
make install
74+
```
75+
76+
To run the unit tests run:
77+
78+
```
79+
make test
80+
```
81+
82+
### Building a Docker image
83+
84+
Make sure you install docker first, opening a Docker terminal, then issue:
85+
86+
```
87+
make docker
88+
```
89+
90+
The `Dockerfile` is building a minimalistic Docker image installing go and its dependencies only to build the project, uninstalling them afterwards,
91+
and only keeping statically built binaries. In addition it adds `rclone` ( +~14MB ) and `awscli` ( +~70MB ) for convenience but `awscli` is to be removed once `rclone` supports IAM Roles.
92+
93+
The container's entrypoint is `api-gateway-config-supervisor` so that its usage looks similar to the command without docker. For example:
94+
```
95+
docker run adobeapiplatform/api-gateway-config-supervisor:latest \
96+
--reload-cmd="api-gateway -s reload" \
97+
--sync-folder=/etc/api-gateway \
98+
--sync-interval=10s \
99+
--sync-cmd="aws s3 sync s3://api-gateway-config /etc/api-gateway" \
100+
--http-addr=127.0.0.1:8888
101+
```

config-supervisor.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/signal"
8+
"runtime"
9+
"runtime/pprof"
10+
"time"
11+
12+
"github.com/adobe-apiplatform/api-gateway-config-supervisor/sync"
13+
"github.com/adobe-apiplatform/api-gateway-config-supervisor/ws"
14+
_ "net/http/pprof"
15+
16+
"github.com/carlescere/scheduler"
17+
18+
"github.com/spf13/pflag"
19+
20+
"github.com/koyachi/go-term-ansicolor/ansicolor"
21+
)
22+
23+
var (
24+
// Flags
25+
cpuprofile = pflag.StringP("cpuprofile", "", "", "Write cpu profile to file")
26+
version = pflag.BoolP("version", "V", false, "Print the version number")
27+
syncInterval = pflag.DurationP("sync-interval", "", time.Second*5, "Time interval for the next sync")
28+
syncCmd = pflag.StringP("sync-cmd", "", "echo sync-cmd not defined", "Command used to syncing")
29+
syncFolder = pflag.StringP("sync-folder", "", "~/tmp/api-gateway-config", "The folder to watch for changes.")
30+
reloadCmd = pflag.StringP("reload-cmd", "", "echo reload-cmd not defined", "Command used to reload the gateway")
31+
httpAddr = pflag.StringP("http-addr", "", "127.0.0.1:8888", "Http Address exposing a /health-check for the sync process")
32+
status = sync.GetStatusInstance()
33+
)
34+
35+
func syntaxError() {
36+
fmt.Fprintf(os.Stderr, `Execute a sync command and watch a folder for changes.`)
37+
}
38+
39+
// ParseFlags parses the command line flags
40+
func ParseFlags() {
41+
pflag.Usage = syntaxError
42+
pflag.Parse()
43+
runtime.GOMAXPROCS(runtime.NumCPU())
44+
45+
// Setup profiling if desired
46+
if *cpuprofile != "" {
47+
log.Println(ansicolor.Red("Starting CPU Profiling"))
48+
f, err := os.Create(*cpuprofile)
49+
if err != nil {
50+
log.Fatal(err)
51+
}
52+
defer f.Close()
53+
54+
err = pprof.StartCPUProfile(f)
55+
if err != nil {
56+
log.Fatal(err)
57+
}
58+
defer pprof.StopCPUProfile()
59+
}
60+
}
61+
62+
func executeSyncCmd() {
63+
go sync.Execute(*syncCmd)
64+
status.LastSync = time.Now()
65+
}
66+
67+
func executeReloadCmd() {
68+
log.Println(ansicolor.Red("Executing Reload Cmd"))
69+
go sync.Execute(*reloadCmd)
70+
status.LastReload = time.Now()
71+
}
72+
73+
func checkForReload() {
74+
if time.Since(status.LastFSChangeDetected) < time.Since(status.LastReload) && time.Since(status.LastReload) > *syncInterval {
75+
status.LastReload = time.Now()
76+
executeReloadCmd()
77+
}
78+
}
79+
80+
//watches for changes in the syncFolder, execute reloadCmd when there are changes
81+
func watchForFSChanges() {
82+
c := sync.WatchFolderRecursive(*syncFolder)
83+
for {
84+
select {
85+
case file := <-c:
86+
if file == "" {
87+
continue
88+
}
89+
status.LastFSChangeDetected = time.Now()
90+
if time.Since(status.LastReload) > *syncInterval {
91+
status.LastReload = time.Now()
92+
go func() {
93+
// wait a little in case there are more changes to sync
94+
for time.Since(status.LastFSChangeDetected) < time.Second*1 {
95+
time.Sleep(1 * time.Second)
96+
}
97+
executeReloadCmd()
98+
}()
99+
}
100+
}
101+
}
102+
}
103+
104+
func startWebServer() {
105+
go ws.RunWS(*httpAddr)
106+
}
107+
108+
func startWatchingFS() {
109+
go watchForFSChanges()
110+
scheduler.Every(int(syncInterval.Seconds())).Seconds().Run(executeSyncCmd)
111+
scheduler.Every(int(syncInterval.Seconds())).Seconds().Run(checkForReload)
112+
}
113+
114+
func waitToTerminate() {
115+
// Waiting for terminating (i use a sighandler like in vitess)
116+
terminate := make(chan os.Signal)
117+
signal.Notify(terminate, os.Interrupt)
118+
<-terminate
119+
}
120+
121+
func main() {
122+
ParseFlags()
123+
if *version {
124+
fmt.Printf("config-supervisor %s\n", "0.1")
125+
os.Exit(0)
126+
}
127+
128+
startWebServer() // expose a /health-check API on the localhost
129+
startWatchingFS() // watch for file system changes
130+
waitToTerminate() // wait until the program terminates
131+
132+
log.Printf("Stopped ... ")
133+
}

0 commit comments

Comments
 (0)