Skip to content

Initial version #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 12, 2016
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin
/pkg
49 changes: 49 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# api-gateway-config-supervisor
#
# VERSION 1.9.3.1
#
# From https://hub.docker.com/_/alpine/
#
FROM alpine:latest

ENV GOPATH /usr/lib/go/bin
ENV GOBIN /usr/lib/go/bin
ENV PATH $PATH:/usr/lib/go/bin


RUN mkdir -p /tmp/go
ADD . /tmp/go
RUN echo "http://dl-4.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
&& apk update \
&& apk add make git go \

&& echo " building local project ... " \
&& cd /tmp/go \
&& make setup \
&& mkdir -p /tmp/go/Godeps/_workspace \
&& ln -s /tmp/go/vendor /tmp/go/Godeps/_workspace/src \
&& mkdir -p /tmp/go-src/src/github.com/adobe-apiplatform \
&& ln -s /tmp/go /tmp/go-src/src/github.com/adobe-apiplatform/api-gateway-config-supervisor \
&& 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 ./ \
&& cp /tmp/go/api-gateway-config-supervisor /usr/lib/go/bin \

&& echo "installing rclone sync ... " \
&& go get github.com/ncw/rclone \

&& echo " cleaning up ... " \
&& rm -rf /usr/lib/go/bin/src \
&& rm -rf /tmp/go \
&& rm -rf /tmp/go-src \
&& rm -rf /usr/lib/go/bin/pkg/ \
&& rm -rf /usr/lib/go/bin/godep \
&& apk del make git go \
&& rm -rf /var/cache/apk/*

RUN echo " installing aws-cli ..." \
&& apk update \
&& apk add python \
&& apk add py-pip \
&& pip install --upgrade pip \
&& pip install awscli

ENTRYPOINT ["api-gateway-config-supervisor"]
27 changes: 27 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Godeps/Readme

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
GOPATH ?= `pwd`
GOBIN ?= `pwd`/bin
GOOS ?= $(`uname -a | awk '{print tolower($1)}'`)

setup:
go get github.com/tools/godep

install:
@go version
export GO15VENDOREXPERIMENT=1
# GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install -v ./...
GOPATH=$(GOPATH) GOBIN=$(GOBIN) $(GOPATH)/bin/godep go install -v ./...

format:
gofmt -e -w ./

static:
# CGO_ENABLED=0 GOOS=linux go build -ldflags "-s" -a -installsuffix cgo -o $(GOBIN)/api-gateway-config-supervisor-static ./
CGO_ENABLED=0 go build -ldflags "-s" -a -installsuffix cgo -o $(GOBIN)/api-gateway-config-supervisor-static ./

docker:
docker build -t adobeapiplatform/api-gateway-config-supervisor .

.PHONY: docker-ssh
docker-ssh:
docker run -ti --entrypoint='/bin/sh' adobeapiplatform/api-gateway-config-supervisor:latest
75 changes: 70 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# api-gateway-config-supervisor
Syncs config files from Amazon S3, Google Drive, Dropbox, Amazon Cloud Drive reloading the gateway with the updates
Syncs config files from Amazon S3, Google Drive, Dropbox, Amazon Cloud Drive reloading the gateway when there are any changes.

Table of Contents
=================
Expand All @@ -11,20 +11,85 @@ Table of Contents

Status
======
This module is in design phase at the moment.
This module is experimental at the moment.

Quick Start
============

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.

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.
```
api-gateway-config-supervisor \
--reload-cmd="api-gateway -s reload" \
--sync-folder=/etc/api-gateway \
--sync-interval=10s \
--sync-cmd="rclone sync s3-gw-config:api-gateway-config/ /etc/api-gateway -q" \
--http-addr=127.0.0.1:8888
```

`sync-cmd` is executed each `sync-interval`. If there are changes to the files in `sync-folder` `reload-cmd` is executed.
`sync-folder` needs to exist before executing the command otherwise it exits with an error.
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`
so that in the unlikely event that this process dies the gateway appears unhealthy too.

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.
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` )

### Using AWS-CLI
After installing `aws cli` the only change required to use this tool is to edit `sync-cmd` to something like:
```
--sync-cmd="aws s3 sync s3://api-gateway-config /etc/api-gateway"
```

```
api-gateway-config-supervisor \
--reload-cmd="api-gateway -s reload" \
--sync-folder=/etc/api-gateway \
--sync-interval=10s \
--sync-cmd="aws s3 sync s3://api-gateway-config /etc/api-gateway" \
--http-addr=127.0.0.1:8888
```

Dependencies
============
To be reviewed:

* https://github.com/tools/godep
* https://gowalker.org/github.com/ncw/rclone
* gopkg.in/fsnotify.v1
* TBD

Developer guide
===============
TBD

Make sure you have go 1.5.1+ installed. On a Mac you can execute:
```
brew install go
```

The go dependencies have been already added using `godeps` in the existing GitRepo in order to achieve repeatable builds and isolated envs.
To build the project you can run:

```
make install
```

### Building a Docker image

Make sure you install docker first, opening a Docker terminal, then issue:

```
make docker
```

The `Dockerfile` is building a minimalistic Docker image installing go and its dependencies only to build the project, uninstalling them afterwards,
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.

The container's entrypoint is `api-gateway-config-supervisor` so that its usage looks similar to the command without docker. For example:
```
docker run adobeapiplatform/api-gateway-config-supervisor:latest \
--reload-cmd="api-gateway -s reload" \
--sync-folder=/etc/api-gateway \
--sync-interval=10s \
--sync-cmd="aws s3 sync s3://api-gateway-config /etc/api-gateway" \
--http-addr=127.0.0.1:8888
```
127 changes: 127 additions & 0 deletions config-supervisor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package main

import (
"fmt"
"log"
"os"
"os/signal"
"runtime"
"runtime/pprof"
"time"
// "net/http"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove please

"github.com/adobe-apiplatform/api-gateway-config-supervisor/sync"
"github.com/adobe-apiplatform/api-gateway-config-supervisor/ws"
_ "net/http/pprof"

"github.com/carlescere/scheduler"

"github.com/spf13/pflag"

"github.com/koyachi/go-term-ansicolor/ansicolor"
)

var (
// Flags
cpuprofile = pflag.StringP("cpuprofile", "", "", "Write cpu profile to file")
version = pflag.BoolP("version", "V", false, "Print the version number")
syncInterval = pflag.DurationP("sync-interval", "", time.Second*5, "Time interval for the next sync")
syncCmd = pflag.StringP("sync-cmd", "", "echo sync-cmd not defined", "Command used to syncing")
syncFolder = pflag.StringP("sync-folder", "", "~/tmp/api-gateway-config", "The folder to watch for changes.")
reloadCmd = pflag.StringP("reload-cmd", "", "echo reload-cmd not defined", "Command used to reload the gateway")
httpAddr = pflag.StringP("http-addr", "", "127.0.0.1:8888", "Http Address exposing a /health-check for the sync process")
// when was the reload cmd executed last time
lastReload = time.Now()
// when did the last change occur
lastChange = time.Now()
)

func syntaxError() {
fmt.Fprintf(os.Stderr, `Execute a sync command and watch a folder for changes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to same line. Is this after go fmt ?

`)
}

// ParseFlags parses the command line flags
func ParseFlags() {
pflag.Usage = syntaxError
pflag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU())

// Setup profiling if desired
if *cpuprofile != "" {
log.Println(ansicolor.Red("Starting CPU Profiling"))
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
defer f.Close()

err = pprof.StartCPUProfile(f)
if err != nil {
log.Fatal(err)
}
defer pprof.StopCPUProfile()
}
}

func executeSyncCmd() {
go sync.Execute(*syncCmd)
}

func executeReloadCmd() {
log.Println(ansicolor.Red("Executing Reload Cmd"))
go sync.Execute(*reloadCmd)
lastReload = time.Now()
}

func checkForReload() {
if time.Since(lastChange) < time.Since(lastReload) && time.Since(lastReload) > *syncInterval {
lastReload = time.Now()
executeReloadCmd()
}
}

//watches for changes in the syncFolder, execute reloadCmd when there are changes
func watchForFSChanges() {
c := sync.WatchFolderRecursive(*syncFolder)
for {
select {
case file := <-c:
if file == "" {
continue
}
lastChange = time.Now()
if time.Since(lastReload) > *syncInterval {
lastReload = time.Now()
go func() {
// wait a little in case there are more changes to sync
for time.Since(lastChange) < time.Second*1 {
time.Sleep(1 * time.Second)
}
executeReloadCmd()
}()
}
}
}
}

func main() {
ParseFlags()
if *version {
fmt.Printf("config-supervisor %s\n", "0.1")
os.Exit(0)
}

go ws.RunWS(*httpAddr)

go watchForFSChanges()
scheduler.Every(int(syncInterval.Seconds())).Seconds().Run(executeSyncCmd)
scheduler.Every(int(syncInterval.Seconds())).Seconds().Run(checkForReload)

// http.ListenAndServe(":8181", nil)
// Waiting for terminating (i use a sighandler like in vitess)
terminate := make(chan os.Signal)
signal.Notify(terminate, os.Interrupt)
<-terminate

log.Printf("Stopped ... ")
}
Loading