From 27ff50c47cfa78501582e6adc6b0bb59ceb50133 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Thu, 3 Sep 2015 16:53:45 +0900 Subject: [PATCH 01/34] Create router with new application --- irukad/controllers/app.go | 10 ++ registry/route.go | 204 ++++++++++++++++++++++++++++++++++++++ schema/route.go | 26 +++++ 3 files changed, 240 insertions(+) create mode 100644 registry/route.go create mode 100644 schema/route.go diff --git a/irukad/controllers/app.go b/irukad/controllers/app.go index 0832593..3927a77 100644 --- a/irukad/controllers/app.go +++ b/irukad/controllers/app.go @@ -41,6 +41,16 @@ func (c *AppController) Create(rw http.ResponseWriter, r *http.Request) { return } + ropts := schema.RouteCreateOpts{ + Location: "/.*", + Upstream: app.ID.String(), + } + + if _, err := c.Registry.CreateRoute(app.ID.String(), ropts); err != nil { + c.JSON(rw, http.StatusInternalServerError, "error") + return + } + c.JSON(rw, http.StatusCreated, app) } diff --git a/registry/route.go b/registry/route.go new file mode 100644 index 0000000..d66212e --- /dev/null +++ b/registry/route.go @@ -0,0 +1,204 @@ +package registry + +import ( + "errors" + "path" + "sort" + "time" + + "code.google.com/p/go-uuid/uuid" + + "github.com/spesnova/iruka/schema" +) + +const ( + routePrefix = "routes" +) + +type Routes []schema.Route + +// imprement the sort interface +func (r Routes) Len() int { + return len(r) +} + +func (r Routes) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +func (r Routes) Less(i, j int) bool { + return r[i].ID.String() < r[j].ID.String() +} + +func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) (schema.Route, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Route{}, err + } + + if opts.Location == "" { + return schema.Route{}, errors.New("location parameter is required, but missing") + } + + if opts.Upstream == "" { + return schema.Route{}, errors.New("upstream parameter is required, but missing") + } + + id := uuid.NewUUID() + currentTime := time.Now() + route := schema.Route{ + ID: id, + Location: opts.Location, + Upstream: opts.Upstream, + CreatedAt: currentTime, + UpdatedAt: currentTime, + } + + j, err := marshal(route) + + if err != nil { + return schema.Route{}, err + } + + key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, route.ID.String()) + + if _, err = r.etcd.Create(key, string(j), 0); err != nil { + return schema.Route{}, err + } + + return route, nil +} + +func (r *Registry) DestroyRoute(appIdentity, routeID string) (schema.Route, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Route{}, err + } + + route, err := r.Route(appIdentity, routeID) + + if err != nil { + return schema.Route{}, err + } + + key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, routeID) + _, err = r.etcd.Delete(key, true) + + if err != nil { + if isKeyNotFound(err) { + err = nil + } + return schema.Route{}, err + } + + return route, nil +} + +func (r *Registry) Route(appIdentity, routeID string) (schema.Route, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Route{}, err + } + + key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, routeID) + res, err := r.etcd.Get(key, false, true) + + if err != nil { + if isKeyNotFound(err) { + err = nil + } + + return schema.Route{}, err + } + + var route schema.Route + err = unmarshal(res.Node.Value, &route) + + if err != nil { + return schema.Route{}, err + } + + return route, nil +} + +func (r *Registry) Routes(appIdentity string) ([]schema.Route, error) { + app, err := r.App(appIdentity) + + if err != nil { + return nil, err + } + + key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix) + res, err := r.etcd.Get(key, false, true) + + if err != nil { + if isKeyNotFound(err) { + err = nil + } + return nil, err + } + + if len(res.Node.Nodes) == 0 { + return nil, nil + } + + var routes Routes + + for _, node := range res.Node.Nodes { + var route schema.Route + err = unmarshal(node.Value, &route) + + if err != nil { + return nil, err + } + + routes = append(routes, route) + } + + sort.Sort(sort.Reverse(routes)) + + return routes, nil +} + +func (r *Registry) UpdateRoute(appIdentity string, routeID string, opts schema.RouteUpdateOpts) (schema.Route, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Route{}, err + } + + route, err := r.Route(appIdentity, routeID) + + if err != nil { + return schema.Route{}, err + } + + if opts.ID.String() == "" { + return schema.Route{}, errors.New("id parameter is required, but missing") + } + + if opts.Location != "" { + route.Location = opts.Location + } + + if opts.Upstream != "" { + route.Upstream = opts.Upstream + } + + j, err := marshal(route) + + if err != nil { + return schema.Route{}, err + } + + key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, routeID) + + if _, err := r.etcd.Set(key, string(j), 0); err != nil { + return schema.Route{}, err + } + + return route, nil +} diff --git a/schema/route.go b/schema/route.go new file mode 100644 index 0000000..8f2d0d9 --- /dev/null +++ b/schema/route.go @@ -0,0 +1,26 @@ +package schema + +import ( + "time" + + "code.google.com/p/go-uuid/uuid" +) + +type Route struct { + ID uuid.UUID `json:"id"` + Location string `json:"location"` + Upstream string `json:"upstream"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type RouteCreateOpts struct { + Location string `json:"location"` + Upstream string `json:"upstream"` +} + +type RouteUpdateOpts struct { + ID uuid.UUID `json:"id"` + Location string `json:"location"` + Upstream string `json:"upstream"` +} From e6c20827bddd9df06b7fa373be07873325e67ca8 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Thu, 3 Sep 2015 18:13:04 +0900 Subject: [PATCH 02/34] Create initial route correctly --- registry/route.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/registry/route.go b/registry/route.go index d66212e..67bcc8b 100644 --- a/registry/route.go +++ b/registry/route.go @@ -31,11 +31,13 @@ func (r Routes) Less(i, j int) bool { } func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) (schema.Route, error) { - app, err := r.App(appIdentity) + // TODO(dtan4): + // etcd does not reflect inserted item immediately... + // app, err := r.App(appIdentity) - if err != nil { - return schema.Route{}, err - } + // if err != nil { + // return schema.Route{}, err + // } if opts.Location == "" { return schema.Route{}, errors.New("location parameter is required, but missing") @@ -61,7 +63,8 @@ func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) return schema.Route{}, err } - key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, route.ID.String()) + // key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), route.ID.String()) + key := path.Join(r.keyPrefix, routePrefix, appIdentity, route.ID.String()) if _, err = r.etcd.Create(key, string(j), 0); err != nil { return schema.Route{}, err @@ -83,7 +86,7 @@ func (r *Registry) DestroyRoute(appIdentity, routeID string) (schema.Route, erro return schema.Route{}, err } - key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, routeID) + key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), routeID) _, err = r.etcd.Delete(key, true) if err != nil { @@ -103,7 +106,7 @@ func (r *Registry) Route(appIdentity, routeID string) (schema.Route, error) { return schema.Route{}, err } - key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, routeID) + key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), routeID) res, err := r.etcd.Get(key, false, true) if err != nil { @@ -131,7 +134,7 @@ func (r *Registry) Routes(appIdentity string) ([]schema.Route, error) { return nil, err } - key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix) + key := path.Join(r.keyPrefix, routePrefix, app.ID.String()) res, err := r.etcd.Get(key, false, true) if err != nil { @@ -194,7 +197,7 @@ func (r *Registry) UpdateRoute(appIdentity string, routeID string, opts schema.R return schema.Route{}, err } - key := path.Join(r.keyPrefix, appPrefix, app.ID.String(), routePrefix, routeID) + key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), routeID) if _, err := r.etcd.Set(key, string(j), 0); err != nil { return schema.Route{}, err From d929d474b4a56d627c27e4ec758d30699a8bdcf9 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Thu, 3 Sep 2015 18:35:31 +0900 Subject: [PATCH 03/34] Create domain with new application --- irukad/controllers/app.go | 16 +++- registry/domain.go | 190 ++++++++++++++++++++++++++++++++++++++ schema/domain.go | 23 +++++ 3 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 registry/domain.go create mode 100644 schema/domain.go diff --git a/irukad/controllers/app.go b/irukad/controllers/app.go index 3927a77..b97796a 100644 --- a/irukad/controllers/app.go +++ b/irukad/controllers/app.go @@ -3,6 +3,7 @@ package controllers import ( "encoding/json" "net/http" + "os" "github.com/gorilla/mux" "github.com/unrolled/render" @@ -41,12 +42,25 @@ func (c *AppController) Create(rw http.ResponseWriter, r *http.Request) { return } + dopts := schema.DomainCreateOpts{ + Hostname: app.ID.String() + "." + os.Getenv("DEFAULT_DOMAIN"), + } + + _, err = c.Registry.CreateDomain(app.ID.String(), dopts) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, "error") + return + } + ropts := schema.RouteCreateOpts{ Location: "/.*", Upstream: app.ID.String(), } - if _, err := c.Registry.CreateRoute(app.ID.String(), ropts); err != nil { + _, err = c.Registry.CreateRoute(app.ID.String(), ropts) + + if err != nil { c.JSON(rw, http.StatusInternalServerError, "error") return } diff --git a/registry/domain.go b/registry/domain.go new file mode 100644 index 0000000..2e35b11 --- /dev/null +++ b/registry/domain.go @@ -0,0 +1,190 @@ +package registry + +import ( + "errors" + "path" + "sort" + "time" + + "code.google.com/p/go-uuid/uuid" + + "github.com/spesnova/iruka/schema" +) + +const ( + domainPrefix = "domains" +) + +type Domains []schema.Domain + +// imprement the sort interface +func (d Domains) Len() int { + return len(d) +} + +func (d Domains) Swap(i, j int) { + d[i], d[j] = d[j], d[i] +} + +func (d Domains) Less(i, j int) bool { + return d[i].ID.String() < d[j].ID.String() +} + +func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts) (schema.Domain, error) { + if opts.Hostname == "" { + return schema.Domain{}, errors.New("hostname parameter is required, but missing") + } + + id := uuid.NewUUID() + currentTime := time.Now() + domain := schema.Domain{ + ID: id, + Hostname: opts.Hostname, + CreatedAt: currentTime, + UpdatedAt: currentTime, + } + + j, err := marshal(domain) + + if err != nil { + return schema.Domain{}, err + } + + key := path.Join(r.keyPrefix, domainPrefix, appIdentity, domain.ID.String()) + _, err = r.etcd.Create(key, string(j), 0) + + if err != nil { + return schema.Domain{}, err + } + + return domain, nil +} + +func (r *Registry) DestroyDomain(appIdentity, domainID string) (schema.Domain, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Domain{}, err + } + + domain, err := r.Domain(appIdentity, domainID) + + if err != nil { + return schema.Domain{}, err + } + + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domainID) + _, err = r.etcd.Delete(key, true) + + if err != nil { + if isKeyNotFound(err) { + err = nil + } + return schema.Domain{}, err + } + + return domain, nil +} + +func (r *Registry) Domain(appIdentity, domainID string) (schema.Domain, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Domain{}, err + } + + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domainID) + res, err := r.etcd.Get(key, false, true) + + if err != nil { + if isKeyNotFound(err) { + err = nil + } + + return schema.Domain{}, err + } + + var domain schema.Domain + err = unmarshal(res.Node.Value, &domain) + + if err != nil { + return schema.Domain{}, err + } + + return domain, nil +} + +func (r *Registry) Domains(appIdentity string) ([]schema.Domain, error) { + app, err := r.App(appIdentity) + + if err != nil { + return nil, err + } + + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String()) + res, err := r.etcd.Get(key, false, true) + + if err != nil { + if isKeyNotFound(err) { + err = nil + } + return nil, err + } + + if len(res.Node.Nodes) == 0 { + return nil, nil + } + + var domains Domains + + for _, node := range res.Node.Nodes { + var domain schema.Domain + err = unmarshal(node.Value, &domain) + + if err != nil { + return nil, err + } + + domains = append(domains, domain) + } + + sort.Sort(sort.Reverse(domains)) + + return domains, nil +} + +func (r *Registry) UpdateDomain(appIdentity string, domainID string, opts schema.DomainUpdateOpts) (schema.Domain, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Domain{}, err + } + + domain, err := r.Domain(appIdentity, domainID) + + if err != nil { + return schema.Domain{}, err + } + + if opts.ID.String() == "" { + return schema.Domain{}, errors.New("id parameter is required, but missing") + } + + if opts.Hostname != "" { + domain.Hostname = opts.Hostname + } + + j, err := marshal(domain) + + if err != nil { + return schema.Domain{}, err + } + + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domainID) + + if _, err := r.etcd.Set(key, string(j), 0); err != nil { + return schema.Domain{}, err + } + + return domain, nil +} diff --git a/schema/domain.go b/schema/domain.go new file mode 100644 index 0000000..fdd5cc7 --- /dev/null +++ b/schema/domain.go @@ -0,0 +1,23 @@ +package schema + +import ( + "time" + + "code.google.com/p/go-uuid/uuid" +) + +type Domain struct { + ID uuid.UUID `json:"id"` + Hostname string `json:"hostname"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type DomainCreateOpts struct { + Hostname string `json:"hostname"` +} + +type DomainUpdateOpts struct { + ID uuid.UUID `json:"id"` + Hostname string `json:"hostname"` +} From 242baf3e519c47a7eb83d4ea6b8296ee14c6bbf5 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Thu, 3 Sep 2015 18:48:57 +0900 Subject: [PATCH 04/34] Register routing information for Vulcand --- irukad/controllers/app.go | 17 ++++-- irukad/iruka.go | 15 +++-- router/router.go | 115 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 router/router.go diff --git a/irukad/controllers/app.go b/irukad/controllers/app.go index b97796a..2cdba4b 100644 --- a/irukad/controllers/app.go +++ b/irukad/controllers/app.go @@ -9,16 +9,18 @@ import ( "github.com/unrolled/render" "github.com/spesnova/iruka/registry" + "github.com/spesnova/iruka/router" "github.com/spesnova/iruka/schema" ) type AppController struct { *registry.Registry *render.Render + *router.Router } -func NewAppController(reg *registry.Registry, ren *render.Render) AppController { - return AppController{reg, ren} +func NewAppController(reg *registry.Registry, ren *render.Render, rou *router.Router) AppController { + return AppController{reg, ren, rou} } func (c *AppController) Create(rw http.ResponseWriter, r *http.Request) { @@ -46,7 +48,7 @@ func (c *AppController) Create(rw http.ResponseWriter, r *http.Request) { Hostname: app.ID.String() + "." + os.Getenv("DEFAULT_DOMAIN"), } - _, err = c.Registry.CreateDomain(app.ID.String(), dopts) + domain, err := c.Registry.CreateDomain(app.ID.String(), dopts) if err != nil { c.JSON(rw, http.StatusInternalServerError, "error") @@ -58,7 +60,14 @@ func (c *AppController) Create(rw http.ResponseWriter, r *http.Request) { Upstream: app.ID.String(), } - _, err = c.Registry.CreateRoute(app.ID.String(), ropts) + route, err := c.Registry.CreateRoute(app.ID.String(), ropts) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, "error") + return + } + + err = c.Router.AddRoute(domain.Hostname, route.ID.String(), route.Location, route.Upstream) if err != nil { c.JSON(rw, http.StatusInternalServerError, "error") diff --git a/irukad/iruka.go b/irukad/iruka.go index ce25acf..bc333f1 100644 --- a/irukad/iruka.go +++ b/irukad/iruka.go @@ -11,13 +11,16 @@ import ( "github.com/spesnova/iruka/agent" "github.com/spesnova/iruka/irukad/controllers" "github.com/spesnova/iruka/registry" + "github.com/spesnova/iruka/router" "github.com/spesnova/iruka/scheduler" ) func main() { // Registry - machines := registry.DefaultMachines - reg := registry.NewRegistry(machines, registry.DefaultKeyPrefix) + reg := registry.NewRegistry(registry.DefaultMachines, registry.DefaultKeyPrefix) + + // Sub-domain router + rou := router.NewRouter(router.DefaultMachines, router.DefaultKeyPrefix) // Scheduler url := scheduler.DefaultAPIURL @@ -34,13 +37,13 @@ func main() { ren := render.New() // Controllers - appController := controllers.NewAppController(reg, ren) + appController := controllers.NewAppController(reg, ren, rou) containerController := controllers.NewContainerController(reg, ren, sch) configVarsController := controllers.NewConfigVarsController(reg, ren) // Router - rou := mux.NewRouter() - v1rou := rou.PathPrefix("/api/v1-alpha").Subrouter() + muxRou := mux.NewRouter() + v1rou := muxRou.PathPrefix("/api/v1-alpha").Subrouter() // App Resource v1rou.Path("/apps").Methods("POST").HandlerFunc(appController.Create) @@ -68,7 +71,7 @@ func main() { negroni.NewLogger(), ) - n.UseHandler(rou) + n.UseHandler(muxRou) go age.Pulse() // Disable retrieving unit state from fleet for now diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..4cc130b --- /dev/null +++ b/router/router.go @@ -0,0 +1,115 @@ +package router + +import ( + "os" + "path" + "strings" + + "github.com/coreos/go-etcd/etcd" +) + +const ( + DefaultKeyPrefix = "/vulcand" + DefaultMachines = "http://localhost:4001" +) + +type Router struct { + etcd etcd.Client + keyPrefix string +} + +func NewRouter(machines, keyPrefix string) *Router { + if os.Getenv("IRUKA_ETCD_MACHINES") != "" { + machines = os.Getenv("IRUKA_ETCD_MACHINES") + } + + if machines == "" { + machines = DefaultMachines + } + + m := strings.Split(machines, ",") + etcdClient := *etcd.NewClient(m) + return &Router{etcdClient, keyPrefix} +} + +func (r *Router) AddRoute(host, location, locPath, upstream string) error { + pKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "path") + _, err := r.etcd.Create(pKey, locPath, 0) + + if err != nil { + return err + } + + uKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "upstream") + _, err = r.etcd.Create(uKey, upstream, 0) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) RemoveRoute(host, location string) error { + key := path.Join(r.keyPrefix, "hosts", host, "locations", location) + _, err := r.etcd.Delete(key, true) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) UpdateRoute(host, location, locPath, upstream string) error { + if locPath != "" { + pKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "path") + _, err := r.etcd.Set(pKey, locPath, 0) + if err != nil { + return err + } + } + + if upstream != "" { + uKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "upstream") + _, err := r.etcd.Set(uKey, upstream, 0) + if err != nil { + return err + } + } + + return nil +} + +func (r *Router) RemoveHost(host string) error { + key := path.Join(r.keyPrefix, "hosts", host) + _, err := r.etcd.Delete(key, true) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) AddUpstream(upstream string) error { + key := path.Join(r.keyPrefix, "upstreams", upstream) + _, err := r.etcd.SetDir(key, 0) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) RemoveUpstream(upstream string) error { + key := path.Join(r.keyPrefix, "upstreams", upstream) + _, err := r.etcd.Delete(key, true) + + if err != nil { + return err + } + + return nil +} From 7701449cc0fa96acf15d3e5c6d62ca31bb6ee218 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 14:53:24 +0900 Subject: [PATCH 05/34] Create DomainController --- irukad/controllers/domain.go | 122 +++++++++++++++++++++++++++++++++++ irukad/iruka.go | 8 +++ 2 files changed, 130 insertions(+) create mode 100644 irukad/controllers/domain.go diff --git a/irukad/controllers/domain.go b/irukad/controllers/domain.go new file mode 100644 index 0000000..7d3037f --- /dev/null +++ b/irukad/controllers/domain.go @@ -0,0 +1,122 @@ +package controllers + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/unrolled/render" + + "github.com/spesnova/iruka/registry" + "github.com/spesnova/iruka/schema" +) + +type DomainController struct { + reg *registry.Registry + *render.Render +} + +func NewDomainController(reg *registry.Registry, ren *render.Render) DomainController { + return DomainController{reg, ren} +} + +func (c *DomainController) Create(rw http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + vars := mux.Vars(r) + appIdentity := vars["appIdentity"] + + var opts schema.DomainCreateOpts + err := json.NewDecoder(r.Body).Decode(&opts) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + domain, err := c.reg.CreateDomain(appIdentity, opts) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + c.JSON(rw, http.StatusCreated, domain) +} + +func (c *DomainController) Delete(rw http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + appIdentity := vars["appIdentity"] + identity := vars["identity"] + + domain, err := c.reg.DestroyDomain(appIdentity, identity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + c.JSON(rw, http.StatusAccepted, domain) +} + +func (c *DomainController) Info(rw http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + appIdentity := vars["appIdentity"] + identity := vars["identity"] + + domain, err := c.reg.Domain(appIdentity, identity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + c.JSON(rw, http.StatusOK, domain) +} + +func (c *DomainController) List(rw http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + appIdentity := vars["appIdentity"] + + domains, err := c.reg.Domains(appIdentity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + return + } + + if domains == nil { + c.JSON(rw, http.StatusOK, []schema.Domain{}) + return + } + + c.JSON(rw, http.StatusOK, domains) +} + +func (c *DomainController) Update(rw http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + vars := mux.Vars(r) + appIdentity := vars["appIdentity"] + identity := vars["identity"] + + var opts schema.DomainUpdateOpts + err := json.NewDecoder(r.Body).Decode(&opts) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + return + } + + domain, err := c.reg.Domain(appIdentity, identity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + return + } + + domain, err = c.reg.UpdateDomain(appIdentity, identity, opts) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(rw, http.StatusAccepted, domain) +} diff --git a/irukad/iruka.go b/irukad/iruka.go index bc333f1..5711aad 100644 --- a/irukad/iruka.go +++ b/irukad/iruka.go @@ -40,6 +40,7 @@ func main() { appController := controllers.NewAppController(reg, ren, rou) containerController := controllers.NewContainerController(reg, ren, sch) configVarsController := controllers.NewConfigVarsController(reg, ren) + domainController := controllers.NewDomainController(reg, ren) // Router muxRou := mux.NewRouter() @@ -65,6 +66,13 @@ func main() { v1subrou.Path("/config-vars").Methods("GET").HandlerFunc(configVarsController.Info) v1subrou.Path("/config-vars").Methods("PATCH").HandlerFunc(configVarsController.Update) + // Domain Resource + v1subrou.Path("/domains").Methods("POST").HandlerFunc(domainController.Create) + v1subrou.Path("/domains/{identity}").Methods("DELETE").HandlerFunc(domainController.Delete) + v1subrou.Path("/domains/{identity}").Methods("GET").HandlerFunc(domainController.Info) + v1subrou.Path("/domains").Methods("GET").HandlerFunc(domainController.List) + v1subrou.Path("/domains/{identity}").Methods("PATCH").HandlerFunc(domainController.Update) + // Middleware stack n := negroni.New( negroni.NewRecovery(), From d8742fc362a0c5c42ac9db613dadc2a668e293a1 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 15:36:58 +0900 Subject: [PATCH 06/34] Do not track iruka artifacts --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 789538f..46665ba 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,6 @@ gin-bin # iruka artifacts # artifacts -iruka +iruka/iruka +irukad/irukad iruka.yml From 0d55ca91d086c9d02068ff868aef70a3b24b5e7c Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 15:39:48 +0900 Subject: [PATCH 07/34] Implement `iruka domain list` --- client/domain.go | 10 ++++++ iruka/commands.go | 1 + iruka/domain.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 client/domain.go create mode 100644 iruka/domain.go diff --git a/client/domain.go b/client/domain.go new file mode 100644 index 0000000..400c80e --- /dev/null +++ b/client/domain.go @@ -0,0 +1,10 @@ +package iruka + +import ( + "github.com/spesnova/iruka/schema" +) + +func (c *Client) DomainList(appIdentity string) ([]schema.Domain, error) { + var domainsRes []schema.Domain + return domainsRes, c.Get(&domainsRes, "/apps/"+appIdentity+"/domains") +} diff --git a/iruka/commands.go b/iruka/commands.go index f3ad8f2..6ffe284 100644 --- a/iruka/commands.go +++ b/iruka/commands.go @@ -9,6 +9,7 @@ var Commands = []cli.Command{ cmdCreate, cmdConfig, cmdDestroy, + cmdDomain, cmdPs, cmdDeploy, cmdOpen, diff --git a/iruka/domain.go b/iruka/domain.go new file mode 100644 index 0000000..af176f0 --- /dev/null +++ b/iruka/domain.go @@ -0,0 +1,90 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/codegangsta/cli" + + _ "github.com/wantedly/kujira/schema" +) + +var cmdDomain = cli.Command{ + Name: "domain", + Usage: "Manage domains (add, list, remove)", + Subcommands: []cli.Command{ + cmdDomainList, + cmdDomainAdd, + cmdDomainRemove, + }, +} + +var cmdDomainList = cli.Command{ + Name: "list", + Usage: "List custom domains for the app", + Description: ` + $ iruka domain list + +EXAMPLE: + + $ iruka domain list + example.com +`, + Action: runDomainList, +} + +var cmdDomainAdd = cli.Command{ + Name: "add", + Usage: "Add a custom domain to the app", + Description: ` + $ iruka domain add + +EXAMPLE: + + $ iruka domain add example.com + Adding example.com to example... done +`, + Action: runDomainAdd, +} + +var cmdDomainRemove = cli.Command{ + Name: "remove", + Usage: "Remove a custom domain from the app", + Description: ` + $ iruka domain remove + +EXAMPLE: + + $ iruka domain remove example.com + Removing example.com from example... done +`, + Action: runDomainRemove, +} + +func runDomainList(c *cli.Context) { + appIdentity := getAppIdentity(c) + domains, err := client.DomainList(appIdentity) + + if err != nil { + fmt.Println("error") + fmt.Println(err) + os.Exit(1) + } + + for _, d := range domains { + var f []string + f = append(f, d.Hostname) + fmt.Fprintln(out, strings.Join(f, "\t")) + } + + out.Flush() +} + +func runDomainAdd(c *cli.Context) { + +} + +func runDomainRemove(c *cli.Context) { + +} From 1f9c38143334945c98909766031b3de94241aa80 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 18:10:57 +0900 Subject: [PATCH 08/34] Implement `iruka domain add` --- client/domain.go | 5 +++++ iruka/domain.go | 21 ++++++++++++++++++++- registry/domain.go | 8 +++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/client/domain.go b/client/domain.go index 400c80e..7668710 100644 --- a/client/domain.go +++ b/client/domain.go @@ -4,6 +4,11 @@ import ( "github.com/spesnova/iruka/schema" ) +func (c *Client) DomainCreate(appIdentity string, opts schema.DomainCreateOpts) (schema.Domain, error) { + var domainRes schema.Domain + return domainRes, c.Post(&domainRes, "/apps/"+appIdentity+"/domains", opts) +} + func (c *Client) DomainList(appIdentity string) ([]schema.Domain, error) { var domainsRes []schema.Domain return domainsRes, c.Get(&domainsRes, "/apps/"+appIdentity+"/domains") diff --git a/iruka/domain.go b/iruka/domain.go index af176f0..ce0f683 100644 --- a/iruka/domain.go +++ b/iruka/domain.go @@ -7,7 +7,7 @@ import ( "github.com/codegangsta/cli" - _ "github.com/wantedly/kujira/schema" + "github.com/spesnova/iruka/schema" ) var cmdDomain = cli.Command{ @@ -82,7 +82,26 @@ func runDomainList(c *cli.Context) { } func runDomainAdd(c *cli.Context) { + if len(c.Args()) < 1 { + cli.ShowCommandHelp(c, "add") + os.Exit(1) + } + + appIdentity := getAppIdentity(c) + hostname := c.Args().First() + opts := schema.DomainCreateOpts{ + Hostname: hostname, + } + + fmt.Printf("Adding %s to %s...", hostname, appIdentity) + _, err := client.DomainCreate(appIdentity, opts) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("done") } func runDomainRemove(c *cli.Context) { diff --git a/registry/domain.go b/registry/domain.go index 2e35b11..e2830a4 100644 --- a/registry/domain.go +++ b/registry/domain.go @@ -31,6 +31,12 @@ func (d Domains) Less(i, j int) bool { } func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts) (schema.Domain, error) { + app, err := r.App(appIdentity) + + if err != nil { + return schema.Domain{}, err + } + if opts.Hostname == "" { return schema.Domain{}, errors.New("hostname parameter is required, but missing") } @@ -50,7 +56,7 @@ func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts return schema.Domain{}, err } - key := path.Join(r.keyPrefix, domainPrefix, appIdentity, domain.ID.String()) + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domain.ID.String()) _, err = r.etcd.Create(key, string(j), 0) if err != nil { From 4f100bfb0fdcb42288ddd5950d9d119af1dadbf3 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 18:15:43 +0900 Subject: [PATCH 09/34] Flatten domain CLI commands --- iruka/commands.go | 3 +- iruka/domain-add.go | 51 +++++++++++++++++++++ iruka/domain.go | 109 -------------------------------------------- iruka/domains.go | 42 +++++++++++++++++ 4 files changed, 95 insertions(+), 110 deletions(-) create mode 100644 iruka/domain-add.go delete mode 100644 iruka/domain.go create mode 100644 iruka/domains.go diff --git a/iruka/commands.go b/iruka/commands.go index 6ffe284..65fd69d 100644 --- a/iruka/commands.go +++ b/iruka/commands.go @@ -9,7 +9,8 @@ var Commands = []cli.Command{ cmdCreate, cmdConfig, cmdDestroy, - cmdDomain, + cmdDomains, + cmdDomainAdd, cmdPs, cmdDeploy, cmdOpen, diff --git a/iruka/domain-add.go b/iruka/domain-add.go new file mode 100644 index 0000000..76cb436 --- /dev/null +++ b/iruka/domain-add.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "os" + + "github.com/codegangsta/cli" + + "github.com/spesnova/iruka/schema" +) + +var cmdDomainAdd = cli.Command{ + Name: "domain-add", + Usage: "Add a custom domain to the app", + Description: ` + $ iruka domain- add + +EXAMPLE: + + $ iruka domain-add example.com + Adding example.com to example... done +`, + Action: runDomainAdd, +} + +func runDomainAdd(c *cli.Context) { + if len(c.Args()) < 1 { + cli.ShowCommandHelp(c, "add") + os.Exit(1) + } + + appIdentity := getAppIdentity(c) + hostname := c.Args().First() + opts := schema.DomainCreateOpts{ + Hostname: hostname, + } + + fmt.Printf("Adding %s to %s...", hostname, appIdentity) + _, err := client.DomainCreate(appIdentity, opts) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Println("done") +} + +func runDomainRemove(c *cli.Context) { + +} diff --git a/iruka/domain.go b/iruka/domain.go deleted file mode 100644 index ce0f683..0000000 --- a/iruka/domain.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/codegangsta/cli" - - "github.com/spesnova/iruka/schema" -) - -var cmdDomain = cli.Command{ - Name: "domain", - Usage: "Manage domains (add, list, remove)", - Subcommands: []cli.Command{ - cmdDomainList, - cmdDomainAdd, - cmdDomainRemove, - }, -} - -var cmdDomainList = cli.Command{ - Name: "list", - Usage: "List custom domains for the app", - Description: ` - $ iruka domain list - -EXAMPLE: - - $ iruka domain list - example.com -`, - Action: runDomainList, -} - -var cmdDomainAdd = cli.Command{ - Name: "add", - Usage: "Add a custom domain to the app", - Description: ` - $ iruka domain add - -EXAMPLE: - - $ iruka domain add example.com - Adding example.com to example... done -`, - Action: runDomainAdd, -} - -var cmdDomainRemove = cli.Command{ - Name: "remove", - Usage: "Remove a custom domain from the app", - Description: ` - $ iruka domain remove - -EXAMPLE: - - $ iruka domain remove example.com - Removing example.com from example... done -`, - Action: runDomainRemove, -} - -func runDomainList(c *cli.Context) { - appIdentity := getAppIdentity(c) - domains, err := client.DomainList(appIdentity) - - if err != nil { - fmt.Println("error") - fmt.Println(err) - os.Exit(1) - } - - for _, d := range domains { - var f []string - f = append(f, d.Hostname) - fmt.Fprintln(out, strings.Join(f, "\t")) - } - - out.Flush() -} - -func runDomainAdd(c *cli.Context) { - if len(c.Args()) < 1 { - cli.ShowCommandHelp(c, "add") - os.Exit(1) - } - - appIdentity := getAppIdentity(c) - hostname := c.Args().First() - opts := schema.DomainCreateOpts{ - Hostname: hostname, - } - - fmt.Printf("Adding %s to %s...", hostname, appIdentity) - _, err := client.DomainCreate(appIdentity, opts) - - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - fmt.Println("done") -} - -func runDomainRemove(c *cli.Context) { - -} diff --git a/iruka/domains.go b/iruka/domains.go new file mode 100644 index 0000000..4326d93 --- /dev/null +++ b/iruka/domains.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/codegangsta/cli" +) + +var cmdDomains = cli.Command{ + Name: "domains", + Usage: "List custom domains for the app", + Description: ` + $ iruka domains + +EXAMPLE: + + $ iruka domains + example.com +`, + Action: runDomains, +} + +func runDomains(c *cli.Context) { + appIdentity := getAppIdentity(c) + domains, err := client.DomainList(appIdentity) + + if err != nil { + fmt.Println("error") + fmt.Println(err) + os.Exit(1) + } + + for _, d := range domains { + var f []string + f = append(f, d.Hostname) + fmt.Fprintln(out, strings.Join(f, "\t")) + } + + out.Flush() +} From 1d0078f43c7254b871c8b0a898fec2792a687fb5 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 18:34:51 +0900 Subject: [PATCH 10/34] Fetch domain whichever ID or hostname --- registry/domain.go | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/registry/domain.go b/registry/domain.go index e2830a4..724d294 100644 --- a/registry/domain.go +++ b/registry/domain.go @@ -92,32 +92,30 @@ func (r *Registry) DestroyDomain(appIdentity, domainID string) (schema.Domain, e return domain, nil } -func (r *Registry) Domain(appIdentity, domainID string) (schema.Domain, error) { - app, err := r.App(appIdentity) - - if err != nil { - return schema.Domain{}, err - } +func (r *Registry) Domain(appIdentity, domainIdentity string) (schema.Domain, error) { + var domain schema.Domain - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domainID) - res, err := r.etcd.Get(key, false, true) + domains, err := r.Domains(appIdentity) if err != nil { - if isKeyNotFound(err) { - err = nil - } - - return schema.Domain{}, err + return domain, err } - var domain schema.Domain - err = unmarshal(res.Node.Value, &domain) - - if err != nil { - return schema.Domain{}, err + if uuid.Parse(domainIdentity) == nil { + for _, domain := range domains { + if domain.Hostname == domainIdentity { + return domain, nil + } + } + } else { + for _, domain := range domains { + if uuid.Equal(domain.ID, uuid.Parse(domainIdentity)) { + return domain, nil + } + } } - return domain, nil + return domain, errors.New("No such domain: " + domainIdentity) } func (r *Registry) Domains(appIdentity string) ([]schema.Domain, error) { From efc99f7014f961b7cb59b16412c254878f0622b1 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 18:39:29 +0900 Subject: [PATCH 11/34] Use domainIdentity --- registry/domain.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/registry/domain.go b/registry/domain.go index 724d294..267e998 100644 --- a/registry/domain.go +++ b/registry/domain.go @@ -66,20 +66,20 @@ func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts return domain, nil } -func (r *Registry) DestroyDomain(appIdentity, domainID string) (schema.Domain, error) { +func (r *Registry) DestroyDomain(appIdentity, domainIdentity string) (schema.Domain, error) { app, err := r.App(appIdentity) if err != nil { return schema.Domain{}, err } - domain, err := r.Domain(appIdentity, domainID) + domain, err := r.Domain(appIdentity, domainIdentity) if err != nil { return schema.Domain{}, err } - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domainID) + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domain.ID.String()) _, err = r.etcd.Delete(key, true) if err != nil { @@ -157,14 +157,14 @@ func (r *Registry) Domains(appIdentity string) ([]schema.Domain, error) { return domains, nil } -func (r *Registry) UpdateDomain(appIdentity string, domainID string, opts schema.DomainUpdateOpts) (schema.Domain, error) { +func (r *Registry) UpdateDomain(appIdentity string, domainIdentity string, opts schema.DomainUpdateOpts) (schema.Domain, error) { app, err := r.App(appIdentity) if err != nil { return schema.Domain{}, err } - domain, err := r.Domain(appIdentity, domainID) + domain, err := r.Domain(appIdentity, domainIdentity) if err != nil { return schema.Domain{}, err @@ -184,7 +184,7 @@ func (r *Registry) UpdateDomain(appIdentity string, domainID string, opts schema return schema.Domain{}, err } - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domainID) + key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domain.ID.String()) if _, err := r.etcd.Set(key, string(j), 0); err != nil { return schema.Domain{}, err From c8150fad280e0f9b15a28e83ca8add7161265a1b Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 18:39:43 +0900 Subject: [PATCH 12/34] Implement `iruka domain-remove` --- client/domain.go | 4 ++++ iruka/commands.go | 1 + iruka/domain-add.go | 4 ---- iruka/domain-remove.go | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 iruka/domain-remove.go diff --git a/client/domain.go b/client/domain.go index 7668710..9bb8fc0 100644 --- a/client/domain.go +++ b/client/domain.go @@ -9,6 +9,10 @@ func (c *Client) DomainCreate(appIdentity string, opts schema.DomainCreateOpts) return domainRes, c.Post(&domainRes, "/apps/"+appIdentity+"/domains", opts) } +func (c *Client) DomainDelete(appIdentity, domainIdentity string) error { + return c.Delete("/apps/" + appIdentity + "/domains/" + domainIdentity) +} + func (c *Client) DomainList(appIdentity string) ([]schema.Domain, error) { var domainsRes []schema.Domain return domainsRes, c.Get(&domainsRes, "/apps/"+appIdentity+"/domains") diff --git a/iruka/commands.go b/iruka/commands.go index 65fd69d..daa54fc 100644 --- a/iruka/commands.go +++ b/iruka/commands.go @@ -11,6 +11,7 @@ var Commands = []cli.Command{ cmdDestroy, cmdDomains, cmdDomainAdd, + cmdDomainRemove, cmdPs, cmdDeploy, cmdOpen, diff --git a/iruka/domain-add.go b/iruka/domain-add.go index 76cb436..91cf694 100644 --- a/iruka/domain-add.go +++ b/iruka/domain-add.go @@ -45,7 +45,3 @@ func runDomainAdd(c *cli.Context) { fmt.Println("done") } - -func runDomainRemove(c *cli.Context) { - -} diff --git a/iruka/domain-remove.go b/iruka/domain-remove.go new file mode 100644 index 0000000..c7aa6d4 --- /dev/null +++ b/iruka/domain-remove.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "os" + + "github.com/codegangsta/cli" +) + +var cmdDomainRemove = cli.Command{ + Name: "domain-remove", + Usage: "Remove a custom domain from the app", + Description: ` + $ iruka domain-remove + +EXAMPLE: + + $ iruka domain-remove example.com + Removing example.com from example... done +`, + Action: runDomainRemove, +} + +func runDomainRemove(c *cli.Context) { + appIdentity := getAppIdentity(c) + hostname := c.Args().First() + + fmt.Printf("Removing %s from %s...", hostname, appIdentity) + err := client.DomainDelete(appIdentity, hostname) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Println("done") +} From e1b67c76320c73ff3a40a807a91012cb5ce0fe69 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 19:15:15 +0900 Subject: [PATCH 13/34] Write JSON Schema and doc about domain --- docs/api-v1-alpha.md | 194 +++++++++++++++++++++++++++++++++++++ schema/schema.json | 129 ++++++++++++++++++++++++ schema/schemata/domain.yml | 90 +++++++++++++++++ script/build | 4 +- 4 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 schema/schemata/domain.yml diff --git a/docs/api-v1-alpha.md b/docs/api-v1-alpha.md index 5d3c726..3f63fc6 100644 --- a/docs/api-v1-alpha.md +++ b/docs/api-v1-alpha.md @@ -565,4 +565,198 @@ HTTP/1.1 200 OK ``` +## Domain + +Domain attached to an app on iruka. + +### Attributes + +| Name | Type | Description | Example | +| ------- | ------- | ------- | ------- | +| **created_at** | *date-time* | when domain was created | `"2012-01-01T12:00:00Z"` | +| **id** | *uuid* | unique identifier of domain | `"01234567-89ab-cdef-0123-456789abcdef"` | +| **hostname** | *string* | domain hostname | `"example.com"` | +| **updated_at** | *date-time* | when domain was updated | `"2012-01-01T12:00:00Z"` | + +### Domain Create + +Create a new domain. + +``` +POST /apps/{app_id_or_name}/domains +``` + +#### Optional Parameters + +| Name | Type | Description | Example | +| ------- | ------- | ------- | ------- | +| **hostname** | *string* | domain hostname | `"example.com"` | + + +#### Curl Example + +```bash +$ curl -n -X POST https://.com/api/v1-alpha/apps/$APP_ID_OR_NAME/domains \ + -H "Content-Type: application/json" \ + \ + -d '{ + "hostname": "example.com" +}' +``` + + +#### Response Example + +``` +HTTP/1.1 201 Created +``` + +```json +{ + "created_at": "2012-01-01T12:00:00Z", + "id": "01234567-89ab-cdef-0123-456789abcdef", + "hostname": "example.com", + "updated_at": "2012-01-01T12:00:00Z" +} +``` + +### Domain Delete + +Delete an existing domain. + +``` +DELETE /apps/{app_id_or_name}/domains/{domain_id_or_hostname} +``` + + +#### Curl Example + +```bash +$ curl -n -X DELETE https://.com/api/v1-alpha/apps/$APP_ID_OR_NAME/domains/$DOMAIN_ID_OR_HOSTNAME \ + -H "Content-Type: application/json" \ +``` + + +#### Response Example + +``` +HTTP/1.1 200 OK +``` + +```json +{ + "created_at": "2012-01-01T12:00:00Z", + "id": "01234567-89ab-cdef-0123-456789abcdef", + "hostname": "example.com", + "updated_at": "2012-01-01T12:00:00Z" +} +``` + +### Domain Info + +Info for existing domain. + +``` +GET /apps/{app_id_or_name}/domains/{domain_id_or_hostname} +``` + + +#### Curl Example + +```bash +$ curl -n https://.com/api/v1-alpha/apps/$APP_ID_OR_NAME/domains/$DOMAIN_ID_OR_HOSTNAME +``` + + +#### Response Example + +``` +HTTP/1.1 200 OK +``` + +```json +{ + "created_at": "2012-01-01T12:00:00Z", + "id": "01234567-89ab-cdef-0123-456789abcdef", + "hostname": "example.com", + "updated_at": "2012-01-01T12:00:00Z" +} +``` + +### Domain List + +List existing domains. + +``` +GET /apps/{app_id_or_name}/domains +``` + + +#### Curl Example + +```bash +$ curl -n https://.com/api/v1-alpha/apps/$APP_ID_OR_NAME/domains +``` + + +#### Response Example + +``` +HTTP/1.1 200 OK +``` + +```json +[ + { + "created_at": "2012-01-01T12:00:00Z", + "id": "01234567-89ab-cdef-0123-456789abcdef", + "hostname": "example.com", + "updated_at": "2012-01-01T12:00:00Z" + } +] +``` + +### Domain Update + +Update an existing domain. + +``` +PATCH /domains/{domain_id_or_hostname} +``` + +#### Optional Parameters + +| Name | Type | Description | Example | +| ------- | ------- | ------- | ------- | +| **hostname** | *string* | domain hostname | `"example.com"` | + + +#### Curl Example + +```bash +$ curl -n -X PATCH https://.com/api/v1-alpha/domains/$DOMAIN_ID_OR_HOSTNAME \ + -H "Content-Type: application/json" \ + \ + -d '{ + "hostname": "example.com" +}' +``` + + +#### Response Example + +``` +HTTP/1.1 200 OK +``` + +```json +{ + "created_at": "2012-01-01T12:00:00Z", + "id": "01234567-89ab-cdef-0123-456789abcdef", + "hostname": "example.com", + "updated_at": "2012-01-01T12:00:00Z" +} +``` + + diff --git a/schema/schema.json b/schema/schema.json index 797bfe2..20f8bb4 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -467,6 +467,132 @@ } } }, + "domain": { + "$schema": "http://json-schema.org/draft-04/hyper-schema", + "title": "Domain", + "description": "Domain attached to an app on iruka.", + "stability": "prototype", + "strictProperties": true, + "type": [ + "object" + ], + "definitions": { + "identity": { + "anyOf": [ + { + "$ref": "#/definitions/domain/definitions/id" + }, + { + "$ref": "#/definitions/domain/definitions/hostname" + } + ] + }, + "id": { + "description": "unique identifier of domain", + "example": "01234567-89ab-cdef-0123-456789abcdef", + "readOnly": true, + "format": "uuid", + "type": [ + "string" + ] + }, + "hostname": { + "description": "domain hostname", + "example": "example.com", + "readOnly": true, + "type": [ + "string" + ] + }, + "created_at": { + "description": "when domain was created", + "example": "2012-01-01T12:00:00Z", + "format": "date-time", + "type": [ + "string" + ] + }, + "updated_at": { + "description": "when domain was updated", + "example": "2012-01-01T12:00:00Z", + "format": "date-time", + "type": [ + "string" + ] + } + }, + "links": [ + { + "description": "Create a new domain.", + "href": "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fidentity)}/domains", + "method": "POST", + "rel": "create", + "schema": { + "properties": { + "hostname": { + "$ref": "#/definitions/domain/definitions/hostname" + } + }, + "type": [ + "object" + ] + }, + "title": "Create" + }, + { + "description": "Delete an existing domain.", + "href": "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fidentity)}/domains/{(%23%2Fdefinitions%2Fdomain%2Fdefinitions%2Fidentity)}", + "method": "DELETE", + "rel": "destroy", + "title": "Delete" + }, + { + "description": "Info for existing domain.", + "href": "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fidentity)}/domains/{(%23%2Fdefinitions%2Fdomain%2Fdefinitions%2Fidentity)}", + "method": "GET", + "rel": "self", + "title": "Info" + }, + { + "description": "List existing domains.", + "href": "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fidentity)}/domains", + "method": "GET", + "rel": "instances", + "title": "List" + }, + { + "description": "Update an existing domain.", + "href": "/domains/{(%23%2Fdefinitions%2Fdomain%2Fdefinitions%2Fidentity)}", + "method": "PATCH", + "rel": "update", + "schema": { + "properties": { + "hostname": { + "$ref": "#/definitions/domain/definitions/hostname" + } + }, + "type": [ + "object" + ] + }, + "title": "Update" + } + ], + "properties": { + "created_at": { + "$ref": "#/definitions/domain/definitions/created_at" + }, + "id": { + "$ref": "#/definitions/domain/definitions/id" + }, + "hostname": { + "$ref": "#/definitions/domain/definitions/hostname" + }, + "updated_at": { + "$ref": "#/definitions/domain/definitions/updated_at" + } + } + }, "error": { "$schema": "http://json-schema.org/draft-04/hyper-schema", "title": "Errors", @@ -531,6 +657,9 @@ "container": { "$ref": "#/definitions/container" }, + "domain": { + "$ref": "#/definitions/domain" + }, "error": { "$ref": "#/definitions/error" } diff --git a/schema/schemata/domain.yml b/schema/schemata/domain.yml new file mode 100644 index 0000000..f590bba --- /dev/null +++ b/schema/schemata/domain.yml @@ -0,0 +1,90 @@ +--- +"$schema": http://json-schema.org/draft-04/hyper-schema +title: Domain +description: Domain attached to an app on iruka. +stability: prototype +strictProperties: true +type: +- object +definitions: + identity: + anyOf: + - "$ref": "/schemata/domain#/definitions/id" + - "$ref": "/schemata/domain#/definitions/hostname" + id: + description: unique identifier of domain + example: 01234567-89ab-cdef-0123-456789abcdef + readOnly: true + format: uuid + type: + - string + hostname: + description: domain hostname + example: example.com + readOnly: true + type: + - string + created_at: + description: when domain was created + example: '2012-01-01T12:00:00Z' + format: date-time + type: + - string + updated_at: + description: when domain was updated + example: '2012-01-01T12:00:00Z' + format: date-time + type: + - string +links: +- description: Create a new domain. + href: "/apps/{(%2Fschemata%2Fapp%23%2Fdefinitions%2Fidentity)}/domains" + method: POST + rel: create + schema: + properties: { + "hostname": { + "$ref": "/schemata/domain#/definitions/hostname" + } + } + type: + - object + title: Create +- description: Delete an existing domain. + href: "/apps/{(%2Fschemata%2Fapp%23%2Fdefinitions%2Fidentity)}/domains/{(%2Fschemata%2Fdomain%23%2Fdefinitions%2Fidentity)}" + method: DELETE + rel: destroy + title: Delete +- description: Info for existing domain. + href: "/apps/{(%2Fschemata%2Fapp%23%2Fdefinitions%2Fidentity)}/domains/{(%2Fschemata%2Fdomain%23%2Fdefinitions%2Fidentity)}" + method: GET + rel: self + title: Info +- description: List existing domains. + href: "/apps/{(%2Fschemata%2Fapp%23%2Fdefinitions%2Fidentity)}/domains" + method: GET + rel: instances + title: List +- description: Update an existing domain. + href: "/domains/{(%2Fschemata%2Fdomain%23%2Fdefinitions%2Fidentity)}" + method: PATCH + rel: update + schema: + properties: { + "hostname": { + "$ref": "/schemata/domain#/definitions/hostname" + } + } + type: + - object + title: Update +properties: + created_at: + "$ref": "/schemata/domain#/definitions/created_at" + id: + "$ref": "/schemata/domain#/definitions/id" + hostname: + "$ref": "/schemata/domain#/definitions/hostname" + updated_at: + "$ref": "/schemata/domain#/definitions/updated_at" +id: schemata/domain diff --git a/script/build b/script/build index 94347dd..775fc11 100755 --- a/script/build +++ b/script/build @@ -15,10 +15,10 @@ fi echo "==> Generating docs..." cd $BASE_DIRECTORY -bin/prmd combine \ +bundle exec prmd combine \ --meta schema/meta.yml \ schema/schemata/ > schema/schema.json -bin/prmd doc \ +bundle exec prmd doc \ --prepend docs/api-v1-alpha-header.md \ schema/schema.json > docs/api-v1-alpha.md echo " Successfully generated" From 0030d71831cc3480b676665b2a00f32ae50aa8b7 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 19:19:01 +0900 Subject: [PATCH 14/34] Remove update domain API --- docs/api-v1-alpha.md | 42 ------------------------------------ irukad/controllers/domain.go | 32 --------------------------- irukad/iruka.go | 1 - registry/domain.go | 36 ------------------------------- schema/domain.go | 5 ----- schema/schema.json | 17 --------------- schema/schemata/domain.yml | 13 ----------- 7 files changed, 146 deletions(-) diff --git a/docs/api-v1-alpha.md b/docs/api-v1-alpha.md index 3f63fc6..cad9352 100644 --- a/docs/api-v1-alpha.md +++ b/docs/api-v1-alpha.md @@ -716,47 +716,5 @@ HTTP/1.1 200 OK ] ``` -### Domain Update - -Update an existing domain. - -``` -PATCH /domains/{domain_id_or_hostname} -``` - -#### Optional Parameters - -| Name | Type | Description | Example | -| ------- | ------- | ------- | ------- | -| **hostname** | *string* | domain hostname | `"example.com"` | - - -#### Curl Example - -```bash -$ curl -n -X PATCH https://.com/api/v1-alpha/domains/$DOMAIN_ID_OR_HOSTNAME \ - -H "Content-Type: application/json" \ - \ - -d '{ - "hostname": "example.com" -}' -``` - - -#### Response Example - -``` -HTTP/1.1 200 OK -``` - -```json -{ - "created_at": "2012-01-01T12:00:00Z", - "id": "01234567-89ab-cdef-0123-456789abcdef", - "hostname": "example.com", - "updated_at": "2012-01-01T12:00:00Z" -} -``` - diff --git a/irukad/controllers/domain.go b/irukad/controllers/domain.go index 7d3037f..b65abff 100644 --- a/irukad/controllers/domain.go +++ b/irukad/controllers/domain.go @@ -88,35 +88,3 @@ func (c *DomainController) List(rw http.ResponseWriter, r *http.Request) { c.JSON(rw, http.StatusOK, domains) } - -func (c *DomainController) Update(rw http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - - vars := mux.Vars(r) - appIdentity := vars["appIdentity"] - identity := vars["identity"] - - var opts schema.DomainUpdateOpts - err := json.NewDecoder(r.Body).Decode(&opts) - - if err != nil { - c.JSON(rw, http.StatusInternalServerError, err.Error()) - return - } - - domain, err := c.reg.Domain(appIdentity, identity) - - if err != nil { - c.JSON(rw, http.StatusInternalServerError, err.Error()) - return - } - - domain, err = c.reg.UpdateDomain(appIdentity, identity, opts) - - if err != nil { - c.JSON(rw, http.StatusInternalServerError, err.Error()) - return - } - - c.JSON(rw, http.StatusAccepted, domain) -} diff --git a/irukad/iruka.go b/irukad/iruka.go index 5711aad..56ef565 100644 --- a/irukad/iruka.go +++ b/irukad/iruka.go @@ -71,7 +71,6 @@ func main() { v1subrou.Path("/domains/{identity}").Methods("DELETE").HandlerFunc(domainController.Delete) v1subrou.Path("/domains/{identity}").Methods("GET").HandlerFunc(domainController.Info) v1subrou.Path("/domains").Methods("GET").HandlerFunc(domainController.List) - v1subrou.Path("/domains/{identity}").Methods("PATCH").HandlerFunc(domainController.Update) // Middleware stack n := negroni.New( diff --git a/registry/domain.go b/registry/domain.go index 267e998..631c312 100644 --- a/registry/domain.go +++ b/registry/domain.go @@ -156,39 +156,3 @@ func (r *Registry) Domains(appIdentity string) ([]schema.Domain, error) { return domains, nil } - -func (r *Registry) UpdateDomain(appIdentity string, domainIdentity string, opts schema.DomainUpdateOpts) (schema.Domain, error) { - app, err := r.App(appIdentity) - - if err != nil { - return schema.Domain{}, err - } - - domain, err := r.Domain(appIdentity, domainIdentity) - - if err != nil { - return schema.Domain{}, err - } - - if opts.ID.String() == "" { - return schema.Domain{}, errors.New("id parameter is required, but missing") - } - - if opts.Hostname != "" { - domain.Hostname = opts.Hostname - } - - j, err := marshal(domain) - - if err != nil { - return schema.Domain{}, err - } - - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domain.ID.String()) - - if _, err := r.etcd.Set(key, string(j), 0); err != nil { - return schema.Domain{}, err - } - - return domain, nil -} diff --git a/schema/domain.go b/schema/domain.go index fdd5cc7..435edd1 100644 --- a/schema/domain.go +++ b/schema/domain.go @@ -16,8 +16,3 @@ type Domain struct { type DomainCreateOpts struct { Hostname string `json:"hostname"` } - -type DomainUpdateOpts struct { - ID uuid.UUID `json:"id"` - Hostname string `json:"hostname"` -} diff --git a/schema/schema.json b/schema/schema.json index 20f8bb4..28a0db9 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -559,23 +559,6 @@ "method": "GET", "rel": "instances", "title": "List" - }, - { - "description": "Update an existing domain.", - "href": "/domains/{(%23%2Fdefinitions%2Fdomain%2Fdefinitions%2Fidentity)}", - "method": "PATCH", - "rel": "update", - "schema": { - "properties": { - "hostname": { - "$ref": "#/definitions/domain/definitions/hostname" - } - }, - "type": [ - "object" - ] - }, - "title": "Update" } ], "properties": { diff --git a/schema/schemata/domain.yml b/schema/schemata/domain.yml index f590bba..0c83020 100644 --- a/schema/schemata/domain.yml +++ b/schema/schemata/domain.yml @@ -65,19 +65,6 @@ links: method: GET rel: instances title: List -- description: Update an existing domain. - href: "/domains/{(%2Fschemata%2Fdomain%23%2Fdefinitions%2Fidentity)}" - method: PATCH - rel: update - schema: - properties: { - "hostname": { - "$ref": "/schemata/domain#/definitions/hostname" - } - } - type: - - object - title: Update properties: created_at: "$ref": "/schemata/domain#/definitions/created_at" From 64822f0775496b4a65a7cfe333439a0d33bd5ef5 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 19:49:01 +0900 Subject: [PATCH 15/34] Reconstruct registry of domain --- irukad/controllers/domain.go | 12 ++++-- registry/domain.go | 80 +++++++++++++++++++++++++----------- schema/domain.go | 1 + 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/irukad/controllers/domain.go b/irukad/controllers/domain.go index b65abff..4bfd369 100644 --- a/irukad/controllers/domain.go +++ b/irukad/controllers/domain.go @@ -47,7 +47,13 @@ func (c *DomainController) Delete(rw http.ResponseWriter, r *http.Request) { appIdentity := vars["appIdentity"] identity := vars["identity"] - domain, err := c.reg.DestroyDomain(appIdentity, identity) + domain, err := c.reg.DomainFilteredByApp(appIdentity, identity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + _, err = c.reg.DestroyDomain(identity) if err != nil { c.JSON(rw, http.StatusInternalServerError, err.Error()) @@ -61,7 +67,7 @@ func (c *DomainController) Info(rw http.ResponseWriter, r *http.Request) { appIdentity := vars["appIdentity"] identity := vars["identity"] - domain, err := c.reg.Domain(appIdentity, identity) + domain, err := c.reg.DomainFilteredByApp(appIdentity, identity) if err != nil { c.JSON(rw, http.StatusInternalServerError, err.Error()) @@ -74,7 +80,7 @@ func (c *DomainController) List(rw http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) appIdentity := vars["appIdentity"] - domains, err := c.reg.Domains(appIdentity) + domains, err := c.reg.DomainsFilteredByApp(appIdentity) if err != nil { c.JSON(rw, http.StatusInternalServerError, err.Error()) diff --git a/registry/domain.go b/registry/domain.go index 631c312..6e927c7 100644 --- a/registry/domain.go +++ b/registry/domain.go @@ -45,6 +45,7 @@ func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts currentTime := time.Now() domain := schema.Domain{ ID: id, + AppID: app.ID, Hostname: opts.Hostname, CreatedAt: currentTime, UpdatedAt: currentTime, @@ -56,7 +57,7 @@ func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts return schema.Domain{}, err } - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domain.ID.String()) + key := path.Join(r.keyPrefix, domainPrefix, domain.ID.String()) _, err = r.etcd.Create(key, string(j), 0) if err != nil { @@ -66,66 +67,71 @@ func (r *Registry) CreateDomain(appIdentity string, opts schema.DomainCreateOpts return domain, nil } -func (r *Registry) DestroyDomain(appIdentity, domainIdentity string) (schema.Domain, error) { - app, err := r.App(appIdentity) - - if err != nil { - return schema.Domain{}, err - } - - domain, err := r.Domain(appIdentity, domainIdentity) +func (r *Registry) DestroyDomain(identity string) (schema.Domain, error) { + domain, err := r.Domain(identity) if err != nil { return schema.Domain{}, err } - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String(), domain.ID.String()) + key := path.Join(r.keyPrefix, domainPrefix, domain.ID.String()) _, err = r.etcd.Delete(key, true) if err != nil { - if isKeyNotFound(err) { - err = nil - } - return schema.Domain{}, err + return schema.Domain{}, errors.New("Failed to delete domain: " + domain.ID.String()) } return domain, nil } -func (r *Registry) Domain(appIdentity, domainIdentity string) (schema.Domain, error) { +func (r *Registry) Domain(identity string) (schema.Domain, error) { var domain schema.Domain - domains, err := r.Domains(appIdentity) + domains, err := r.Domains() if err != nil { return domain, err } - if uuid.Parse(domainIdentity) == nil { + if uuid.Parse(identity) == nil { for _, domain := range domains { - if domain.Hostname == domainIdentity { + if domain.Hostname == identity { return domain, nil } } } else { for _, domain := range domains { - if uuid.Equal(domain.ID, uuid.Parse(domainIdentity)) { + if uuid.Equal(domain.ID, uuid.Parse(identity)) { return domain, nil } } } - return domain, errors.New("No such domain: " + domainIdentity) + return domain, errors.New("No such domain: " + identity) } -func (r *Registry) Domains(appIdentity string) ([]schema.Domain, error) { +func (r *Registry) DomainFilteredByApp(appIdentity, identity string) (schema.Domain, error) { + domain, err := r.Domain(identity) + + if err != nil { + return schema.Domain{}, err + } + app, err := r.App(appIdentity) if err != nil { - return nil, err + return schema.Domain{}, err + } + + if uuid.Equal(domain.AppID, app.ID) { + return domain, nil } - key := path.Join(r.keyPrefix, domainPrefix, app.ID.String()) + return domain, errors.New("No such domain: " + identity) +} + +func (r *Registry) Domains() ([]schema.Domain, error) { + key := path.Join(r.keyPrefix, domainPrefix) res, err := r.etcd.Get(key, false, true) if err != nil { @@ -156,3 +162,31 @@ func (r *Registry) Domains(appIdentity string) ([]schema.Domain, error) { return domains, nil } + +func (r *Registry) DomainsFilteredByApp(appIdentity string) ([]schema.Domain, error) { + var domains []schema.Domain + + app, err := r.App(appIdentity) + + if err != nil { + return nil, err + } + + ds, err := r.Domains() + + if err != nil { + return nil, err + } + + if ds == nil { + return nil, nil + } + + for _, d := range ds { + if uuid.Equal(d.AppID, app.ID) { + domains = append(domains, d) + } + } + + return domains, nil +} diff --git a/schema/domain.go b/schema/domain.go index 435edd1..ba8bf8d 100644 --- a/schema/domain.go +++ b/schema/domain.go @@ -8,6 +8,7 @@ import ( type Domain struct { ID uuid.UUID `json:"id"` + AppID uuid.UUID `json:"app_id"` Hostname string `json:"hostname"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` From d2eae67e3cae553f5691f0538e3a459a81a53a64 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 19:55:40 +0900 Subject: [PATCH 16/34] Add domain.app_id in JSON Schema --- schema/schema.json | 9 +++++++++ schema/schemata/domain.yml | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/schema/schema.json b/schema/schema.json index 28a0db9..cdafcc0 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -496,6 +496,15 @@ "string" ] }, + "app_id": { + "description": "unique identifier of app the container is belong to", + "example": "01234567-89ab-cdef-0123-456789abcdef", + "readOnly": true, + "format": "uuid", + "type": [ + "string" + ] + }, "hostname": { "description": "domain hostname", "example": "example.com", diff --git a/schema/schemata/domain.yml b/schema/schemata/domain.yml index 0c83020..d3bfd67 100644 --- a/schema/schemata/domain.yml +++ b/schema/schemata/domain.yml @@ -18,6 +18,13 @@ definitions: format: uuid type: - string + app_id: + description: unique identifier of app the container is belong to + example: 01234567-89ab-cdef-0123-456789abcdef + readOnly: true + format: uuid + type: + - string hostname: description: domain hostname example: example.com From 011d9be3b647f5e89cc995730773beaf2386ef6f Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 20:26:08 +0900 Subject: [PATCH 17/34] Reconstruct registry of route --- registry/route.go | 107 +++++++++++++++++++++++++++------------------- schema/route.go | 1 + 2 files changed, 64 insertions(+), 44 deletions(-) diff --git a/registry/route.go b/registry/route.go index 67bcc8b..985ca7a 100644 --- a/registry/route.go +++ b/registry/route.go @@ -31,13 +31,11 @@ func (r Routes) Less(i, j int) bool { } func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) (schema.Route, error) { - // TODO(dtan4): - // etcd does not reflect inserted item immediately... - // app, err := r.App(appIdentity) + app, err := r.App(appIdentity) - // if err != nil { - // return schema.Route{}, err - // } + if err != nil { + return schema.Route{}, err + } if opts.Location == "" { return schema.Route{}, errors.New("location parameter is required, but missing") @@ -51,6 +49,7 @@ func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) currentTime := time.Now() route := schema.Route{ ID: id, + AppID: app.ID, Location: opts.Location, Upstream: opts.Upstream, CreatedAt: currentTime, @@ -63,8 +62,7 @@ func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) return schema.Route{}, err } - // key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), route.ID.String()) - key := path.Join(r.keyPrefix, routePrefix, appIdentity, route.ID.String()) + key := path.Join(r.keyPrefix, routePrefix, route.ID.String()) if _, err = r.etcd.Create(key, string(j), 0); err != nil { return schema.Route{}, err @@ -73,68 +71,67 @@ func (r *Registry) CreateRoute(appIdentity string, opts schema.RouteCreateOpts) return route, nil } -func (r *Registry) DestroyRoute(appIdentity, routeID string) (schema.Route, error) { - app, err := r.App(appIdentity) - - if err != nil { - return schema.Route{}, err - } - - route, err := r.Route(appIdentity, routeID) +func (r *Registry) DestroyRoute(identity string) (schema.Route, error) { + route, err := r.Route(identity) if err != nil { return schema.Route{}, err } - key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), routeID) + key := path.Join(r.keyPrefix, routePrefix, route.ID.String()) _, err = r.etcd.Delete(key, true) if err != nil { - if isKeyNotFound(err) { - err = nil - } - return schema.Route{}, err + return schema.Route{}, errors.New("Failed to delete route: " + route.ID.String()) } return route, nil } -func (r *Registry) Route(appIdentity, routeID string) (schema.Route, error) { - app, err := r.App(appIdentity) +func (r *Registry) Route(identity string) (schema.Route, error) { + var route schema.Route + + routes, err := r.Routes() if err != nil { - return schema.Route{}, err + return route, err } - key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), routeID) - res, err := r.etcd.Get(key, false, true) - - if err != nil { - if isKeyNotFound(err) { - err = nil + if uuid.Parse(identity) == nil { + return route, errors.New("No such route: " + identity) + } else { + for _, route := range routes { + if uuid.Equal(route.ID, uuid.Parse(identity)) { + return route, nil + } } - - return schema.Route{}, err } - var route schema.Route - err = unmarshal(res.Node.Value, &route) + return route, errors.New("No such route: " + identity) +} + +func (r *Registry) RouteFilteredByApp(appIdentity, identity string) (schema.Route, error) { + route, err := r.Route(identity) if err != nil { return schema.Route{}, err } - return route, nil -} - -func (r *Registry) Routes(appIdentity string) ([]schema.Route, error) { app, err := r.App(appIdentity) if err != nil { - return nil, err + return schema.Route{}, err + } + + if uuid.Equal(route.AppID, app.ID) { + return route, nil } - key := path.Join(r.keyPrefix, routePrefix, app.ID.String()) + return route, errors.New("No such route: " + identity) +} + +func (r *Registry) Routes() ([]schema.Route, error) { + key := path.Join(r.keyPrefix, routePrefix) res, err := r.etcd.Get(key, false, true) if err != nil { @@ -166,14 +163,36 @@ func (r *Registry) Routes(appIdentity string) ([]schema.Route, error) { return routes, nil } -func (r *Registry) UpdateRoute(appIdentity string, routeID string, opts schema.RouteUpdateOpts) (schema.Route, error) { +func (r *Registry) RoutesFilteredByApp(appIdentity string) ([]schema.Route, error) { + var routes []schema.Route + app, err := r.App(appIdentity) if err != nil { - return schema.Route{}, err + return nil, err } - route, err := r.Route(appIdentity, routeID) + rs, err := r.Routes() + + if err != nil { + return nil, err + } + + if rs == nil { + return nil, nil + } + + for _, r := range rs { + if uuid.Equal(r.AppID, app.ID) { + routes = append(routes, r) + } + } + + return routes, nil +} + +func (r *Registry) UpdateRoute(appIdentity, identity string, opts schema.RouteUpdateOpts) (schema.Route, error) { + route, err := r.RouteFilteredByApp(appIdentity, identity) if err != nil { return schema.Route{}, err @@ -197,7 +216,7 @@ func (r *Registry) UpdateRoute(appIdentity string, routeID string, opts schema.R return schema.Route{}, err } - key := path.Join(r.keyPrefix, routePrefix, app.ID.String(), routeID) + key := path.Join(r.keyPrefix, routePrefix, identity) if _, err := r.etcd.Set(key, string(j), 0); err != nil { return schema.Route{}, err diff --git a/schema/route.go b/schema/route.go index 8f2d0d9..ea10378 100644 --- a/schema/route.go +++ b/schema/route.go @@ -8,6 +8,7 @@ import ( type Route struct { ID uuid.UUID `json:"id"` + AppID uuid.UUID `json:"app_id"` Location string `json:"location"` Upstream string `json:"upstream"` CreatedAt time.Time `json:"created_at"` From b837a0a801e923b7d181bba87a4400c5b37d0ad7 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 20:36:20 +0900 Subject: [PATCH 18/34] Add new route to Vulcand after adding new domain --- irukad/controllers/domain.go | 34 ++++++++++++++++++++++++++++++++-- irukad/iruka.go | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/irukad/controllers/domain.go b/irukad/controllers/domain.go index 4bfd369..4f3c5d3 100644 --- a/irukad/controllers/domain.go +++ b/irukad/controllers/domain.go @@ -8,16 +8,18 @@ import ( "github.com/unrolled/render" "github.com/spesnova/iruka/registry" + "github.com/spesnova/iruka/router" "github.com/spesnova/iruka/schema" ) type DomainController struct { reg *registry.Registry *render.Render + rou *router.Router } -func NewDomainController(reg *registry.Registry, ren *render.Render) DomainController { - return DomainController{reg, ren} +func NewDomainController(reg *registry.Registry, ren *render.Render, rou *router.Router) DomainController { + return DomainController{reg, ren, rou} } func (c *DomainController) Create(rw http.ResponseWriter, r *http.Request) { @@ -33,6 +35,20 @@ func (c *DomainController) Create(rw http.ResponseWriter, r *http.Request) { c.JSON(rw, http.StatusInternalServerError, err.Error()) } + routes, err := c.reg.RoutesFilteredByApp(appIdentity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + for _, route := range routes { + err := c.rou.AddRoute(opts.Hostname, route.ID.String(), route.Location, route.Upstream) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + } + domain, err := c.reg.CreateDomain(appIdentity, opts) if err != nil { @@ -53,6 +69,20 @@ func (c *DomainController) Delete(rw http.ResponseWriter, r *http.Request) { c.JSON(rw, http.StatusInternalServerError, err.Error()) } + routes, err := c.reg.RoutesFilteredByApp(appIdentity) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + + for _, route := range routes { + err := c.rou.RemoveRoute(domain.Hostname, route.Location) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + } + _, err = c.reg.DestroyDomain(identity) if err != nil { diff --git a/irukad/iruka.go b/irukad/iruka.go index 56ef565..d6f81b6 100644 --- a/irukad/iruka.go +++ b/irukad/iruka.go @@ -40,7 +40,7 @@ func main() { appController := controllers.NewAppController(reg, ren, rou) containerController := controllers.NewContainerController(reg, ren, sch) configVarsController := controllers.NewConfigVarsController(reg, ren) - domainController := controllers.NewDomainController(reg, ren) + domainController := controllers.NewDomainController(reg, ren, rou) // Router muxRou := mux.NewRouter() From 155b4459b2f68fd2fe271a15fdbf9b19ff9540b4 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 23:36:51 +0900 Subject: [PATCH 19/34] Use the latest Vulcand --- irukad/controllers/app.go | 9 +++- irukad/controllers/domain.go | 41 ++++++++------ router/router.go | 101 ++++++++++++++++++++++++++++------- schema/vulcand.go | 15 ++++++ 4 files changed, 129 insertions(+), 37 deletions(-) create mode 100644 schema/vulcand.go diff --git a/irukad/controllers/app.go b/irukad/controllers/app.go index 2cdba4b..d288106 100644 --- a/irukad/controllers/app.go +++ b/irukad/controllers/app.go @@ -67,7 +67,14 @@ func (c *AppController) Create(rw http.ResponseWriter, r *http.Request) { return } - err = c.Router.AddRoute(domain.Hostname, route.ID.String(), route.Location, route.Upstream) + err = c.Router.AddBackend(app.ID.String()) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, "error") + return + } + + err = c.Router.AddRoute(app.ID.String(), domain.Hostname, route.Location) if err != nil { c.JSON(rw, http.StatusInternalServerError, "error") diff --git a/irukad/controllers/domain.go b/irukad/controllers/domain.go index 4f3c5d3..bf6a0df 100644 --- a/irukad/controllers/domain.go +++ b/irukad/controllers/domain.go @@ -35,20 +35,31 @@ func (c *DomainController) Create(rw http.ResponseWriter, r *http.Request) { c.JSON(rw, http.StatusInternalServerError, err.Error()) } - routes, err := c.reg.RoutesFilteredByApp(appIdentity) + app, err := c.reg.App(appIdentity) if err != nil { c.JSON(rw, http.StatusInternalServerError, err.Error()) } - for _, route := range routes { - err := c.rou.AddRoute(opts.Hostname, route.ID.String(), route.Location, route.Upstream) + routes, err := c.reg.RoutesFilteredByApp(appIdentity) - if err != nil { - c.JSON(rw, http.StatusInternalServerError, err.Error()) - } + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) } + // TODO(dtan4): + // Add multiple routes + route := routes[0] + err = c.rou.UpdateRoute(app.ID.String(), opts.Hostname, route.Location) + + // for _, route := range routes { + // err := c.rou.UpdateRoute(app.ID.String(), opts.Hostname, route.Location) + + // if err != nil { + // c.JSON(rw, http.StatusInternalServerError, err.Error()) + // } + // } + domain, err := c.reg.CreateDomain(appIdentity, opts) if err != nil { @@ -69,19 +80,17 @@ func (c *DomainController) Delete(rw http.ResponseWriter, r *http.Request) { c.JSON(rw, http.StatusInternalServerError, err.Error()) } - routes, err := c.reg.RoutesFilteredByApp(appIdentity) + // TODO(dtan4): + // Remove multiple routes + // routes, err := c.reg.RoutesFilteredByApp(appIdentity) - if err != nil { - c.JSON(rw, http.StatusInternalServerError, err.Error()) - } + // if err != nil { + // c.JSON(rw, http.StatusInternalServerError, err.Error()) + // } - for _, route := range routes { - err := c.rou.RemoveRoute(domain.Hostname, route.Location) + // for _, route := range routes { - if err != nil { - c.JSON(rw, http.StatusInternalServerError, err.Error()) - } - } + // } _, err = c.reg.DestroyDomain(identity) diff --git a/router/router.go b/router/router.go index 4cc130b..c142c08 100644 --- a/router/router.go +++ b/router/router.go @@ -1,11 +1,15 @@ package router import ( + "encoding/json" + "fmt" "os" "path" "strings" "github.com/coreos/go-etcd/etcd" + + "github.com/spesnova/iruka/schema" ) const ( @@ -32,16 +36,18 @@ func NewRouter(machines, keyPrefix string) *Router { return &Router{etcdClient, keyPrefix} } -func (r *Router) AddRoute(host, location, locPath, upstream string) error { - pKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "path") - _, err := r.etcd.Create(pKey, locPath, 0) +func (r *Router) AddBackend(name string) error { + backend := schema.VulcandBackend{ + Type: "http", + } + j, err := marshal(backend) if err != nil { return err } - uKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "upstream") - _, err = r.etcd.Create(uKey, upstream, 0) + key := path.Join(r.keyPrefix, "backends", name, "backend") + _, err = r.etcd.Create(key, string(j), 0) if err != nil { return err @@ -50,8 +56,8 @@ func (r *Router) AddRoute(host, location, locPath, upstream string) error { return nil } -func (r *Router) RemoveRoute(host, location string) error { - key := path.Join(r.keyPrefix, "hosts", host, "locations", location) +func (r *Router) RemoveBackend(name string) error { + key := path.Join(r.keyPrefix, "backends", name, "backend") _, err := r.etcd.Delete(key, true) if err != nil { @@ -61,21 +67,56 @@ func (r *Router) RemoveRoute(host, location string) error { return nil } -func (r *Router) UpdateRoute(host, location, locPath, upstream string) error { - if locPath != "" { - pKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "path") - _, err := r.etcd.Set(pKey, locPath, 0) - if err != nil { - return err - } +func (r *Router) AddRoute(name, host, location string) error { + frontend := schema.VulcandFrontend{ + Type: "http", + BackendID: name, + Route: routeString(host, location), + } + j, err := marshal(frontend) + + if err != nil { + return err + } + + key := path.Join(r.keyPrefix, "frontends", name, "frontend") + _, err = r.etcd.Create(key, j, 0) + + if err != nil { + return err } - if upstream != "" { - uKey := path.Join(r.keyPrefix, "hosts", host, "locations", location, "upstream") - _, err := r.etcd.Set(uKey, upstream, 0) - if err != nil { - return err - } + return nil +} + +func (r *Router) RemoveRoute(name string) error { + key := path.Join(r.keyPrefix, "frontends", name, "frontend") + _, err := r.etcd.Delete(key, true) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) UpdateRoute(name, host, location string) error { + frontend := schema.VulcandFrontend{ + Type: "http", + BackendID: name, + Route: routeString(host, location), + } + j, err := marshal(frontend) + + if err != nil { + return err + } + + key := path.Join(r.keyPrefix, "frontends", name, "frontend") + _, err = r.etcd.Set(key, j, 0) + + if err != nil { + return err } return nil @@ -113,3 +154,23 @@ func (r *Router) RemoveUpstream(upstream string) error { return nil } + +func routeString(hostname, path string) string { + return fmt.Sprintf("Host(`%s`) && PathRegexp(`%s`)", hostname, path) +} + +func marshal(obj interface{}) (string, error) { + encoded, err := json.Marshal(obj) + if err != nil { + return "", fmt.Errorf("unable to JSON-serialize object: %s", err) + } + return string(encoded), nil +} + +func unmarshal(val string, obj interface{}) error { + err := json.Unmarshal([]byte(val), &obj) + if err != nil { + return fmt.Errorf("unable to JSON-deserialize object: %s", err) + } + return nil +} diff --git a/schema/vulcand.go b/schema/vulcand.go new file mode 100644 index 0000000..3b568df --- /dev/null +++ b/schema/vulcand.go @@ -0,0 +1,15 @@ +package schema + +type VulcandBackend struct { + Type string `json:"Type"` +} + +type VulcandServer struct { + URL string `json:"URL"` +} + +type VulcandFrontend struct { + Type string `json:"Type"` + BackendID string `json:"BackendId"` + Route string `json:"Route"` +} From 18a40d310a1576774dce21a0c0fec13182021772 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 4 Sep 2015 23:52:53 +0900 Subject: [PATCH 20/34] Tell new container to Vulcand --- agent/agent.go | 15 ++++++++++++++- router/router.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index 3f40b29..77ebb6a 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -3,6 +3,7 @@ package agent import ( "fmt" "os" + "strconv" "strings" "time" @@ -10,6 +11,7 @@ import ( "github.com/fsouza/go-dockerclient" "github.com/spesnova/iruka/registry" + "github.com/spesnova/iruka/router" "github.com/spesnova/iruka/schema" ) @@ -25,6 +27,7 @@ type Agent interface { type IrukaAgent struct { docker *docker.Client reg *registry.Registry + rou *router.Router Machine string } @@ -76,10 +79,20 @@ func (a *IrukaAgent) Pulse() { PublishedPort: container.Ports[0].PublicPort, } - _, err := a.reg.UpdateContainerState(name, opts) + c, err := a.reg.UpdateContainerState(name, opts) if err != nil { fmt.Println(err.Error()) } + + url := "http://" + c.Machine + ":" + strconv.FormatInt(c.PublishedPort, 10) + + if !a.rou.IsServerExists(c.AppID.String(), name) { + err = a.rou.AddServer(c.AppID.String(), name, url) + + if err != nil { + fmt.Println(err.Error()) + } + } } time.Sleep(pulseInterval * time.Second) diff --git a/router/router.go b/router/router.go index c142c08..8da44e6 100644 --- a/router/router.go +++ b/router/router.go @@ -67,6 +67,44 @@ func (r *Router) RemoveBackend(name string) error { return nil } +func (r *Router) AddServer(appID, containerName, url string) error { + server := schema.VulcandServer{ + URL: url, + } + j, err := marshal(server) + + if err != nil { + return err + } + + key := path.Join(r.keyPrefix, "backends", appID, "backend", containerName) + _, err = r.etcd.Create(key, string(j), 0) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) RemoveServer(appID, containerName string) error { + key := path.Join(r.keyPrefix, "backends", appID, "backend", containerName) + _, err := r.etcd.Delete(key, true) + + if err != nil { + return err + } + + return nil +} + +func (r *Router) IsServerExists(appID, containerName string) bool { + key := path.Join(r.keyPrefix, "backends", appID, "backend", containerName) + _, err := r.etcd.Get(key, false, false) + + return err != nil +} + func (r *Router) AddRoute(name, host, location string) error { frontend := schema.VulcandFrontend{ Type: "http", From 5bfd856fdd7b47678502114dec3ecd7998a02923 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 14:53:40 +0900 Subject: [PATCH 21/34] Pass router object to new agent --- agent/agent.go | 3 ++- irukad/iruka.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 77ebb6a..bb231ee 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -31,7 +31,7 @@ type IrukaAgent struct { Machine string } -func NewAgent(host, machine string, reg *registry.Registry) Agent { +func NewAgent(host, machine string, reg *registry.Registry, rou *router.Router) Agent { if os.Getenv("IRUKA_DOCKER_HOST") != "" { host = os.Getenv("IRUKA_DOCKER_HOST") } @@ -47,6 +47,7 @@ func NewAgent(host, machine string, reg *registry.Registry) Agent { return &IrukaAgent{ docker: client, reg: reg, + rou: rou, Machine: machine, } } diff --git a/irukad/iruka.go b/irukad/iruka.go index d6f81b6..5a5315e 100644 --- a/irukad/iruka.go +++ b/irukad/iruka.go @@ -31,7 +31,7 @@ func main() { if machine == "" { log.Fatal("IRUKA_MACHINE is required, but missing") } - age := agent.NewAgent(agent.DefaultHost, machine, reg) + age := agent.NewAgent(agent.DefaultHost, machine, reg, rou) // Render ren := render.New() From 688ff657a2343e4c364ced11927d4fac49353749 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 14:54:03 +0900 Subject: [PATCH 22/34] Modify etcd path of servers --- router/router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router/router.go b/router/router.go index 8da44e6..88febf2 100644 --- a/router/router.go +++ b/router/router.go @@ -77,7 +77,7 @@ func (r *Router) AddServer(appID, containerName, url string) error { return err } - key := path.Join(r.keyPrefix, "backends", appID, "backend", containerName) + key := path.Join(r.keyPrefix, "backends", appID, "servers", containerName) _, err = r.etcd.Create(key, string(j), 0) if err != nil { @@ -88,7 +88,7 @@ func (r *Router) AddServer(appID, containerName, url string) error { } func (r *Router) RemoveServer(appID, containerName string) error { - key := path.Join(r.keyPrefix, "backends", appID, "backend", containerName) + key := path.Join(r.keyPrefix, "backends", appID, "servers", containerName) _, err := r.etcd.Delete(key, true) if err != nil { @@ -99,7 +99,7 @@ func (r *Router) RemoveServer(appID, containerName string) error { } func (r *Router) IsServerExists(appID, containerName string) bool { - key := path.Join(r.keyPrefix, "backends", appID, "backend", containerName) + key := path.Join(r.keyPrefix, "backends", appID, "servers", containerName) _, err := r.etcd.Get(key, false, false) return err != nil From b67cc3148335faa75f13a0d76c0fc94e0153bb10 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 15:04:23 +0900 Subject: [PATCH 23/34] Inverse condition --- router/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index 88febf2..5fecc12 100644 --- a/router/router.go +++ b/router/router.go @@ -102,7 +102,7 @@ func (r *Router) IsServerExists(appID, containerName string) bool { key := path.Join(r.keyPrefix, "backends", appID, "servers", containerName) _, err := r.etcd.Get(key, false, false) - return err != nil + return err == nil } func (r *Router) AddRoute(name, host, location string) error { From 08e53797c867db31a736a06223426c2f3ce0fe9e Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 15:41:06 +0900 Subject: [PATCH 24/34] Do not escape '&' in JSON value --- router/router.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index 5fecc12..23c594f 100644 --- a/router/router.go +++ b/router/router.go @@ -1,6 +1,7 @@ package router import ( + "bytes" "encoding/json" "fmt" "os" @@ -202,7 +203,9 @@ func marshal(obj interface{}) (string, error) { if err != nil { return "", fmt.Errorf("unable to JSON-serialize object: %s", err) } - return string(encoded), nil + + // To print '&' + return string(bytes.Replace(encoded, []byte("\\u0026"), []byte("&"), -1)), nil } func unmarshal(val string, obj interface{}) error { From c62fa123a2594484926204f9d3b1963c5c452dbd Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 15:42:41 +0900 Subject: [PATCH 25/34] Remove unused methods --- router/router.go | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/router/router.go b/router/router.go index 23c594f..d414aeb 100644 --- a/router/router.go +++ b/router/router.go @@ -161,39 +161,6 @@ func (r *Router) UpdateRoute(name, host, location string) error { return nil } -func (r *Router) RemoveHost(host string) error { - key := path.Join(r.keyPrefix, "hosts", host) - _, err := r.etcd.Delete(key, true) - - if err != nil { - return err - } - - return nil -} - -func (r *Router) AddUpstream(upstream string) error { - key := path.Join(r.keyPrefix, "upstreams", upstream) - _, err := r.etcd.SetDir(key, 0) - - if err != nil { - return err - } - - return nil -} - -func (r *Router) RemoveUpstream(upstream string) error { - key := path.Join(r.keyPrefix, "upstreams", upstream) - _, err := r.etcd.Delete(key, true) - - if err != nil { - return err - } - - return nil -} - func routeString(hostname, path string) string { return fmt.Sprintf("Host(`%s`) && PathRegexp(`%s`)", hostname, path) } From f632db3f3ac32572aa2d5b746bf65d0fb4ca412b Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:08:41 +0900 Subject: [PATCH 26/34] Modify frontend naming rules --- router/router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router/router.go b/router/router.go index d414aeb..3bc90b8 100644 --- a/router/router.go +++ b/router/router.go @@ -118,7 +118,7 @@ func (r *Router) AddRoute(name, host, location string) error { return err } - key := path.Join(r.keyPrefix, "frontends", name, "frontend") + key := path.Join(r.keyPrefix, "frontends", fmt.Sprintf("%s-%s", name, host), "frontend") _, err = r.etcd.Create(key, j, 0) if err != nil { @@ -128,8 +128,8 @@ func (r *Router) AddRoute(name, host, location string) error { return nil } -func (r *Router) RemoveRoute(name string) error { - key := path.Join(r.keyPrefix, "frontends", name, "frontend") +func (r *Router) RemoveRoute(name, host string) error { + key := path.Join(r.keyPrefix, "frontends", fmt.Sprintf("%s-%s", name, host), "frontend") _, err := r.etcd.Delete(key, true) if err != nil { From d0767de9cab1b8803d12b59fe431f03baf101a89 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:08:59 +0900 Subject: [PATCH 27/34] Access to one app via multiple domains --- irukad/controllers/domain.go | 39 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/irukad/controllers/domain.go b/irukad/controllers/domain.go index bf6a0df..f6135f0 100644 --- a/irukad/controllers/domain.go +++ b/irukad/controllers/domain.go @@ -47,18 +47,15 @@ func (c *DomainController) Create(rw http.ResponseWriter, r *http.Request) { c.JSON(rw, http.StatusInternalServerError, err.Error()) } - // TODO(dtan4): - // Add multiple routes - route := routes[0] - err = c.rou.UpdateRoute(app.ID.String(), opts.Hostname, route.Location) - - // for _, route := range routes { - // err := c.rou.UpdateRoute(app.ID.String(), opts.Hostname, route.Location) - - // if err != nil { - // c.JSON(rw, http.StatusInternalServerError, err.Error()) - // } - // } + for _, route := range routes { + // TODO(dtan4): + // Remove duplicates (most routes are the same) + err := c.rou.AddRoute(app.ID.String(), opts.Hostname, route.Location) + + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } + } domain, err := c.reg.CreateDomain(appIdentity, opts) @@ -74,23 +71,23 @@ func (c *DomainController) Delete(rw http.ResponseWriter, r *http.Request) { appIdentity := vars["appIdentity"] identity := vars["identity"] - domain, err := c.reg.DomainFilteredByApp(appIdentity, identity) + app, err := c.reg.App(appIdentity) if err != nil { c.JSON(rw, http.StatusInternalServerError, err.Error()) } - // TODO(dtan4): - // Remove multiple routes - // routes, err := c.reg.RoutesFilteredByApp(appIdentity) + domain, err := c.reg.DomainFilteredByApp(appIdentity, identity) - // if err != nil { - // c.JSON(rw, http.StatusInternalServerError, err.Error()) - // } + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } - // for _, route := range routes { + err = c.rou.RemoveRoute(app.ID.String(), domain.Hostname) - // } + if err != nil { + c.JSON(rw, http.StatusInternalServerError, err.Error()) + } _, err = c.reg.DestroyDomain(identity) From 2178a06ecca61e3937c194e8c41dc1b4d7c69760 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:27:07 +0900 Subject: [PATCH 28/34] Skip if errors are raised in agent --- agent/agent.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/agent.go b/agent/agent.go index bb231ee..3745548 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -83,6 +83,7 @@ func (a *IrukaAgent) Pulse() { c, err := a.reg.UpdateContainerState(name, opts) if err != nil { fmt.Println(err.Error()) + continue } url := "http://" + c.Machine + ":" + strconv.FormatInt(c.PublishedPort, 10) @@ -92,6 +93,7 @@ func (a *IrukaAgent) Pulse() { if err != nil { fmt.Println(err.Error()) + continue } } } From ed446bbefe82bbda62be5d42b27f227ccad8fb01 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:27:24 +0900 Subject: [PATCH 29/34] Show register container message --- agent/agent.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/agent.go b/agent/agent.go index 3745548..faa2775 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -95,6 +95,8 @@ func (a *IrukaAgent) Pulse() { fmt.Println(err.Error()) continue } + + fmt.Printf("Registered new container to %s: %s\n", c.AppID.String(), name) } } From 4c94ab90142e32c9472174292628f18929e94af7 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:27:57 +0900 Subject: [PATCH 30/34] Remove etcd directory with route --- router/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index 3bc90b8..0c268fe 100644 --- a/router/router.go +++ b/router/router.go @@ -129,7 +129,7 @@ func (r *Router) AddRoute(name, host, location string) error { } func (r *Router) RemoveRoute(name, host string) error { - key := path.Join(r.keyPrefix, "frontends", fmt.Sprintf("%s-%s", name, host), "frontend") + key := path.Join(r.keyPrefix, "frontends", fmt.Sprintf("%s-%s", name, host)) _, err := r.etcd.Delete(key, true) if err != nil { From a7159b126bb83ac0bfe298cdedbdbfb9c0236afe Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:43:38 +0900 Subject: [PATCH 31/34] Add TODO comments --- router/router.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/router/router.go b/router/router.go index 0c268fe..95b9112 100644 --- a/router/router.go +++ b/router/router.go @@ -37,6 +37,8 @@ func NewRouter(machines, keyPrefix string) *Router { return &Router{etcdClient, keyPrefix} } +// TODO(dtan4): +// Divide by upstream (blue/green) func (r *Router) AddBackend(name string) error { backend := schema.VulcandBackend{ Type: "http", @@ -68,6 +70,8 @@ func (r *Router) RemoveBackend(name string) error { return nil } +// TODO(dtan4): +// Divide by upstream (blue/green) func (r *Router) AddServer(appID, containerName, url string) error { server := schema.VulcandServer{ URL: url, From fdeeddc1700c0e48016d15c0c7e4dbb11b6007be Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:44:09 +0900 Subject: [PATCH 32/34] Suppress skip messages about irukad and vulcand --- agent/agent.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index faa2775..d5e72ef 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -65,7 +65,11 @@ func (a *IrukaAgent) Pulse() { // Regist only containers that managed by iruka s := strings.Split(name, ".") if uuid.Parse((s[len(s)-1])) == nil { - fmt.Println("Skipped to register:", name) + // Vulcand is always running with iruka + if name != "irukad" && name != "vulcand" { + fmt.Println("Skipped to register:", name) + } + continue } From 4a7d94adf6126819c04f3d9403892b2df85865b0 Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 16:45:26 +0900 Subject: [PATCH 33/34] Start vulcand service at launch --- coreos/user-data.yml.erb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/coreos/user-data.yml.erb b/coreos/user-data.yml.erb index 0bde93a..0a44ac5 100644 --- a/coreos/user-data.yml.erb +++ b/coreos/user-data.yml.erb @@ -41,6 +41,26 @@ coreos: [Install] WantedBy=sockets.target + - name: vulcand.service + command: start + enable: true + content: | + [Unit] + Description=Vulcand + After=docker.service + Requires=docker.service + + [Service] + TimeoutStartSec=0 + User=core + ExecStartPre=-/usr/bin/docker kill vulcand + ExecStartPre=-/usr/bin/docker rm vulcand + ExecStartPre=/usr/bin/docker pull mailgun/vulcand:v0.8.0-beta.2 + ExecStart=/usr/bin/docker run --name vulcand -p 80:80 -p 443:443 -p 8182:8182 -p 8181:8181 mailgun/vulcand:v0.8.0-beta.2 /go/bin/vulcand -apiInterface=0.0.0.0 -interface=0.0.0.0 -etcd=http://10.1.42.1:4001 -port=80 -apiPort=8182 + ExecStop=/usr/bin/docker stop vulcand + + [Install] + WantedBy=multi-user.target write_files: - path: /etc/ssh/sshd_config permissions: 0600 From 54c8718957c27d4faaef88d05fa6d02c9bed034f Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Tue, 8 Sep 2015 22:11:31 +0900 Subject: [PATCH 34/34] Disable timeout not to prevent docker pull --- scheduler/container.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scheduler/container.go b/scheduler/container.go index fc8d401..0ae8fbb 100644 --- a/scheduler/container.go +++ b/scheduler/container.go @@ -69,6 +69,7 @@ func (s *Scheduler) containerToUnitOptions(c schema.Container, cv schema.ConfigV // Service section example: // // [Service] + // TimeoutStartSec=0 // ExecStartPre=/usr/bin/docker pull quay.io/spesnova/example:latest // ExecStartPre=-/usr/bin/docker kill hello.web.a888f12a-0806-11e5-b898-5cf93896cc38 // ExecStartPre=-/usr/bin/docker rm hello.web.a888f12a-0806-11e5-b898-5cf93896cc38 @@ -81,6 +82,11 @@ func (s *Scheduler) containerToUnitOptions(c schema.Container, cv schema.ConfigV // ExecStop=/usr/bin/docker rm -f hello.web.a888f12a-0806-11e5-b898-5cf93896cc38 // Restart=on-failure // + &fleet.UnitOption{ + Section: "Service", + Name: "TimeoutStartSec", + Value: "0", + }, &fleet.UnitOption{ Section: "Service", Name: "ExecStartPre",