func routes(reg *cookoo.Registry) { reg.AddRoute(cookoo.Route{ Name: "boot", Help: "Boot Etcd", Does: []cookoo.Task{ cookoo.Cmd{ Name: "setenv", Fn: iam, }, cookoo.Cmd{ Name: "discoveryToken", Fn: discovery.GetToken, }, // This synchronizes the local copy of env vars with the actual // environment. So all of these vars are available in cxt: or in // os.Getenv(). They will also be available to the etcd process // we spawn. cookoo.Cmd{ Name: "vars", Fn: env.Get, Using: []cookoo.Param{ {Name: "DEIS_ETCD_DISCOVERY_SERVICE_HOST"}, {Name: "DEIS_ETCD_DISCOVERY_SERVICE_PORT"}, {Name: "DEIS_ETCD_1_SERVICE_HOST"}, {Name: "DEIS_ETCD_1_SERVICE_PORT_CLIENT"}, {Name: "DEIS_ETCD_CLUSTER_SIZE", DefaultValue: "3"}, {Name: "DEIS_ETCD_DISCOVERY_TOKEN", From: "cxt:discoveryToken"}, {Name: "HOSTNAME"}, // Peer URLs are for traffic between etcd nodes. // These point to internal IP addresses, not service addresses. {Name: "ETCD_LISTEN_PEER_URLS", DefaultValue: "http://$MY_IP:$MY_PORT_PEER"}, {Name: "ETCD_INITIAL_ADVERTISE_PEER_URLS", DefaultValue: "http://$MY_IP:$MY_PORT_PEER"}, { Name: "ETCD_LISTEN_CLIENT_URLS", DefaultValue: "http://$MY_IP:$MY_PORT_CLIENT,http://127.0.0.1:$MY_PORT_CLIENT", }, {Name: "ETCD_ADVERTISE_CLIENT_URLS", DefaultValue: "http://$MY_IP:$MY_PORT_CLIENT"}, // {Name: "ETCD_WAL_DIR", DefaultValue: "/var/"}, // {Name: "ETCD_MAX_WALS", DefaultValue: "5"}, }, }, // We need to connect to the discovery service to find out whether // we're part of a new cluster, or part of an existing cluster. cookoo.Cmd{ Name: "discoveryClient", Fn: etcd.CreateClient, Using: []cookoo.Param{ { Name: "url", DefaultValue: "http://$DEIS_ETCD_DISCOVERY_SERVICE_HOST:$DEIS_ETCD_DISCOVERY_SERVICE_PORT", }, }, }, cookoo.Cmd{ Name: "clusterClient", Fn: etcd.CreateClient, Using: []cookoo.Param{ { Name: "url", DefaultValue: "http://$DEIS_ETCD_1_SERVICE_HOST:$DEIS_ETCD_1_SERVICE_PORT_CLIENT", }, }, }, // If there is an existing cluster, set join mode to "existing", // Otherwise this gets set to "new". Note that we check the // 'status' directory on the discovery service. If the discovery // service is down, we will bail out, which will force a pod // restart. cookoo.Cmd{ Name: "joinMode", Fn: setJoinMode, Using: []cookoo.Param{ {Name: "client", From: "cxt:discoveryClient"}, {Name: "path", DefaultValue: "/deis/status/$DEIS_ETCD_DISCOVERY_TOKEN"}, {Name: "desiredLen", From: "cxt:DEIS_ETCD_CLUSTER_SIZE"}, }, }, // If joinMode is "new", we reroute to the route that creates new // clusters. Otherwise, we keep going on this chain, assuming // we're working with an existing cluster. cookoo.Cmd{ Name: "rerouteIfNew", Fn: func(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { if m := p.Get("joinMode", "").(string); m == "new" { return nil, cookoo.NewReroute("@newCluster") } else { log.Infof(c, "Join mode is %s", m) } return nil, nil }, Using: []cookoo.Param{ {Name: "joinMode", From: "cxt:joinMode"}, }, }, // If we didn't get rerouted by the last command, we're in an // existing cluster, and we need to do some cleanup. First thing is // to remove any previous copies of this pod. cookoo.Cmd{ Name: "removeMember", Fn: etcd.RemoveMemberByName, Using: []cookoo.Param{ {Name: "client", From: "cxt:clusterClient"}, {Name: "name", From: "cxt:HOSTNAME"}, }, }, // If any other etcd cluster members are gone (pod does not exist) // then we need to remove them from the cluster or else they will // remain voting members in the master election, and can eventually // deadlock the cluster. cookoo.Cmd{ Name: "removeStale", Fn: etcd.RemoveStaleMembers, Using: []cookoo.Param{ {Name: "client", From: "cxt:clusterClient"}, {Name: "namespace", From: "cxt:MY_NAMESPACE", DefaultValue: "default"}, {Name: "label", DefaultValue: "name=deis-etcd-1"}, }, }, // Now add self to cluster. cookoo.Cmd{ Name: "addMember", Fn: etcd.AddMember, Using: []cookoo.Param{ {Name: "client", From: "cxt:clusterClient"}, {Name: "name", From: "cxt:HOSTNAME"}, {Name: "url", From: "cxt:ETCD_INITIAL_ADVERTISE_PEER_URLS"}, }, }, // Find out who is in the cluster. cookoo.Cmd{ Name: "initialCluster", Fn: etcd.GetInitialCluster, Using: []cookoo.Param{ {Name: "client", From: "cxt:clusterClient"}, }, }, // Start etcd. Note that we use environment variables to pass // info into etcd, which is why there is so much env munging // in this startup script. cookoo.Cmd{ Name: "startEtcd", Fn: startEtcd, Using: []cookoo.Param{ {Name: "client", From: "cxt:discoveryClient"}, {Name: "discover", From: "cxt:discoveryUrl"}, }, }, }, }) // This route gets called if boot gets part way through and discovers // that this is a new cluster. This short-circuits around the logic // that tries to detect a cluster and manage membership, and skips straight // to discovery via etcd-discovery. reg.AddRoute(cookoo.Route{ Name: "@newCluster", Help: "Start as part of a new cluster", Does: []cookoo.Task{ cookoo.Cmd{ Name: "vars2", Fn: env.Get, Using: []cookoo.Param{ { Name: "ETCD_DISCOVERY", DefaultValue: "http://$DEIS_ETCD_DISCOVERY_SERVICE_HOST:$DEIS_ETCD_DISCOVERY_SERVICE_PORT/v2/keys/deis/discovery/$DEIS_ETCD_DISCOVERY_TOKEN", }, }, }, cookoo.Cmd{ Name: "startEtcd", Fn: startEtcd, Using: []cookoo.Param{ {Name: "client", From: "cxt:discoveryClient"}, {Name: "discover", From: "cxt:discoveryUrl"}, }, }, }, }) }
// routes builds the Cookoo registry. // // Esssentially this is a list of all of the things that Builder can do, broken // down into a step-by-step list. func routes(reg *cookoo.Registry) { // The "boot" route starts up the builder as a daemon process. Along the // way, it starts and configures multiple services, including sshd. reg.AddRoute(cookoo.Route{ Name: "boot", Help: "Boot the builder", Does: []cookoo.Task{ // SSHD: Create and configure host keys. cookoo.Cmd{ Name: "installSshHostKeys", Fn: sshd.GenSSHKeys, }, cookoo.Cmd{ Name: sshd.HostKeys, Fn: sshd.ParseHostKeys, }, cookoo.Cmd{ Name: sshd.ServerConfig, Fn: sshd.Configure, }, // If there's an EXTERNAL_PORT, we publish info to etcd. cookoo.Cmd{ Name: "externalport", Fn: env.Get, Using: []cookoo.Param{ {Name: "EXTERNAL_PORT", DefaultValue: ""}, }, }, // DAEMON: Finally, we wait around for a signal, and then cleanup. cookoo.Cmd{ Name: "listen", Fn: KillOnExit, Using: []cookoo.Param{ {Name: "sshd", From: "cxt:sshdstart"}, }, }, }, }) // This provides a very basic SSH ping. // Called by the sshd.Server reg.AddRoute(cookoo.Route{ Name: "sshPing", Help: "Handles an ssh exec ping.", Does: []cookoo.Task{ cookoo.Cmd{ Name: "ping", Fn: sshd.Ping, Using: []cookoo.Param{ {Name: "request", From: "cxt:request"}, {Name: "channel", From: "cxt:channel"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "pubkeyAuth", Does: []cookoo.Task{ // Auth against the keys cookoo.Cmd{ Name: "authN", Fn: sshd.AuthKey, Using: []cookoo.Param{ {Name: "metadata", From: "cxt:metadata"}, {Name: "key", From: "cxt:key"}, {Name: "repoName", From: "cxt:repository"}, }, }, }, }) // This proxies a client session into a git receive. // // Called by the sshd.Server reg.AddRoute(cookoo.Route{ Name: "sshGitReceive", Help: "Handle a git receive over an SSH connection.", Does: []cookoo.Task{ cookoo.Cmd{ Name: "receive", Fn: git.Receive, Using: []cookoo.Param{ {Name: "request", From: "cxt:request"}, {Name: "channel", From: "cxt:channel"}, {Name: "operation", From: "cxt:operation"}, {Name: "repoName", From: "cxt:repository"}, {Name: "permissions", From: "cxt:authN"}, {Name: "userinfo", From: "cxt:userinfo"}, }, }, }, }) }
// routes builds the Cookoo registry. // // Esssentially this is a list of all of the things that Builder can do, broken // down into a step-by-step list. func routes(reg *cookoo.Registry) { // The "boot" route starts up the builder as a daemon process. Along the // way, it starts and configures multiple services, including etcd, confd, // and sshd. reg.AddRoute(cookoo.Route{ Name: "boot", Help: "Boot the builder", Does: []cookoo.Task{ // ENV: Make sure the environment is correct. cookoo.Cmd{ Name: "vars", Fn: env.Get, Using: []cookoo.Param{ {Name: "HOST", DefaultValue: "127.0.0.1"}, {Name: "ETCD_PORT", DefaultValue: "4001"}, {Name: "ETCD_PATH", DefaultValue: "/deis/builder"}, {Name: "ETCD_TTL", DefaultValue: "20"}, }, }, cookoo.Cmd{ // This depends on others being processed first. Name: "vars2", Fn: env.Get, Using: []cookoo.Param{ {Name: "ETCD", DefaultValue: "$HOST:$ETCD_PORT"}, }, }, // DOCKER: start up Docker and make sure it's running. // Then let it download the images while we keep going. cookoo.Cmd{ Name: "docker", Fn: docker.CreateClient, Using: []cookoo.Param{ {Name: "url", DefaultValue: "unix:///var/run/docker.sock"}, }, }, cookoo.Cmd{ Name: "dockerclean", Fn: docker.Cleanup, }, cookoo.Cmd{ Name: "dockerstart", Fn: docker.Start, }, cookoo.Cmd{ Name: "waitfordocker", Fn: docker.WaitForStart, Using: []cookoo.Param{ {Name: "client", From: "cxt:docker"}, }, }, cookoo.Cmd{ Name: "buildImages", Fn: docker.ParallelBuild, Using: []cookoo.Param{ {Name: "client", From: "cxt:docker"}, { Name: "images", DefaultValue: []docker.BuildImg{ {Path: "/usr/local/src/slugbuilder/", Tag: "deis/slugbuilder"}, {Path: "/usr/local/src/slugrunner/", Tag: "deis/slugrunner"}, }, }, }, }, // ETCD: Make sure Etcd is running, and do the initial population. cookoo.Cmd{ Name: "client", Fn: etcd.CreateClient, Using: []cookoo.Param{{Name: "url", DefaultValue: "http://127.0.0.1:4001", From: "cxt:ETCD"}}, }, cookoo.Cmd{ Name: "etcdup", Fn: etcd.IsRunning, Using: []cookoo.Param{ {Name: "client", From: "cxt:client"}, {Name: "count", DefaultValue: 20}, }, }, cookoo.Cmd{ Name: "-", Fn: Sleep, Using: []cookoo.Param{ {Name: "duration", DefaultValue: 21 * time.Second}, {Name: "message", DefaultValue: "Sleeping while etcd expires keys."}, }, }, cookoo.Cmd{ Name: "newdir", Fn: fmt.Sprintf, Using: []cookoo.Param{ {Name: "format", DefaultValue: "%s/users"}, {Name: "0", From: "cxt:ETCD_PATH"}, }, }, cookoo.Cmd{ Name: "mkdir", Fn: etcd.MakeDir, Using: []cookoo.Param{ {Name: "path", From: "cxt:newdir"}, {Name: "client", From: "cxt:client"}, }, }, // SSHD: Create and configure host keys. cookoo.Cmd{ Name: "installSshHostKeys", Fn: etcd.StoreHostKeys, Using: []cookoo.Param{ {Name: "client", From: "cxt:client"}, {Name: "basepath", From: "cxt:ETCD_PATH"}, }, }, cookoo.Cmd{ Name: sshd.HostKeys, Fn: sshd.ParseHostKeys, }, cookoo.Cmd{ Name: sshd.ServerConfig, Fn: sshd.Configure, }, // CONFD: Build out the templates, then start the Confd server. cookoo.Cmd{ Name: "once", Fn: confd.RunOnce, Using: []cookoo.Param{{Name: "node", From: "cxt:ETCD"}}, }, cookoo.Cmd{ Name: "confd", Fn: confd.Run, Using: []cookoo.Param{{Name: "node", From: "cxt:ETCD"}}, }, // Now we wait for Docker to finish downloading. cookoo.Cmd{ Name: "dowloadImages", Fn: docker.Wait, Using: []cookoo.Param{ {Name: "wg", From: "cxt:buildImages"}, {Name: "msg", DefaultValue: "Images downloaded"}, {Name: "waiting", DefaultValue: "Downloading Docker images. This may take a long time. https://xkcd.com/303/"}, {Name: "failures", From: "cxt:ParallelBuild.failN"}, }, }, cookoo.Cmd{ Name: "pushImages", Fn: docker.Push, Using: []cookoo.Param{ {Name: "tag", DefaultValue: "deis/slugrunner:latest"}, {Name: "client", From: "cxt:client"}, }, }, // ETDCD: Now watch for events on etcd, and trigger a git check-repos for // each. For the most part, this runs in the background. cookoo.Cmd{ Name: "Cleanup", Fn: etcd.Watch, Using: []cookoo.Param{ {Name: "client", From: "cxt:client"}, }, }, // If there's an EXTERNAL_PORT, we publish info to etcd. cookoo.Cmd{ Name: "externalport", Fn: env.Get, Using: []cookoo.Param{ {Name: "EXTERNAL_PORT", DefaultValue: ""}, }, }, cookoo.Cmd{ Name: "etcdupdate", Fn: etcd.UpdateHostPort, Using: []cookoo.Param{ {Name: "base", From: "cxt:ETCD_PATH"}, {Name: "host", From: "cxt:HOST"}, {Name: "port", From: "cxt:EXTERNAL_PORT"}, {Name: "client", From: "cxt:client"}, {Name: "sshdPid", From: "cxt:sshd"}, }, }, // DAEMON: Finally, we wait around for a signal, and then cleanup. cookoo.Cmd{ Name: "listen", Fn: KillOnExit, Using: []cookoo.Param{ {Name: "docker", From: "cxt:dockerstart"}, {Name: "sshd", From: "cxt:sshdstart"}, }, }, }, }) // This route is called during a user authentication for SSH. // The rough pattern is that we parse the local authorized keys file, and // then validate that the supplied user key matches an authorized key. // // This grants access to running git-receive, but does not grant access // to writing to the repo. That's handled by the sshReceive. reg.AddRoute(cookoo.Route{ Name: "pubkeyAuth", Does: []cookoo.Task{ // Parse the authorized keys file. // We do this every time because confd is constantly regenerating // the auth keys file. cookoo.Cmd{ Name: "authorizedKeys", Fn: sshd.ParseAuthorizedKeys, Using: []cookoo.Param{ {Name: "path", DefaultValue: "/home/git/.ssh/authorized_keys"}, }, }, // Auth against the keys cookoo.Cmd{ Name: "authN", Fn: sshd.AuthKey, Using: []cookoo.Param{ {Name: "metadata", From: "cxt:metadata"}, {Name: "key", From: "cxt:key"}, {Name: "authorizedKeys", From: "cxt:authorizedKeys"}, }, }, }, }) // This provides a very basic SSH ping. // Called by the sshd.Server reg.AddRoute(cookoo.Route{ Name: "sshPing", Help: "Handles an ssh exec ping.", Does: []cookoo.Task{ cookoo.Cmd{ Name: "ping", Fn: sshd.Ping, Using: []cookoo.Param{ {Name: "request", From: "cxt:request"}, {Name: "channel", From: "cxt:channel"}, }, }, }, }) // This proxies a client session into a git receive. // // Called by the sshd.Server reg.AddRoute(cookoo.Route{ Name: "sshGitReceive", Help: "Handle a git receive over an SSH connection.", Does: []cookoo.Task{ // The Git receive handler needs the username. So we provide // it by looking up the name based on the key. When the // controller no longer requires username for SSH auth, we can // ditch this. cookoo.Cmd{ Name: "fingerprint", Fn: sshd.FingerprintKey, Using: []cookoo.Param{ {Name: "key", From: "cxt:key"}, }, }, cookoo.Cmd{ Name: "username", Fn: etcd.FindSSHUser, Using: []cookoo.Param{ {Name: "client", From: "cxt:client"}, {Name: "fingerprint", From: "cxt:fingerprint"}, }, }, cookoo.Cmd{ Name: "receive", Fn: git.Receive, Using: []cookoo.Param{ {Name: "request", From: "cxt:request"}, {Name: "channel", From: "cxt:channel"}, {Name: "operation", From: "cxt:operation"}, {Name: "repoName", From: "cxt:repository"}, {Name: "fingerprint", From: "cxt:fingerprint"}, {Name: "permissions", From: "cxt:authN"}, {Name: "user", From: "cxt:username"}, }, }, }, }) }
func routes(reg *cookoo.Registry) { reg.AddRoute(cookoo.Route{ Name: "@json", Help: "Parse incoming JSON out of HTTP body", Does: []cookoo.Task{ cookoo.Cmd{ Name: "data", Fn: readBody, }, cookoo.Cmd{ Name: "json", Fn: fromJSON, Using: []cookoo.Param{ {Name: "data", From: "cxt:data"}, {Name: "dest", From: "cxt:prototype"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "@out", Help: "Serialize JSON and write it to http.Response", Does: []cookoo.Task{ cookoo.Cmd{ Name: "content", Fn: toJSON, Using: []cookoo.Param{ {Name: "o", From: "cxt:res"}, }, }, cookoo.Cmd{ Name: "flush", Fn: web.Flush, Using: []cookoo.Param{ {Name: "contentType", DefaultValue: "application/json"}, {Name: "content", From: "cxt:content"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "GET /package", Help: "List packages", Does: []cookoo.Task{ cookoo.Cmd{ Name: "res", Fn: backend.Packages, }, cookoo.Include{"@out"}, }, }) reg.AddRoute(cookoo.Route{ Name: "GET /package/*", Help: "Get an individual package", Does: []cookoo.Task{ cookoo.Cmd{ Name: "res", Fn: backend.Package, Using: []cookoo.Param{ {Name: "name", From: "path:1"}, }, }, cookoo.Include{"@out"}, }, }) reg.AddRoute(cookoo.Route{ Name: "POST /package", Help: "Create a package", Does: []cookoo.Task{ cookoo.Cmd{ Name: "-", Fn: cookoo.AddToContext, Using: []cookoo.Param{ {Name: "prototype", DefaultValue: &model.Package{}}, }, }, cookoo.Include{"@json"}, cookoo.Cmd{ Name: "res", Fn: backend.AddPackage, Using: []cookoo.Param{ {Name: "pkg", From: "cxt:json"}, }, }, cookoo.Include{"@out"}, }, }) reg.AddRoute(cookoo.Route{ Name: "POST /package/*", Help: "Create a package release", Does: []cookoo.Task{ //cookoo.Cmd{ //Name: "pkg", //Fn: backend.Package, //Using: []cookoo.Param{ //{Name: "name", From: "path:1"}, //}, //}, cookoo.Cmd{ Name: "-", Fn: cookoo.AddToContext, Using: []cookoo.Param{ {Name: "prototype", DefaultValue: &model.Package{}}, }, }, cookoo.Include{"@json"}, cookoo.Cmd{ Name: "res", Fn: backend.AddRelease, Using: []cookoo.Param{ {Name: "pkg", From: "cxt:json"}, }, }, cookoo.Include{"@out"}, }, }) }
func buildRegistry(reg *cookoo.Registry, router *cookoo.Router, cxt cookoo.Context) { reg.AddRoute(cookoo.Route{ Name: "PUT /v1/t/*", Help: "Create a new topic.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "topic", Fn: pubsub.CreateTopic, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "POST /v1/t/*", Help: "Publish a message to a channel.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "postBody", Fn: httputil.BufferPost, }, cookoo.Cmd{ Name: "publish", Fn: pubsub.Publish, Using: []cookoo.Param{ {Name: "message", From: "cxt:postBody"}, {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "GET /v1/t/*", Help: "Subscribe to a topic.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "history", Fn: pubsub.ReplayHistory, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, cookoo.Cmd{ Name: "subscribe", Fn: pubsub.Subscribe, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "HEAD /v1/t/*", Help: "Check whether a topic exists.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "has", Fn: pubsub.TopicExists, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "DELETE /v1/t/*", Help: "Delete a topic and close all subscriptions to the topic.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "delete", Fn: pubsub.DeleteTopic, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) }
func buildRegistry(reg *cookoo.Registry, router *cookoo.Router, cxt cookoo.Context) { reg.AddRoute(cookoo.Route{ Name: "GET /ping", Help: "Ping the server, get a pong reponse.", Does: cookoo.Tasks{ cookoo.Cmd{ Fn: func(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { w := c.Get("http.ResponseWriter", nil).(http.ResponseWriter) w.Write([]byte("pong")) return nil, nil }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "GET /v1/time", Help: "Print the current server time as a UNIX seconds-since-epoch", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "timestamp", Fn: httputil.Timestamp, }, cookoo.Cmd{ Name: "_", Fn: web.Flush, Using: []cookoo.Param{ {Name: "content", From: "cxt:timestamp"}, {Name: "contentType", DefaultValue: "text/plain"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "GET /", Help: "API Reference", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "help", Fn: cfmt.Template, Using: []cookoo.Param{ {Name: "template", DefaultValue: helpTemplate}, {Name: "Routes", From: "cxt:routes"}, }, }, cookoo.Cmd{ Name: "_", Fn: web.Flush, Using: []cookoo.Param{ {Name: "content", From: "cxt:help"}, {Name: "contentType", DefaultValue: "text/html"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "PUT /v1/t/*", Help: "Create a new topic.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "topic", Fn: pubsub.CreateTopic, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "POST /v1/t/*", Help: "Publish a message to a channel.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "postBody", Fn: httputil.BufferPost, }, cookoo.Cmd{ Name: "publish", Fn: pubsub.Publish, Using: []cookoo.Param{ {Name: "message", From: "cxt:postBody"}, {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "GET /v1/t/*", Help: "Subscribe to a channel.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "history", Fn: pubsub.ReplayHistory, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, cookoo.Cmd{ Name: "subscribe", Fn: pubsub.Subscribe, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "HEAD /v1/t/*", Help: "Check whether a topic exists.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "has", Fn: pubsub.TopicExists, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) reg.AddRoute(cookoo.Route{ Name: "DELETE /v1/t/*", Help: "Delete a topic and close all subscriptions to the topic.", Does: cookoo.Tasks{ cookoo.Cmd{ Name: "delete", Fn: pubsub.DeleteTopic, Using: []cookoo.Param{ {Name: "topic", From: "path:2"}, }, }, }, }) }