// WriteYaml writes the config as YAML. // // Params: // - conf: A *cfg.Config to render. // - out (io.Writer): An output stream to write to. Default is os.Stdout. // - filename (string): If set, the file will be opened and the content will be written to it. func WriteYaml(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { conf := p.Get("conf", nil).(*cfg.Config) toStdout := p.Get("toStdout", true).(bool) data, err := conf.Marshal() if err != nil { return nil, err } var out io.Writer if nn, ok := p.Has("filename"); ok && len(nn.(string)) > 0 { file, err := os.Create(nn.(string)) if err != nil { } defer file.Close() out = io.Writer(file) //fmt.Fprint(out, yml) out.Write(data) } else if toStdout { out = p.Get("out", os.Stdout).(io.Writer) //fmt.Fprint(out, yml) out.Write(data) } // Otherwise we supress output. return true, nil }
// Watch watches a given path, and executes a git check-repos for each event. // // It starts the watcher and then returns. The watcher runs on its own // goroutine. To stop the watching, send the returned channel a bool. // // Params: // - client (Watcher): An Etcd client. // - path (string): The path to watch // // Returns: // - chan bool: Send this a message to stop the watcher. func Watch(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { // etcdctl -C $ETCD watch --recursive /deis/services path := p.Get("path", "/deis/services").(string) cli, ok := p.Has("client") if !ok { return nil, errors.New("No etcd client found.") } client := cli.(Watcher) // Stupid hack because etcd watch seems to be broken, constantly complaining // that the JSON it received is malformed. safely.GoDo(c, func() { for { response, err := client.Watch(path, 0, true, nil, nil) if err != nil { log.Errf(c, "Etcd Watch failed: %s", err) time.Sleep(50 * time.Millisecond) continue } if response.Node == nil { log.Infof(c, "Unexpected Etcd message: %v", response) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } } }) return nil, nil }
// Show help. // This command is useful for placing at the front of a CLI "subcommand" to have it output // help information. It will only trigger when "show" is set to true, so another command // can, for example, check for a "-h" or "-help" flag and set "show" based on that. // // Params: // - show (bool): If `true`, show help. // - summary (string): A one-line summary of the command. // - description (string): A short description of what the command does. // - usage (string): usage information. // - flags (FlagSet): Flags that are supported. The FlagSet will be converted to help text. // - writer (Writer): The location that this will write to. Default is os.Stdout // - subcommands ([]string): A list of subcommands. This will be formatted as help text. func ShowHelp(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { showHelp := false showHelpO := params.Get("show", false) switch showHelpO.(type) { case string: showHelp = strings.ToLower(showHelpO.(string)) == "true" case bool: showHelp = showHelpO.(bool) } writer := params.Get("writer", os.Stdout).(io.Writer) pmap := params.AsMap() // Last resort: If no summary, pull it from the route description. if summary, ok := pmap["summary"]; !ok || len(summary.(string)) == 0 { pmap["summary"] = cxt.Get("route.Description", "").(string) } sections := []string{"summary", "description", "usage"} if _, ok := params.Has("subcommands"); ok { sections = append(sections, "subcommands") } if showHelp { displayHelp(sections, pmap, writer) return true, new(cookoo.Stop) } return false, nil }
// Recurse does glide installs on dependent packages. // Recurse looks in all known packages for a glide.yaml files and installs for // each one it finds. func Recurse(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { if !p.Get("enable", true).(bool) { return nil, nil } force := p.Get("force", true).(bool) godeps, gpm, gb, deleteFlatten := false, false, false, false if g, ok := p.Has("importGodeps"); ok { godeps = g.(bool) } if g, ok := p.Has("importGPM"); ok { gpm = g.(bool) } if g, ok := p.Has("importGb"); ok { gb = g.(bool) } if g, ok := p.Has("deleteFlatten"); ok { deleteFlatten = g.(bool) } Info("Checking dependencies for updates. Godeps: %v, GPM: %v, gb: %v\n", godeps, gpm, gb) if deleteFlatten == true { Info("Deleting flattened dependencies enabled\n") } conf := p.Get("conf", &Config{}).(*Config) vend, _ := VendorPath(c) return recDepResolve(conf, vend, godeps, gpm, gb, force, deleteFlatten) }
// WriteYaml writes a yaml.Node to the console as a string. // // Params: // - yaml.Node (yaml.Node): A yaml.Node to render. // - out (io.Writer): An output stream to write to. Default is os.Stdout. // - filename (string): If set, the file will be opened and the content will be written to it. func WriteYaml(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { top := p.Get("yaml.Node", yaml.Scalar("nothing to print")).(yaml.Node) var out io.Writer if nn, ok := p.Has("filename"); ok && len(nn.(string)) > 0 { file, err := os.Create(nn.(string)) if err != nil { } defer file.Close() out = io.Writer(file) } else { out = p.Get("out", os.Stdout).(io.Writer) } fmt.Fprint(out, yaml.Render(top)) return true, nil }
// Get performs an etcd Get operation. // // Params: // - client (EtcdGetter): Etcd client // - path (string): The path/key to fetch // // Returns: // - This puts an `etcd.Response` into the context, and returns an error // if the client could not connect. func Get(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { cli, ok := p.Has("client") if !ok { return nil, errors.New("No Etcd client found.") } client := cli.(Getter) path := p.Get("path", "/").(string) res, err := client.Get(path, false, false) if err != nil { return res, err } if !res.Node.Dir { return res, fmt.Errorf("Expected / to be a dir.") } return res, nil }
// Recurse does glide installs on dependent packages. // Recurse looks in all known packages for a glide.yaml files and installs for // each one it finds. func Recurse(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { if !p.Get("enable", true).(bool) { return nil, nil } force := p.Get("force", true).(bool) godeps, gpm := false, false if g, ok := p.Has("importGodeps"); ok { godeps = g.(bool) } if g, ok := p.Has("importGPM"); ok { gpm = g.(bool) } Info("Checking dependencies for updates. Godeps: %v, GPM: %v\n", godeps, gpm) conf := p.Get("conf", &Config{}).(*Config) vend, _ := VendorPath(c) return recDepResolve(conf, vend, godeps, gpm, force) }
// MakeDir makes a directory in Etcd. // // Params: // - client (EtcdDirCreator): Etcd client // - path (string): The name of the directory to create. // - ttl (uint64): Time to live. // Returns: // *etcd.Response func MakeDir(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { name := p.Get("path", "").(string) ttl := p.Get("ttl", uint64(0)).(uint64) cli, ok := p.Has("client") if !ok { return nil, errors.New("No Etcd client found.") } client := cli.(DirCreator) if len(name) == 0 { return false, errors.New("Expected directory name to be more than zero characters.") } res, err := client.CreateDir(name, ttl) if err != nil { return res, &cookoo.RecoverableError{err.Error()} } return res, nil }
// ServeFiles is a cookoo command to serve files from a set of filesystem directories. // // If no writer is specified, this will attempt to write to whatever is in the // Context with the key "http.ResponseWriter". If no suitable writer is found, it will // not write to anything at all. // // Example: // // registry.Route("GET /**", "Serve assets"). // Does(web.ServeFiles, "fileServer"). // Using("directory").WithDefault("static") // // Example 2: // // registry.Route("GET /foo/**", "Serve assets"). // Does(web.ServeFiles, "fileServer"). // Using("directory").WithDefault("static"). // Using("removePrefix").WithDefault("/foo") // // Params: // - directory: A directory to serve files from. // - removePrefix: A prefix to remove from the url before looking for it on the filesystem. // - writer: A Writer of some sort. This will try to write to the HTTP response if no writer // is specified. // - request: A request of some sort. This will try to use the HTTP request if no request // is specified. func ServeFiles(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { writer, ok := params.Has("writer") if writer == nil { writer, ok = cxt.Has("http.ResponseWriter") if !ok { return nil, &cookoo.Reroute{"@404"} } } out := writer.(http.ResponseWriter) req, ok := params.Has("request") if req == nil { req, ok = cxt.Has("http.Request") if !ok { return nil, &cookoo.Reroute{"@404"} } } in := req.(*http.Request) directory := params.Get("directory", nil) if directory == nil { return nil, &cookoo.Reroute{"@404"} } prefix := params.Get("removePrefix", "").(string) urlPath := strings.TrimPrefix(in.URL.Path, prefix) staticFile := path.Join(directory.(string), urlPath) info, err := os.Stat(staticFile) if err != nil { return nil, &cookoo.Reroute{"@404"} } if info.IsDir() == false { http.ServeFile(out, in, staticFile) return true, nil } return nil, &cookoo.Reroute{"@404"} }
// Get performs an etcd Get operation. // // Params: // - client (EtcdGetter): Etcd client // - path (string): The path/key to fetch // - recursive (bool): Get children, too. Default: false. // - sort (bool): Lexigraphically sort by name. Default: false. // // Returns: // - This puts an `etcd.Response` into the context, and returns an error // if the client could not connect. func Get(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { cli, ok := p.Has("client") if !ok { return nil, errors.New("No Etcd client found.") } ec := cli.(client.Client) path := p.Get("path", "/").(string) rec := p.Get("recursive", false).(bool) sort := p.Get("sort", false).(bool) k := client.NewKeysAPI(ec) res, err := k.Get(dctx(), path, &client.GetOptions{Sort: sort, Recursive: rec}) if err != nil { return res, err } if !res.Node.Dir { return res, fmt.Errorf("Expected / to be a dir.") } return res, nil }
// Sprintf formats a string and then returns the result to the context. // // This is a command wrapper for the core `fmt.Sprintf` function, but tooled // to work the Cookoo way. // // The following returns 'Hello World' to the context. // // //... // Does(Sprintf, "out"). // Using("format").WithDefault("%s %s\n") // Using("0").WithDefault("Hello") // Using("1").WithDefault("World") // // Params: // - format (string): The format string // - "0"... (string): String representation of an integer ascending from 0. // These are treated as positional. func Sprintf(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { msg := params.Get("format", "").(string) maxP := len(params.AsMap()) vals := make([]interface{}, 0, maxP-1) var istr string var i = 0 for i < maxP { istr = fmt.Sprintf("%d", i) // FIXME if v, ok := params.Has(istr); ok { //fmt.Printf("%d: Found %v\n", i, v) vals = append(vals, v) i++ } else { break } } return fmt.Sprintf(msg, vals...), nil }
// Flush sends content to output. // // If no writer is specified, this will attempt to write to whatever is in the // Context with the key "http.ResponseWriter". If no suitable writer is found, it will // not write to anything at all. // // Params: // - writer: A Writer of some sort. This will try to write to the HTTP response if no writer // is specified. // - content: The content to write as a body. If this is a byte[], it is sent unchanged. Otherwise. // we first try to convert to a string, then pass it into a writer. // - contentType: The content type header (e.g. text/html). Default is text/plain // - responseCode: Integer HTTP Response Code: Default is `http.StatusOK`. // - headers: a map[string]string of HTTP headers. The keys will be run through // http.CannonicalHeaderKey() // // Note that this is optimized for writing from strings or arrays, not Readers. For larger // objects, you may find it more efficient to use a different command. // // Context: // - If this finds `web.ContentEncoding`, it will set a content-encoding header. // // Returns // // - boolean true func Flush(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { // Make sure we have a place to write this stuff. writer, ok := params.Has("writer") if writer == nil { writer, ok = cxt.Has("http.ResponseWriter") if !ok { return false, nil } } out := writer.(http.ResponseWriter) // Get the rest of the info. code := params.Get("responseCode", http.StatusOK).(int) header := out.Header() contentType := params.Get("contentType", "text/plain; charset=utf-8").(string) // Prepare the content. var content []byte rawContent, ok := params.Has("content") if !ok { // No content. Send nothing in the body. content = []byte("") } else if byteContent, ok := rawContent.([]byte); ok { // Got a byte[]; add it as is. content = byteContent } else { // Use the formatter to convert to a string, and then // cast it to bytes. content = []byte(fmt.Sprintf("%v", rawContent)) } // Add headers: header.Set(http.CanonicalHeaderKey("content-type"), contentType) te := cxt.Get(ContentEncoding, "").(string) if len(te) > 0 { header.Set(http.CanonicalHeaderKey("transfer-encoding"), te) } headerO, ok := params.Has("headers") if ok { headers := headerO.(map[string]string) for k, v := range headers { header.Add(http.CanonicalHeaderKey(k), v) } } // Send the headers. out.WriteHeader(code) //io.WriteString(out, content) out.Write(content) return true, nil }
// Watch watches a given path, and executes a git check-repos for each event. // // It starts the watcher and then returns. The watcher runs on its own // goroutine. To stop the watching, send the returned channel a bool. // // Params: // - client (Watcher): An Etcd client. // - path (string): The path to watch // // Returns: // - chan bool: Send this a message to stop the watcher. func Watch(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { // etcdctl -C $ETCD watch --recursive /deis/services path := p.Get("path", "/deis/services").(string) cli, ok := p.Has("client") if !ok { return nil, errors.New("No etcd client found.") } client := cli.(Watcher) // Stupid hack because etcd watch seems to be broken, constantly complaining // that the JSON it received is malformed. safely.GoDo(c, func() { for { response, err := client.Watch(path, 0, true, nil, nil) if err != nil { log.Errf(c, "Etcd Watch failed: %s", err) time.Sleep(50 * time.Millisecond) continue } if response.Node == nil { log.Infof(c, "Unexpected Etcd message: %v", response) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } } }) return nil, nil /* Watch seems to be broken. So we do this stupid watch loop instead. receiver := make(chan *etcd.Response) stop := make(chan bool) // Buffer the channels so that we don't hang waiting for go-etcd to // read off the channel. stopetcd := make(chan bool, 1) stopwatch := make(chan bool, 1) // Watch for errors. safely.GoDo(c, func() { // When a receiver is passed in, no *Response is ever returned. Instead, // Watch acts like an error channel, and receiver gets all of the messages. _, err := client.Watch(path, 0, true, receiver, stopetcd) if err != nil { log.Infof(c, "Watcher stopped with error '%s'", err) stopwatch <- true //close(stopwatch) } }) // Watch for events safely.GoDo(c, func() { for { select { case msg := <-receiver: if msg.Node != nil { log.Infof(c, "Received notification %s for %s", msg.Action, msg.Node.Key) } else { log.Infof(c, "Received unexpected etcd message: %v", msg) } git := exec.Command("/home/git/check-repos") if out, err := git.CombinedOutput(); err != nil { log.Errf(c, "Failed git check-repos: %s", err) log.Infof(c, "Output: %s", out) } case <-stopwatch: c.Logf("debug", "Received signal to stop watching events.") return } } }) // Fan out stop requests. safely.GoDo(c, func() { <-stop stopwatch <- true stopetcd <- true close(stopwatch) close(stopetcd) }) return stop, nil */ }