diff --git a/.gitignore b/.gitignore index 5d854dd3a..486d0d9ff 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ app.zip vendor/ /microgateway /gateway + +private.sh diff --git a/README.md b/README.md index 0b6689e65..5636ed40e 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,39 @@ - +Note: currently running at: http://gateway.iron.computer:8080/ # MicroServices Gateway / API Gateway -First things first, register an app: +First things first, create an app/service: + +TOOD: App or service?? ```sh -curl -H "Content-Type: application/json" -X POST -d '{"name":"myapp","password":"xyz"}' http://localhost:8080/test/1/projects/123/apps +iron create app +# OR +curl -H "Content-Type: application/json" -X POST -d '{"name":"myapp"}' http://localhost:8080/api/v1/apps ``` Now add routes to the app. First we'll add a route to the output of a docker container: ```sh -curl -H "Content-Type: application/json" -X POST -d '{"path":"/hello.rb","image":"treeder/hello.rb", "type":"run"}' http://localhost:8080/test/1/projects/123/apps/myapp/routes +iron add route myapp /hello iron/hello:0.0.1 +# OR +curl -H "Content-Type: application/json" -X POST -d '{"path":"/hello", "image":"iron/hello", "type":"run"}' http://localhost:8080/api/v1/apps/myapp/routes ``` -curl -H "Content-Type: application/json" -X POST -d '{"path":"/helloiron","image":"iron/hello", "type":"run"}' http://localhost:8080/test/1/projects/123/apps/myapp/routes +And how about a [slackbot](https://github.com/treeder/slackbots/tree/master/guppy) endpoint: + +```sh +curl -H "Content-Type: application/json" -X POST -d '{"path":"/guppy","image":"treeder/guppy:0.0.2"}' http://localhost:8080/api/v1/apps/myapp/routes +``` Test out the route: -```sh -curl -i -X GET http://localhost:8080/hello.rb?app=myapp -``` +Surf to: http://localhost:8080/hello?app=myapp Now try mapping an app endpoint: ```sh -curl -H "Content-Type: application/json" -X POST -d '{"path":"/sinatra","image":"treeder/hello-sinatra", "type":"app", "cpath":"/"}' http://localhost:8080/test/1/projects/123/apps/myapp/routes +curl -H "Content-Type: application/json" -X POST -d '{"path":"/sinatra","image":"treeder/hello-sinatra", "type":"app", "cpath":"/"}' http://localhost:8080/api/v1/apps/myapp/routes ``` And test it out: @@ -37,7 +45,7 @@ curl -i -X GET http://localhost:8080/sinatra?app=myapp And another: ```sh -curl -H "Content-Type: application/json" -X POST -d '{"path":"/sinatra/ping","image":"treeder/hello-sinatra", "type":"app", "cpath":"/ping"}' http://localhost:8080/test/1/projects/123/apps/myapp/routes +curl -H "Content-Type: application/json" -X POST -d '{"path":"/sinatra/ping","image":"treeder/hello-sinatra", "type":"app", "cpath":"/ping"}' http://localhost:8080/api/v1/apps/myapp/routes ``` And test it out: @@ -49,7 +57,7 @@ curl -i -X GET http://localhost:8080/sinatra?app=myapp You'all also get a custom URL like this when in production. ``` -appname.projectid.iron.computer +appname.iron.computer ``` ## Building/Testing @@ -66,7 +74,7 @@ glide install Test it, the iron token and project id are for cache. ```sh -docker run -e "IRON_TOKEN=GP8cqlKSrcpmqeR8x9WKD4qSAss" -e "IRON_PROJECT_ID=4fd2729368a0197d1102056b" --rm -it --privileged -p 8080:8080 iron/gateway +docker run -e "IRON_TOKEN=GP8cqlKSrcpmqeR8x9WKD4qSAss" -e "IRON_PROJECT_ID=4fd2729368a0197d1102056b" -e "CLOUDFLARE_EMAIL=treeder@gmail.com" -e "CLOUDFLARE_API_KEY=X" --rm -it --privileged -p 8080:8080 iron/gateway ``` Push it: @@ -82,3 +90,12 @@ After deploying, running it with: ```sh docker run -e "IRON_TOKEN=GP8cqlKSrcpmqeR8x9WKD4qSAss" -e "IRON_PROJECT_ID=4fd2729368a0197d1102056b" --name irongateway -it --privileged --net=host -p 8080:8080 -d --name irongateway iron/gateway ``` + +## TODOS + +* [ ] Check if image exists when registering the endpoint, not at run time +* [ ] Put stats into influxdb or something to show to user (requests, errors) +* [ ] Store recent logs for user. +* [ ] Allow env vars for config on the app and routes (routes override apps). +* [ ] Provide a base url for each app, eg: appname.userid.iron.computer +* [ ] Allow setting content-type on a route, then use that when responding diff --git a/dns.go b/dns.go index b454560f7..e969e306b 100644 --- a/dns.go +++ b/dns.go @@ -7,8 +7,8 @@ import ( "net/http" "strings" + log "github.com/Sirupsen/logrus" "github.com/iron-io/go/common" - "gopkg.in/inconshreveable/log15.v2" ) type CloudFlareResult struct { @@ -30,7 +30,7 @@ func registerHost(w http.ResponseWriter, r *http.Request, app *App) bool { // WORKER_NAME.PROJECT_ID.iron.computer. dnsHost := fmt.Sprintf("%v.%v.iron.computer", app.Name, 123) app.Dns = dnsHost - log15.Info("registering dns", "dnsname", dnsHost) + log.Info("registering dns", "dnsname", dnsHost) if app.CloudFlareId == "" { // Tad hacky, but their go lib is pretty confusing. @@ -53,7 +53,7 @@ func registerHost(w http.ResponseWriter, r *http.Request, app *App) bool { req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { - log15.Error("Could not register dns entry.", "err", err) + log.Error("Could not register dns entry.", "err", err) common.SendError(w, 500, fmt.Sprint("Could not register dns entry.", err)) return false } @@ -61,14 +61,14 @@ func registerHost(w http.ResponseWriter, r *http.Request, app *App) bool { // todo: get error message from body for bad status code body, err := ioutil.ReadAll(resp.Body) if resp.StatusCode != 200 { - log15.Error("Could not register dns entry 2.", "code", resp.StatusCode, "body", string(body)) + log.Error("Could not register dns entry 2.", "code", resp.StatusCode, "body", string(body)) common.SendError(w, 500, fmt.Sprint("Could not register dns entry 2. ", resp.StatusCode)) return false } cfResult := CloudFlareResponse{} err = json.Unmarshal(body, &cfResult) if err != nil { - log15.Error("Could not parse DNS response.", "err", err, "code", resp.StatusCode, "body", string(body)) + log.Error("Could not parse DNS response.", "err", err, "code", resp.StatusCode, "body", string(body)) common.SendError(w, 500, fmt.Sprint("Could not parse DNS response. ", resp.StatusCode)) return false } @@ -76,6 +76,6 @@ func registerHost(w http.ResponseWriter, r *http.Request, app *App) bool { app.CloudFlareId = cfResult.Result.Id } - log15.Info("host registered successfully with cloudflare", "app", app) + log.Info("host registered successfully with cloudflare", "app", app) return true } diff --git a/gateway.pem b/gateway.pem new file mode 100644 index 000000000..7124fbd84 --- /dev/null +++ b/gateway.pem @@ -0,0 +1,23 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAhof/Rgb5YUGUCynxYAkbkSRQqIaaZem7g6r/lyePBk6IKzjia6QA+Ut+1KZt +uGrNXFR0THtfEWCFjIPBJfYLGmkG1gaYocWJUu/b3be3rKGlLWEuSpfHsYyLh4803QNU79Uu0ft8 +ODB4QJl54WImD1JKzAZyarDalyb+GKbnU5NAULBbTccbGFbNSwPwebvoK9G6Z8qWChPqsYAZxfyC +D2LBt0PANwB+haC6Rj0t99R6mtLRz/iKYaHz26d6UxSzNsXArJlhSCABHdQ71rbPkO0M9PvJrhfg +y+bLA4sMrHvOSjEDWGY+j1qqEYXSc/Rwe5SMd8kV7i902ks7PjcCZQIDAQABAoIBAHjvlHk9F71o +GE+Y2tV8Gn31aVS1++IVpW2NsMoO07HVsu836cLd4co5JcDAA+4+hHG1sf53AVU7sZJJdr5LWlvZ +gj2wHFGApBwcZ0f/OWxEu5n5vIVtwCRJtbyc7eaochhPShGVw2s3l0JrNXd4pcIsNfUG7qAeb8Jl +WRKMJ3OmoEMOz5M3scRypQKOulRjO6RMJCtbl4AntMYNF7cdWeuIJ3eaMD8HaYbkr1USrwGk65QC +mKdUcNl9k++Txuf7UtbRB0apFsMnAKRPUTU+9TPGwMsZSzszk8TClMNO1ALYKKY5mE+cPqrAl5gC +ZpPOf45oT+2lxktqq5u5N8XMFoECgYEA16eGE3inh23EVly5ARnowGgFtorZsI6XIJHkYsETRocf +GAvQrEMAuFWZy0n5TlNBfzPhHh9rUXWCKlbldgnDgWKpphPux3UXTqTUak+j6397rtEP85RfYqDm +QxtW0uOkKkZSGyXXEYBwTbCsQBH28VZsJEYVe+G3uXPcUu3WEZUCgYEAn7Mwgk/JP5wgYo4E3gga +fOi0/de11MV5ad337qfdUC1pf1ju9q2CyHaV3g6eo2OnynGZHYq5qlyLWoi/hTr6A+yMZSnQ15io +9ker4uyAX6DdVDmWK9uErwrqLAV+Q6HoVmxoyBbMihQW8TqX/5jZegxqipDW16+qOFxtPbZhmZEC +gYEAlD85UBFVOSggHC5Jj5Q8CGh55O62j0S2Z1Fjau/HTGh+24zjukelKxLNUo5br5hUIhmL26VF +pQ3emTR7MRWtLDii3uQ89ShtCUcOLrbovG86mwZkrNGGcMqi/+a/XOHYbKdCsh7lJcbhbMbS4oh2 +9ZivZpA3HJ4iKn6XKvsMebECgYEAhNWXU8zpqG9EwLVAdy5mWd92LG5wYDqhct2ejHQ0MayUQ8jF +e4l3bya0IbAnY+BQgKNcqKXrKTkw8G0uYLNdokXvwXW2sJ3abH/RCT+Ox/wWHSiJMJG3G6IIhfVL +wRW7G6ewwD22hGORcbU7GO8addo+BGPVUDJdc+PtOZeqNwECgYEAvLJQas3PKLL+qVmO9asvDtCl +tvOuvPFAuZBk6hLm9SSrFt5cCW+rulL8Kx1PxUk0C8LiJV8uwZIqQxE+gY5MkCfvt+xG1yurkywd +SCHOFr0m4gi8+XHvL4fmGzYPgHRi10kepSta5G8USigbcf5fOmw+Upa3qVnwot3CQdxmSfU= +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/glide.lock b/glide.lock index bc6d0939a..8de7295f0 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: a104a522dc2ba982d25101330bbd5d44778565128f0cb09f34061c167921046c -updated: 2016-06-15T17:17:05.21953321-07:00 +updated: 2016-06-17T14:27:42.156301621-07:00 imports: - name: github.com/amir/raidman version: 91c20f3f475cab75bb40ad7951d9bbdde357ade7 @@ -12,7 +12,7 @@ imports: subpackages: - statsd - name: github.com/dgrijalva/jwt-go - version: c04502f106d7c5b3fae17c5da49a1bbdd3006b3c + version: f0777076321ab64f6efc15a82d9d23b98539b943 - name: github.com/golang/protobuf version: 0c1f6d65b5a189c2250d10e71a5506f06f9fa0a0 subpackages: @@ -29,7 +29,7 @@ imports: - semaphore - serverutil - name: github.com/iron-io/go - version: 277190779e20ad6bff386a5f7f6a8009d73fa475 + version: 6b082f50f20cb0e0146bdf0ffbc6c07216eeea77 subpackages: - common - common/httpshutdown @@ -50,12 +50,14 @@ imports: version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 - name: github.com/mattn/go-isatty version: 56b76bdf51f7708750eac80fa38b952bb9f32639 +- name: github.com/Sirupsen/logrus + version: f3cfb454f4c209e6668c95216c4744b8fddb2356 - name: github.com/vmihailenco/bufio version: 24e7e48f60fc2d9e99e43c07485d9fff42051e66 - name: github.com/vrischmann/envconfig version: 9e6e1c4d3b73427d03118518603bb904d9c55236 - name: golang.org/x/net - version: 3f122ce3dbbe488b7e6a8bdb26f41edec852a40b + version: d7bf3545bb0dacf009c535b3d3fbf53ac0a339ab subpackages: - proxy - name: golang.org/x/sys diff --git a/models.go b/models.go index 682d62394..40cc173a2 100644 --- a/models.go +++ b/models.go @@ -17,11 +17,11 @@ type Route2 struct { // An app is that base object for api gateway type App struct { - Name string `json:"name"` - ProjectId string `json:"project_id"` - CloudFlareId string `json:"-"` - Dns string `json:"dns"` - Routes []Route3 `json:"routes"` + Name string `json:"name"` + ProjectId string `json:"project_id"` + CloudFlareId string `json:"-"` + Dns string `json:"dns"` + Routes map[string]*Route3 `json:"routes"` } // this is for the new api gateway diff --git a/router.go b/router.go index 4e1c43dbc..f9079de82 100644 --- a/router.go +++ b/router.go @@ -11,20 +11,19 @@ import ( "encoding/json" "flag" "fmt" - "github.com/gorilla/mux" - "github.com/iron-io/go/common" - "github.com/iron-io/golog" - "github.com/iron-io/iron_go/cache" - "github.com/iron-io/iron_go/worker" - "gopkg.in/inconshreveable/log15.v2" - "log" "math/rand" "net/http" "net/url" + "os" "reflect" - "runtime" "strings" "time" + + log "github.com/Sirupsen/logrus" + "github.com/gorilla/mux" + "github.com/iron-io/go/common" + "github.com/iron-io/iron_go/cache" + "github.com/iron-io/iron_go/worker" ) var config struct { @@ -82,11 +81,12 @@ func main() { common.LoadConfigFile(configFile, &config) // common.SetLogging(common.LoggingConfig{To: config.Logging.To, Level: config.Logging.Level, Prefix: config.Logging.Prefix}) - log.Println("config:", config) - golog.Infoln("Starting up router version", version) + // TODO: validate inputs, iron tokens, cloudflare stuff, etc + config.CloudFlare.Email = os.Getenv("CLOUDFLARE_EMAIL") + config.CloudFlare.AuthKey = os.Getenv("CLOUDFLARE_API_KEY") - runtime.GOMAXPROCS(runtime.NumCPU()) - log.Println("Running on", runtime.NumCPU(), "CPUs") + log.Println("config:", config) + log.Infoln("Starting up router version", version) r := mux.NewRouter() @@ -95,35 +95,34 @@ func main() { s2.Handle("/", &WorkerHandler{}) // dev: - fmt.Println("Setting /test subrouter") - s := r.PathPrefix("/test").Subrouter() + s := r.PathPrefix("/api").Subrouter() // production: // s := r.Host("router.irondns.info").Subrouter() - s.Handle("/1/projects/{project_id}/register", &Register{}) - s.Handle("/1/projects/{project_id}/apps", &NewApp{}) - s.HandleFunc("/1/projects/{project_id}/apps/{app_name}/routes", NewRoute) + // s.Handle("/1/projects/{project_id}/register", &Register{}) + s.Handle("/v1/apps", &NewApp{}) + s.HandleFunc("/v1/apps/{app_name}/routes", NewRoute) s.HandleFunc("/ping", Ping) s.HandleFunc("/version", Version) - s.Handle("/addworker", &WorkerHandler{}) + // s.Handle("/addworker", &WorkerHandler{}) s.HandleFunc("/", Ping) r.HandleFunc("/elb-ping-router", Ping) // for ELB health check // for testing app responses, pass in app name, can use localhost s4 := r.Queries("app", "").Subrouter() - s4.HandleFunc("/appsr", Ping) + // s4.HandleFunc("/appsr", Ping) s4.HandleFunc("/{rest:.*}", Run) s4.NotFoundHandler = http.HandlerFunc(Run) - s3 := r.Queries("rhost", "").Subrouter() - s3.HandleFunc("/", ProxyFunc2) + // s3 := r.Queries("rhost", "").Subrouter() + // s3.HandleFunc("/", ProxyFunc2) // This is where all the main incoming traffic goes r.NotFoundHandler = http.HandlerFunc(Run) http.Handle("/", r) port := 8080 - golog.Infoln("Router started, listening and serving on port", port) + log.Infoln("Router started, listening and serving on port", port) log.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", port), nil)) } @@ -133,10 +132,10 @@ func ProxyFunc2(w http.ResponseWriter, req *http.Request) { } func ProxyFunc(w http.ResponseWriter, req *http.Request) { - golog.Infoln("HOST:", req.Host) + log.Infoln("HOST:", req.Host) host := strings.Split(req.Host, ":")[0] rhost := req.FormValue("rhost") - golog.Infoln("rhost:", rhost) + log.Infoln("rhost:", rhost) if rhost != "" { host = rhost } @@ -146,10 +145,10 @@ func ProxyFunc(w http.ResponseWriter, req *http.Request) { // 2) This host has active workers so we do the proxy // 3) This host has no active workers so we queue one (or more) up and return a 503 or something with message that says "try again in a minute" // route := routingTable[host] - golog.Infoln("getting route for host:", host, "--") + log.Infoln("getting route for host:", host, "--") route, err := getRoute(host) - golog.Infoln("route:", route) - golog.Infoln("err:", err) + log.Infoln("route:", route) + log.Infoln("err:", err) if err != nil { common.SendError(w, 400, fmt.Sprintln("Host not registered or error!", err)) return @@ -165,13 +164,13 @@ func ProxyFunc(w http.ResponseWriter, req *http.Request) { func serveEndpoint(w http.ResponseWriter, req *http.Request, route *Route) { dlen := len(route.Destinations) if dlen == 0 { - golog.Infoln("No workers running, starting new task.") + log.Infoln("No workers running, starting new task.") startNewWorker(route) common.SendError(w, 500, fmt.Sprintln("No workers running, starting them up...")) return } if dlen < 3 { - golog.Infoln("Less than three workers running, starting a new task.") + log.Infoln("Less than three workers running, starting a new task.") startNewWorker(route) } destIndex := rand.Intn(dlen) @@ -181,21 +180,21 @@ func serveEndpoint(w http.ResponseWriter, req *http.Request, route *Route) { destUrl, err := url.Parse(destUrlString2) if err != nil { removeDestination(route, destIndex, w) - golog.Infoln("error!", err) + log.Infoln("error!", err) common.SendError(w, 500, fmt.Sprintln("Internal error occurred:", err)) return } // todo: check destination runtime and remove it if it's expired so we don't send requests to an endpoint that is about to be killed - golog.Infoln("proxying to", destUrl) + log.Infoln("proxying to", destUrl) proxy := NewSingleHostReverseProxy(destUrl) err = proxy.ServeHTTP(w, req) if err != nil { - golog.Infoln("Error proxying!", err) + log.Infoln("Error proxying!", err) etype := reflect.TypeOf(err) - golog.Infoln("err type:", etype) + log.Infoln("err type:", etype) // can't figure out how to compare types so comparing strings.... lame. if true || strings.Contains(etype.String(), "net.OpError") { // == reflect.TypeOf(net.OpError{}) { // couldn't figure out a better way to do this - golog.Infoln("It's a network error so we're going to remove destination.") + log.Infoln("It's a network error so we're going to remove destination.") removeDestination(route, destIndex, w) serveEndpoint(w, req, route) return @@ -203,28 +202,28 @@ func serveEndpoint(w http.ResponseWriter, req *http.Request, route *Route) { common.SendError(w, 500, fmt.Sprintln("Internal error occurred:", err)) return } - golog.Infoln("Served!") + log.Infoln("Served!") } func removeDestination(route *Route, destIndex int, w http.ResponseWriter) { - golog.Infoln("Removing destination", destIndex, "from route:", route) + log.Infoln("Removing destination", destIndex, "from route:", route) route.Destinations = append(route.Destinations[:destIndex], route.Destinations[destIndex+1:]...) err := putRoute(route) if err != nil { - golog.Infoln("Couldn't update routing table:", err) + log.Infoln("Couldn't update routing table:", err) common.SendError(w, 500, fmt.Sprintln("couldn't update routing table", err)) return } - golog.Infoln("New route after remove destination:", route) + log.Infoln("New route after remove destination:", route) // if len(route.Destinations) < 3 { - // golog.Infoln("After network error, there are less than three destinations, so starting a new one. ") + // log.Infoln("After network error, there are less than three destinations, so starting a new one. ") // // always want at least three running // startNewWorker(route) // } } func startNewWorker(route *Route) error { - golog.Infoln("Starting a new worker") + log.Infoln("Starting a new worker") // start new worker payload := map[string]interface{}{ "token": config.Iron.SuperToken, @@ -238,7 +237,7 @@ func startNewWorker(route *Route) error { workerapi.Settings.UseConfigMap(payload) jsonPayload, err := json.Marshal(payload) if err != nil { - golog.Infoln("Couldn't marshal json!", err) + log.Infoln("Couldn't marshal json!", err) return err } timeout := time.Second * time.Duration(1800+rand.Intn(600)) // a little random factor in here to spread out worker deaths @@ -250,9 +249,9 @@ func startNewWorker(route *Route) error { tasks := make([]worker.Task, 1) tasks[0] = task taskIds, err := workerapi.TaskQueue(tasks...) - golog.Infoln("Tasks queued.", taskIds) + log.Infoln("Tasks queued.", taskIds) if err != nil { - golog.Infoln("Couldn't queue up worker!", err) + log.Infoln("Couldn't queue up worker!", err) return err } return err @@ -267,13 +266,13 @@ func (r *Register) ServeHTTP(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) projectId := vars["project_id"] token := common.GetToken(req) - golog.Infoln("project_id:", projectId, "token:", token.Token) + log.Infoln("project_id:", projectId, "token:", token.Token) route := Route{} if !common.ReadJSON(w, req, &route) { return } - golog.Infoln("body read into route:", route) + log.Infoln("body read into route:", route) route.ProjectId = projectId route.Token = token.Token @@ -287,11 +286,11 @@ func (r *Register) ServeHTTP(w http.ResponseWriter, req *http.Request) { // todo: do we need to close body? err = putRoute(&route) if err != nil { - golog.Infoln("couldn't register host:", err) + log.Infoln("couldn't register host:", err) common.SendError(w, 400, fmt.Sprintln("Could not register host!", err)) return } - golog.Infoln("registered route:", route) + log.Infoln("registered route:", route) fmt.Fprintln(w, "Host registered successfully.") } @@ -304,13 +303,13 @@ func (r *NewApp) ServeHTTP(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) projectId := vars["project_id"] // token := common.GetToken(req) - golog.Infoln("project_id:", projectId) + log.Infoln("project_id:", projectId) app := App{} if !common.ReadJSON(w, req, &app) { return } - golog.Infoln("body read into app:", app) + log.Infoln("body read into app:", app) app.ProjectId = projectId _, err := getApp(app.Name) @@ -319,9 +318,11 @@ func (r *NewApp) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } + app.Routes = make(map[string]*Route3) + // create dns entry // TODO: Add project id to this. eg: appname.projectid.iron.computer - log15.Debug("Creating dns entry.") + log.Debug("Creating dns entry.") regOk := registerHost(w, req, &app) if !regOk { return @@ -330,11 +331,11 @@ func (r *NewApp) ServeHTTP(w http.ResponseWriter, req *http.Request) { // todo: do we need to close body? err = putApp(&app) if err != nil { - golog.Infoln("couldn't create app:", err) + log.Infoln("couldn't create app:", err) common.SendError(w, 400, fmt.Sprintln("Could not create app!", err)) return } - golog.Infoln("registered app:", app) + log.Infoln("registered app:", app) v := map[string]interface{}{"app": app} common.SendSuccess(w, "App created successfully.", v) } @@ -344,13 +345,13 @@ func NewRoute(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) projectId := vars["project_id"] appName := vars["app_name"] - golog.Infoln("project_id:", projectId, "app_name", appName) + log.Infoln("project_id:", projectId, "app_name", appName) - route := Route3{} + route := &Route3{} if !common.ReadJSON(w, req, &route) { return } - golog.Infoln("body read into route:", route) + log.Infoln("body read into route:", route) // TODO: validate route @@ -360,14 +361,19 @@ func NewRoute(w http.ResponseWriter, req *http.Request) { return } - app.Routes = append(app.Routes, route) + if route.Type == "" { + route.Type = "run" + } + + // app.Routes = append(app.Routes, route) + app.Routes[route.Path] = route err = putApp(app) if err != nil { - golog.Errorln("Couldn't create route!:", err) + log.Errorln("Couldn't create route!:", err) common.SendError(w, 400, fmt.Sprintln("Could not create route!", err)) return } - golog.Infoln("Route created:", route) + log.Infoln("Route created:", route) fmt.Fprintln(w, "Route created successfully.") } @@ -376,13 +382,13 @@ type WorkerHandler struct { // When a worker starts up, it calls this func (wh *WorkerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - golog.Infoln("AddWorker called!") + log.Infoln("AddWorker called!") // get project id and token projectId := req.FormValue("project_id") token := req.FormValue("token") // codeName := req.FormValue("code_name") - golog.Infoln("project_id:", projectId, "token:", token) + log.Infoln("project_id:", projectId, "token:", token) // check header for what operation to perform routerHeader := req.Header.Get("Iron-Router") @@ -394,28 +400,28 @@ func (wh *WorkerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } // todo: do we need to close body? - golog.Infoln("Incoming body from worker:", r2) + log.Infoln("Incoming body from worker:", r2) route, err := getRoute(r2.Host) if err != nil { common.SendError(w, 400, fmt.Sprintln("This host is not registered!", err)) return } - golog.Infoln("ROUTE:", route) + log.Infoln("ROUTE:", route) route.Destinations = append(route.Destinations, r2.Dest) - golog.Infoln("ROUTE new:", route) + log.Infoln("ROUTE new:", route) err = putRoute(route) if err != nil { - golog.Infoln("couldn't register host:", err) + log.Infoln("couldn't register host:", err) common.SendError(w, 400, fmt.Sprintln("Could not register host!", err)) return } fmt.Fprintln(w, "Worker added") - golog.Infoln("Worked added.") + log.Infoln("Worked added.") } } func getRoute(host string) (*Route, error) { - golog.Infoln("getRoute for host:", host) + log.Infoln("getRoute for host:", host) rx, err := icache.Get(host) if err != nil { return nil, err @@ -441,7 +447,7 @@ func putRoute(route *Route) error { } func getApp(name string) (*App, error) { - golog.Infoln("getapp:", name) + log.Infoln("getapp:", name) rx, err := icache.Get(name) if err != nil { return nil, err diff --git a/run.sh b/run.sh new file mode 100755 index 000000000..e69de29bb diff --git a/runner.go b/runner.go index b745f98e2..89b5c2ebd 100644 --- a/runner.go +++ b/runner.go @@ -4,8 +4,6 @@ import ( "bufio" "bytes" "fmt" - "github.com/iron-io/go/common" - "github.com/iron-io/golog" "io" "io/ioutil" "math/rand" @@ -13,7 +11,9 @@ import ( "os" "os/exec" "strings" + log "github.com/Sirupsen/logrus" + "github.com/iron-io/go/common" ) type RunningApp struct { @@ -31,15 +31,15 @@ func init() { func Run(w http.ResponseWriter, req *http.Request) { fmt.Println("RUN!!!!") - golog.Infoln("HOST:", req.Host) + log.Infoln("HOST:", req.Host) appName := req.FormValue("app") - golog.Infoln("app_name", appName, "path:", req.URL.Path) + log.Infoln("app_name", appName, "path:", req.URL.Path) if appName != "" { // passed in the name } else { host := strings.Split(req.Host, ":")[0] appName = strings.Split(host, ".")[0] - golog.Infoln("app_name from host", appName) + log.Infoln("app_name from host", appName) } app, err := getApp(appName) @@ -47,7 +47,7 @@ func Run(w http.ResponseWriter, req *http.Request) { common.SendError(w, 400, fmt.Sprintln("This app does not exist. Please create app first.", err)) return } - golog.Infoln("app", app) + log.Infoln("app", app) // find route for _, el := range app.Routes { @@ -60,11 +60,11 @@ func Run(w http.ResponseWriter, req *http.Request) { return } if el.Type == "app" { - DockerHost(&el, w) + DockerHost(el, w) return } else { // "run" // TODO: timeout 59 seconds - DockerRun(el.Image, w) + DockerRun(el.Image, w, req) return } } @@ -73,8 +73,18 @@ func Run(w http.ResponseWriter, req *http.Request) { } // TODO: use Docker utils from docker-job for this and a few others in here -func DockerRun(image string, w http.ResponseWriter) { - cmd := exec.Command("docker", "run", "--rm", "-i", image) +func DockerRun(image string, w http.ResponseWriter, req *http.Request) { + + payload, err := ioutil.ReadAll(req.Body) + if err != nil { + log.WithError(err).Errorln("Error reading request body") + return + } + log.WithField("payload", "---"+string(payload)+"---").Infoln("incoming request") + log.WithField("image", image).Infoln("About to run using this image") + + // TODO: swap all this out with Titan's running via API + cmd := exec.Command("docker", "run", "--rm", "-i", "-e", fmt.Sprintf("PAYLOAD=%v", string(payload)), image) stdout, err := cmd.StdoutPipe() if err != nil { log.Fatal(err) @@ -94,14 +104,18 @@ func DockerRun(image string, w http.ResponseWriter) { log.Printf("Waiting for command to finish...") if err = cmd.Wait(); err != nil { - // this probably shouldn't be fatal? test with iron/error image - log.Fatal(err) + // job failed + log.Infoln("job finished with err:", err) + log.WithFields(log.Fields{"metric": "run.errors", "value": 1, "type": "count"}).Infoln("failed run") + // TODO: wrap error in json "error": buff + } else { + log.Infoln("Docker ran successfully:", b.String()) + // print + log.WithFields(log.Fields{"metric": "run.success", "value": 1, "type": "count"}).Infoln("successful run") } - log.WithFields(log.Fields{"metric": "ran", "value": 1, "type": "count"}).Infoln("") - log.Printf("Command finished with error: %v", err) + log.WithFields(log.Fields{"metric": "run", "value": 1, "type": "count"}).Infoln("job ran") buff.Flush() - golog.Infoln("Docker ran successfully:", b.String()) - fmt.Fprintln(w, b.String()) + fmt.Fprintln(w, string(bytes.Trim(b.Bytes(), "\x00"))) } func DockerHost(el *Route3, w http.ResponseWriter) { @@ -120,8 +134,8 @@ func DockerHost(el *Route3, w http.ResponseWriter) { // TODO: Need to catch interrupt and stop all containers that are started, see devo/dj for how to do this if err := cmd.Start(); err != nil { log.Fatal(err) + // TODO: What if the app fails to start? Don't want to keep starting the container } - // TODO: What if the app fails to start? Don't want to keep starting the container } else { // TODO: check if it's still running? // TODO: if ra.terminated, then start new container? diff --git a/ssh.sh b/ssh.sh new file mode 100755 index 000000000..7909cc06e --- /dev/null +++ b/ssh.sh @@ -0,0 +1,5 @@ +# ssh into the box on staging that the prototype is running on + +set -ex + +ssh -i gateway.pem rancher@ec2-52-205-252-82.compute-1.amazonaws.com