Example #1
0
File: git.go Project: helgi/builder
// 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
}
Example #2
0
// 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()
}
Example #3
0
// 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

}
Example #4
0
// 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
}
Example #5
0
// 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
}
Example #6
0
// 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
}
Example #7
0
// 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}
}
Example #8
0
// 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
}