<commit_before>/*
 * Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
Package waddrmgr provides a secure hierarchical deterministic wallet address
manager.

Overview

One of the fundamental jobs of a wallet is to manage addresses, private keys,
and script data associated with them.  At a high level, this package provides
the facilities to perform this task with a focus on security and also allows
recovery through the use of hierarchical deterministic keys (BIP0032) generated
from a caller provided seed.  The specific structure used is as described in
BIP0044.  This setup means as long as the user writes the seed down (even better
is to use a mnemonic for the seed), all their addresses and private keys can be
regenerated from the seed.

There are two master keys which are protected by two independent passphrases.
One is intended for public facing data, while the other is intended for private
data.  The public password can be hardcoded for callers who don't want the
additional public data protection or the same password can be used if a single
password is desired.  These choices provide a usability versus security
tradeoff.  However, keep in mind that extended hd keys, as called out in BIP0032
need to be handled more carefully than normal EC public keys because they can be
used to generate all future addresses.  While this is part of what makes them
attractive, it also means an attacker getting access to your extended public key
for an account will allow them to know all addresses you will use and hence
reduces privacy.  For this reason, it is highly recommended that you do not hard
code a password which allows any attacker who gets a copy of your address
manager database to access your effectively plain text extended public keys.

Each master key in turn protects the three real encryption keys (called crypto
keys) for public, private, and script data.  Some examples include payment
addresses, extended hd keys, and scripts associated with pay-to-script-hash
addresses.  This scheme makes changing passphrases more efficient since only the
crypto keys need to be re-encrypted versus every single piece of information
(which is what is needed for *rekeying*).  This results in a fully encrypted
database where access to it does not compromise address, key, or script privacy.
This differs from the handling by other wallets at the time of this writing in
that they divulge your addresses, and worse, some even expose the chain code
which can be used by the attacker to know all future addresses that will be
used.

The address manager is also hardened against memory scrapers.  This is
accomplished by typically having the address manager locked meaning no private
keys or scripts are in memory.  Unlocking the address manager causes the crypto
private and script keys to be decrypted and loaded in memory which in turn are
used to decrypt private keys and scripts on demand.  Relocking the address
manager actively zeros all private material from memory.  In addition, temp
private key material used internally is zeroed as soon as it's used.

Locking and Unlocking

As previously mentioned, this package provide facilities for locking and
unlocking the address manager to protect access to private material and remove
it from memory when locked.  The Lock, Unlock, and IsLocked functions are used
for this purpose.

Creating a New Address Manager

A new address manager is created via the Create function.  This function accepts
a wallet database namespace, passphrases, network, and perhaps most importantly,
a cryptographically random seed which is used to generate the master node of the
hierarchical deterministic keychain which allows all addresses and private keys
to be recovered with only the seed.  The GenerateSeed function in the hdkeychain
package can be used as a convenient way to create a random seed for use with
this function.  The address manager is locked immediately upon being created.

Opening an Existing Address Manager

An existing address manager is opened via the Open function.  This function
accepts an existing wallet database namespace, the public passphrase, and
network.  The address manager is opened locked as expected since the open
function does not take the private passphrase to unlock it.

Closing the Address Manager

The Close method should be called on the address manager when the caller is done
with it.  While it is not required, it is recommended because it sanely shuts
down the database and ensures all private and public key material is purged from
memory.

Managed Addresses

Each address returned by the address manager satisifies the ManagedAddress
interface as well as either the ManagedPubKeyAddress or ManagedScriptAddress
interfaces.  These interfaces provide the means to obtain relevant information
about the addresses such as their private keys and scripts.

Chained Addresses

Most callers will make use of the chained addresses for normal operations.
Internal addresses are intended for internal wallet uses such as change outputs,
while external addresses are intended for uses such payment addresses that are
shared.  The NextInternalAddresses and NextExternalAddresses functions provide
the means to acquire one or more of the next addresses that have not already
been provided.  In addition, the LastInternalAddress and LastExternalAddress
functions can be used to get the most recently provided internal and external
address, respectively.

Requesting Existing Addresses

In addition to generating new addresses, access to old addresses is often
required.  Most notably, to sign transactions in order to redeem them.  The
Address function provides this capability and returns a ManagedAddress.

Importing Addresses

While the recommended approach is to use the chained addresses discussed above
because they can be deterministically regenerated to avoid losing funds as long
as the user has the master seed, there are many addresses that already exist,
and as a result, this package provides the ability to import existing private
keys in Wallet Import Format (WIF) and hence the associated public key and
address.

Importing Scripts

In order to support pay-to-script-hash transactions, the script must be securely
stored as it is needed to redeem the transaction.  This can be useful for a
variety of scenarios, however the most common use is currently multi-signature
transactions.

Syncing

The address manager also supports storing and retrieving a block hash and height
which the manager is known to have all addresses synced through.  The manager
itself does not have any notion of which addresses are synced or not.  It only
provides the storage as a convenience for the caller.

Network

The address manager must be associated with a given network in order to provide
appropriate addresses and reject imported addresses and scripts which don't
apply to the associated network.

Errors

All errors returned from this package are of type ManagerError.  This allows the
caller to programmatically ascertain the specific reasons for failure by
examining the ErrorCode field of the type asserted ManagerError.  For certain
error codes, as documented by the specific error codes, the underlying error
will be contained in the Err field.

Bitcoin Improvement Proposals

This package includes concepts outlined by the following BIPs:

		BIP0032 (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
		BIP0043 (https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)
		BIP0044 (https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
*/
package waddrmgr
<commit_msg>waddrmgr: Update more documentation for walletdb.<commit_after>/*
 * Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
Package waddrmgr provides a secure hierarchical deterministic wallet address
manager.

Overview

One of the fundamental jobs of a wallet is to manage addresses, private keys,
and script data associated with them.  At a high level, this package provides
the facilities to perform this task with a focus on security and also allows
recovery through the use of hierarchical deterministic keys (BIP0032) generated
from a caller provided seed.  The specific structure used is as described in
BIP0044.  This setup means as long as the user writes the seed down (even better
is to use a mnemonic for the seed), all their addresses and private keys can be
regenerated from the seed.

There are two master keys which are protected by two independent passphrases.
One is intended for public facing data, while the other is intended for private
data.  The public password can be hardcoded for callers who don't want the
additional public data protection or the same password can be used if a single
password is desired.  These choices provide a usability versus security
tradeoff.  However, keep in mind that extended hd keys, as called out in BIP0032
need to be handled more carefully than normal EC public keys because they can be
used to generate all future addresses.  While this is part of what makes them
attractive, it also means an attacker getting access to your extended public key
for an account will allow them to know all derived addresses you will use and
hence reduces privacy.  For this reason, it is highly recommended that you do
not hard code a password which allows any attacker who gets a copy of your
address manager database to access your effectively plain text extended public
keys.

Each master key in turn protects the three real encryption keys (called crypto
keys) for public, private, and script data.  Some examples include payment
addresses, extended hd keys, and scripts associated with pay-to-script-hash
addresses.  This scheme makes changing passphrases more efficient since only the
crypto keys need to be re-encrypted versus every single piece of information
(which is what is needed for *rekeying*).  This results in a fully encrypted
database where access to it does not compromise address, key, or script privacy.
This differs from the handling by other wallets at the time of this writing in
that they divulge your addresses, and worse, some even expose the chain code
which can be used by the attacker to know all future addresses that will be
used.

The address manager is also hardened against memory scrapers.  This is
accomplished by typically having the address manager locked meaning no private
keys or scripts are in memory.  Unlocking the address manager causes the crypto
private and script keys to be decrypted and loaded in memory which in turn are
used to decrypt private keys and scripts on demand.  Relocking the address
manager actively zeros all private material from memory.  In addition, temp
private key material used internally is zeroed as soon as it's used.

Locking and Unlocking

As previously mentioned, this package provide facilities for locking and
unlocking the address manager to protect access to private material and remove
it from memory when locked.  The Lock, Unlock, and IsLocked functions are used
for this purpose.

Creating a New Address Manager

A new address manager is created via the Create function.  This function accepts
a wallet database namespace, passphrases, network, and perhaps most importantly,
a cryptographically random seed which is used to generate the master node of the
hierarchical deterministic keychain which allows all addresses and private keys
to be recovered with only the seed.  The GenerateSeed function in the hdkeychain
package can be used as a convenient way to create a random seed for use with
this function.  The address manager is locked immediately upon being created.

Opening an Existing Address Manager

An existing address manager is opened via the Open function.  This function
accepts an existing wallet database namespace, the public passphrase, and
network.  The address manager is opened locked as expected since the open
function does not take the private passphrase to unlock it.

Closing the Address Manager

The Close method should be called on the address manager when the caller is done
with it.  While it is not required, it is recommended because it sanely shuts
down the database and ensures all private and public key material is purged from
memory.

Managed Addresses

Each address returned by the address manager satisifies the ManagedAddress
interface as well as either the ManagedPubKeyAddress or ManagedScriptAddress
interfaces.  These interfaces provide the means to obtain relevant information
about the addresses such as their private keys and scripts.

Chained Addresses

Most callers will make use of the chained addresses for normal operations.
Internal addresses are intended for internal wallet uses such as change outputs,
while external addresses are intended for uses such payment addresses that are
shared.  The NextInternalAddresses and NextExternalAddresses functions provide
the means to acquire one or more of the next addresses that have not already
been provided.  In addition, the LastInternalAddress and LastExternalAddress
functions can be used to get the most recently provided internal and external
address, respectively.

Requesting Existing Addresses

In addition to generating new addresses, access to old addresses is often
required.  Most notably, to sign transactions in order to redeem them.  The
Address function provides this capability and returns a ManagedAddress.

Importing Addresses

While the recommended approach is to use the chained addresses discussed above
because they can be deterministically regenerated to avoid losing funds as long
as the user has the master seed, there are many addresses that already exist,
and as a result, this package provides the ability to import existing private
keys in Wallet Import Format (WIF) and hence the associated public key and
address.

Importing Scripts

In order to support pay-to-script-hash transactions, the script must be securely
stored as it is needed to redeem the transaction.  This can be useful for a
variety of scenarios, however the most common use is currently multi-signature
transactions.

Syncing

The address manager also supports storing and retrieving a block hash and height
which the manager is known to have all addresses synced through.  The manager
itself does not have any notion of which addresses are synced or not.  It only
provides the storage as a convenience for the caller.

Network

The address manager must be associated with a given network in order to provide
appropriate addresses and reject imported addresses and scripts which don't
apply to the associated network.

Errors

All errors returned from this package are of type ManagerError.  This allows the
caller to programmatically ascertain the specific reasons for failure by
examining the ErrorCode field of the type asserted ManagerError.  For certain
error codes, as documented by the specific error codes, the underlying error
will be contained in the Err field.

Bitcoin Improvement Proposals

This package includes concepts outlined by the following BIPs:

		BIP0032 (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
		BIP0043 (https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)
		BIP0044 (https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
*/
package waddrmgr
<|endoftext|>


<commit_before>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author: polaris	polaris@studygolang.com

package controller

import (
	"html/template"
	"http/middleware"
	"logic"
	"model"
	"net/http"

	. "http"

	"github.com/labstack/echo"
	"github.com/polaris1119/goutils"
)

// 在需要评论（喜欢）且要回调的地方注册评论（喜欢）对象
func init() {
	// 注册评论（喜欢）对象
	logic.RegisterCommentObject(model.TypeTopic, logic.TopicComment{})
	logic.RegisterLikeObject(model.TypeTopic, logic.TopicLike{})
}

type TopicController struct{}

// 注册路由
func (self TopicController) RegisterRoute(g *echo.Group) {
	g.GET("/topics", self.TopicList)
	g.GET("/topics/no_reply", self.TopicsNoReply)
	g.GET("/topics/last", self.TopicsLast)
	g.GET("/topics/:tid", self.Detail)
	g.GET("/topics/node/:nid", self.NodeTopics)
	g.GET("/go/:node", self.GoNodeTopics)

	g.Match([]string{"GET", "POST"}, "/topics/new", self.Create, middleware.NeedLogin(), middleware.Sensivite(), middleware.BalanceCheck(), middleware.PublishNotice())
	g.Match([]string{"GET", "POST"}, "/topics/modify", self.Modify, middleware.NeedLogin(), middleware.Sensivite())
}

func (self TopicController) TopicList(ctx echo.Context) error {
	tab := ctx.QueryParam("tab")
	if tab == "" {
		tab = GetFromCookie(ctx, "TOPIC_TAB")
	}

	if tab != "" && tab != "all" {
		nid := logic.GetNidByEname(tab)
		if nid > 0 {
			SetCookie(ctx, "TOPIC_TAB", tab)
			return self.topicList(ctx, tab, "topics.mtime DESC", "nid=? AND top!=1", nid)
		}
	}

	return self.topicList(ctx, "all", "topics.mtime DESC", "top!=1")
}

func (self TopicController) Topics(ctx echo.Context) error {
	return self.topicList(ctx, "", "topics.mtime DESC", "")
}

func (self TopicController) TopicsNoReply(ctx echo.Context) error {
	return self.topicList(ctx, "no_reply", "topics.mtime DESC", "lastreplyuid=?", 0)
}

func (self TopicController) TopicsLast(ctx echo.Context) error {
	return self.topicList(ctx, "last", "ctime DESC", "")
}

func (TopicController) topicList(ctx echo.Context, tab, orderBy, querystring string, args ...interface{}) error {
	curPage := goutils.MustInt(ctx.QueryParam("p"), 1)
	paginator := logic.NewPaginator(curPage)

	// 置顶的topic
	topTopics := logic.DefaultTopic.FindAll(ctx, paginator, "ctime DESC", "top=1")

	topics := logic.DefaultTopic.FindAll(ctx, paginator, orderBy, querystring, args...)
	total := logic.DefaultTopic.Count(ctx, querystring, args...)
	pageHtml := paginator.SetTotal(total).GetPageHtml(ctx.Request().URL().Path())

	hotNodes := logic.DefaultTopic.FindHotNodes(ctx)

	data := map[string]interface{}{
		"topics":       append(topTopics, topics...),
		"activeTopics": "active",
		"nodes":        logic.GenNodes(),
		"tab":          tab,
		"tab_list":     hotNodes,
		"page":         template.HTML(pageHtml),
	}

	return render(ctx, "topics/list.html", data)
}

// NodeTopics 某节点下的主题列表
func (TopicController) NodeTopics(ctx echo.Context) error {
	curPage := goutils.MustInt(ctx.QueryParam("p"), 1)
	paginator := logic.NewPaginator(curPage)

	querystring, nid := "nid=?", goutils.MustInt(ctx.Param("nid"))
	topics := logic.DefaultTopic.FindAll(ctx, paginator, "topics.mtime DESC", querystring, nid)
	total := logic.DefaultTopic.Count(ctx, querystring, nid)
	pageHtml := paginator.SetTotal(total).GetPageHtml(ctx.Request().URL().Path())

	// 当前节点信息
	node := logic.GetNode(nid)

	return render(ctx, "topics/node.html", map[string]interface{}{"activeTopics": "active", "topics": topics, "page": template.HTML(pageHtml), "total": total, "node": node})
}

// GoNodeTopics 某节点下的主题列表，uri: /go/golang
func (TopicController) GoNodeTopics(ctx echo.Context) error {
	curPage := goutils.MustInt(ctx.QueryParam("p"), 1)
	paginator := logic.NewPaginator(curPage)

	ename := ctx.Param("node")
	node := logic.GetNodeByEname(ename)
	if node == nil {
		return render(ctx, "notfound.html", nil)
	}

	querystring, nid := "nid=?", node["nid"].(int)
	topics := logic.DefaultTopic.FindAll(ctx, paginator, "topics.mtime DESC", querystring, nid)
	total := logic.DefaultTopic.Count(ctx, querystring, nid)
	pageHtml := paginator.SetTotal(total).GetPageHtml(ctx.Request().URL().Path())

	return render(ctx, "topics/node.html", map[string]interface{}{"activeTopics": "active", "topics": topics, "page": template.HTML(pageHtml), "total": total, "node": node})
}

// Detail 社区主题详细页
func (TopicController) Detail(ctx echo.Context) error {
	tid := goutils.MustInt(ctx.Param("tid"))
	if tid == 0 {
		return render(ctx, "notfound.html", nil)
	}

	topic, replies, err := logic.DefaultTopic.FindByTid(ctx, tid)
	if err != nil {
		return render(ctx, "notfound.html", nil)
	}

	data := map[string]interface{}{
		"activeTopics": "active",
		"topic":        topic,
		"replies":      replies,
	}

	me, ok := ctx.Get("user").(*model.Me)
	if ok {
		tid := topic["tid"].(int)
		data["likeflag"] = logic.DefaultLike.HadLike(ctx, me.Uid, tid, model.TypeTopic)
		data["hadcollect"] = logic.DefaultFavorite.HadFavorite(ctx, me.Uid, tid, model.TypeTopic)

		logic.Views.Incr(Request(ctx), model.TypeTopic, tid, me.Uid)

		if me.Uid != topic["uid"].(int) {
			go logic.DefaultViewRecord.Record(tid, model.TypeTopic, me.Uid)
		} else {
			data["view_user_num"] = logic.DefaultViewRecord.FindUserNum(ctx, tid, model.TypeTopic)
		}
	} else {
		logic.Views.Incr(Request(ctx), model.TypeTopic, tid)
	}

	return render(ctx, "topics/detail.html,common/comment.html", data)
}

// Create 新建主题
func (TopicController) Create(ctx echo.Context) error {
	nodes := logic.GenNodes()
	nid := goutils.MustInt(ctx.QueryParam("nid"))

	title := ctx.FormValue("title")
	// 请求新建主题页面
	if title == "" || ctx.Request().Method() != "POST" {
		return render(ctx, "topics/new.html", map[string]interface{}{"nodes": nodes, "activeTopics": "active", "nid": nid})
	}

	me := ctx.Get("user").(*model.Me)
	tid, err := logic.DefaultTopic.Publish(ctx, me, ctx.FormParams())
	if err != nil {
		return fail(ctx, 1, "内部服务错误")
	}

	return success(ctx, map[string]interface{}{"tid": tid})
}

// Modify 修改主题
func (TopicController) Modify(ctx echo.Context) error {
	tid := goutils.MustInt(ctx.FormValue("tid"))
	if tid == 0 {
		return ctx.Redirect(http.StatusSeeOther, "/topics")
	}

	nodes := logic.GenNodes()

	if ctx.Request().Method() != "POST" {
		topics := logic.DefaultTopic.FindByTids([]int{tid})
		if len(topics) == 0 {
			return ctx.Redirect(http.StatusSeeOther, "/topics")
		}

		return render(ctx, "topics/new.html", map[string]interface{}{"nodes": nodes, "topic": topics[0], "activeTopics": "active"})
	}

	me := ctx.Get("user").(*model.Me)
	_, err := logic.DefaultTopic.Publish(ctx, me, ctx.FormParams())
	if err != nil {
		if err == logic.NotModifyAuthorityErr {
			return fail(ctx, 1, "没有权限操作")
		}

		return fail(ctx, 2, "服务错误，请稍后重试！")
	}
	return success(ctx, map[string]interface{}{"tid": tid})
}
<commit_msg>将错误返回给前端<commit_after>// Copyright 2016 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author: polaris	polaris@studygolang.com

package controller

import (
	"html/template"
	"http/middleware"
	"logic"
	"model"
	"net/http"

	. "http"

	"github.com/labstack/echo"
	"github.com/polaris1119/goutils"
)

// 在需要评论（喜欢）且要回调的地方注册评论（喜欢）对象
func init() {
	// 注册评论（喜欢）对象
	logic.RegisterCommentObject(model.TypeTopic, logic.TopicComment{})
	logic.RegisterLikeObject(model.TypeTopic, logic.TopicLike{})
}

type TopicController struct{}

// 注册路由
func (self TopicController) RegisterRoute(g *echo.Group) {
	g.GET("/topics", self.TopicList)
	g.GET("/topics/no_reply", self.TopicsNoReply)
	g.GET("/topics/last", self.TopicsLast)
	g.GET("/topics/:tid", self.Detail)
	g.GET("/topics/node/:nid", self.NodeTopics)
	g.GET("/go/:node", self.GoNodeTopics)

	g.Match([]string{"GET", "POST"}, "/topics/new", self.Create, middleware.NeedLogin(), middleware.Sensivite(), middleware.BalanceCheck(), middleware.PublishNotice())
	g.Match([]string{"GET", "POST"}, "/topics/modify", self.Modify, middleware.NeedLogin(), middleware.Sensivite())
}

func (self TopicController) TopicList(ctx echo.Context) error {
	tab := ctx.QueryParam("tab")
	if tab == "" {
		tab = GetFromCookie(ctx, "TOPIC_TAB")
	}

	if tab != "" && tab != "all" {
		nid := logic.GetNidByEname(tab)
		if nid > 0 {
			SetCookie(ctx, "TOPIC_TAB", tab)
			return self.topicList(ctx, tab, "topics.mtime DESC", "nid=? AND top!=1", nid)
		}
	}

	return self.topicList(ctx, "all", "topics.mtime DESC", "top!=1")
}

func (self TopicController) Topics(ctx echo.Context) error {
	return self.topicList(ctx, "", "topics.mtime DESC", "")
}

func (self TopicController) TopicsNoReply(ctx echo.Context) error {
	return self.topicList(ctx, "no_reply", "topics.mtime DESC", "lastreplyuid=?", 0)
}

func (self TopicController) TopicsLast(ctx echo.Context) error {
	return self.topicList(ctx, "last", "ctime DESC", "")
}

func (TopicController) topicList(ctx echo.Context, tab, orderBy, querystring string, args ...interface{}) error {
	curPage := goutils.MustInt(ctx.QueryParam("p"), 1)
	paginator := logic.NewPaginator(curPage)

	// 置顶的topic
	topTopics := logic.DefaultTopic.FindAll(ctx, paginator, "ctime DESC", "top=1")

	topics := logic.DefaultTopic.FindAll(ctx, paginator, orderBy, querystring, args...)
	total := logic.DefaultTopic.Count(ctx, querystring, args...)
	pageHtml := paginator.SetTotal(total).GetPageHtml(ctx.Request().URL().Path())

	hotNodes := logic.DefaultTopic.FindHotNodes(ctx)

	data := map[string]interface{}{
		"topics":       append(topTopics, topics...),
		"activeTopics": "active",
		"nodes":        logic.GenNodes(),
		"tab":          tab,
		"tab_list":     hotNodes,
		"page":         template.HTML(pageHtml),
	}

	return render(ctx, "topics/list.html", data)
}

// NodeTopics 某节点下的主题列表
func (TopicController) NodeTopics(ctx echo.Context) error {
	curPage := goutils.MustInt(ctx.QueryParam("p"), 1)
	paginator := logic.NewPaginator(curPage)

	querystring, nid := "nid=?", goutils.MustInt(ctx.Param("nid"))
	topics := logic.DefaultTopic.FindAll(ctx, paginator, "topics.mtime DESC", querystring, nid)
	total := logic.DefaultTopic.Count(ctx, querystring, nid)
	pageHtml := paginator.SetTotal(total).GetPageHtml(ctx.Request().URL().Path())

	// 当前节点信息
	node := logic.GetNode(nid)

	return render(ctx, "topics/node.html", map[string]interface{}{"activeTopics": "active", "topics": topics, "page": template.HTML(pageHtml), "total": total, "node": node})
}

// GoNodeTopics 某节点下的主题列表，uri: /go/golang
func (TopicController) GoNodeTopics(ctx echo.Context) error {
	curPage := goutils.MustInt(ctx.QueryParam("p"), 1)
	paginator := logic.NewPaginator(curPage)

	ename := ctx.Param("node")
	node := logic.GetNodeByEname(ename)
	if node == nil {
		return render(ctx, "notfound.html", nil)
	}

	querystring, nid := "nid=?", node["nid"].(int)
	topics := logic.DefaultTopic.FindAll(ctx, paginator, "topics.mtime DESC", querystring, nid)
	total := logic.DefaultTopic.Count(ctx, querystring, nid)
	pageHtml := paginator.SetTotal(total).GetPageHtml(ctx.Request().URL().Path())

	return render(ctx, "topics/node.html", map[string]interface{}{"activeTopics": "active", "topics": topics, "page": template.HTML(pageHtml), "total": total, "node": node})
}

// Detail 社区主题详细页
func (TopicController) Detail(ctx echo.Context) error {
	tid := goutils.MustInt(ctx.Param("tid"))
	if tid == 0 {
		return render(ctx, "notfound.html", nil)
	}

	topic, replies, err := logic.DefaultTopic.FindByTid(ctx, tid)
	if err != nil {
		return render(ctx, "notfound.html", nil)
	}

	data := map[string]interface{}{
		"activeTopics": "active",
		"topic":        topic,
		"replies":      replies,
	}

	me, ok := ctx.Get("user").(*model.Me)
	if ok {
		tid := topic["tid"].(int)
		data["likeflag"] = logic.DefaultLike.HadLike(ctx, me.Uid, tid, model.TypeTopic)
		data["hadcollect"] = logic.DefaultFavorite.HadFavorite(ctx, me.Uid, tid, model.TypeTopic)

		logic.Views.Incr(Request(ctx), model.TypeTopic, tid, me.Uid)

		if me.Uid != topic["uid"].(int) {
			go logic.DefaultViewRecord.Record(tid, model.TypeTopic, me.Uid)
		} else {
			data["view_user_num"] = logic.DefaultViewRecord.FindUserNum(ctx, tid, model.TypeTopic)
		}
	} else {
		logic.Views.Incr(Request(ctx), model.TypeTopic, tid)
	}

	return render(ctx, "topics/detail.html,common/comment.html", data)
}

// Create 新建主题
func (TopicController) Create(ctx echo.Context) error {
	nodes := logic.GenNodes()
	nid := goutils.MustInt(ctx.QueryParam("nid"))

	title := ctx.FormValue("title")
	// 请求新建主题页面
	if title == "" || ctx.Request().Method() != "POST" {
		return render(ctx, "topics/new.html", map[string]interface{}{"nodes": nodes, "activeTopics": "active", "nid": nid})
	}

	me := ctx.Get("user").(*model.Me)
	tid, err := logic.DefaultTopic.Publish(ctx, me, ctx.FormParams())
	if err != nil {
		return fail(ctx, 1, "内部服务错误:"+err.Error())
	}

	return success(ctx, map[string]interface{}{"tid": tid})
}

// Modify 修改主题
func (TopicController) Modify(ctx echo.Context) error {
	tid := goutils.MustInt(ctx.FormValue("tid"))
	if tid == 0 {
		return ctx.Redirect(http.StatusSeeOther, "/topics")
	}

	nodes := logic.GenNodes()

	if ctx.Request().Method() != "POST" {
		topics := logic.DefaultTopic.FindByTids([]int{tid})
		if len(topics) == 0 {
			return ctx.Redirect(http.StatusSeeOther, "/topics")
		}

		return render(ctx, "topics/new.html", map[string]interface{}{"nodes": nodes, "topic": topics[0], "activeTopics": "active"})
	}

	me := ctx.Get("user").(*model.Me)
	_, err := logic.DefaultTopic.Publish(ctx, me, ctx.FormParams())
	if err != nil {
		if err == logic.NotModifyAuthorityErr {
			return fail(ctx, 1, "没有权限操作")
		}

		return fail(ctx, 2, "服务错误，请稍后重试！")
	}
	return success(ctx, map[string]interface{}{"tid": tid})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	test := []string{"string1", "string2", "string3"}

	rand.Seed(time.Now().UnixNano())

	for i := 0; i < 10; i++ {
		random := rand.Intn(len(test))
		fmt.Println(test[random])
	}
}
<commit_msg>Update sandbox for filter testing<commit_after>package main

import (
	/*
		"fmt"
		"math/rand"
		"time"
	*/

	"fmt"
	"strings"
)

type Object struct {
	Id     int
	Fields map[string]string
}

func main() {
	/*
		test := []string{"string1", "string2", "string3"}

		rand.Seed(time.Now().UnixNano())

		for i := 0; i < 10; i++ {
			random := rand.Intn(len(test))
			fmt.Println(test[random])
		}
	*/

	/*
		test := "ping 1"
		iindex := strings.Index(test, " ")
		value := test[iindex+1:]
		fmt.Println("value: ", value)
	*/

	//match
	obj1 := new(Object)
	obj1.Id = 1
	fields1 := map[string]string{"foo1": "bar1", "foo2": "bar1"}
	obj1.Fields = fields1

	obj2 := new(Object)
	obj2.Id = 2
	fields2 := map[string]string{"foo1": "bar1", "foo2": "bar5"}
	obj2.Fields = fields2

	obj3 := new(Object)
	obj3.Id = 3
	fields3 := map[string]string{"foo1": "bar5", "foo4": "bar1"}
	obj3.Fields = fields3

	obj4 := new(Object)
	obj4.Id = 4
	fields4 := map[string]string{"foo3": "bar5", "foo4": "bar1"}
	obj4.Fields = fields4

	obj5 := new(Object)
	obj5.Id = 5
	fields5 := map[string]string{"foo1": "bar5"}
	obj5.Fields = fields5

	objs := []*Object{obj1, obj2, obj3, obj4, obj5}

	//filtering is done by query parameters on the URI
	filters := map[string][]string{"foo1": []string{"bar1", "bar2", "bar3"},
		"foo2": []string{"bar1", "bar4"}}

	include := true
	for _, obj := range objs {
		for key, values := range filters {
			fmt.Print("Filter Key: ", key, "\n")
			if len(obj.Fields[key]) == 0 {
				fmt.Print("Key ", key, " not found\n")
				include = false
				break
			}
			if !include {
				fmt.Print("Exiting early with no key found\n")
				break
			}

			found := false
			for _, value := range values {
				fmt.Print("Filter Val: ", value, "\n")
				//omit adding to the slice if the key and value doesnt exist
				if strings.Compare(value, obj.Fields[key]) == 0 {
					fmt.Print(value, " = ", obj.Fields[key], "\n")
					found = true //key exists and value exists in the map
					break
				}
			}
			if !found {
				fmt.Print("Exiting early with no value found\n")
				include = false
				break
			}

			fmt.Print("Found: ", found, "\n")
			include = include && found

			if !include {
				fmt.Print("Exiting early with no key found\n")
				break
			}
		}

		if !include {
			fmt.Print("\n\n\n")
			continue
		}

		fmt.Print("ADDING: ", obj.Id, "\n\n\n")
	}

}
<|endoftext|>


<commit_before>package main

import (
	"strings"
)

func (ic *Compiler) Shunt(name string) string {
	var token = ic.Scan(0)

	switch token {
		case ")", ",", "\n", "]", ";", "{":
			ic.NextToken = token
			return name

		case ".":
			if ic.ExpressionType.IsUser() == Undefined {
				ic.RaiseError("Type '%v', cannot be indexed!", ic.ExpressionType.Name)
			}
			var index = ic.Scan(Name)
			return ic.Shunt(ic.IndexUserType(name, index))

		case ":":
			if ic.ExpressionType.Push == "PUSH" {
				ic.NextToken = token
				return name
			}
			if ic.ExpressionType.Push != "SHARE" {
				ic.RaiseError("Cannot index "+name+", not an array! ("+ic.ExpressionType.Name+")")
			}

			var original = ic.ExpressionType

			var slice = ic.Tmp("slice")
			ic.Assembly("SHARE ", name)

			var low,high string
			if tok := ic.Scan(0); tok != ":" {
				ic.NextToken = tok
				low = ic.ScanExpression()
				ic.Scan(':')
			} else {
				low = "0"
			}

			if tok := ic.Scan(0); tok != ":" {
				ic.NextToken = tok
				high = ic.ScanExpression()
				ic.Scan(':')
			} else {
				high = "#"+name
			}

			ic.Assembly("PUSH ", high)
			ic.Assembly("PUSH ", low)

			ic.Assembly("SLICE")

			ic.Assembly("GRAB ", slice)

			ic.ExpressionType = original

			return ic.Shunt(slice)

		case "(":
			if ic.ExpressionType != InFunction {
				ic.RaiseError("Cannot call "+name+", not a function! ("+ic.ExpressionType.Name+")")
			}
			var r = ic.ScanFunctionCall(name)
			ic.Scan(')')

			return ic.Shunt(r)

		case "[":
			var list bool
			var typename string
			if ic.ExpressionType.Push != "SHARE" {
				ic.RaiseError("Cannot index "+name+", not an array! ("+ic.ExpressionType.Name+")")
			}
			if ic.ExpressionType.List {
				list = true
				typename = ic.ExpressionType.Name
			}
			var index = ic.ScanExpression()
			ic.Scan(']')

			ic.ExpressionType = Number
			if ic.ExpressionType == Text {
				ic.ExpressionType = Letter
			}

			if !list {
				return ic.Shunt(ic.Index(name, index))
			} else {
				var listdex = ic.Tmp("listdex")
				ic.Assembly("PUSH ", ic.Index(name, index))
				ic.Assembly("HEAP")
				ic.Assembly("GRAB ", listdex)
				ic.ExpressionType = ic.DefinedTypes[typename]
				return ic.Shunt(listdex)
			}

		default:

			if IsOperator(token+ic.Peek()) {
				token += ic.Peek()
				ic.Scan(0)
			}

			if IsOperator(token) {
				id := ic.Tmp("operator")

				var operator Operator
				var ok bool

				var A = ic.ExpressionType
				var B Type
				var next string
				if operator, ok = GetOperator(token, ic.ExpressionType, Undefined); !ok {

					if OperatorPrecident(token) {
						next = ic.ScanExpression()
					} else {
						next = ic.expression()
					}
					B = ic.ExpressionType

					operator, ok = GetOperator(token, A, B)

					if token == "=" && A == Text && B == Text {
						ic.ExpressionType = Number
					}
				} else if token == "²" {
					next = name
					B = ic.ExpressionType
					operator, ok = GetOperator("*", A, B)
					token = "*"
				}

				if ok {

					asm := operator.Assembly
					asm = strings.Replace(asm, "%a", name, -1)
					asm = strings.Replace(asm, "%b", next, -1)
					asm = strings.Replace(asm, "%c", id, -1)

					ic.Assembly(asm)

					if operator.ExpressionType != Undefined {
						ic.ExpressionType = operator.ExpressionType
					}

					if !OperatorPrecident(token) {
						return ic.Shunt(id)
					}
					return id

				} else {
					ic.RaiseError("Invalid Operator Matchup! ", A.Name , token, B.Name, "(types do not support the opperator)")
				}
		}
	}

	ic.RaiseError()
	return ""
}
<commit_msg>Call Pipes in expressions like functions.<commit_after>package main

import (
	"strings"
)

func (ic *Compiler) Shunt(name string) string {
	var token = ic.Scan(0)

	switch token {
		case ")", ",", "\n", "]", ";", "{":
			ic.NextToken = token
			return name

		case ".":
			if ic.ExpressionType.IsUser() == Undefined {
				ic.RaiseError("Type '%v', cannot be indexed!", ic.ExpressionType.Name)
			}
			var index = ic.Scan(Name)
			return ic.Shunt(ic.IndexUserType(name, index))

		case ":":
			if ic.ExpressionType.Push == "PUSH" {
				ic.NextToken = token
				return name
			}
			if ic.ExpressionType.Push != "SHARE" {
				ic.RaiseError("Cannot index "+name+", not an array! ("+ic.ExpressionType.Name+")")
			}

			var original = ic.ExpressionType

			var slice = ic.Tmp("slice")
			ic.Assembly("SHARE ", name)

			var low,high string
			if tok := ic.Scan(0); tok != ":" {
				ic.NextToken = tok
				low = ic.ScanExpression()
				ic.Scan(':')
			} else {
				low = "0"
			}

			if tok := ic.Scan(0); tok != ":" {
				ic.NextToken = tok
				high = ic.ScanExpression()
				ic.Scan(':')
			} else {
				high = "#"+name
			}

			ic.Assembly("PUSH ", high)
			ic.Assembly("PUSH ", low)

			ic.Assembly("SLICE")

			ic.Assembly("GRAB ", slice)

			ic.ExpressionType = original

			return ic.Shunt(slice)

		case "(":
			//Calling pipes.
			if ic.ExpressionType == Pipe {

				token := ic.Scan(0)
				if token == ")" {
					//Read default from the pipe.
					var r = ic.Tmp("read")
					ic.Assembly("RELAY ", name)
					ic.Assembly("PUSH 0")
					ic.Assembly("IN")
					ic.Assembly("GRAB ", r)
					ic.ExpressionType = Text
					return ic.Shunt(r)
				}

				ic.NextToken = token

				argument := ic.ScanExpression()

				switch ic.ExpressionType {
					case Letter:
						var r = ic.Tmp("reada")
						ic.Assembly("RELAY ", name)
						ic.Assembly("PUSH ", argument)
						ic.Assembly("RUN reada_m_pipe")
						ic.Assembly("GRAB ", r)
						ic.LoadFunction("reada_m_pipe")
						ic.ExpressionType = Text
						ic.Scan(')')
						return ic.Shunt(r)
					case Number:
						var r = ic.Tmp("reada")
						ic.Assembly("RELAY ", name)
						ic.Assembly("PUSH ", argument)
						ic.Assembly("IN")
						ic.Assembly("GRAB ", r)
						ic.ExpressionType = Text
						ic.Scan(')')
						return ic.Shunt(r)
					default:
						ic.RaiseError("Cannot call a pipe with a ", ic.ExpressionType.Name, " argument in an expression!")
				}

			}

			if ic.ExpressionType != InFunction {
				ic.RaiseError("Cannot call "+name+", not a function! ("+ic.ExpressionType.Name+")")
			}
			var r = ic.ScanFunctionCall(name)
			ic.Scan(')')

			return ic.Shunt(r)

		case "[":
			var list bool
			var typename string
			if ic.ExpressionType.Push != "SHARE" {
				ic.RaiseError("Cannot index "+name+", not an array! ("+ic.ExpressionType.Name+")")
			}
			if ic.ExpressionType.List {
				list = true
				typename = ic.ExpressionType.Name
			}
			var index = ic.ScanExpression()
			ic.Scan(']')

			ic.ExpressionType = Number
			if ic.ExpressionType == Text {
				ic.ExpressionType = Letter
			}

			if !list {
				return ic.Shunt(ic.Index(name, index))
			} else {
				var listdex = ic.Tmp("listdex")
				ic.Assembly("PUSH ", ic.Index(name, index))
				ic.Assembly("HEAP")
				ic.Assembly("GRAB ", listdex)
				ic.ExpressionType = ic.DefinedTypes[typename]
				return ic.Shunt(listdex)
			}

		default:

			if IsOperator(token+ic.Peek()) {
				token += ic.Peek()
				ic.Scan(0)
			}

			if IsOperator(token) {
				id := ic.Tmp("operator")

				var operator Operator
				var ok bool

				var A = ic.ExpressionType
				var B Type
				var next string
				if operator, ok = GetOperator(token, ic.ExpressionType, Undefined); !ok {

					if OperatorPrecident(token) {
						next = ic.ScanExpression()
					} else {
						next = ic.expression()
					}
					B = ic.ExpressionType

					operator, ok = GetOperator(token, A, B)

					if token == "=" && A == Text && B == Text {
						ic.ExpressionType = Number
					}
				} else if token == "²" {
					next = name
					B = ic.ExpressionType
					operator, ok = GetOperator("*", A, B)
					token = "*"
				}

				if ok {

					asm := operator.Assembly
					asm = strings.Replace(asm, "%a", name, -1)
					asm = strings.Replace(asm, "%b", next, -1)
					asm = strings.Replace(asm, "%c", id, -1)

					ic.Assembly(asm)

					if operator.ExpressionType != Undefined {
						ic.ExpressionType = operator.ExpressionType
					}

					if !OperatorPrecident(token) {
						return ic.Shunt(id)
					}
					return id

				} else {
					ic.RaiseError("Invalid Operator Matchup! ", A.Name , token, B.Name, "(types do not support the opperator)")
				}
		}
	}

	ic.RaiseError()
	return ""
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package build

import (
	"crypto/hmac"
	"fmt"
	"http"
	"json"
	"os"

	"appengine"
	"appengine/datastore"
	"cache"
)

const commitsPerPage = 30

// defaultPackages specifies the Package records to be created by initHandler.
var defaultPackages = []*Package{
	&Package{Name: "Go"},
}

// commitHandler retrieves commit data or records a new commit.
//
// For GET requests it returns a Commit value for the specified
// packagePath and hash.
//
// For POST requests it reads a JSON-encoded Commit value from the request
// body and creates a new Commit entity. It also updates the "tip" Tag for
// each new commit at tip.
//
// This handler is used by a gobuilder process in -commit mode.
func commitHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	com := new(Commit)

	if r.Method == "GET" {
		com.PackagePath = r.FormValue("packagePath")
		com.Hash = r.FormValue("hash")
		if err := datastore.Get(c, com.Key(c), com); err != nil {
			return nil, fmt.Errorf("getting Commit: %v", err)
		}
		return com, nil
	}
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	// POST request
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(com); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if len(com.Desc) > maxDatastoreStringLen {
		com.Desc = com.Desc[:maxDatastoreStringLen]
	}
	if err := com.Valid(); err != nil {
		return nil, fmt.Errorf("validating Commit: %v", err)
	}
	defer cache.Tick(c)
	tx := func(c appengine.Context) os.Error {
		return addCommit(c, com)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// addCommit adds the Commit entity to the datastore and updates the tip Tag.
// It must be run inside a datastore transaction.
func addCommit(c appengine.Context, com *Commit) os.Error {
	var tc Commit // temp value so we don't clobber com
	err := datastore.Get(c, com.Key(c), &tc)
	if err != datastore.ErrNoSuchEntity {
		// if this commit is already in the datastore, do nothing
		if err == nil {
			return nil
		}
		return fmt.Errorf("getting Commit: %v", err)
	}
	// get the next commit number
	p, err := GetPackage(c, com.PackagePath)
	if err != nil {
		return fmt.Errorf("GetPackage: %v", err)
	}
	com.Num = p.NextNum
	p.NextNum++
	if _, err := datastore.Put(c, p.Key(c), p); err != nil {
		return fmt.Errorf("putting Package: %v", err)
	}
	// if this isn't the first Commit test the parent commit exists
	if com.Num > 0 {
		n, err := datastore.NewQuery("Commit").
			Filter("Hash =", com.ParentHash).
			Ancestor(p.Key(c)).
			Count(c)
		if err != nil {
			return fmt.Errorf("testing for parent Commit: %v", err)
		}
		if n == 0 {
			return os.NewError("parent commit not found")
		}
	}
	// update the tip Tag if this is the Go repo
	if p.Path == "" {
		t := &Tag{Kind: "tip", Hash: com.Hash}
		if _, err = datastore.Put(c, t.Key(c), t); err != nil {
			return fmt.Errorf("putting Tag: %v", err)
		}
	}
	// put the Commit
	if _, err = datastore.Put(c, com.Key(c), com); err != nil {
		return fmt.Errorf("putting Commit: %v", err)
	}
	return nil
}

// tagHandler records a new tag. It reads a JSON-encoded Tag value from the
// request body and updates the Tag entity for the Kind of tag provided.
//
// This handler is used by a gobuilder process in -commit mode.
func tagHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	t := new(Tag)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(t); err != nil {
		return nil, err
	}
	if err := t.Valid(); err != nil {
		return nil, err
	}
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	_, err := datastore.Put(c, t.Key(c), t)
	return nil, err
}

// Todo is a todoHandler response.
type Todo struct {
	Kind string // "build-go-commit" or "build-package"
	Data interface{}
}

// todoHandler returns the next action to be performed by a builder.
// It expects "builder" and "kind" query parameters and returns a *Todo value.
// Multiple "kind" parameters may be specified.
func todoHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	key := "build-todo-" + r.Form.Encode()
	cachedTodo := new(Todo)
	if cache.Get(r, now, key, cachedTodo) {
		return cachedTodo, nil
	}
	var todo *Todo
	var err os.Error
	builder := r.FormValue("builder")
	for _, kind := range r.Form["kind"] {
		var data interface{}
		switch kind {
		case "build-go-commit":
			data, err = buildTodo(c, builder, "", "")
		case "build-package":
			packagePath := r.FormValue("packagePath")
			goHash := r.FormValue("goHash")
			data, err = buildTodo(c, builder, packagePath, goHash)
		}
		if data != nil || err != nil {
			todo = &Todo{Kind: kind, Data: data}
			break
		}
	}
	if err == nil {
		cache.Set(r, now, key, todo)
	}
	return todo, err
}

// buildTodo returns the next Commit to be built (or nil if none available).
//
// If packagePath and goHash are empty, it scans the first 20 Go Commits in
// Num-descending order and returns the first one it finds that doesn't have a
// Result for this builder.
//
// If provided with non-empty packagePath and goHash args, it scans the first
// 20 Commits in Num-descending order for the specified packagePath and
// returns the first that doesn't have a Result for this builder and goHash.
func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, os.Error) {
	p, err := GetPackage(c, packagePath)
	if err != nil {
		return nil, err
	}

	t := datastore.NewQuery("Commit").
		Ancestor(p.Key(c)).
		Limit(commitsPerPage).
		Order("-Num").
		Run(c)
	for {
		com := new(Commit)
		if _, err := t.Next(com); err != nil {
			if err == datastore.Done {
				err = nil
			}
			return nil, err
		}
		if com.Result(builder, goHash) == nil {
			return com, nil
		}
	}
	panic("unreachable")
}

// packagesHandler returns a list of the non-Go Packages monitored
// by the dashboard.
func packagesHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	const key = "build-packages"
	var p []*Package
	if cache.Get(r, now, key, &p) {
		return p, nil
	}
	p, err := Packages(c)
	if err != nil {
		return nil, err
	}
	cache.Set(r, now, key, p)
	return p, nil
}

// resultHandler records a build result.
// It reads a JSON-encoded Result value from the request body,
// creates a new Result entity, and updates the relevant Commit entity.
// If the Log field is not empty, resultHandler creates a new Log entity
// and updates the LogHash field before putting the Commit entity.
func resultHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	c := appengine.NewContext(r)
	res := new(Result)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if err := res.Valid(); err != nil {
		return nil, fmt.Errorf("validating Result: %v", err)
	}
	defer cache.Tick(c)
	// store the Log text if supplied
	if len(res.Log) > 0 {
		hash, err := PutLog(c, res.Log)
		if err != nil {
			return nil, fmt.Errorf("putting Log: %v", err)
		}
		res.LogHash = hash
	}
	tx := func(c appengine.Context) os.Error {
		// check Package exists
		if _, err := GetPackage(c, res.PackagePath); err != nil {
			return fmt.Errorf("GetPackage: %v", err)
		}
		// put Result
		if _, err := datastore.Put(c, res.Key(c), res); err != nil {
			return fmt.Errorf("putting Result: %v", err)
		}
		// add Result to Commit
		com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
		if err := com.AddResult(c, res); err != nil {
			return fmt.Errorf("AddResult: %v", err)
		}
		// Send build failure notifications, if necessary.
		// Note this must run after the call AddResult, which
		// populates the Commit's ResultData field.
		return notifyOnFailure(c, com, res.Builder)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "text/plain")
	c := appengine.NewContext(r)
	hash := r.URL.Path[len("/log/"):]
	key := datastore.NewKey(c, "Log", hash, 0, nil)
	l := new(Log)
	if err := datastore.Get(c, key, l); err != nil {
		logErr(w, r, err)
		return
	}
	b, err := l.Text()
	if err != nil {
		logErr(w, r, err)
		return
	}
	w.Write(b)
}

type dashHandler func(*http.Request) (interface{}, os.Error)

type dashResponse struct {
	Response interface{}
	Error    string
}

// errBadMethod is returned by a dashHandler when
// the request has an unsuitable method.
type errBadMethod string

func (e errBadMethod) String() string {
	return "bad method: " + string(e)
}

// AuthHandler wraps a http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters.
func AuthHandler(h dashHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)

		// Put the URL Query values into r.Form to avoid parsing the
		// request body when calling r.FormValue.
		r.Form = r.URL.Query()

		var err os.Error
		var resp interface{}

		// Validate key query parameter for POST requests only.
		key := r.FormValue("key")
		builder := r.FormValue("builder")
		if r.Method == "POST" && !validKey(c, key, builder) {
			err = os.NewError("invalid key: " + key)
		}

		// Call the original HandlerFunc and return the response.
		if err == nil {
			resp, err = h(r)
		}

		// Write JSON response.
		dashResp := &dashResponse{Response: resp}
		if err != nil {
			c.Errorf("%v", err)
			dashResp.Error = err.String()
		}
		w.Header().Set("Content-Type", "application/json")
		if err = json.NewEncoder(w).Encode(dashResp); err != nil {
			c.Criticalf("encoding response: %v", err)
		}
	}
}

func initHandler(w http.ResponseWriter, r *http.Request) {
	// TODO(adg): devise a better way of bootstrapping new packages
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	for _, p := range defaultPackages {
		if err := datastore.Get(c, p.Key(c), new(Package)); err == nil {
			continue
		} else if err != datastore.ErrNoSuchEntity {
			logErr(w, r, err)
			return
		}
		if _, err := datastore.Put(c, p.Key(c), p); err != nil {
			logErr(w, r, err)
			return
		}
	}
	fmt.Fprint(w, "OK")
}

func keyHandler(w http.ResponseWriter, r *http.Request) {
	builder := r.FormValue("builder")
	if builder == "" {
		logErr(w, r, os.NewError("must supply builder in query string"))
		return
	}
	c := appengine.NewContext(r)
	fmt.Fprint(w, builderKey(c, builder))
}

func init() {
	// admin handlers
	http.HandleFunc("/init", initHandler)
	http.HandleFunc("/key", keyHandler)

	// authenticated handlers
	http.HandleFunc("/commit", AuthHandler(commitHandler))
	http.HandleFunc("/packages", AuthHandler(packagesHandler))
	http.HandleFunc("/result", AuthHandler(resultHandler))
	http.HandleFunc("/tag", AuthHandler(tagHandler))
	http.HandleFunc("/todo", AuthHandler(todoHandler))

	// public handlers
	http.HandleFunc("/log/", logHandler)
}

func validHash(hash string) bool {
	// TODO(adg): correctly validate a hash
	return hash != ""
}

func validKey(c appengine.Context, key, builder string) bool {
	if appengine.IsDevAppServer() {
		return true
	}
	if key == secretKey(c) {
		return true
	}
	return key == builderKey(c, builder)
}

func builderKey(c appengine.Context, builder string) string {
	h := hmac.NewMD5([]byte(secretKey(c)))
	h.Write([]byte(builder))
	return fmt.Sprintf("%x", h.Sum())
}

func logErr(w http.ResponseWriter, r *http.Request, err os.Error) {
	appengine.NewContext(r).Errorf("Error: %v", err)
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprint(w, "Error: ", err)
}
<commit_msg>dashboard: fix todo caching nil<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package build

import (
	"crypto/hmac"
	"fmt"
	"http"
	"json"
	"os"

	"appengine"
	"appengine/datastore"
	"cache"
)

const commitsPerPage = 30

// defaultPackages specifies the Package records to be created by initHandler.
var defaultPackages = []*Package{
	&Package{Name: "Go"},
}

// commitHandler retrieves commit data or records a new commit.
//
// For GET requests it returns a Commit value for the specified
// packagePath and hash.
//
// For POST requests it reads a JSON-encoded Commit value from the request
// body and creates a new Commit entity. It also updates the "tip" Tag for
// each new commit at tip.
//
// This handler is used by a gobuilder process in -commit mode.
func commitHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	com := new(Commit)

	if r.Method == "GET" {
		com.PackagePath = r.FormValue("packagePath")
		com.Hash = r.FormValue("hash")
		if err := datastore.Get(c, com.Key(c), com); err != nil {
			return nil, fmt.Errorf("getting Commit: %v", err)
		}
		return com, nil
	}
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	// POST request
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(com); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if len(com.Desc) > maxDatastoreStringLen {
		com.Desc = com.Desc[:maxDatastoreStringLen]
	}
	if err := com.Valid(); err != nil {
		return nil, fmt.Errorf("validating Commit: %v", err)
	}
	defer cache.Tick(c)
	tx := func(c appengine.Context) os.Error {
		return addCommit(c, com)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// addCommit adds the Commit entity to the datastore and updates the tip Tag.
// It must be run inside a datastore transaction.
func addCommit(c appengine.Context, com *Commit) os.Error {
	var tc Commit // temp value so we don't clobber com
	err := datastore.Get(c, com.Key(c), &tc)
	if err != datastore.ErrNoSuchEntity {
		// if this commit is already in the datastore, do nothing
		if err == nil {
			return nil
		}
		return fmt.Errorf("getting Commit: %v", err)
	}
	// get the next commit number
	p, err := GetPackage(c, com.PackagePath)
	if err != nil {
		return fmt.Errorf("GetPackage: %v", err)
	}
	com.Num = p.NextNum
	p.NextNum++
	if _, err := datastore.Put(c, p.Key(c), p); err != nil {
		return fmt.Errorf("putting Package: %v", err)
	}
	// if this isn't the first Commit test the parent commit exists
	if com.Num > 0 {
		n, err := datastore.NewQuery("Commit").
			Filter("Hash =", com.ParentHash).
			Ancestor(p.Key(c)).
			Count(c)
		if err != nil {
			return fmt.Errorf("testing for parent Commit: %v", err)
		}
		if n == 0 {
			return os.NewError("parent commit not found")
		}
	}
	// update the tip Tag if this is the Go repo
	if p.Path == "" {
		t := &Tag{Kind: "tip", Hash: com.Hash}
		if _, err = datastore.Put(c, t.Key(c), t); err != nil {
			return fmt.Errorf("putting Tag: %v", err)
		}
	}
	// put the Commit
	if _, err = datastore.Put(c, com.Key(c), com); err != nil {
		return fmt.Errorf("putting Commit: %v", err)
	}
	return nil
}

// tagHandler records a new tag. It reads a JSON-encoded Tag value from the
// request body and updates the Tag entity for the Kind of tag provided.
//
// This handler is used by a gobuilder process in -commit mode.
func tagHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	t := new(Tag)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(t); err != nil {
		return nil, err
	}
	if err := t.Valid(); err != nil {
		return nil, err
	}
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	_, err := datastore.Put(c, t.Key(c), t)
	return nil, err
}

// Todo is a todoHandler response.
type Todo struct {
	Kind string // "build-go-commit" or "build-package"
	Data interface{}
}

// todoHandler returns the next action to be performed by a builder.
// It expects "builder" and "kind" query parameters and returns a *Todo value.
// Multiple "kind" parameters may be specified.
func todoHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	key := "build-todo-" + r.Form.Encode()
	var todo *Todo
	if cache.Get(r, now, key, &todo) {
		return todo, nil
	}
	var err os.Error
	builder := r.FormValue("builder")
	for _, kind := range r.Form["kind"] {
		var data interface{}
		switch kind {
		case "build-go-commit":
			data, err = buildTodo(c, builder, "", "")
		case "build-package":
			packagePath := r.FormValue("packagePath")
			goHash := r.FormValue("goHash")
			data, err = buildTodo(c, builder, packagePath, goHash)
		}
		if data != nil || err != nil {
			todo = &Todo{Kind: kind, Data: data}
			break
		}
	}
	if err == nil {
		cache.Set(r, now, key, todo)
	}
	return todo, err
}

// buildTodo returns the next Commit to be built (or nil if none available).
//
// If packagePath and goHash are empty, it scans the first 20 Go Commits in
// Num-descending order and returns the first one it finds that doesn't have a
// Result for this builder.
//
// If provided with non-empty packagePath and goHash args, it scans the first
// 20 Commits in Num-descending order for the specified packagePath and
// returns the first that doesn't have a Result for this builder and goHash.
func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, os.Error) {
	p, err := GetPackage(c, packagePath)
	if err != nil {
		return nil, err
	}

	t := datastore.NewQuery("Commit").
		Ancestor(p.Key(c)).
		Limit(commitsPerPage).
		Order("-Num").
		Run(c)
	for {
		com := new(Commit)
		if _, err := t.Next(com); err != nil {
			if err == datastore.Done {
				err = nil
			}
			return nil, err
		}
		if com.Result(builder, goHash) == nil {
			return com, nil
		}
	}
	panic("unreachable")
}

// packagesHandler returns a list of the non-Go Packages monitored
// by the dashboard.
func packagesHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	const key = "build-packages"
	var p []*Package
	if cache.Get(r, now, key, &p) {
		return p, nil
	}
	p, err := Packages(c)
	if err != nil {
		return nil, err
	}
	cache.Set(r, now, key, p)
	return p, nil
}

// resultHandler records a build result.
// It reads a JSON-encoded Result value from the request body,
// creates a new Result entity, and updates the relevant Commit entity.
// If the Log field is not empty, resultHandler creates a new Log entity
// and updates the LogHash field before putting the Commit entity.
func resultHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	c := appengine.NewContext(r)
	res := new(Result)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if err := res.Valid(); err != nil {
		return nil, fmt.Errorf("validating Result: %v", err)
	}
	defer cache.Tick(c)
	// store the Log text if supplied
	if len(res.Log) > 0 {
		hash, err := PutLog(c, res.Log)
		if err != nil {
			return nil, fmt.Errorf("putting Log: %v", err)
		}
		res.LogHash = hash
	}
	tx := func(c appengine.Context) os.Error {
		// check Package exists
		if _, err := GetPackage(c, res.PackagePath); err != nil {
			return fmt.Errorf("GetPackage: %v", err)
		}
		// put Result
		if _, err := datastore.Put(c, res.Key(c), res); err != nil {
			return fmt.Errorf("putting Result: %v", err)
		}
		// add Result to Commit
		com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
		if err := com.AddResult(c, res); err != nil {
			return fmt.Errorf("AddResult: %v", err)
		}
		// Send build failure notifications, if necessary.
		// Note this must run after the call AddResult, which
		// populates the Commit's ResultData field.
		return notifyOnFailure(c, com, res.Builder)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "text/plain")
	c := appengine.NewContext(r)
	hash := r.URL.Path[len("/log/"):]
	key := datastore.NewKey(c, "Log", hash, 0, nil)
	l := new(Log)
	if err := datastore.Get(c, key, l); err != nil {
		logErr(w, r, err)
		return
	}
	b, err := l.Text()
	if err != nil {
		logErr(w, r, err)
		return
	}
	w.Write(b)
}

type dashHandler func(*http.Request) (interface{}, os.Error)

type dashResponse struct {
	Response interface{}
	Error    string
}

// errBadMethod is returned by a dashHandler when
// the request has an unsuitable method.
type errBadMethod string

func (e errBadMethod) String() string {
	return "bad method: " + string(e)
}

// AuthHandler wraps a http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters.
func AuthHandler(h dashHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)

		// Put the URL Query values into r.Form to avoid parsing the
		// request body when calling r.FormValue.
		r.Form = r.URL.Query()

		var err os.Error
		var resp interface{}

		// Validate key query parameter for POST requests only.
		key := r.FormValue("key")
		builder := r.FormValue("builder")
		if r.Method == "POST" && !validKey(c, key, builder) {
			err = os.NewError("invalid key: " + key)
		}

		// Call the original HandlerFunc and return the response.
		if err == nil {
			resp, err = h(r)
		}

		// Write JSON response.
		dashResp := &dashResponse{Response: resp}
		if err != nil {
			c.Errorf("%v", err)
			dashResp.Error = err.String()
		}
		w.Header().Set("Content-Type", "application/json")
		if err = json.NewEncoder(w).Encode(dashResp); err != nil {
			c.Criticalf("encoding response: %v", err)
		}
	}
}

func initHandler(w http.ResponseWriter, r *http.Request) {
	// TODO(adg): devise a better way of bootstrapping new packages
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	for _, p := range defaultPackages {
		if err := datastore.Get(c, p.Key(c), new(Package)); err == nil {
			continue
		} else if err != datastore.ErrNoSuchEntity {
			logErr(w, r, err)
			return
		}
		if _, err := datastore.Put(c, p.Key(c), p); err != nil {
			logErr(w, r, err)
			return
		}
	}
	fmt.Fprint(w, "OK")
}

func keyHandler(w http.ResponseWriter, r *http.Request) {
	builder := r.FormValue("builder")
	if builder == "" {
		logErr(w, r, os.NewError("must supply builder in query string"))
		return
	}
	c := appengine.NewContext(r)
	fmt.Fprint(w, builderKey(c, builder))
}

func init() {
	// admin handlers
	http.HandleFunc("/init", initHandler)
	http.HandleFunc("/key", keyHandler)

	// authenticated handlers
	http.HandleFunc("/commit", AuthHandler(commitHandler))
	http.HandleFunc("/packages", AuthHandler(packagesHandler))
	http.HandleFunc("/result", AuthHandler(resultHandler))
	http.HandleFunc("/tag", AuthHandler(tagHandler))
	http.HandleFunc("/todo", AuthHandler(todoHandler))

	// public handlers
	http.HandleFunc("/log/", logHandler)
}

func validHash(hash string) bool {
	// TODO(adg): correctly validate a hash
	return hash != ""
}

func validKey(c appengine.Context, key, builder string) bool {
	if appengine.IsDevAppServer() {
		return true
	}
	if key == secretKey(c) {
		return true
	}
	return key == builderKey(c, builder)
}

func builderKey(c appengine.Context, builder string) string {
	h := hmac.NewMD5([]byte(secretKey(c)))
	h.Write([]byte(builder))
	return fmt.Sprintf("%x", h.Sum())
}

func logErr(w http.ResponseWriter, r *http.Request, err os.Error) {
	appengine.NewContext(r).Errorf("Error: %v", err)
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprint(w, "Error: ", err)
}
<|endoftext|>


<commit_before>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"testing"

	"github.com/zenoss/serviced/cli/api"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/pool"
	"github.com/zenoss/serviced/facade"
)

const (
	NilPool = "NilPool"
)

var DefaultPoolAPITest = PoolAPITest{pools: DefaultTestPools, hostIPs: DefaultTestHostIPs}

var DefaultTestPools = []*pool.ResourcePool{
	{
		ID:          "test-pool-id-1",
		ParentID:    "",
		Priority:    1,
		CoreLimit:   8,
		MemoryLimit: 0,
	}, {
		ID:          "test-pool-id-2",
		ParentID:    "test-pool-id-1",
		Priority:    2,
		CoreLimit:   4,
		MemoryLimit: 4 * 1024 * 1024 * 1024,
	}, {
		ID:          "test-pool-id-3",
		ParentID:    "test-pool-id-1",
		Priority:    3,
		CoreLimit:   2,
		MemoryLimit: 512 * 1024 * 1024,
	},
}

var DefaultTestHostIPs = []host.HostIPResource{
	{
		HostID:        "test-host-id-1",
		IPAddress:     "127.0.0.1",
		InterfaceName: "test-interface-name-1",
	}, {
		HostID:        "test-host-id-2",
		IPAddress:     "192.168.0.1",
		InterfaceName: "test-interface-name-2",
	}, {
		HostID:        "test-host-id-3",
		IPAddress:     "0.0.0.0",
		InterfaceName: "test-interface-name-3",
	},
}

var (
	ErrNoPoolFound = errors.New("no pool found")
	ErrInvalidPool = errors.New("invalid pool")
)

type PoolAPITest struct {
	api.API
	fail    bool
	pools   []*pool.ResourcePool
	hostIPs []host.HostIPResource
}

func InitPoolAPITest(args ...string) {
	New(DefaultPoolAPITest).Run(args)
}

func (t PoolAPITest) GetResourcePools() ([]*pool.ResourcePool, error) {
	if t.fail {
		return nil, ErrInvalidPool
	}

	return t.pools, nil
}

func (t PoolAPITest) GetResourcePool(id string) (*pool.ResourcePool, error) {
	if t.fail {
		return nil, ErrInvalidPool
	}

	for _, p := range t.pools {
		if p.ID == id {
			return p, nil
		}
	}

	return nil, nil
}

func (t PoolAPITest) AddResourcePool(config api.PoolConfig) (*pool.ResourcePool, error) {
	if p, err := t.GetResourcePool(config.PoolID); p != nil || err != nil {
		return nil, ErrInvalidPool
	} else if config.PoolID == NilPool {
		return nil, nil
	}

	p := &pool.ResourcePool{
		ID:          config.PoolID,
		ParentID:    "",
		Priority:    0,
		CoreLimit:   config.CoreLimit,
		MemoryLimit: config.MemoryLimit,
	}

	return p, nil
}

func (t PoolAPITest) RemoveResourcePool(id string) error {
	if p, err := t.GetResourcePool(id); err != nil {
		return err
	} else if p == nil {
		return ErrNoPoolFound
	}

	return nil
}

func (t PoolAPITest) GetPoolIPs(id string) (*facade.PoolIPs, error) {
	p, err := t.GetResourcePool(id)
	if err != nil {
		return nil, err
	} else if p == nil {
		return nil, ErrNoPoolFound
	}

	return &facade.PoolIPs{PoolID: p.ID, HostIPs: t.hostIPs}, nil
}

func TestServicedCLI_CmdPoolList_one(t *testing.T) {
	poolID := "test-pool-id-1"

	expected, err := DefaultPoolAPITest.GetResourcePool(poolID)
	if err != nil {
		t.Fatal(err)
	}

	var actual pool.ResourcePool
	output := pipe(InitPoolAPITest, "serviced", "pool", "list", poolID)
	if err := json.Unmarshal(output, &actual); err != nil {
		t.Fatalf("error unmarshalling resource: %s", err)
	}

	// Did you remember to update ResourcePool.Equals?
	if !actual.Equals(expected) {
		t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
	}
}

func TestServicedCLI_CmdPoolList_all(t *testing.T) {
	expected, err := DefaultPoolAPITest.GetResourcePools()
	if err != nil {
		t.Fatal(err)
	}

	var actual []*pool.ResourcePool
	output := pipe(InitPoolAPITest, "serviced", "pool", "list", "--verbose")
	if err := json.Unmarshal(output, &actual); err != nil {
		t.Fatalf("error unmarshalling resource: %s", err)
	}

	// Did you remember to update ResourcePool.Equals?
	if len(actual) != len(expected) {
		t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
	}
	for i, _ := range actual {
		if !actual[i].Equals(expected[i]) {
			t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
		}
	}
}

func ExampleServicedCLI_CmdPoolList() {
	// Gofmt cleans up the spaces at the end of each row
	InitPoolAPITest("serviced", "pool", "list")
}

func ExampleServicedCLI_CmdPoolList_fail() {
	DefaultPoolAPITest.fail = true
	defer func() { DefaultPoolAPITest.fail = false }()
	// Error retrieving pool
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list", "test-pool-id-1")
	// Error retrieving all pools
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list")

	// Output:
	// invalid pool
	// invalid pool
}

func ExampleServicedCLI_CmdPoolList_err() {
	DefaultPoolAPITest.pools = make([]*pool.ResourcePool, 0)
	defer func() { DefaultPoolAPITest.pools = DefaultTestPools }()
	// Pool not found
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list", "test-pool-id-0")
	// No pools found
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list")

	// Output:
	// pool not found
	// no resource pools found
}

func ExampleServicedCLI_CmdPoolList_complete() {
	InitPoolAPITest("serviced", "pool", "list", "--generate-bash-completion")

	// Output:
	// test-pool-id-1
	// test-pool-id-2
	// test-pool-id-3
}

func ExampleServicedCLI_CmdPoolAdd() {
	// Bad CoreLimit
	InitPoolAPITest("serviced", "pool", "add", "test-pool", "abc", "1024", "3")
	// Bad MemoryLimit
	InitPoolAPITest("serviced", "pool", "add", "test-pool", "4", "abc", "3")
	// Bad Priority
	InitPoolAPITest("serviced", "pool", "add", "test-pool", "4", "1024", "abc")
	// Bad Result
	InitPoolAPITest("serviced", "pool", "add", "test-pool-id-1", "4", "1024", "3")
	// Success
	InitPoolAPITest("serviced", "pool", "add", "test-pool", "4", "1024", "3")

	// Output:
	// CORE_LIMIT must be a number
	// MEMORY_LIMIT must be a number
	// PRIORITY must be a number
	// test-pool
}

func ExampleServicedCLI_CmdPoolAdd_usage() {
	InitPoolAPITest("serviced", "pool", "add")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    add - Adds a new resource pool
	//
	// USAGE:
	//    command add [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced pool add POOLID CORE_LIMIT MEMORY_LIMIT PRIORITY
	//
	// OPTIONS:
}

func ExampleServicedCLI_CmdPoolAdd_err() {
	pipeStderr(InitPoolAPITest, "serviced", "pool", "add", NilPool, "4", "1024", "3")

	// Output:
	// received nil resource pool
}

func ExampleServicedCLI_CmdPoolRemove() {
	InitPoolAPITest("serviced", "pool", "remove", "test-pool-id-1")

	// Output:
	// test-pool-id-1
}

func ExampleServicedCLI_CmdPoolRemove_usage() {
	InitPoolAPITest("serviced", "pool", "rm")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    remove - Removes an existing resource pool
	//
	// USAGE:
	//    command remove [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced pool remove POOLID ...
	//
	// OPTIONS:
}

func ExampleServicedCLI_CmdPoolRemove_err() {
	pipeStderr(InitPoolAPITest, "serviced", "pool", "remove", "test-pool-id-0")

	// Output:
	// test-pool-id-0: pool not found
}

func ExampleServicedCLI_CmdPoolRemove_complete() {
	InitPoolAPITest("serviced", "pool", "rm", "--generate-bash-completion")
	fmt.Println("")
	InitPoolAPITest("serviced", "pool", "rm", "test-pool-id-2", "--generate-bash-completion")

	// Output:
	// test-pool-id-1
	// test-pool-id-2
	// test-pool-id-3
	//
	// test-pool-id-1
	// test-pool-id-3
}

func TestExampleServicedCLI_CmdPoolListIPs(t *testing.T) {
	poolID := "test-pool-id-1"

	var expected []host.HostIPResource
	if ips, err := DefaultPoolAPITest.GetPoolIPs(poolID); err != nil {
		t.Fatal(err)
	} else {
		expected = ips.HostIPs
	}

	var actual []host.HostIPResource
	output := pipe(InitPoolAPITest, "serviced", "pool", "list-ips", poolID, "--verbose")
	if err := json.Unmarshal(output, &actual); err != nil {
		t.Fatalf("error unmarshalling resource: %s", err)
	}

	if !reflect.DeepEqual(actual, expected) {
		t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
	}
}

func ExampleServicedCLI_CmdPoolListIPs() {
	// Gofmt cleans up the spaces at the end of each row
	InitPoolAPITest("serviced", "pool", "list-ips", "test-pool-id-1")
}

func ExampleServicedCLI_CmdPoolListIPs_usage() {
	InitPoolAPITest("serviced", "pool", "list-ips")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    list-ips - Lists the IP addresses for a resource pool
	//
	// USAGE:
	//    command list-ips [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced pool list-ips POOLID
	//
	// OPTIONS:
	//    --verbose, -v	Show JSON format
}

func ExampleServicedCLI_CmdPoolListIPs_fail() {
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list-ips", "test-pool-id-0")

	// Output:
	// no pool found
}

func ExampleServicedCLI_CmdPoolListIPs_err() {
	DefaultPoolAPITest.hostIPs = nil
	defer func() { DefaultPoolAPITest.hostIPs = DefaultTestHostIPs }()
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list-ips", "test-pool-id-1")

	// Output:
	// no resource pool IPs found
}
<commit_msg>Fix tests<commit_after>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"testing"

	"github.com/zenoss/serviced/cli/api"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/pool"
	"github.com/zenoss/serviced/facade"
)

const (
	NilPool = "NilPool"
)

var DefaultPoolAPITest = PoolAPITest{pools: DefaultTestPools, hostIPs: DefaultTestHostIPs}

var DefaultTestPools = []*pool.ResourcePool{
	{
		ID:          "test-pool-id-1",
		ParentID:    "",
		Priority:    1,
		CoreLimit:   8,
		MemoryLimit: 0,
	}, {
		ID:          "test-pool-id-2",
		ParentID:    "test-pool-id-1",
		Priority:    2,
		CoreLimit:   4,
		MemoryLimit: 4 * 1024 * 1024 * 1024,
	}, {
		ID:          "test-pool-id-3",
		ParentID:    "test-pool-id-1",
		Priority:    3,
		CoreLimit:   2,
		MemoryLimit: 512 * 1024 * 1024,
	},
}

var DefaultTestHostIPs = []host.HostIPResource{
	{
		HostID:        "test-host-id-1",
		IPAddress:     "127.0.0.1",
		InterfaceName: "test-interface-name-1",
	}, {
		HostID:        "test-host-id-2",
		IPAddress:     "192.168.0.1",
		InterfaceName: "test-interface-name-2",
	}, {
		HostID:        "test-host-id-3",
		IPAddress:     "0.0.0.0",
		InterfaceName: "test-interface-name-3",
	},
}

var (
	ErrNoPoolFound = errors.New("no pool found")
	ErrInvalidPool = errors.New("invalid pool")
)

type PoolAPITest struct {
	api.API
	fail    bool
	pools   []*pool.ResourcePool
	hostIPs []host.HostIPResource
}

func InitPoolAPITest(args ...string) {
	New(DefaultPoolAPITest).Run(args)
}

func (t PoolAPITest) GetResourcePools() ([]*pool.ResourcePool, error) {
	if t.fail {
		return nil, ErrInvalidPool
	}

	return t.pools, nil
}

func (t PoolAPITest) GetResourcePool(id string) (*pool.ResourcePool, error) {
	if t.fail {
		return nil, ErrInvalidPool
	}

	for _, p := range t.pools {
		if p.ID == id {
			return p, nil
		}
	}

	return nil, nil
}

func (t PoolAPITest) AddResourcePool(config api.PoolConfig) (*pool.ResourcePool, error) {
	if p, err := t.GetResourcePool(config.PoolID); p != nil || err != nil {
		return nil, ErrInvalidPool
	} else if config.PoolID == NilPool {
		return nil, nil
	}

	p := &pool.ResourcePool{
		ID:          config.PoolID,
		ParentID:    "",
		Priority:    0,
		CoreLimit:   config.CoreLimit,
		MemoryLimit: config.MemoryLimit,
	}

	return p, nil
}

func (t PoolAPITest) RemoveResourcePool(id string) error {
	if p, err := t.GetResourcePool(id); err != nil {
		return err
	} else if p == nil {
		return ErrNoPoolFound
	}

	return nil
}

func (t PoolAPITest) GetPoolIPs(id string) (*facade.PoolIPs, error) {
	p, err := t.GetResourcePool(id)
	if err != nil {
		return nil, err
	} else if p == nil {
		return nil, ErrNoPoolFound
	}

	return &facade.PoolIPs{PoolID: p.ID, HostIPs: t.hostIPs}, nil
}

func TestServicedCLI_CmdPoolList_one(t *testing.T) {
	poolID := "test-pool-id-1"

	expected, err := DefaultPoolAPITest.GetResourcePool(poolID)
	if err != nil {
		t.Fatal(err)
	}

	var actual pool.ResourcePool
	output := pipe(InitPoolAPITest, "serviced", "pool", "list", poolID)
	if err := json.Unmarshal(output, &actual); err != nil {
		t.Fatalf("error unmarshalling resource: %s", err)
	}

	// Did you remember to update ResourcePool.Equals?
	if !actual.Equals(expected) {
		t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
	}
}

func TestServicedCLI_CmdPoolList_all(t *testing.T) {
	expected, err := DefaultPoolAPITest.GetResourcePools()
	if err != nil {
		t.Fatal(err)
	}

	var actual []*pool.ResourcePool
	output := pipe(InitPoolAPITest, "serviced", "pool", "list", "--verbose")
	if err := json.Unmarshal(output, &actual); err != nil {
		t.Fatalf("error unmarshalling resource: %s", err)
	}

	// Did you remember to update ResourcePool.Equals?
	if len(actual) != len(expected) {
		t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
	}
	for i, _ := range actual {
		if !actual[i].Equals(expected[i]) {
			t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
		}
	}
}

func ExampleServicedCLI_CmdPoolList() {
	// Gofmt cleans up the spaces at the end of each row
	InitPoolAPITest("serviced", "pool", "list")
}

func ExampleServicedCLI_CmdPoolList_fail() {
	DefaultPoolAPITest.fail = true
	defer func() { DefaultPoolAPITest.fail = false }()
	// Error retrieving pool
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list", "test-pool-id-1")
	// Error retrieving all pools
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list")

	// Output:
	// invalid pool
	// invalid pool
}

func ExampleServicedCLI_CmdPoolList_err() {
	DefaultPoolAPITest.pools = make([]*pool.ResourcePool, 0)
	defer func() { DefaultPoolAPITest.pools = DefaultTestPools }()
	// Pool not found
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list", "test-pool-id-0")
	// No pools found
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list")

	// Output:
	// pool not found
	// no resource pools found
}

func ExampleServicedCLI_CmdPoolList_complete() {
	InitPoolAPITest("serviced", "pool", "list", "--generate-bash-completion")

	// Output:
	// test-pool-id-1
	// test-pool-id-2
	// test-pool-id-3
}

func ExampleServicedCLI_CmdPoolAdd() {
	// // Bad CoreLimit
	// InitPoolAPITest("serviced", "pool", "add", "test-pool", "abc", "1024", "3")
	// // Bad MemoryLimit
	// InitPoolAPITest("serviced", "pool", "add", "test-pool", "4", "abc", "3")
	// Bad Priority
	InitPoolAPITest("serviced", "pool", "add", "test-pool", "abc")
	// Bad Result
	InitPoolAPITest("serviced", "pool", "add", "test-pool-id-1", "3")
	// Success
	InitPoolAPITest("serviced", "pool", "add", "test-pool", "3")

	// Output:
	// PRIORITY must be a number
	// test-pool
}

func ExampleServicedCLI_CmdPoolAdd_usage() {
	InitPoolAPITest("serviced", "pool", "add")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    add - Adds a new resource pool
	//
	// USAGE:
	//    command add [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced pool add POOLID PRIORITY
	//
	// OPTIONS:
}

func ExampleServicedCLI_CmdPoolAdd_err() {
	pipeStderr(InitPoolAPITest, "serviced", "pool", "add", NilPool, "4", "1024", "3")

	// Output:
	// received nil resource pool
}

func ExampleServicedCLI_CmdPoolRemove() {
	InitPoolAPITest("serviced", "pool", "remove", "test-pool-id-1")

	// Output:
	// test-pool-id-1
}

func ExampleServicedCLI_CmdPoolRemove_usage() {
	InitPoolAPITest("serviced", "pool", "rm")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    remove - Removes an existing resource pool
	//
	// USAGE:
	//    command remove [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced pool remove POOLID ...
	//
	// OPTIONS:
}

func ExampleServicedCLI_CmdPoolRemove_err() {
	pipeStderr(InitPoolAPITest, "serviced", "pool", "remove", "test-pool-id-0")

	// Output:
	// test-pool-id-0: pool not found
}

func ExampleServicedCLI_CmdPoolRemove_complete() {
	InitPoolAPITest("serviced", "pool", "rm", "--generate-bash-completion")
	fmt.Println("")
	InitPoolAPITest("serviced", "pool", "rm", "test-pool-id-2", "--generate-bash-completion")

	// Output:
	// test-pool-id-1
	// test-pool-id-2
	// test-pool-id-3
	//
	// test-pool-id-1
	// test-pool-id-3
}

func TestExampleServicedCLI_CmdPoolListIPs(t *testing.T) {
	poolID := "test-pool-id-1"

	var expected []host.HostIPResource
	if ips, err := DefaultPoolAPITest.GetPoolIPs(poolID); err != nil {
		t.Fatal(err)
	} else {
		expected = ips.HostIPs
	}

	var actual []host.HostIPResource
	output := pipe(InitPoolAPITest, "serviced", "pool", "list-ips", poolID, "--verbose")
	if err := json.Unmarshal(output, &actual); err != nil {
		t.Fatalf("error unmarshalling resource: %s", err)
	}

	if !reflect.DeepEqual(actual, expected) {
		t.Fatalf("\ngot:\n%+v\nwant:\n%+v", actual, expected)
	}
}

func ExampleServicedCLI_CmdPoolListIPs() {
	// Gofmt cleans up the spaces at the end of each row
	InitPoolAPITest("serviced", "pool", "list-ips", "test-pool-id-1")
}

func ExampleServicedCLI_CmdPoolListIPs_usage() {
	InitPoolAPITest("serviced", "pool", "list-ips")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    list-ips - Lists the IP addresses for a resource pool
	//
	// USAGE:
	//    command list-ips [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced pool list-ips POOLID
	//
	// OPTIONS:
	//    --verbose, -v	Show JSON format
}

func ExampleServicedCLI_CmdPoolListIPs_fail() {
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list-ips", "test-pool-id-0")

	// Output:
	// no pool found
}

func ExampleServicedCLI_CmdPoolListIPs_err() {
	DefaultPoolAPITest.hostIPs = nil
	defer func() { DefaultPoolAPITest.hostIPs = DefaultTestHostIPs }()
	pipeStderr(InitPoolAPITest, "serviced", "pool", "list-ips", "test-pool-id-1")

	// Output:
	// no resource pool IPs found
}
<|endoftext|>


<commit_before>// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package cas provides remote-apis-sdks client with luci integration.
package cas

import (
	"context"
	"strings"

	"github.com/bazelbuild/remote-apis-sdks/go/pkg/client"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/hardcoded/chromeinfra"
)

// NewClient returns luci auth configured Client for RBE-CAS.
func NewClient(ctx context.Context, instance string, opts auth.Options, readOnly bool) (*client.Client, error) {
	project := strings.Split(instance, "/")[1]
	var role string
	if readOnly {
		role = "cas-read-only"
	} else {
		role = "cas-read-write"
	}

	// Construct auth.Options.
	opts.ActAsServiceAccount = role + "@" + project + ".iam.gserviceaccount.com"
	opts.ActViaLUCIRealm = "@internal:" + project + "/" + role
	opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}

	if strings.HasSuffix(project, "-dev") || strings.HasSuffix(project, "-staging") {
		// use dev token server for dev/staging projects.
		opts.TokenServerHost = chromeinfra.TokenServerDevHost
	}

	a := auth.NewAuthenticator(ctx, auth.SilentLogin, opts)
	creds, err := a.PerRPCCredentials()
	if err != nil {
		return nil, errors.Annotate(err, "failed to get PerRPCCredentials").Err()
	}

	client, err := client.NewClient(ctx, instance,
		client.DialParams{
			Service:            "remotebuildexecution.googleapis.com:443",
			TransportCredsOnly: true,
		}, &client.PerRPCCreds{Creds: creds})
	if err != nil {
		return nil, errors.Annotate(err, "failed to create client").Err()
	}

	// Set restricted permission for written files.
	client.DirMode = 0700
	client.ExecutableMode = 0700
	client.RegularMode = 0600

	return client, nil
}
<commit_msg>[cas] enable UtilizeLocality<commit_after>// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package cas provides remote-apis-sdks client with luci integration.
package cas

import (
	"context"
	"strings"

	"github.com/bazelbuild/remote-apis-sdks/go/pkg/client"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/hardcoded/chromeinfra"
)

// NewClient returns luci auth configured Client for RBE-CAS.
func NewClient(ctx context.Context, instance string, opts auth.Options, readOnly bool) (*client.Client, error) {
	project := strings.Split(instance, "/")[1]
	var role string
	if readOnly {
		role = "cas-read-only"
	} else {
		role = "cas-read-write"
	}

	// Construct auth.Options.
	opts.ActAsServiceAccount = role + "@" + project + ".iam.gserviceaccount.com"
	opts.ActViaLUCIRealm = "@internal:" + project + "/" + role
	opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}

	if strings.HasSuffix(project, "-dev") || strings.HasSuffix(project, "-staging") {
		// use dev token server for dev/staging projects.
		opts.TokenServerHost = chromeinfra.TokenServerDevHost
	}

	a := auth.NewAuthenticator(ctx, auth.SilentLogin, opts)
	creds, err := a.PerRPCCredentials()
	if err != nil {
		return nil, errors.Annotate(err, "failed to get PerRPCCredentials").Err()
	}

	client, err := client.NewClient(ctx, instance,
		client.DialParams{
			Service:            "remotebuildexecution.googleapis.com:443",
			TransportCredsOnly: true,
		}, &client.PerRPCCreds{Creds: creds})
	if err != nil {
		return nil, errors.Annotate(err, "failed to create client").Err()
	}

	// Set restricted permission for written files.
	client.DirMode = 0700
	client.ExecutableMode = 0700
	client.RegularMode = 0600
	client.UtilizeLocality = true
	return client, nil
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apis

import (
	"github.com/spf13/cobra"
)

//TraceCmd to manage tracing of apis
var TraceCmd = &cobra.Command{
	Use:   "trace",
	Short: "Manage debugging/tracing of Apigee API proxies",
	Long:  "Manage debugging/tracing of Apigee API proxy revisions deployed in an environment",
}

func init() {

	TraceCmd.PersistentFlags().StringVarP(&env, "env", "e",
		"", "Apigee environment name")

	_ = TraceCmd.MarkPersistentFlagRequired("env")

	TraceCmd.AddCommand(CreateTrcCmd)
	TraceCmd.AddCommand(ListTrcCmd)
	TraceCmd.AddCommand(GetTrcCmd)
}
<commit_msg>rename trace to debug sessions<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apis

import (
	"github.com/spf13/cobra"
)

//TraceCmd to manage tracing of apis
var TraceCmd = &cobra.Command{
	Use:   "debugsessions",
	Short: "Manage debusessions of Apigee API proxies",
	Long:  "Manage debusessions of Apigee API proxy revisions deployed in an environment",
}

func init() {

	TraceCmd.PersistentFlags().StringVarP(&env, "env", "e",
		"", "Apigee environment name")

	_ = TraceCmd.MarkPersistentFlagRequired("env")

	TraceCmd.AddCommand(CreateTrcCmd)
	TraceCmd.AddCommand(ListTrcCmd)
	TraceCmd.AddCommand(GetTrcCmd)
}
<|endoftext|>


<commit_before>package main

import (
	"../../pkg/crank"
	"../../pkg/netutil"
	"flag"
	"fmt"
	"net/rpc"
	"os"
)

type Command func(*rpc.Client) error
type CommandSetup func(*flag.FlagSet) Command

var (
	commands map[string]CommandSetup
	flags    *flag.FlagSet
	name     string
	sock     string
)

func init() {
	commands = make(map[string]CommandSetup)
	commands["run"] = Run
	commands["ps"] = Ps
	commands["kill"] = Kill

	flags = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	flags.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [opts] <command> [command opts]\n\nOptions:\n", os.Args[0])
		flags.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\nCommands:\n")
		for name := range commands {
			fmt.Fprintf(os.Stderr, "  %s\n", name)
		}
	}
	defaultFlags(flags)
}

func defaultFlags(flagSet *flag.FlagSet) {
	flagSet.StringVar(&sock, "sock", sock, "path to control socket")
	flagSet.StringVar(&name, "name", name, "crank process name. Used to infer -sock if specified.")
}

func fail(reason string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "ERROR: "+reason+"\n\n", args...)
	flags.Usage()
	os.Exit(1)
}

func main() {
	var err error

	if err = flags.Parse(os.Args[1:]); err != nil {
		panic(err)
	}

	command := flags.Arg(0)

	if command == "" {
		fail("command missing")
	}

	cmdSetup, ok := commands[command]
	if !ok {
		fail("unknown command %s", command)
	}

	flagSet := flag.NewFlagSet(os.Args[0]+" "+command, flag.ExitOnError)
	defaultFlags(flagSet)

	cmd := cmdSetup(flagSet)

	if err = flagSet.Parse(flags.Args()[1:]); err != nil {
		fail("oops: %s", err)
	}

	sock = crank.DefaultSock(sock, name)
	conn, err := netutil.DialURI(sock)
	if err != nil {
		fail("couldn't connect: %s", err)
	}
	client := rpc.NewClient(conn)

	if err = cmd(client); err != nil {
		fail("command failed: %v", err)
	}
}

func Run(flag *flag.FlagSet) Command {
	query := crank.StartQuery{}
	flag.IntVar(&query.StopTimeout, "stop", -1, "Stop timeout in seconds")
	flag.IntVar(&query.StartTimeout, "start", -1, "Start timeout in seconds")
	//flag.BoolVar(&query.Wait, "wait", false, "Wait for a result")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s run [opts] -- [command ...args]:\n", os.Args[0])
		flag.PrintDefaults()
	}

	return func(client *rpc.Client) (err error) {
		var reply crank.StartReply

		// Command and args are passed after
		if flag.NArg() > 0 {
			query.Command = flag.Args()
		}

		if err = client.Call("crank.Run", &query, &reply); err != nil {
			return
		}

		return
	}
}

func Ps(flag *flag.FlagSet) Command {
	query := crank.PsQuery{}
	processQueryFlags(&query.ProcessQuery, flag)

	return func(client *rpc.Client) (err error) {
		var reply crank.PsReply

		if err = client.Call("crank.Ps", &query, &reply); err != nil {
			return
		}

		for _, pi := range reply.PS {
			fmt.Println(pi)
		}

		return
	}
}

func Kill(flag *flag.FlagSet) Command {
	query := crank.KillQuery{}
	processQueryFlags(&query.ProcessQuery, flag)
	flag.StringVar(&query.Signal, "signal", "SIGTERM", "signal to send to the processes")
	flag.BoolVar(&query.Wait, "wait", false, "wait for the target processes to exit")

	return func(client *rpc.Client) (err error) {
		var reply crank.KillReply

		return client.Call("crank.Kill", &query, &reply)
	}
}

func processQueryFlags(query *crank.ProcessQuery, flag *flag.FlagSet) {
	flag.BoolVar(&query.Starting, "starting", false, "lists the starting process")
	flag.BoolVar(&query.Ready, "ready", false, "lists the ready process")
	flag.BoolVar(&query.Stopping, "stoppping", false, "lists all processes shutting down")
	flag.IntVar(&query.Pid, "pid", 0, "filters to only include that pid")
}
<commit_msg>Separate crankctl usage and runtime errors<commit_after>package main

import (
	"../../pkg/crank"
	"../../pkg/netutil"
	"flag"
	"fmt"
	"net/rpc"
	"os"
)

type Command func(*rpc.Client) error
type CommandSetup func(*flag.FlagSet) Command

var (
	commands map[string]CommandSetup
	flags    *flag.FlagSet
	name     string
	sock     string
)

func init() {
	commands = make(map[string]CommandSetup)
	commands["run"] = Run
	commands["ps"] = Ps
	commands["kill"] = Kill

	flags = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	flags.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [opts] <command> [command opts]\n\nOptions:\n", os.Args[0])
		flags.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\nCommands:\n")
		for name := range commands {
			fmt.Fprintf(os.Stderr, "  %s\n", name)
		}
	}
	defaultFlags(flags)
}

func defaultFlags(flagSet *flag.FlagSet) {
	flagSet.StringVar(&sock, "sock", sock, "path to control socket")
	flagSet.StringVar(&name, "name", name, "crank process name. Used to infer -sock if specified.")
}

func usageError(reason string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "ERROR: "+reason+"\n\n", args...)
	flags.Usage()
	os.Exit(1)
}

func fail(reason string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "ERROR: "+reason+"\n", args...)
	os.Exit(1)
}

func main() {
	var err error

	if err = flags.Parse(os.Args[1:]); err != nil {
		usageError("%s", err)
	}

	command := flags.Arg(0)

	if command == "" {
		usageError("command missing")
	}

	cmdSetup, ok := commands[command]
	if !ok {
		usageError("unknown command %s", command)
	}

	flagSet := flag.NewFlagSet(os.Args[0]+" "+command, flag.ExitOnError)
	defaultFlags(flagSet)

	cmd := cmdSetup(flagSet)

	if err = flagSet.Parse(flags.Args()[1:]); err != nil {
		usageError("%s", err)
	}

	sock = crank.DefaultSock(sock, name)
	conn, err := netutil.DialURI(sock)
	if err != nil {
		fail("couldn't connect: %s", err)
	}
	client := rpc.NewClient(conn)

	if err = cmd(client); err != nil {
		fail("command failed: %v", err)
	}
}

func Run(flag *flag.FlagSet) Command {
	query := crank.StartQuery{}
	flag.IntVar(&query.StopTimeout, "stop", -1, "Stop timeout in seconds")
	flag.IntVar(&query.StartTimeout, "start", -1, "Start timeout in seconds")
	//flag.BoolVar(&query.Wait, "wait", false, "Wait for a result")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s run [opts] -- [command ...args]:\n", os.Args[0])
		flag.PrintDefaults()
	}

	return func(client *rpc.Client) (err error) {
		var reply crank.StartReply

		// Command and args are passed after
		if flag.NArg() > 0 {
			query.Command = flag.Args()
		}

		if err = client.Call("crank.Run", &query, &reply); err != nil {
			return
		}

		return
	}
}

func Ps(flag *flag.FlagSet) Command {
	query := crank.PsQuery{}
	processQueryFlags(&query.ProcessQuery, flag)

	return func(client *rpc.Client) (err error) {
		var reply crank.PsReply

		if err = client.Call("crank.Ps", &query, &reply); err != nil {
			return
		}

		for _, pi := range reply.PS {
			fmt.Println(pi)
		}

		return
	}
}

func Kill(flag *flag.FlagSet) Command {
	query := crank.KillQuery{}
	processQueryFlags(&query.ProcessQuery, flag)
	flag.StringVar(&query.Signal, "signal", "SIGTERM", "signal to send to the processes")
	flag.BoolVar(&query.Wait, "wait", false, "wait for the target processes to exit")

	return func(client *rpc.Client) (err error) {
		var reply crank.KillReply

		return client.Call("crank.Kill", &query, &reply)
	}
}

func processQueryFlags(query *crank.ProcessQuery, flag *flag.FlagSet) {
	flag.BoolVar(&query.Starting, "starting", false, "lists the starting process")
	flag.BoolVar(&query.Ready, "ready", false, "lists the ready process")
	flag.BoolVar(&query.Stopping, "stoppping", false, "lists all processes shutting down")
	flag.IntVar(&query.Pid, "pid", 0, "filters to only include that pid")
}
<|endoftext|>