// Receive receives a Git repo. // This will only work for git-receive-pack. // // Params: // - operation (string): e.g. git-receive-pack // - repoName (string): The repository name, in the form '/REPO.git'. // - channel (ssh.Channel): The channel. // - request (*ssh.Request): The channel. // - gitHome (string): Defaults to /home/git. // - fingerprint (string): The fingerprint of the user's SSH key. // - user (string): The name of the Deis user. // // Returns: // - nothing func Receive(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { if ok, z := p.Requires("channel", "request", "fingerprint", "permissions"); !ok { return nil, fmt.Errorf("Missing requirements %q", z) } repoName := p.Get("repoName", "").(string) operation := p.Get("operation", "").(string) channel := p.Get("channel", nil).(ssh.Channel) gitHome := p.Get("gitHome", "/home/git").(string) fingerprint := p.Get("fingerprint", nil).(string) user := p.Get("user", "").(string) repo, err := cleanRepoName(repoName) if err != nil { log.Warnf(c, "Illegal repo name: %s.", err) channel.Stderr().Write([]byte("No repo given")) return nil, err } repo += ".git" if _, err := createRepo(c, filepath.Join(gitHome, repo), gitHome); err != nil { log.Infof(c, "Did not create new repo: %s", err) } cmd := exec.Command("git-shell", "-c", fmt.Sprintf("%s '%s'", operation, repo)) log.Infof(c, strings.Join(cmd.Args, " ")) var errbuff bytes.Buffer cmd.Dir = gitHome cmd.Env = []string{ fmt.Sprintf("RECEIVE_USER=%s", user), fmt.Sprintf("RECEIVE_REPO=%s", repo), fmt.Sprintf("RECEIVE_FINGERPRINT=%s", fingerprint), fmt.Sprintf("SSH_ORIGINAL_COMMAND=%s '%s'", operation, repo), fmt.Sprintf("SSH_CONNECTION=%s", c.Get("SSH_CONNECTION", "0 0 0 0").(string)), } cmd.Env = append(cmd.Env, os.Environ()...) done := plumbCommand(cmd, channel, &errbuff) if err := cmd.Start(); err != nil { log.Warnf(c, "Failed git receive immediately: %s %s", err, errbuff.Bytes()) return nil, err } fmt.Printf("Waiting for git-receive to run.\n") done.Wait() fmt.Printf("Waiting for deploy.\n") if err := cmd.Wait(); err != nil { log.Errf(c, "Error on command: %s %s", err, errbuff.Bytes()) return nil, err } if errbuff.Len() > 0 { log.Warnf(c, "Unreported error: %s", errbuff.Bytes()) } log.Infof(c, "Deploy complete.\n") return nil, nil }
// A command that can be used during a shutdown chain. // // Params: // - dbname (required): the name of the db datasource. func Close(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { ok, _ := params.Requires("dbname") if !ok { return nil, &cookoo.FatalError{"Expected dbname param."} } dbname := params.Get("dbname", nil).(string) db, err := GetDb(cxt, dbname) if err != nil { return fatalError(err) } return nil, db.Close() }
// Parse arguments for a "subcommand" // // The cookoo.cli.RequestResolver allows you to specify global level flags. This command // allows you to augment those with subcommand flags. Example: // // $ myprog -foo=yes subcommand -bar=no // // In the above example, `-foo` is a global flag (set before the subcommand), while // `-bar` is a local flag. It is specific to `subcommand`. This command lets you parse // an arguments list given a pointer to a `flag.FlagSet`. // // Like the cookoo.cli.RequestResolver, this will place the parsed params directly into the // context. For this reason, you ought not use the same flag names at both global and local // flag levels. (The local will overwrite the global.) // // Params: // // - args: (required) A slice of arguments. Typically, this is `cxt:args` as set by // cookoo.cli.RequestResolver. // - flagset: (required) A set if flags (see flag.FlagSet) to parse. // // A slice of all non-flag arguments remaining after the parse are returned into the context. // // For example, if ['-foo', 'bar', 'some', 'other', 'data'] is passed in, '-foo' and 'bar' will // be parsed out, while ['some', 'other', 'data'] will be returned into the context. (Assuming, of // course, that the flag definition for -foo exists, and is a type that accepts a value). // // Thus, you will have `cxt:foo` available (with value `bar`) and everything else will be available // in the slice under this command's context entry. func ParseArgs(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { params.Requires("args", "flagset") flagset := params.Get("flagset", nil).(*flag.FlagSet) args := params.Get("args", nil).([]string) // If this is true, we shift the args first. if params.Get("subcommand", false).(bool) { args = args[1:] } flagset.Parse(args) addFlagsToContext(flagset, cxt) return flagset.Args(), nil }
// This is a utility function for executing statements. // // While we don't wrap all SQL statements, this particular command is here to // facilitate creating databases. In other situations, it is assumed that the // commands will handle SQL internally, and not use high-level commands to run // each query. // // Params: // - "statement": The statement to execute (as a string) // - "dbname": The name of the datasource that references the DB. // // Returns: // - database.sql.Result (core Go API) // // Example: // req.Route("install", "Create DB"). // Does(sql.Execute, "exec"). // Using("dbname").WithDefault("db"). // Using("statement").WithDefault("CREATE TABLE IF NOT EXISTS names (id INT, varchar NAME)") func Execute(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { ok, missing := params.Requires("statement", "dbname") if !ok { return nil, &cookoo.FatalError{fmt.Sprintf("Missing params: %s", strings.Join(missing, ","))} } dbname := params.Get("dbname", nil).(string) statement := params.Get("statement", nil).(string) db, err := GetDb(cxt, dbname) if err != nil { return nil, err } res, err := db.Exec(statement) if err != nil { return fatalError(err) } return res, nil }
// Ping a database. // // If the Ping fails, this will return an error. // // Params // - dbname: (required) the name of the database datasource. // // Returns: // - boolean flag set to true if the Ping was successful. func Ping(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { ok, _ := params.Requires("dbname") if !ok { e := &cookoo.RecoverableError{"Expected a dbname param."} return false, e } dbname := params.Get("dbname", nil).(string) db, err := GetDb(cxt, dbname) if err != nil { return false, err } err = db.Ping() if err != nil { return fatalError(err) } return true, nil }
// RenderHTML renders an HTML template. // // This uses the `html/template` system built into Go to render data into a writer. // // Params: // - template (required): An html/templates.Template object. // - templateName (required): The name of the template to render. // - values: An interface{} with the values to be passed to the template. If // this is not specified, the contents of the Context are passed as a map[string]interface{}. // Note that datasources, in this model, are not accessible to the template. // - writer: The writer that data should be sent to. By default, this will create a new // Buffer and put it into the context. (If no Writer was passed in, the returned writer // is actually a bytes.Buffer.) To flush the contents directly to the client, you can // use `.Using('writer').From('http.ResponseWriter')`. // // Returns // - An io.Writer. The template's contents have already been written into the writer. // // Example: // // reg.Route("GET /html", "Test HTML"). // Does(cookoo.AddToContext, "_"). // Using("Title").WithDefault("Hello World"). // Using("Body").WithDefault("This is the body."). // Does(web.RenderHTML, "render"). // Using("template").From('cxt:templateCache'). // Using("templateName").WithDefault("index.html"). // Does(web.Flush, "_"). // Using("contentType").WithDefault("text/html"). // Using("content").From("cxt:render") // // In the example above, we do three things: // - Add Title and Body to the context. For the template rendered, it will see these as // {{.Title}} and {{.Body}}. // - Render the template located in a local file called "index.html". It is recommended that // a template.Template object be created at startup. This way, all of the templates can // be cached immediately and shared throughout processing. // - Flush the result out to the client. This gives you a chance to add any additional headers. func RenderHTML(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { ok, missing := params.Requires("template", "templateName") if !ok { return nil, &cookoo.FatalError{"Missing params: " + strings.Join(missing, ", ")} } var buf bytes.Buffer out := params.Get("writer", &buf).(io.Writer) tplName := params.Get("templateName", nil).(string) tpl := params.Get("template", nil).(*template.Template) vals := params.Get("values", cxt.AsMap()) err := tpl.ExecuteTemplate(out, tplName, vals) if err != nil { log.Printf("Recoverable error parsing template: %s", err) // XXX: This outputs partially completed templates. Is this what we want? io.WriteString(out, "Template error. The error has been logged.") return out, &cookoo.RecoverableError{"Template failed to completely render."} } return out, nil }
// Run a subcommand. // // Params: // - args: a string[] of arguments, like you get from os.Args. This will assume the first arg // is a subcommand. If you have options, you should parse those out first with ParseArgs. // - default: The default subcommand to run if none is found. // - offset: By default, this assumes an os.Args, and looks up the item in os.Args[1]. You can // override this behavior by setting offset to something else. // - ignoreRoutes: A []string of routes that should not be executed. func RunSubcommand(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) { params.Requires("args") args := params.Get("args", nil).([]string) offset := params.Get("offset", 1).(int) var route string if len(args) <= offset { route = params.Get("default", "default").(string) } else { route = args[offset] } stoplist := params.Get("ignoreRoutes", []string{}).([]string) if len(stoplist) > 0 { for _, stop := range stoplist { if stop == route { return nil, &cookoo.FatalError{"Illegal route."} } } } return nil, &cookoo.Reroute{route} }
// Receive receives a Git repo. // This will only work for git-receive-pack. // // Params: // - operation (string): e.g. git-receive-pack // - repoName (string): The repository name, in the form '/REPO.git'. // - channel (ssh.Channel): The channel. // - request (*ssh.Request): The channel. // - gitHome (string): Defaults to /home/git. // - fingerprint (string): The fingerprint of the user's SSH key. // - user (string): The name of the Deis user. // // Returns: // - nothing func Receive(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { if ok, z := p.Requires("channel", "request", "fingerprint", "permissions"); !ok { return nil, fmt.Errorf("Missing requirements %q", z) } repoName := p.Get("repoName", "").(string) operation := p.Get("operation", "").(string) channel := p.Get("channel", nil).(ssh.Channel) gitHome := p.Get("gitHome", "/home/git").(string) fingerprint := p.Get("fingerprint", nil).(string) user := p.Get("user", "").(string) log.Debugf(c, "receiving git repo name: %s, operation: %s, fingerprint: %s, user: %s", repoName, operation, fingerprint, user) repo, err := cleanRepoName(repoName) if err != nil { log.Warnf(c, "Illegal repo name: %s.", err) channel.Stderr().Write([]byte("No repo given")) return nil, err } repo += ".git" repoPath := filepath.Join(gitHome, repo) log.Debugf(c, "creating repo directory %s", repoPath) if _, err := createRepo(c, repoPath); err != nil { err = fmt.Errorf("Did not create new repo (%s)", err) log.Warnf(c, err.Error()) return nil, err } log.Debugf(c, "writing pre-receive hook under %s", repoPath) if err := createPreReceiveHook(c, gitHome, repoPath); err != nil { err = fmt.Errorf("Did not write pre-receive hook (%s)", err) log.Warnf(c, err.Error()) return nil, err } cmd := exec.Command("git-shell", "-c", fmt.Sprintf("%s '%s'", operation, repo)) log.Infof(c, strings.Join(cmd.Args, " ")) var errbuff bytes.Buffer cmd.Dir = gitHome cmd.Env = []string{ fmt.Sprintf("RECEIVE_USER=%s", user), fmt.Sprintf("RECEIVE_REPO=%s", repo), fmt.Sprintf("RECEIVE_FINGERPRINT=%s", fingerprint), fmt.Sprintf("SSH_ORIGINAL_COMMAND=%s '%s'", operation, repo), fmt.Sprintf("SSH_CONNECTION=%s", c.Get("SSH_CONNECTION", "0 0 0 0").(string)), } cmd.Env = append(cmd.Env, os.Environ()...) log.Debugf(c, "Working Dir: %s", cmd.Dir) log.Debugf(c, "Environment: %s", strings.Join(cmd.Env, ",")) inpipe, err := cmd.StdinPipe() if err != nil { return nil, err } cmd.Stdout = channel cmd.Stderr = io.MultiWriter(channel.Stderr(), &errbuff) if err := cmd.Start(); err != nil { err = fmt.Errorf("Failed to start git pre-receive hook: %s (%s)", err, errbuff.Bytes()) log.Warnf(c, err.Error()) return nil, err } if _, err := io.Copy(inpipe, channel); err != nil { err = fmt.Errorf("Failed to write git objects into the git pre-receive hook (%s)", err) log.Warnf(c, err.Error()) return nil, err } fmt.Println("Waiting for git-receive to run.") fmt.Println("Waiting for deploy.") if err := cmd.Wait(); err != nil { err = fmt.Errorf("Failed to run git pre-receive hook: %s (%s)", errbuff.Bytes(), err) log.Errf(c, err.Error()) return nil, err } if errbuff.Len() > 0 { log.Warnf(c, "Unreported error: %s", errbuff.Bytes()) } log.Infof(c, "Deploy complete.\n") return nil, nil }