<commit_before>package install

import (
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/apprenda/kismatic-platform/pkg/tls"
	"github.com/cloudflare/cfssl/csr"
)

var defaultCertHosts = []string{
	"kubernetes",
	"kubernetes.default",
	"kubernetes.default.svc",
	"kubernetes.default.svc.cluster.local",
	"10.3.0.1",
	"10.3.0.5",
	"10.3.0.10",
	"127.0.0.1",
}

// The PKI provides a way for generating certificates for the cluster described by the Plan
type PKI interface {
	GenerateClusterCerts(p *Plan) error
}

// LocalPKI is a file-based PKI
type LocalPKI struct {
	CACsr            string
	CAConfigFile     string
	CASigningProfile string
}

// GenerateClusterCerts creates a Certificate Authority and Certificates
// for all nodes on the cluster.
func (lp *LocalPKI) GenerateClusterCerts(p *Plan) error {
	// First, generate a CA
	key, cert, err := tls.NewCACert(lp.CACsr)
	if err != nil {
		return fmt.Errorf("failed to create CA Cert: %v", err)
	}

	err = writeFiles(key, cert, "ca")
	if err != nil {
		return fmt.Errorf("error writing CA files: %v", err)
	}

	ca := &tls.CA{
		Key:        key,
		Cert:       cert,
		ConfigFile: lp.CAConfigFile,
		Profile:    lp.CASigningProfile,
	}

	// Then, create certs for all nodes
	nodes := []Node{}
	nodes = append(nodes, p.Etcd.Nodes...)
	nodes = append(nodes, p.Master.Nodes...)
	nodes = append(nodes, p.Worker.Nodes...)

	for _, n := range nodes {
		key, cert, err := generateNodeCert(p, &n, ca)
		if err != nil {
			return fmt.Errorf("error during cluster cert generation: %v", err)
		}
		err = writeFiles(key, cert, n.Host)
		if err != nil {
			return fmt.Errorf("error writing cert files for host %q: %v", n.Host, err)
		}
	}
	return nil
}

func writeFiles(key, cert []byte, name string) error {
	// Write into ansible directory for now...
	destDir := filepath.Join("ansible", "playbooks", "tls")
	keyName := fmt.Sprintf("%s-key.pem", name)
	dest := filepath.Join(destDir, keyName)
	err := ioutil.WriteFile(dest, key, 0600)
	if err != nil {
		return fmt.Errorf("error writing private key: %v", err)
	}
	certName := fmt.Sprintf("%s.pem", name)
	dest = filepath.Join(destDir, certName)
	err = ioutil.WriteFile(dest, cert, 0644)
	if err != nil {
		return fmt.Errorf("error writing certificate: %v", err)
	}
	return nil
}

func generateNodeCert(p *Plan, n *Node, ca *tls.CA) (key, cert []byte, err error) {
	hosts := append(defaultCertHosts, n.Host, n.InternalIP, n.IP)
	req := csr.CertificateRequest{
		CN: p.Cluster.Name,
		KeyRequest: &csr.BasicKeyRequest{
			A: "rsa",
			S: 2048,
		},
		Hosts: hosts,
		Names: []csr.Name{
			{
				C:  p.Cluster.Certificates.LocationCountry,
				ST: p.Cluster.Certificates.LocationState,
				L:  p.Cluster.Certificates.LocationCity,
			},
		},
	}

	key, cert, err = tls.GenerateNewCertificate(ca, req)
	if err != nil {
		return nil, nil, fmt.Errorf("error generating certs for node %q: %v", n.Host, err)
	}

	return key, cert, err
}
<commit_msg>No Ticket: Remove IP from default list<commit_after>package install

import (
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/apprenda/kismatic-platform/pkg/tls"
	"github.com/cloudflare/cfssl/csr"
)

var defaultCertHosts = []string{
	"kubernetes",
	"kubernetes.default",
	"kubernetes.default.svc",
	"kubernetes.default.svc.cluster.local",
	"10.3.0.1",
	"10.3.0.10",
	"127.0.0.1",
}

// The PKI provides a way for generating certificates for the cluster described by the Plan
type PKI interface {
	GenerateClusterCerts(p *Plan) error
}

// LocalPKI is a file-based PKI
type LocalPKI struct {
	CACsr            string
	CAConfigFile     string
	CASigningProfile string
}

// GenerateClusterCerts creates a Certificate Authority and Certificates
// for all nodes on the cluster.
func (lp *LocalPKI) GenerateClusterCerts(p *Plan) error {
	// First, generate a CA
	key, cert, err := tls.NewCACert(lp.CACsr)
	if err != nil {
		return fmt.Errorf("failed to create CA Cert: %v", err)
	}

	err = writeFiles(key, cert, "ca")
	if err != nil {
		return fmt.Errorf("error writing CA files: %v", err)
	}

	ca := &tls.CA{
		Key:        key,
		Cert:       cert,
		ConfigFile: lp.CAConfigFile,
		Profile:    lp.CASigningProfile,
	}

	// Then, create certs for all nodes
	nodes := []Node{}
	nodes = append(nodes, p.Etcd.Nodes...)
	nodes = append(nodes, p.Master.Nodes...)
	nodes = append(nodes, p.Worker.Nodes...)

	for _, n := range nodes {
		key, cert, err := generateNodeCert(p, &n, ca)
		if err != nil {
			return fmt.Errorf("error during cluster cert generation: %v", err)
		}
		err = writeFiles(key, cert, n.Host)
		if err != nil {
			return fmt.Errorf("error writing cert files for host %q: %v", n.Host, err)
		}
	}
	return nil
}

func writeFiles(key, cert []byte, name string) error {
	// Write into ansible directory for now...
	destDir := filepath.Join("ansible", "playbooks", "tls")
	keyName := fmt.Sprintf("%s-key.pem", name)
	dest := filepath.Join(destDir, keyName)
	err := ioutil.WriteFile(dest, key, 0600)
	if err != nil {
		return fmt.Errorf("error writing private key: %v", err)
	}
	certName := fmt.Sprintf("%s.pem", name)
	dest = filepath.Join(destDir, certName)
	err = ioutil.WriteFile(dest, cert, 0644)
	if err != nil {
		return fmt.Errorf("error writing certificate: %v", err)
	}
	return nil
}

func generateNodeCert(p *Plan, n *Node, ca *tls.CA) (key, cert []byte, err error) {
	hosts := append(defaultCertHosts, n.Host, n.InternalIP, n.IP)
	req := csr.CertificateRequest{
		CN: p.Cluster.Name,
		KeyRequest: &csr.BasicKeyRequest{
			A: "rsa",
			S: 2048,
		},
		Hosts: hosts,
		Names: []csr.Name{
			{
				C:  p.Cluster.Certificates.LocationCountry,
				ST: p.Cluster.Certificates.LocationState,
				L:  p.Cluster.Certificates.LocationCity,
			},
		},
	}

	key, cert, err = tls.GenerateNewCertificate(ca, req)
	if err != nil {
		return nil, nil, fmt.Errorf("error generating certs for node %q: %v", n.Host, err)
	}

	return key, cert, err
}
<|endoftext|>


<commit_before>package mount

import (
	"sort"
	"strings"

	"github.com/containers/storage/pkg/fileutils"
)

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
	return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab.
func Mounted(mountpoint string) (bool, error) {
	entries, err := parseMountTable()
	if err != nil {
		return false, err
	}

	mountpoint, err = fileutils.ReadSymlinkedDirectory(mountpoint)
	if err != nil {
		return false, err
	}
	// Search the table for the mountpoint
	for _, e := range entries {
		if e.Mountpoint == mountpoint {
			return true, nil
		}
	}
	return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	if flag&REMOUNT != REMOUNT {
		if mounted, err := Mounted(target); err != nil || mounted {
			return err
		}
	}
	return mount(device, target, mType, uintptr(flag), data)
}

// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	return mount(device, target, mType, uintptr(flag), data)
}

// Unmount lazily unmounts a filesystem on supported platforms, otherwise
// does a normal unmount.
func Unmount(target string) error {
	return unmount(target, mntDetach)
}

// RecursiveUnmount unmounts the target and all mounts underneath, starting with
// the deepest mount first.
func RecursiveUnmount(target string) error {
	mounts, err := GetMounts()
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Slice(mounts, func(i, j int) bool {
		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
	})

	for i, m := range mounts {
		if !strings.HasPrefix(m.Mountpoint, target) {
			continue
		}
		if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 {
			if mounted, err := Mounted(m.Mountpoint); err != nil || mounted {
				return err
			}
			// Ignore errors for submounts and continue trying to unmount others
			// The final unmount should fail if there ane any submounts remaining
		}
	}
	return nil
}

// ForceUnmount will force an unmount of the target filesystem, regardless if
// it is mounted or not.
func ForceUnmount(target string) error {
	return unmount(target, mntDetach)
}
<commit_msg>pkg/mount: deprecate ForceUnmount<commit_after>package mount

import (
	"sort"
	"strings"

	"github.com/containers/storage/pkg/fileutils"
)

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
	return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab.
func Mounted(mountpoint string) (bool, error) {
	entries, err := parseMountTable()
	if err != nil {
		return false, err
	}

	mountpoint, err = fileutils.ReadSymlinkedDirectory(mountpoint)
	if err != nil {
		return false, err
	}
	// Search the table for the mountpoint
	for _, e := range entries {
		if e.Mountpoint == mountpoint {
			return true, nil
		}
	}
	return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	if flag&REMOUNT != REMOUNT {
		if mounted, err := Mounted(target); err != nil || mounted {
			return err
		}
	}
	return mount(device, target, mType, uintptr(flag), data)
}

// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
	flag, data := ParseOptions(options)
	return mount(device, target, mType, uintptr(flag), data)
}

// Unmount lazily unmounts a filesystem on supported platforms, otherwise
// does a normal unmount.
func Unmount(target string) error {
	return unmount(target, mntDetach)
}

// RecursiveUnmount unmounts the target and all mounts underneath, starting with
// the deepest mount first.
func RecursiveUnmount(target string) error {
	mounts, err := GetMounts()
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Slice(mounts, func(i, j int) bool {
		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
	})

	for i, m := range mounts {
		if !strings.HasPrefix(m.Mountpoint, target) {
			continue
		}
		if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 {
			if mounted, err := Mounted(m.Mountpoint); err != nil || mounted {
				return err
			}
			// Ignore errors for submounts and continue trying to unmount others
			// The final unmount should fail if there ane any submounts remaining
		}
	}
	return nil
}

// ForceUnmount lazily unmounts a filesystem on supported platforms,
// otherwise does a normal unmount.
//
// Deprecated: please use Unmount instead, it is identical.
func ForceUnmount(target string) error {
	return unmount(target, mntDetach)
}
<|endoftext|>


<commit_before>package oauth

import (
	"time"

	"github.com/hellofresh/janus/pkg/cors"
	"github.com/hellofresh/janus/pkg/proxy"
	"gopkg.in/mgo.v2/bson"
)

// AccessRequestType is the type for OAuth param `grant_type`
type AccessRequestType string

// AuthorizeRequestType is the type for OAuth param `response_type`
type AuthorizeRequestType string

// OAuth holds the configuration for oauth proxies
type OAuth struct {
	ID                     bson.ObjectId          `bson:"_id,omitempty" json:"id,omitempty" valid:"required"`
	Name                   string                 `bson:"name" json:"name" valid:"required"`
	CreatedAt              time.Time              `bson:"created_at" json:"created_at" valid:"-"`
	UpdatedAt              time.Time              `bson:"updated_at" json:"updated_at" valid:"-"`
	Endpoints              Endpoints              `bson:"oauth_endpoints" json:"oauth_endpoints"`
	ClientEndpoints        ClientEndpoints        `bson:"oauth_client_endpoints" json:"oauth_client_endpoints"`
	AllowedAccessTypes     []AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types"`
	AllowedAuthorizeTypes  []AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
	AuthorizeLoginRedirect string                 `bson:"auth_login_redirect" json:"auth_login_redirect"`
	Secrets                map[string]string      `bson:"secrets" json:"secrets"`
	CorsMeta               cors.Meta              `bson:"cors_meta" json:"cors_meta" valid:"cors_meta"`
}

// Endpoints defines the oauth endpoints that wil be proxied
type Endpoints struct {
	Authorize *proxy.Proxy `bson:"authorize" json:"authorize"`
	Token     *proxy.Proxy `bson:"token" json:"token"`
	Info      *proxy.Proxy `bson:"info" json:"info"`
}

// ClientEndpoints defines the oauth client endpoints that wil be proxied
type ClientEndpoints struct {
	Create *proxy.Proxy `bson:"create" json:"create"`
	Remove *proxy.Proxy `bson:"remove" json:"remove"`
}
<commit_msg>Changed to proxy definition<commit_after>package oauth

import (
	"time"

	"github.com/hellofresh/janus/pkg/cors"
	"github.com/hellofresh/janus/pkg/proxy"
	"gopkg.in/mgo.v2/bson"
)

// AccessRequestType is the type for OAuth param `grant_type`
type AccessRequestType string

// AuthorizeRequestType is the type for OAuth param `response_type`
type AuthorizeRequestType string

// OAuth holds the configuration for oauth proxies
type OAuth struct {
	ID                     bson.ObjectId          `bson:"_id,omitempty" json:"id,omitempty" valid:"required"`
	Name                   string                 `bson:"name" json:"name" valid:"required"`
	CreatedAt              time.Time              `bson:"created_at" json:"created_at" valid:"-"`
	UpdatedAt              time.Time              `bson:"updated_at" json:"updated_at" valid:"-"`
	Endpoints              Endpoints              `bson:"oauth_endpoints" json:"oauth_endpoints"`
	ClientEndpoints        ClientEndpoints        `bson:"oauth_client_endpoints" json:"oauth_client_endpoints"`
	AllowedAccessTypes     []AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types"`
	AllowedAuthorizeTypes  []AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
	AuthorizeLoginRedirect string                 `bson:"auth_login_redirect" json:"auth_login_redirect"`
	Secrets                map[string]string      `bson:"secrets" json:"secrets"`
	CorsMeta               cors.Meta              `bson:"cors_meta" json:"cors_meta" valid:"cors_meta"`
}

// Endpoints defines the oauth endpoints that wil be proxied
type Endpoints struct {
	Authorize *proxy.Definition `bson:"authorize" json:"authorize"`
	Token     *proxy.Definition `bson:"token" json:"token"`
	Info      *proxy.Definition `bson:"info" json:"info"`
}

// ClientEndpoints defines the oauth client endpoints that wil be proxied
type ClientEndpoints struct {
	Create *proxy.Definition `bson:"create" json:"create"`
	Remove *proxy.Definition `bson:"remove" json:"remove"`
}
<|endoftext|>


<commit_before>package oauth

import (
	"time"

	"github.com/hellofresh/janus/pkg/cors"
	"github.com/hellofresh/janus/pkg/proxy"
	"gopkg.in/mgo.v2/bson"
)

// AccessRequestType is the type for OAuth param `grant_type`
type AccessRequestType string

// AuthorizeRequestType is the type for OAuth param `response_type`
type AuthorizeRequestType string

// Spec Holds an api definition and basic options
type Spec struct {
	*OAuth
	Manager Manager
}

// OAuth holds the configuration for oauth proxies
type OAuth struct {
	ID                     bson.ObjectId          `bson:"_id,omitempty" json:"id,omitempty" valid:"required"`
	Name                   string                 `bson:"name" json:"name" valid:"required"`
	CreatedAt              time.Time              `bson:"created_at" json:"created_at" valid:"-"`
	UpdatedAt              time.Time              `bson:"updated_at" json:"updated_at" valid:"-"`
	Endpoints              Endpoints              `bson:"oauth_endpoints" json:"oauth_endpoints"`
	ClientEndpoints        ClientEndpoints        `bson:"oauth_client_endpoints" json:"oauth_client_endpoints"`
	AllowedAccessTypes     []AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types"`
	AllowedAuthorizeTypes  []AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
	AuthorizeLoginRedirect string                 `bson:"auth_login_redirect" json:"auth_login_redirect"`
	Secrets                map[string]string      `bson:"secrets" json:"secrets"`
	CorsMeta               cors.Meta              `bson:"cors_meta" json:"cors_meta" valid:"cors_meta"`
	TokenStrategy          TokenStrategy          `bson:"token_strategy" json:"token_strategy"`
}

// Endpoints defines the oauth endpoints that wil be proxied
type Endpoints struct {
	Authorize *proxy.Definition `bson:"authorize" json:"authorize"`
	Token     *proxy.Definition `bson:"token" json:"token"`
	Info      *proxy.Definition `bson:"info" json:"info"`
	Revoke    *proxy.Definition `bson:"revoke" json:"revoke"`
}

// ClientEndpoints defines the oauth client endpoints that wil be proxied
type ClientEndpoints struct {
	Create *proxy.Definition `bson:"create" json:"create"`
	Remove *proxy.Definition `bson:"remove" json:"remove"`
}

// TokenStrategy defines the token strategy fields
type TokenStrategy struct {
	Name     string                `bson:"name" json:"name"`
	Settings TokenStrategySettings `bson:"settings" json:"settings"`
}

// TokenStrategySettings
type TokenStrategySettings map[string]string
<commit_msg>Removed mgo ID<commit_after>package oauth

import (
	"time"

	"github.com/hellofresh/janus/pkg/cors"
	"github.com/hellofresh/janus/pkg/proxy"
)

// AccessRequestType is the type for OAuth param `grant_type`
type AccessRequestType string

// AuthorizeRequestType is the type for OAuth param `response_type`
type AuthorizeRequestType string

// Spec Holds an api definition and basic options
type Spec struct {
	*OAuth
	Manager Manager
}

// OAuth holds the configuration for oauth proxies
type OAuth struct {
	Name                   string                 `bson:"name" json:"name" valid:"required"`
	Slug                   string                 `bson:"slug" json:"slug" valid:"required"`
	CreatedAt              time.Time              `bson:"created_at" json:"created_at" valid:"-"`
	UpdatedAt              time.Time              `bson:"updated_at" json:"updated_at" valid:"-"`
	Endpoints              Endpoints              `bson:"oauth_endpoints" json:"oauth_endpoints"`
	ClientEndpoints        ClientEndpoints        `bson:"oauth_client_endpoints" json:"oauth_client_endpoints"`
	AllowedAccessTypes     []AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types"`
	AllowedAuthorizeTypes  []AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
	AuthorizeLoginRedirect string                 `bson:"auth_login_redirect" json:"auth_login_redirect"`
	Secrets                map[string]string      `bson:"secrets" json:"secrets"`
	CorsMeta               cors.Meta              `bson:"cors_meta" json:"cors_meta" valid:"cors_meta"`
	TokenStrategy          TokenStrategy          `bson:"token_strategy" json:"token_strategy"`
}

// Endpoints defines the oauth endpoints that wil be proxied
type Endpoints struct {
	Authorize *proxy.Definition `bson:"authorize" json:"authorize"`
	Token     *proxy.Definition `bson:"token" json:"token"`
	Info      *proxy.Definition `bson:"info" json:"info"`
	Revoke    *proxy.Definition `bson:"revoke" json:"revoke"`
}

// ClientEndpoints defines the oauth client endpoints that wil be proxied
type ClientEndpoints struct {
	Create *proxy.Definition `bson:"create" json:"create"`
	Remove *proxy.Definition `bson:"remove" json:"remove"`
}

// TokenStrategy defines the token strategy fields
type TokenStrategy struct {
	Name     string                `bson:"name" json:"name"`
	Settings TokenStrategySettings `bson:"settings" json:"settings"`
}

// TokenStrategySettings
type TokenStrategySettings map[string]string
<|endoftext|>


<commit_before>package perms // import "a4.io/blobstash/pkg/perms"

import (
	"bytes"
	"fmt"
	"html/template"
	"strings"

	"a4.io/blobstash/pkg/config"
	"github.com/zpatrick/rbac"
)

type ActionType string
type ObjectType string
type ServiceName string

// Actions
const (
	Read     ActionType = "read"
	Stat     ActionType = "stat"
	Write    ActionType = "write"
	List     ActionType = "list"
	Snapshot ActionType = "snapshot"
	Search   ActionType = "search"
	GC       ActionType = "gc"
	Destroy  ActionType = "destroy"
)

// Object types
const (
	Blob      ObjectType = "blob"
	KVEntry   ObjectType = "kv"
	FS        ObjectType = "fs"
	Node      ObjectType = "node"
	GitRepo   ObjectType = "git-repo"
	GitNs     ObjectType = "git-ns"
	Namespace ObjectType = "namespace"
)

// Services
const (
	BlobStore ServiceName = "blobstore"
	KvStore   ServiceName = "kvstore"
	DocStore  ServiceName = "docstore"
	Filetree  ServiceName = "filetree"
	GitServer ServiceName = "gitserver"
	Stash     ServiceName = "stash"
)

// Action formats an action `<action_type>:<object_type>`
func Action(action ActionType, objectType ObjectType) string {
	return fmt.Sprintf("action:%s:%s", action, objectType)
}

func ResourceWithID(service ServiceName, objectType ObjectType, objectID string) string {
	return fmt.Sprintf("resource:%s:%s:%s", service, objectType, objectID)
}

func Resource(service ServiceName, objectType ObjectType) string {
	return fmt.Sprintf("resource:%s:%s:NA", service, objectType)
}

func init() {
	SetupRole(&config.Role{
		Name:  "admin",
		Perms: []*config.Perm{&config.Perm{Action: "action:*", Resource: "resource:*"}},
	})
	SetupRole(&config.Role{
		Template:     "backup",
		Managed:      true,
		ArgsRequired: []string{"name"},
		Perms: []*config.Perm{
			&config.Perm{
				Action:   Action(Stat, Blob),
				Resource: ResourceWithID(BlobStore, Blob, "*"),
			},
			&config.Perm{
				Action:   Action(Write, Blob),
				Resource: ResourceWithID(BlobStore, Blob, "*"),
			},
			&config.Perm{
				Action:   Action(Write, KVEntry),
				Resource: ResourceWithID(KvStore, KVEntry, "_filetree:fs:{{.name}}"),
			},
			&config.Perm{
				Action:   Action(GC, Namespace),
				Resource: ResourceWithID(Stash, Namespace, "{{.name}}"),
			},
		},
	})

}

var roles = map[string]rbac.Role{}
var managedRoles = map[string]*config.Role{}

func newManagedRole(r *config.Role) error {
	for _, k := range r.ArgsRequired {
		if _, ok := r.Args[k]; !ok {
			return fmt.Errorf("missing %s arg for role %s", k, r.Name)
		}
	}
	var buf bytes.Buffer
	mperms := []*config.Perm{}
	for _, p := range r.Perms {
		t := template.Must(template.New("resource").Parse(p.Resource))
		if err := t.Execute(&buf, r.Args); err != nil {
			return err
		}
		mperms = append(mperms, &config.Perm{
			Action:   p.Action,
			Resource: buf.String(),
		})
		buf.Reset()
	}
	SetupRole(&config.Role{
		Name:  r.Name,
		Perms: mperms,
	})
	return nil
}

func SetupRole(r *config.Role) error {
	if r.Template != "" && r.Managed {
		managedRoles[r.Template] = r
		return nil
	}
	if mrole, ok := managedRoles[r.Template]; ok {
		mrole.Args = r.Args
		mrole.Name = r.Name
		defer func(cr *config.Role) {
			cr.Args = nil
			cr.Name = ""
		}(r)
		return newManagedRole(mrole)
	}

	if _, used := roles[r.Name]; used {
		return fmt.Errorf("%q is already used", r.Name)
	}
	perms := rbac.Permissions{}
	for _, p := range r.Perms {
		if !strings.HasPrefix(p.Action, "action:") {
			return fmt.Errorf("invalid action %q", p.Action)
		}
		if !strings.HasPrefix(p.Resource, "resource:") {
			return fmt.Errorf("invalid resource %q", p.Resource)
		}
		perms = append(perms, rbac.NewGlobPermission(p.Action, p.Resource))
	}

	role := rbac.Role{
		RoleID:      r.Name,
		Permissions: perms,
	}
	roles[r.Name] = role
	return nil
}

func GetRole(k string) (rbac.Role, error) {
	r, ok := roles[k]
	if !ok {
		return rbac.Role{}, fmt.Errorf("role %q not found", k)
	}
	return r, nil
}

func GetRoles(keys []string) (rbac.Roles, error) {
	res := rbac.Roles{}
	for _, k := range keys {
		role, err := GetRole(k)
		if err != nil {
			return nil, err
		}
		res = append(res, role)
	}
	return res, nil
}

func Setup(conf *config.Config) error {
	for _, role := range conf.Roles {
		if err := SetupRole(role); err != nil {
			panic(err)
		}
	}
	return nil
}
<commit_msg>perms: tweak the backup perms<commit_after>package perms // import "a4.io/blobstash/pkg/perms"

import (
	"bytes"
	"fmt"
	"html/template"
	"strings"

	"a4.io/blobstash/pkg/config"
	"github.com/zpatrick/rbac"
)

type ActionType string
type ObjectType string
type ServiceName string

// Actions
const (
	Read     ActionType = "read"
	Stat     ActionType = "stat"
	Write    ActionType = "write"
	List     ActionType = "list"
	Snapshot ActionType = "snapshot"
	Search   ActionType = "search"
	GC       ActionType = "gc"
	Destroy  ActionType = "destroy"
)

// Object types
const (
	Blob      ObjectType = "blob"
	KVEntry   ObjectType = "kv"
	FS        ObjectType = "fs"
	Node      ObjectType = "node"
	GitRepo   ObjectType = "git-repo"
	GitNs     ObjectType = "git-ns"
	Namespace ObjectType = "namespace"
)

// Services
const (
	BlobStore ServiceName = "blobstore"
	KvStore   ServiceName = "kvstore"
	DocStore  ServiceName = "docstore"
	Filetree  ServiceName = "filetree"
	GitServer ServiceName = "gitserver"
	Stash     ServiceName = "stash"
)

// Action formats an action `<action_type>:<object_type>`
func Action(action ActionType, objectType ObjectType) string {
	return fmt.Sprintf("action:%s:%s", action, objectType)
}

func ResourceWithID(service ServiceName, objectType ObjectType, objectID string) string {
	return fmt.Sprintf("resource:%s:%s:%s", service, objectType, objectID)
}

func Resource(service ServiceName, objectType ObjectType) string {
	return fmt.Sprintf("resource:%s:%s:NA", service, objectType)
}

func init() {
	SetupRole(&config.Role{
		Name:  "admin",
		Perms: []*config.Perm{&config.Perm{Action: "action:*", Resource: "resource:*"}},
	})
	SetupRole(&config.Role{
		Template:     "backup",
		Managed:      true,
		ArgsRequired: []string{"name"},
		Perms: []*config.Perm{
			&config.Perm{
				Action:   Action(Stat, Blob),
				Resource: ResourceWithID(BlobStore, Blob, "*"),
			},
			&config.Perm{
				Action:   Action(Write, Blob),
				Resource: ResourceWithID(BlobStore, Blob, "*"),
			},
			&config.Perm{
				Action:   Action(Write, KVEntry),
				Resource: ResourceWithID(KvStore, KVEntry, "_filetree:fs:{{.name}}"),
			},
			&config.Perm{
				Action:   Action(GC, Namespace),
				Resource: ResourceWithID(Stash, Namespace, "{{.name}}"),
			},
			&config.Perm{
				Action:   Action(Snapshot, FS),
				Resource: ResourceWithID(Filetree, FS, "{{.name}}"),
			},
		},
	})

}

var roles = map[string]rbac.Role{}
var managedRoles = map[string]*config.Role{}

func newManagedRole(r *config.Role) error {
	for _, k := range r.ArgsRequired {
		if _, ok := r.Args[k]; !ok {
			return fmt.Errorf("missing %s arg for role %s", k, r.Name)
		}
	}
	var buf bytes.Buffer
	mperms := []*config.Perm{}
	for _, p := range r.Perms {
		t := template.Must(template.New("resource").Parse(p.Resource))
		if err := t.Execute(&buf, r.Args); err != nil {
			return err
		}
		mperms = append(mperms, &config.Perm{
			Action:   p.Action,
			Resource: buf.String(),
		})
		buf.Reset()
	}
	SetupRole(&config.Role{
		Name:  r.Name,
		Perms: mperms,
	})
	return nil
}

func SetupRole(r *config.Role) error {
	if r.Template != "" && r.Managed {
		managedRoles[r.Template] = r
		return nil
	}
	if mrole, ok := managedRoles[r.Template]; ok {
		mrole.Args = r.Args
		mrole.Name = r.Name
		defer func(cr *config.Role) {
			cr.Args = nil
			cr.Name = ""
		}(r)
		return newManagedRole(mrole)
	}

	if _, used := roles[r.Name]; used {
		return fmt.Errorf("%q is already used", r.Name)
	}
	perms := rbac.Permissions{}
	for _, p := range r.Perms {
		if !strings.HasPrefix(p.Action, "action:") {
			return fmt.Errorf("invalid action %q", p.Action)
		}
		if !strings.HasPrefix(p.Resource, "resource:") {
			return fmt.Errorf("invalid resource %q", p.Resource)
		}
		perms = append(perms, rbac.NewGlobPermission(p.Action, p.Resource))
	}

	role := rbac.Role{
		RoleID:      r.Name,
		Permissions: perms,
	}
	roles[r.Name] = role
	return nil
}

func GetRole(k string) (rbac.Role, error) {
	r, ok := roles[k]
	if !ok {
		return rbac.Role{}, fmt.Errorf("role %q not found", k)
	}
	return r, nil
}

func GetRoles(keys []string) (rbac.Roles, error) {
	res := rbac.Roles{}
	for _, k := range keys {
		role, err := GetRole(k)
		if err != nil {
			return nil, err
		}
		res = append(res, role)
	}
	return res, nil
}

func Setup(conf *config.Config) error {
	for _, role := range conf.Roles {
		if err := SetupRole(role); err != nil {
			panic(err)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
 * Quick - Quick key value store for config files and persistent state files
 *
 * Minio Client (C) 2015 Minio, Inc.
 *
 * 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 quick

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"strings"
	"sync"

	"github.com/fatih/structs"
	"github.com/minio/minio/pkg/iodine"
)

// Config - generic config interface functions
type Config interface {
	String() string
	Version() string
	Save(string) error
	Load(string) error
	Data() interface{}
	Diff(Config) ([]structs.Field, error)
	DeepDiff(Config) ([]structs.Field, error)
}

// config - implements quick.Config interface
type config struct {
	data *interface{}
	lock *sync.RWMutex
}

// CheckData - checks the validity of config data. Data sould be of type struct and contain a string type field called "Version"
func CheckData(data interface{}) error {
	if !structs.IsStruct(data) {
		return iodine.New(errors.New("Invalid argument type. Expecing \"struct\" type."), nil)
	}

	st := structs.New(data)
	f, ok := st.FieldOk("Version")
	if !ok {
		return iodine.New(fmt.Errorf("Invalid type of struct argument. No [%s.Version] field found.", st.Name()), nil)
	}

	if f.Kind() != reflect.String {
		return iodine.New(fmt.Errorf("Invalid type of struct argument. Expecting \"string\" type [%s.Version] field.", st.Name()), nil)
	}

	return nil
}

// New - instantiate a new config
func New(data interface{}) (Config, error) {
	err := CheckData(data)
	if err != nil {
		return nil, err
	}

	d := new(config)
	d.data = &data
	d.lock = new(sync.RWMutex)
	return d, nil
}

// Version returns the current config file format version
func (d config) Version() string {
	st := structs.New(*d.data)

	f, ok := st.FieldOk("Version")
	if !ok {
		return ""
	}

	val := f.Value()
	ver, ok := val.(string)
	if ok {
		return ver
	}
	return ""
}

// String converts JSON config to printable string
func (d config) String() string {
	configBytes, _ := json.MarshalIndent(*d.data, "", "\t")
	return string(configBytes)
}

// Save writes config data in JSON format to a file.
func (d config) Save(filename string) (err error) {
	d.lock.Lock()
	defer d.lock.Unlock()

	jsonData, err := json.MarshalIndent(d.data, "", "\t")
	if err != nil {
		return iodine.New(err, nil)
	}

	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return iodine.New(err, nil)
	}
	defer file.Close()

	if runtime.GOOS == "windows" {
		jsonData = []byte(strings.Replace(string(jsonData), "\n", "\r\n", -1))
	}
	_, err = file.Write(jsonData)
	if err != nil {
		return iodine.New(err, nil)
	}
	return nil
}

// Load - loads JSON config from file and merge with currently set values
func (d *config) Load(filename string) (err error) {
	(*d).lock.Lock()
	defer (*d).lock.Unlock()

	_, err = os.Stat(filename)
	if err != nil {
		return iodine.New(err, nil)
	}

	fileData, err := ioutil.ReadFile(filename)
	if err != nil {
		return iodine.New(err, nil)
	}

	if runtime.GOOS == "windows" {
		fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1))
	}

	err = json.Unmarshal(fileData, (*d).data)
	if err != nil {
		return iodine.New(err, nil)
	}

	err = CheckData(*(*d).data)
	if err != nil {
		return iodine.New(err, nil)
	}

	st := structs.New(*(*d).data)
	f, ok := st.FieldOk("Version")
	if !ok {
		return iodine.New(fmt.Errorf("Argument struct [%s] does not contain field \"Version\".", st.Name()), nil)
	}

	if (*d).Version() != f.Value() {
		return iodine.New(errors.New("Version mismatch"), nil)
	}

	return nil
}

// Data - grab internal data map for reading
func (d config) Data() interface{} {
	return *d.data
}

//Diff  - list fields that are in A but not in B
func (d config) Diff(c Config) (fields []structs.Field, err error) {
	err = CheckData(c.Data())
	if err != nil {
		return []structs.Field{}, iodine.New(err, nil)
	}

	currFields := structs.Fields(d.Data())
	newFields := structs.Fields(c.Data())

	found := false
	for _, currField := range currFields {
		found = false
		for _, newField := range newFields {
			if reflect.DeepEqual(currField.Name(), newField.Name()) {
				found = true
			}
		}
		if !found {
			fields = append(fields, *currField)
		}
	}
	return fields, nil
}

//DeepDiff  - list fields in A that are missing or not equal to fields in B
func (d config) DeepDiff(c Config) (fields []structs.Field, err error) {
	err = CheckData(c.Data())
	if err != nil {
		return []structs.Field{}, iodine.New(err, nil)
	}

	currFields := structs.Fields(d.Data())
	newFields := structs.Fields(c.Data())

	found := false
	for _, currField := range currFields {
		found = false
		for _, newField := range newFields {
			if reflect.DeepEqual(currField.Value(), newField.Value()) {
				found = true
			}
		}
		if !found {
			fields = append(fields, *currField)
		}
	}
	return fields, nil
}
<commit_msg>Avoid corrupted data when saving with quick<commit_after>/*
 * Quick - Quick key value store for config files and persistent state files
 *
 * Minio Client (C) 2015 Minio, Inc.
 *
 * 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 quick

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"strings"
	"sync"

	"github.com/fatih/structs"
	"github.com/minio/minio/pkg/iodine"
)

// Config - generic config interface functions
type Config interface {
	String() string
	Version() string
	Save(string) error
	Load(string) error
	Data() interface{}
	Diff(Config) ([]structs.Field, error)
	DeepDiff(Config) ([]structs.Field, error)
}

// config - implements quick.Config interface
type config struct {
	data *interface{}
	lock *sync.RWMutex
}

// CheckData - checks the validity of config data. Data sould be of type struct and contain a string type field called "Version"
func CheckData(data interface{}) error {
	if !structs.IsStruct(data) {
		return iodine.New(errors.New("Invalid argument type. Expecing \"struct\" type."), nil)
	}

	st := structs.New(data)
	f, ok := st.FieldOk("Version")
	if !ok {
		return iodine.New(fmt.Errorf("Invalid type of struct argument. No [%s.Version] field found.", st.Name()), nil)
	}

	if f.Kind() != reflect.String {
		return iodine.New(fmt.Errorf("Invalid type of struct argument. Expecting \"string\" type [%s.Version] field.", st.Name()), nil)
	}

	return nil
}

// New - instantiate a new config
func New(data interface{}) (Config, error) {
	err := CheckData(data)
	if err != nil {
		return nil, err
	}

	d := new(config)
	d.data = &data
	d.lock = new(sync.RWMutex)
	return d, nil
}

// Version returns the current config file format version
func (d config) Version() string {
	st := structs.New(*d.data)

	f, ok := st.FieldOk("Version")
	if !ok {
		return ""
	}

	val := f.Value()
	ver, ok := val.(string)
	if ok {
		return ver
	}
	return ""
}

// String converts JSON config to printable string
func (d config) String() string {
	configBytes, _ := json.MarshalIndent(*d.data, "", "\t")
	return string(configBytes)
}

// Save writes config data in JSON format to a file.
func (d config) Save(filename string) (err error) {
	d.lock.Lock()
	defer d.lock.Unlock()

	jsonData, err := json.MarshalIndent(d.data, "", "\t")
	if err != nil {
		return iodine.New(err, nil)
	}

	tmpfile := filename + ".tmp"

	file, err := os.OpenFile(tmpfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return iodine.New(err, nil)
	}
	defer file.Close()

	if runtime.GOOS == "windows" {
		jsonData = []byte(strings.Replace(string(jsonData), "\n", "\r\n", -1))
	}
	_, err = file.Write(jsonData)
	if err != nil {
		return iodine.New(err, nil)
	}
	err = os.Rename(tmpfile, filename)
	if err != nil {
		return iodine.New(err, nil)
	}
	return nil
}

// Load - loads JSON config from file and merge with currently set values
func (d *config) Load(filename string) (err error) {
	(*d).lock.Lock()
	defer (*d).lock.Unlock()

	_, err = os.Stat(filename)
	if err != nil {
		return iodine.New(err, nil)
	}

	fileData, err := ioutil.ReadFile(filename)
	if err != nil {
		return iodine.New(err, nil)
	}

	if runtime.GOOS == "windows" {
		fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1))
	}

	err = json.Unmarshal(fileData, (*d).data)
	if err != nil {
		return iodine.New(err, nil)
	}

	err = CheckData(*(*d).data)
	if err != nil {
		return iodine.New(err, nil)
	}

	st := structs.New(*(*d).data)
	f, ok := st.FieldOk("Version")
	if !ok {
		return iodine.New(fmt.Errorf("Argument struct [%s] does not contain field \"Version\".", st.Name()), nil)
	}

	if (*d).Version() != f.Value() {
		return iodine.New(errors.New("Version mismatch"), nil)
	}

	return nil
}

// Data - grab internal data map for reading
func (d config) Data() interface{} {
	return *d.data
}

//Diff  - list fields that are in A but not in B
func (d config) Diff(c Config) (fields []structs.Field, err error) {
	err = CheckData(c.Data())
	if err != nil {
		return []structs.Field{}, iodine.New(err, nil)
	}

	currFields := structs.Fields(d.Data())
	newFields := structs.Fields(c.Data())

	found := false
	for _, currField := range currFields {
		found = false
		for _, newField := range newFields {
			if reflect.DeepEqual(currField.Name(), newField.Name()) {
				found = true
			}
		}
		if !found {
			fields = append(fields, *currField)
		}
	}
	return fields, nil
}

//DeepDiff  - list fields in A that are missing or not equal to fields in B
func (d config) DeepDiff(c Config) (fields []structs.Field, err error) {
	err = CheckData(c.Data())
	if err != nil {
		return []structs.Field{}, iodine.New(err, nil)
	}

	currFields := structs.Fields(d.Data())
	newFields := structs.Fields(c.Data())

	found := false
	for _, currField := range currFields {
		found = false
		for _, newField := range newFields {
			if reflect.DeepEqual(currField.Value(), newField.Value()) {
				found = true
			}
		}
		if !found {
			fields = append(fields, *currField)
		}
	}
	return fields, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 CodisLabs. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package topom

import (
	"container/list"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/CodisLabs/codis/pkg/models"
	"github.com/CodisLabs/codis/pkg/utils"
	"github.com/CodisLabs/codis/pkg/utils/errors"
	"github.com/CodisLabs/codis/pkg/utils/log"
	"github.com/CodisLabs/codis/pkg/utils/math2"
	"github.com/CodisLabs/codis/pkg/utils/redis"
	"github.com/CodisLabs/codis/pkg/utils/rpc"
	"github.com/CodisLabs/codis/pkg/utils/sync2/atomic2"
)

type Topom struct {
	mu sync.Mutex

	xauth string
	model *models.Topom
	store *models.Store
	cache struct {
		hooks list.List
		slots []*models.SlotMapping
		group map[int]*models.Group
		proxy map[string]*models.Proxy

		sentinel *models.Sentinel
	}

	exit struct {
		C chan struct{}
	}

	config *Config
	online bool
	closed bool

	ladmin net.Listener
	redisp *redis.Pool

	action struct {
		interval atomic2.Int64
		disabled atomic2.Bool

		progress struct {
			remain atomic2.Int64
			failed atomic2.Bool
		}
		executor atomic2.Int64
	}

	stats struct {
		servers map[string]*RedisStats
		proxies map[string]*ProxyStats
	}

	ha struct {
		redisp *redis.Pool

		monitor *redis.Sentinel
		masters map[int]string
	}
}

var ErrClosedTopom = errors.New("use of closed topom")

func New(client models.Client, config *Config) (*Topom, error) {
	if err := config.Validate(); err != nil {
		return nil, errors.Trace(err)
	}
	if err := models.ValidateProduct(config.ProductName); err != nil {
		return nil, errors.Trace(err)
	}
	s := &Topom{}
	s.config = config
	s.exit.C = make(chan struct{})
	s.redisp = redis.NewPool(config.ProductAuth, time.Second*10)

	s.ha.redisp = redis.NewPool("", time.Second*5)

	s.model = &models.Topom{
		StartTime: time.Now().String(),
	}
	s.model.ProductName = config.ProductName
	s.model.Pid = os.Getpid()
	s.model.Pwd, _ = os.Getwd()
	if b, err := exec.Command("uname", "-a").Output(); err != nil {
		log.WarnErrorf(err, "run command uname failed")
	} else {
		s.model.Sys = strings.TrimSpace(string(b))
	}
	s.store = models.NewStore(client, config.ProductName)

	s.action.interval.Set(1000 * 10)
	s.stats.servers = make(map[string]*RedisStats)
	s.stats.proxies = make(map[string]*ProxyStats)

	if err := s.setup(config); err != nil {
		s.Close()
		return nil, err
	}

	log.Warnf("create new topom:\n%s", s.model.Encode())

	go s.serveAdmin()

	return s, nil
}

func (s *Topom) setup(config *Config) error {
	if l, err := net.Listen("tcp", config.AdminAddr); err != nil {
		return errors.Trace(err)
	} else {
		s.ladmin = l

		x, err := utils.ReplaceUnspecifiedIP("tcp", l.Addr().String(), s.config.HostAdmin)
		if err != nil {
			return err
		}
		s.model.AdminAddr = x
	}

	s.model.Token = rpc.NewToken(
		config.ProductName,
		s.ladmin.Addr().String(),
	)
	s.xauth = rpc.NewXAuth(config.ProductName)

	return nil
}

func (s *Topom) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return nil
	}
	s.closed = true
	close(s.exit.C)

	if s.ladmin != nil {
		s.ladmin.Close()
	}
	if s.redisp != nil {
		s.redisp.Close()
	}
	if s.ha.redisp != nil {
		s.ha.redisp.Close()
	}

	defer s.store.Close()

	if s.online {
		if err := s.store.Release(); err != nil {
			log.ErrorErrorf(err, "store: release lock of %s failed", s.config.ProductName)
			return errors.Errorf("store: release lock of %s failed", s.config.ProductName)
		}
	}
	return nil
}

func (s *Topom) Start(routines bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedTopom
	}
	if s.online {
		return nil
	} else {
		if err := s.store.Acquire(s.model); err != nil {
			log.ErrorErrorf(err, "store: acquire lock of %s failed", s.config.ProductName)
			return errors.Errorf("store: acquire lock of %s failed", s.config.ProductName)
		}
		s.online = true
	}

	if !routines {
		return nil
	}

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				if w, _ := s.RefreshRedisStats(time.Second * 5); w != nil {
					w.Wait()
				}
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				if w, _ := s.RefreshProxyStats(time.Second * 5); w != nil {
					w.Wait()
				}
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				if err := s.ProcessSlotAction(); err != nil {
					log.WarnErrorf(err, "process slot action failed")
					time.Sleep(time.Second * 5)
				}
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				if err := s.ProcessSyncAction(); err != nil {
					log.WarnErrorf(err, "process sync action failed")
					time.Sleep(time.Second * 5)
				}
			}
			time.Sleep(time.Second)
		}
	}()

	return nil
}

func (s *Topom) XAuth() string {
	return s.xauth
}

func (s *Topom) Model() *models.Topom {
	return s.model
}

var ErrNotOnline = errors.New("topom is not online")

func (s *Topom) newContext() (*context, error) {
	if s.closed {
		return nil, ErrClosedTopom
	}
	if s.online {
		if err := s.refillCache(); err != nil {
			return nil, err
		} else {
			ctx := &context{}
			ctx.slots = s.cache.slots
			ctx.group = s.cache.group
			ctx.proxy = s.cache.proxy
			ctx.sentinel = s.cache.sentinel
			ctx.hosts.m = make(map[string]net.IP)
			return ctx, nil
		}
	} else {
		return nil, ErrNotOnline
	}
}

func (s *Topom) Stats() (*Stats, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	ctx, err := s.newContext()
	if err != nil {
		return nil, err
	}

	stats := &Stats{}
	stats.Closed = s.closed

	stats.Slots = ctx.slots

	stats.Group.Models = models.SortGroup(ctx.group)
	stats.Group.Stats = map[string]*RedisStats{}
	for _, g := range ctx.group {
		for _, x := range g.Servers {
			if v := s.stats.servers[x.Addr]; v != nil {
				stats.Group.Stats[x.Addr] = v
			}
		}
	}

	stats.Proxy.Models = models.SortProxy(ctx.proxy)
	stats.Proxy.Stats = s.stats.proxies

	stats.SlotAction.Interval = s.action.interval.Get()
	stats.SlotAction.Disabled = s.action.disabled.Get()
	stats.SlotAction.Progress.Remain = s.action.progress.remain.Get()
	stats.SlotAction.Progress.Failed = s.action.progress.failed.Get()
	stats.SlotAction.Executor = s.action.executor.Get()

	stats.HA.Model = ctx.sentinel
	stats.HA.Stats = map[string]*RedisStats{}
	for _, server := range ctx.sentinel.Servers {
		if v := s.stats.servers[server]; v != nil {
			stats.HA.Stats[server] = v
		}
	}
	stats.HA.Masters = make(map[string]string)
	if s.ha.masters != nil {
		for gid, addr := range s.ha.masters {
			stats.HA.Masters[strconv.Itoa(gid)] = addr
		}
	}
	return stats, nil
}

type Stats struct {
	Closed bool `json:"closed"`

	Slots []*models.SlotMapping `json:"slots"`

	Group struct {
		Models []*models.Group        `json:"models"`
		Stats  map[string]*RedisStats `json:"stats"`
	} `json:"group"`

	Proxy struct {
		Models []*models.Proxy        `json:"models"`
		Stats  map[string]*ProxyStats `json:"stats"`
	} `json:"proxy"`

	SlotAction struct {
		Interval int64 `json:"interval"`
		Disabled bool  `json:"disabled"`

		Progress struct {
			Remain int64 `json:"remain"`
			Failed bool  `json:"failed"`
		} `json:"progress"`

		Executor int64 `json:"executor"`
	} `json:"slot_action"`

	HA struct {
		Model   *models.Sentinel       `json:"model"`
		Stats   map[string]*RedisStats `json:"stats"`
		Masters map[string]string      `json:"masters"`
	} `json:"sentinels"`
}

func (s *Topom) Config() *Config {
	return s.config
}

func (s *Topom) IsOnline() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.online && !s.closed
}

func (s *Topom) IsClosed() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.closed
}

func (s *Topom) GetSlotActionInterval() int {
	return int(s.action.interval.Get())
}

func (s *Topom) SetSlotActionInterval(us int) {
	us = math2.MinMaxInt(us, 0, 1000*1000)
	s.action.interval.Set(int64(us))
	log.Warnf("set action interval = %d", us)
}

func (s *Topom) GetSlotActionDisabled() bool {
	return s.action.disabled.Get()
}

func (s *Topom) SetSlotActionDisabled(value bool) {
	s.action.disabled.Set(value)
	log.Warnf("set action disabled = %t", value)
}

func (s *Topom) Slots() ([]*models.Slot, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	ctx, err := s.newContext()
	if err != nil {
		return nil, err
	}
	return ctx.toSlotSlice(ctx.slots, nil), nil
}

func (s *Topom) Reload() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	_, err := s.newContext()
	if err != nil {
		return err
	}
	defer s.dirtyCacheAll()
	return nil
}

func (s *Topom) serveAdmin() {
	if s.IsClosed() {
		return
	}
	defer s.Close()

	log.Warnf("admin start service on %s", s.ladmin.Addr())

	eh := make(chan error, 1)
	go func(l net.Listener) {
		h := http.NewServeMux()
		h.Handle("/", newApiServer(s))
		hs := &http.Server{Handler: h}
		eh <- hs.Serve(l)
	}(s.ladmin)

	select {
	case <-s.exit.C:
		log.Warnf("admin shutdown")
	case err := <-eh:
		log.ErrorErrorf(err, "admin exit on error")
	}
}

type Overview struct {
	Version string        `json:"version"`
	Compile string        `json:"compile"`
	Config  *Config       `json:"config,omitempty"`
	Model   *models.Topom `json:"model,omitempty"`
	Stats   *Stats        `json:"stats,omitempty"`
}

func (s *Topom) Overview() (*Overview, error) {
	if stats, err := s.Stats(); err != nil {
		return nil, err
	} else {
		return &Overview{
			Version: utils.Version,
			Compile: utils.Compile,
			Config:  s.Config(),
			Model:   s.Model(),
			Stats:   stats,
		}, nil
	}
}
<commit_msg>topom: set rpc.timeout = time.Second<commit_after>// Copyright 2016 CodisLabs. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package topom

import (
	"container/list"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/CodisLabs/codis/pkg/models"
	"github.com/CodisLabs/codis/pkg/utils"
	"github.com/CodisLabs/codis/pkg/utils/errors"
	"github.com/CodisLabs/codis/pkg/utils/log"
	"github.com/CodisLabs/codis/pkg/utils/math2"
	"github.com/CodisLabs/codis/pkg/utils/redis"
	"github.com/CodisLabs/codis/pkg/utils/rpc"
	"github.com/CodisLabs/codis/pkg/utils/sync2/atomic2"
)

type Topom struct {
	mu sync.Mutex

	xauth string
	model *models.Topom
	store *models.Store
	cache struct {
		hooks list.List
		slots []*models.SlotMapping
		group map[int]*models.Group
		proxy map[string]*models.Proxy

		sentinel *models.Sentinel
	}

	exit struct {
		C chan struct{}
	}

	config *Config
	online bool
	closed bool

	ladmin net.Listener
	redisp *redis.Pool

	action struct {
		interval atomic2.Int64
		disabled atomic2.Bool

		progress struct {
			remain atomic2.Int64
			failed atomic2.Bool
		}
		executor atomic2.Int64
	}

	stats struct {
		servers map[string]*RedisStats
		proxies map[string]*ProxyStats
	}

	ha struct {
		redisp *redis.Pool

		monitor *redis.Sentinel
		masters map[int]string
	}
}

var ErrClosedTopom = errors.New("use of closed topom")

func New(client models.Client, config *Config) (*Topom, error) {
	if err := config.Validate(); err != nil {
		return nil, errors.Trace(err)
	}
	if err := models.ValidateProduct(config.ProductName); err != nil {
		return nil, errors.Trace(err)
	}
	s := &Topom{}
	s.config = config
	s.exit.C = make(chan struct{})
	s.redisp = redis.NewPool(config.ProductAuth, time.Second*10)

	s.ha.redisp = redis.NewPool("", time.Second*5)

	s.model = &models.Topom{
		StartTime: time.Now().String(),
	}
	s.model.ProductName = config.ProductName
	s.model.Pid = os.Getpid()
	s.model.Pwd, _ = os.Getwd()
	if b, err := exec.Command("uname", "-a").Output(); err != nil {
		log.WarnErrorf(err, "run command uname failed")
	} else {
		s.model.Sys = strings.TrimSpace(string(b))
	}
	s.store = models.NewStore(client, config.ProductName)

	s.action.interval.Set(1000 * 10)
	s.stats.servers = make(map[string]*RedisStats)
	s.stats.proxies = make(map[string]*ProxyStats)

	if err := s.setup(config); err != nil {
		s.Close()
		return nil, err
	}

	log.Warnf("create new topom:\n%s", s.model.Encode())

	go s.serveAdmin()

	return s, nil
}

func (s *Topom) setup(config *Config) error {
	if l, err := net.Listen("tcp", config.AdminAddr); err != nil {
		return errors.Trace(err)
	} else {
		s.ladmin = l

		x, err := utils.ReplaceUnspecifiedIP("tcp", l.Addr().String(), s.config.HostAdmin)
		if err != nil {
			return err
		}
		s.model.AdminAddr = x
	}

	s.model.Token = rpc.NewToken(
		config.ProductName,
		s.ladmin.Addr().String(),
	)
	s.xauth = rpc.NewXAuth(config.ProductName)

	return nil
}

func (s *Topom) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return nil
	}
	s.closed = true
	close(s.exit.C)

	if s.ladmin != nil {
		s.ladmin.Close()
	}
	if s.redisp != nil {
		s.redisp.Close()
	}
	if s.ha.redisp != nil {
		s.ha.redisp.Close()
	}

	defer s.store.Close()

	if s.online {
		if err := s.store.Release(); err != nil {
			log.ErrorErrorf(err, "store: release lock of %s failed", s.config.ProductName)
			return errors.Errorf("store: release lock of %s failed", s.config.ProductName)
		}
	}
	return nil
}

func (s *Topom) Start(routines bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedTopom
	}
	if s.online {
		return nil
	} else {
		if err := s.store.Acquire(s.model); err != nil {
			log.ErrorErrorf(err, "store: acquire lock of %s failed", s.config.ProductName)
			return errors.Errorf("store: acquire lock of %s failed", s.config.ProductName)
		}
		s.online = true
	}

	if !routines {
		return nil
	}

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				w, _ := s.RefreshRedisStats(time.Second)
				if w != nil {
					w.Wait()
				}
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				w, _ := s.RefreshProxyStats(time.Second)
				if w != nil {
					w.Wait()
				}
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				if err := s.ProcessSlotAction(); err != nil {
					log.WarnErrorf(err, "process slot action failed")
					time.Sleep(time.Second * 5)
				}
			}
			time.Sleep(time.Second)
		}
	}()

	go func() {
		for !s.IsClosed() {
			if s.IsOnline() {
				if err := s.ProcessSyncAction(); err != nil {
					log.WarnErrorf(err, "process sync action failed")
					time.Sleep(time.Second * 5)
				}
			}
			time.Sleep(time.Second)
		}
	}()

	return nil
}

func (s *Topom) XAuth() string {
	return s.xauth
}

func (s *Topom) Model() *models.Topom {
	return s.model
}

var ErrNotOnline = errors.New("topom is not online")

func (s *Topom) newContext() (*context, error) {
	if s.closed {
		return nil, ErrClosedTopom
	}
	if s.online {
		if err := s.refillCache(); err != nil {
			return nil, err
		} else {
			ctx := &context{}
			ctx.slots = s.cache.slots
			ctx.group = s.cache.group
			ctx.proxy = s.cache.proxy
			ctx.sentinel = s.cache.sentinel
			ctx.hosts.m = make(map[string]net.IP)
			return ctx, nil
		}
	} else {
		return nil, ErrNotOnline
	}
}

func (s *Topom) Stats() (*Stats, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	ctx, err := s.newContext()
	if err != nil {
		return nil, err
	}

	stats := &Stats{}
	stats.Closed = s.closed

	stats.Slots = ctx.slots

	stats.Group.Models = models.SortGroup(ctx.group)
	stats.Group.Stats = map[string]*RedisStats{}
	for _, g := range ctx.group {
		for _, x := range g.Servers {
			if v := s.stats.servers[x.Addr]; v != nil {
				stats.Group.Stats[x.Addr] = v
			}
		}
	}

	stats.Proxy.Models = models.SortProxy(ctx.proxy)
	stats.Proxy.Stats = s.stats.proxies

	stats.SlotAction.Interval = s.action.interval.Get()
	stats.SlotAction.Disabled = s.action.disabled.Get()
	stats.SlotAction.Progress.Remain = s.action.progress.remain.Get()
	stats.SlotAction.Progress.Failed = s.action.progress.failed.Get()
	stats.SlotAction.Executor = s.action.executor.Get()

	stats.HA.Model = ctx.sentinel
	stats.HA.Stats = map[string]*RedisStats{}
	for _, server := range ctx.sentinel.Servers {
		if v := s.stats.servers[server]; v != nil {
			stats.HA.Stats[server] = v
		}
	}
	stats.HA.Masters = make(map[string]string)
	if s.ha.masters != nil {
		for gid, addr := range s.ha.masters {
			stats.HA.Masters[strconv.Itoa(gid)] = addr
		}
	}
	return stats, nil
}

type Stats struct {
	Closed bool `json:"closed"`

	Slots []*models.SlotMapping `json:"slots"`

	Group struct {
		Models []*models.Group        `json:"models"`
		Stats  map[string]*RedisStats `json:"stats"`
	} `json:"group"`

	Proxy struct {
		Models []*models.Proxy        `json:"models"`
		Stats  map[string]*ProxyStats `json:"stats"`
	} `json:"proxy"`

	SlotAction struct {
		Interval int64 `json:"interval"`
		Disabled bool  `json:"disabled"`

		Progress struct {
			Remain int64 `json:"remain"`
			Failed bool  `json:"failed"`
		} `json:"progress"`

		Executor int64 `json:"executor"`
	} `json:"slot_action"`

	HA struct {
		Model   *models.Sentinel       `json:"model"`
		Stats   map[string]*RedisStats `json:"stats"`
		Masters map[string]string      `json:"masters"`
	} `json:"sentinels"`
}

func (s *Topom) Config() *Config {
	return s.config
}

func (s *Topom) IsOnline() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.online && !s.closed
}

func (s *Topom) IsClosed() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.closed
}

func (s *Topom) GetSlotActionInterval() int {
	return int(s.action.interval.Get())
}

func (s *Topom) SetSlotActionInterval(us int) {
	us = math2.MinMaxInt(us, 0, 1000*1000)
	s.action.interval.Set(int64(us))
	log.Warnf("set action interval = %d", us)
}

func (s *Topom) GetSlotActionDisabled() bool {
	return s.action.disabled.Get()
}

func (s *Topom) SetSlotActionDisabled(value bool) {
	s.action.disabled.Set(value)
	log.Warnf("set action disabled = %t", value)
}

func (s *Topom) Slots() ([]*models.Slot, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	ctx, err := s.newContext()
	if err != nil {
		return nil, err
	}
	return ctx.toSlotSlice(ctx.slots, nil), nil
}

func (s *Topom) Reload() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	_, err := s.newContext()
	if err != nil {
		return err
	}
	defer s.dirtyCacheAll()
	return nil
}

func (s *Topom) serveAdmin() {
	if s.IsClosed() {
		return
	}
	defer s.Close()

	log.Warnf("admin start service on %s", s.ladmin.Addr())

	eh := make(chan error, 1)
	go func(l net.Listener) {
		h := http.NewServeMux()
		h.Handle("/", newApiServer(s))
		hs := &http.Server{Handler: h}
		eh <- hs.Serve(l)
	}(s.ladmin)

	select {
	case <-s.exit.C:
		log.Warnf("admin shutdown")
	case err := <-eh:
		log.ErrorErrorf(err, "admin exit on error")
	}
}

type Overview struct {
	Version string        `json:"version"`
	Compile string        `json:"compile"`
	Config  *Config       `json:"config,omitempty"`
	Model   *models.Topom `json:"model,omitempty"`
	Stats   *Stats        `json:"stats,omitempty"`
}

func (s *Topom) Overview() (*Overview, error) {
	if stats, err := s.Stats(); err != nil {
		return nil, err
	} else {
		return &Overview{
			Version: utils.Version,
			Compile: utils.Compile,
			Config:  s.Config(),
			Model:   s.Model(),
			Stats:   stats,
		}, nil
	}
}
<|endoftext|>


<commit_before>package trac

import "fmt"

type Search struct {
	client *Client
}

// SearchFilters retrieve a list of search filters with each element in the
// form (name, description).
// Not implemented.
func (s *Search) SearchFilters() ([]string, error) {
	return nil, fmt.Errorf("Not implemented")
}

// Search using the given filters. Defaults to all if not provided. Results are
// returned as a list of tuples in the form (href, title, date, author,
// excerpt).
// Not implemented.
func (s *Search) Search(query string, filters []string) ([]string, error) {
	return nil, fmt.Errorf("Not implemented")
}
<commit_msg>Add Search doc<commit_after>package trac

import "fmt"

// Search trac.
type Search struct {
	client *Client
}

// SearchFilters retrieve a list of search filters with each element in the
// form (name, description).
// Not implemented.
func (s *Search) SearchFilters() ([]string, error) {
	return nil, fmt.Errorf("Not implemented")
}

// Search using the given filters. Defaults to all if not provided. Results are
// returned as a list of tuples in the form (href, title, date, author,
// excerpt).
// Not implemented.
func (s *Search) Search(query string, filters []string) ([]string, error) {
	return nil, fmt.Errorf("Not implemented")
}
<|endoftext|>


<commit_before>package version

import (
	"fmt"
	"runtime"
)

const Binary = "2.0.0-alpha730"

func String(app string) string {
	return fmt.Sprintf("%s v%s (built w/%s)", app, Binary, runtime.Version())
}
<commit_msg>集成静态文件<commit_after>package version

import (
	"fmt"
	"runtime"
)

const Binary = "2.0.0"

func String(app string) string {
	return fmt.Sprintf("%s v%s (built w/%s)", app, Binary, runtime.Version())
}
<|endoftext|>


<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

package plotter

import (
	"code.google.com/p/plotinum/plot"
	"code.google.com/p/plotinum/vg"
	"math"
	"sort"
)

// fiveStatPlot contains the shared fields for quartile
// and box-whisker plots.
type fiveStatPlot struct {
	// Values is a copy of the values of the values used to
	// create this box plot.
	Values

	// Location is the location of the box along its axis.
	Location float64

	// Width is the width used to draw the box.
	Width vg.Length

	// CapWidth is the width of the cap used to top
	// off a whisker.
	CapWidth vg.Length

	// Median is the median value of the data.
	Median float64

	// Quartile1 and Quartile3 are the first and
	// third quartiles of the data respectively.
	Quartile1, Quartile3 float64

	// AdjLow and AdjHigh are the `adjacent' values
	// on the low and high ends of the data.  The
	// adjacent values are the points to which the
	// whiskers are drawn.
	AdjLow, AdjHigh float64

	// Min and Max are the extreme values of the data.
	Min, Max float64

	// Outside are the indices of Vs for the outside points.
	Outside []int
}

// BoxPlot implements the Plotter interface, drawing
// a boxplot to represent the distribution of values.
type BoxPlot struct {
	fiveStatPlot

	// GlyphStyle is the style of the outside point glyphs.
	GlyphStyle plot.GlyphStyle

	// BoxStyle is the line style for the box.
	BoxStyle plot.LineStyle

	// MedianStyle is the line style for the median line.
	MedianStyle plot.LineStyle

	// WhiskerStyle is the line style used to draw the
	// whiskers.
	WhiskerStyle plot.LineStyle
}

// NewBoxPlot returns a new BoxPlot that represents
// the distribution of the given values.  The style of
// the box plot is that used for Tukey's schematic
// plots is ``Exploratory Data Analysis.''
//
// An error is returned if the boxplot is created with
// no values.
//
// The fence values are 1.5x the interquartile before
// the first quartile and after the third quartile.  Any
// value that is outside of the fences are drawn as
// Outside points.  The adjacent values (to which the
// whiskers stretch) are the minimum and maximum
// values that are not outside the fences.
func NewBoxPlot(w vg.Length, loc float64, values Valuer) *BoxPlot {
	b := new(BoxPlot)
	b.fiveStatPlot = newFiveStat(w, loc, values)

	b.GlyphStyle = DefaultGlyphStyle
	b.BoxStyle = DefaultLineStyle
	b.MedianStyle = DefaultLineStyle
	b.WhiskerStyle = plot.LineStyle{
		Width:  vg.Points(0.5),
		Dashes: []vg.Length{vg.Points(4), vg.Points(2)},
	}

	if len(b.Values) == 0 {
		b.Width = 0
		b.GlyphStyle.Radius = 0
		b.BoxStyle.Width = 0
		b.MedianStyle.Width = 0
		b.WhiskerStyle.Width = 0
	}

	return b
}

func newFiveStat(w vg.Length, loc float64, values Valuer) fiveStatPlot {
	var b fiveStatPlot
	b.Location = loc
	b.Width = w
	b.CapWidth = 3 * w / 4

	b.Values = CopyValues(values)
	sorted := CopyValues(values)
	sort.Float64s(sorted)
	if len(sorted) == 0 {
		return b
	}

	if len(sorted) == 1 {
		b.Median = sorted[0]
		b.Quartile1 = sorted[0]
		b.Quartile3 = sorted[0]
	} else {
		b.Median = median(sorted)
		b.Quartile1 = median(sorted[:len(sorted)/2])
		b.Quartile3 = median(sorted[len(sorted)/2:])
	}
	b.Min = sorted[0]
	b.Max = sorted[len(sorted)-1]

	low := b.Quartile1 - 1.5*(b.Quartile3-b.Quartile1)
	high := b.Quartile3 + 1.5*(b.Quartile3-b.Quartile1)
	b.AdjLow = math.Inf(1)
	b.AdjHigh = math.Inf(-1)
	for i, v := range b.Values {
		if v > high || v < low {
			b.Outside = append(b.Outside, i)
			continue
		}
		if v < b.AdjLow {
			b.AdjLow = v
		}
		if v > b.AdjHigh {
			b.AdjHigh = v
		}
	}

	return b
}

// median returns the median value from a
// sorted Values.
func median(vs Values) float64 {
	if len(vs) == 1 {
		return vs[0]
	}
	med := vs[len(vs)/2]
	if len(vs)%2 == 0 {
		med += vs[len(vs)/2-1]
		med /= 2
	}
	return med
}

func (b *BoxPlot) Plot(da plot.DrawArea, plt *plot.Plot) {
	trX, trY := plt.Transforms(&da)
	x := trX(b.Location)
	if !da.ContainsX(x) {
		return
	}

	med := trY(b.Median)
	q1 := trY(b.Quartile1)
	q3 := trY(b.Quartile3)
	aLow := trY(b.AdjLow)
	aHigh := trY(b.AdjHigh)

	box := da.ClipLinesY([]plot.Point{
		{x - b.Width/2, q1},
		{x - b.Width/2, q3},
		{x + b.Width/2, q3},
		{x + b.Width/2, q1},
		{x - b.Width/2 - b.BoxStyle.Width/2, q1},
	})
	da.StrokeLines(b.BoxStyle, box...)

	medLine := da.ClipLinesY([]plot.Point{
		{x - b.Width/2, med},
		{x + b.Width/2, med},
	})
	da.StrokeLines(b.MedianStyle, medLine...)

	cap := b.CapWidth / 2
	whisks := da.ClipLinesY([]plot.Point{{x, q3}, {x, aHigh}},
		[]plot.Point{{x - cap, aHigh}, {x + cap, aHigh}},
		[]plot.Point{{x, q1}, {x, aLow}},
		[]plot.Point{{x - cap, aLow}, {x + cap, aLow}})
	da.StrokeLines(b.WhiskerStyle, whisks...)

	for _, out := range b.Outside {
		y := trY(b.Value(out))
		da.DrawGlyph(b.GlyphStyle, plot.Point{x, y})
	}
}

// DataRange returns the minimum and maximum x
// and y values, implementing the plot.DataRanger
// interface.
func (b *BoxPlot) DataRange() (float64, float64, float64, float64) {
	return b.Location, b.Location, b.Min, b.Max
}

// GlyphBoxes returns a slice of GlyphBoxes for the
// points and for the median line of the boxplot,
// implementing the plot.GlyphBoxer interface
func (b *BoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
	bs := make([]plot.GlyphBox, len(b.Outside)+1)
	for i, out := range b.Outside {
		bs[i].X = plt.X.Norm(b.Location)
		bs[i].Y = plt.Y.Norm(b.Value(out))
		bs[i].Rect = b.GlyphStyle.Rect()
	}
	bs[len(bs)-1].X = plt.X.Norm(b.Location)
	bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
	bs[len(bs)-1].Rect = plot.Rect{
		Min:  plot.Point{X: -(b.Width/2 + b.BoxStyle.Width/2)},
		Size: plot.Point{X: b.Width + b.BoxStyle.Width},
	}
	return bs
}

// OutsideLabels returns a *Labels that will plot
// a label for each of the outside points.  The
// labels are assumed to correspond to the
// points used to create the box plot.
func (b *BoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
	strs := make([]string, len(b.Outside))
	for i, out := range b.Outside {
		strs[i] = labels.Label(out)
	}
	o := boxPlotOutsideLabels{b, strs}
	ls, err := NewLabels(o)
	if err != nil {
		return nil, err
	}
	ls.XOffset += b.GlyphStyle.Radius / 2
	ls.YOffset += b.GlyphStyle.Radius / 2
	return ls, nil
}

type boxPlotOutsideLabels struct {
	box    *BoxPlot
	labels []string
}

func (o boxPlotOutsideLabels) Len() int {
	return len(o.box.Outside)
}

func (o boxPlotOutsideLabels) XY(i int) (float64, float64) {
	return o.box.Location, o.box.Value(o.box.Outside[i])
}

func (o boxPlotOutsideLabels) Label(i int) string {
	return o.labels[i]
}

// HorizBoxPlot is like a regular BoxPlot, however,
// it draws horizontally instead of Vertically.
type HorizBoxPlot struct{ *BoxPlot }

// MakeHorizBoxPlot returns a HorizBoxPlot,
// plotting the values in a horizontal box plot
// centered along a fixed location of the y axis.
func MakeHorizBoxPlot(w vg.Length, loc float64, vs Values) HorizBoxPlot {
	return HorizBoxPlot{NewBoxPlot(w, loc, vs)}
}

func (b HorizBoxPlot) Plot(da plot.DrawArea, plt *plot.Plot) {
	trX, trY := plt.Transforms(&da)
	y := trY(b.Location)
	if !da.ContainsY(y) {
		return
	}

	med := trX(b.Median)
	q1 := trX(b.Quartile1)
	q3 := trX(b.Quartile3)
	aLow := trX(b.AdjLow)
	aHigh := trX(b.AdjHigh)

	box := da.ClipLinesX([]plot.Point{
		{q1, y - b.Width/2},
		{q3, y - b.Width/2},
		{q3, y + b.Width/2},
		{q1, y + b.Width/2},
		{q1, y - b.Width/2 - b.BoxStyle.Width/2},
	})
	da.StrokeLines(b.BoxStyle, box...)

	medLine := da.ClipLinesX([]plot.Point{
		{med, y - b.Width/2},
		{med, y + b.Width/2},
	})
	da.StrokeLines(b.MedianStyle, medLine...)

	cap := b.CapWidth / 2
	whisks := da.ClipLinesX([]plot.Point{{q3, y}, {aHigh, y}},
		[]plot.Point{{aHigh, y - cap}, {aHigh, y + cap}},
		[]plot.Point{{q1, y}, {aLow, y}},
		[]plot.Point{{aLow, y - cap}, {aLow, y + cap}})
	da.StrokeLines(b.WhiskerStyle, whisks...)

	for _, out := range b.Outside {
		x := trX(b.Value(out))
		da.DrawGlyph(b.GlyphStyle, plot.Point{x, y})
	}
}

// DataRange returns the minimum and maximum x
// and y values, implementing the plot.DataRanger
// interface.
func (b HorizBoxPlot) DataRange() (float64, float64, float64, float64) {
	return b.Min, b.Max, b.Location, b.Location
}

// GlyphBoxes returns a slice of GlyphBoxes for the
// points and for the median line of the boxplot,
// implementing the plot.GlyphBoxer interface
func (b HorizBoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
	bs := make([]plot.GlyphBox, len(b.Outside)+1)
	for i, out := range b.Outside {
		bs[i].X = plt.X.Norm(b.Value(out))
		bs[i].Y = plt.Y.Norm(b.Location)
		bs[i].Rect = b.GlyphStyle.Rect()
	}
	bs[len(bs)-1].X = plt.X.Norm(b.Median)
	bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
	bs[len(bs)-1].Rect = plot.Rect{
		Min:  plot.Point{Y: -(b.Width/2 + b.BoxStyle.Width/2)},
		Size: plot.Point{Y: b.Width + b.BoxStyle.Width},
	}
	return bs
}

// OutsideLabels returns a *Labels that will plot
// a label for each of the outside points.  The
// labels are assumed to correspond to the
// points used to create the box plot.
func (b *HorizBoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
	strs := make([]string, len(b.Outside))
	for i, out := range b.Outside {
		strs[i] = labels.Label(out)
	}
	o := horizBoxPlotOutsideLabels{
		boxPlotOutsideLabels{b.BoxPlot, strs},
	}
	ls, err := NewLabels(o)
	if err != nil {
		return nil, err
	}
	ls.XOffset += b.GlyphStyle.Radius / 2
	ls.YOffset += b.GlyphStyle.Radius / 2
	return ls, nil
}

type horizBoxPlotOutsideLabels struct {
	boxPlotOutsideLabels
}

func (o horizBoxPlotOutsideLabels) XY(i int) (float64, float64) {
	return o.box.Value(o.box.Outside[i]), o.box.Location
}
<commit_msg>Optimize early return here.<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

package plotter

import (
	"code.google.com/p/plotinum/plot"
	"code.google.com/p/plotinum/vg"
	"math"
	"sort"
)

// fiveStatPlot contains the shared fields for quartile
// and box-whisker plots.
type fiveStatPlot struct {
	// Values is a copy of the values of the values used to
	// create this box plot.
	Values

	// Location is the location of the box along its axis.
	Location float64

	// Width is the width used to draw the box.
	Width vg.Length

	// CapWidth is the width of the cap used to top
	// off a whisker.
	CapWidth vg.Length

	// Median is the median value of the data.
	Median float64

	// Quartile1 and Quartile3 are the first and
	// third quartiles of the data respectively.
	Quartile1, Quartile3 float64

	// AdjLow and AdjHigh are the `adjacent' values
	// on the low and high ends of the data.  The
	// adjacent values are the points to which the
	// whiskers are drawn.
	AdjLow, AdjHigh float64

	// Min and Max are the extreme values of the data.
	Min, Max float64

	// Outside are the indices of Vs for the outside points.
	Outside []int
}

// BoxPlot implements the Plotter interface, drawing
// a boxplot to represent the distribution of values.
type BoxPlot struct {
	fiveStatPlot

	// GlyphStyle is the style of the outside point glyphs.
	GlyphStyle plot.GlyphStyle

	// BoxStyle is the line style for the box.
	BoxStyle plot.LineStyle

	// MedianStyle is the line style for the median line.
	MedianStyle plot.LineStyle

	// WhiskerStyle is the line style used to draw the
	// whiskers.
	WhiskerStyle plot.LineStyle
}

// NewBoxPlot returns a new BoxPlot that represents
// the distribution of the given values.  The style of
// the box plot is that used for Tukey's schematic
// plots is ``Exploratory Data Analysis.''
//
// An error is returned if the boxplot is created with
// no values.
//
// The fence values are 1.5x the interquartile before
// the first quartile and after the third quartile.  Any
// value that is outside of the fences are drawn as
// Outside points.  The adjacent values (to which the
// whiskers stretch) are the minimum and maximum
// values that are not outside the fences.
func NewBoxPlot(w vg.Length, loc float64, values Valuer) *BoxPlot {
	b := new(BoxPlot)
	b.fiveStatPlot = newFiveStat(w, loc, values)

	b.GlyphStyle = DefaultGlyphStyle
	b.BoxStyle = DefaultLineStyle
	b.MedianStyle = DefaultLineStyle
	b.WhiskerStyle = plot.LineStyle{
		Width:  vg.Points(0.5),
		Dashes: []vg.Length{vg.Points(4), vg.Points(2)},
	}

	if len(b.Values) == 0 {
		b.Width = 0
		b.GlyphStyle.Radius = 0
		b.BoxStyle.Width = 0
		b.MedianStyle.Width = 0
		b.WhiskerStyle.Width = 0
	}

	return b
}

func newFiveStat(w vg.Length, loc float64, values Valuer) fiveStatPlot {
	var b fiveStatPlot
	b.Location = loc
	b.Width = w
	b.CapWidth = 3 * w / 4

	b.Values = CopyValues(values)
	if len(b.Values) == 0 {
		return b
	}

	sorted := CopyValues(values)
	sort.Float64s(sorted)

	if len(sorted) == 1 {
		b.Median = sorted[0]
		b.Quartile1 = sorted[0]
		b.Quartile3 = sorted[0]
	} else {
		b.Median = median(sorted)
		b.Quartile1 = median(sorted[:len(sorted)/2])
		b.Quartile3 = median(sorted[len(sorted)/2:])
	}
	b.Min = sorted[0]
	b.Max = sorted[len(sorted)-1]

	low := b.Quartile1 - 1.5*(b.Quartile3-b.Quartile1)
	high := b.Quartile3 + 1.5*(b.Quartile3-b.Quartile1)
	b.AdjLow = math.Inf(1)
	b.AdjHigh = math.Inf(-1)
	for i, v := range b.Values {
		if v > high || v < low {
			b.Outside = append(b.Outside, i)
			continue
		}
		if v < b.AdjLow {
			b.AdjLow = v
		}
		if v > b.AdjHigh {
			b.AdjHigh = v
		}
	}

	return b
}

// median returns the median value from a
// sorted Values.
func median(vs Values) float64 {
	if len(vs) == 1 {
		return vs[0]
	}
	med := vs[len(vs)/2]
	if len(vs)%2 == 0 {
		med += vs[len(vs)/2-1]
		med /= 2
	}
	return med
}

func (b *BoxPlot) Plot(da plot.DrawArea, plt *plot.Plot) {
	trX, trY := plt.Transforms(&da)
	x := trX(b.Location)
	if !da.ContainsX(x) {
		return
	}

	med := trY(b.Median)
	q1 := trY(b.Quartile1)
	q3 := trY(b.Quartile3)
	aLow := trY(b.AdjLow)
	aHigh := trY(b.AdjHigh)

	box := da.ClipLinesY([]plot.Point{
		{x - b.Width/2, q1},
		{x - b.Width/2, q3},
		{x + b.Width/2, q3},
		{x + b.Width/2, q1},
		{x - b.Width/2 - b.BoxStyle.Width/2, q1},
	})
	da.StrokeLines(b.BoxStyle, box...)

	medLine := da.ClipLinesY([]plot.Point{
		{x - b.Width/2, med},
		{x + b.Width/2, med},
	})
	da.StrokeLines(b.MedianStyle, medLine...)

	cap := b.CapWidth / 2
	whisks := da.ClipLinesY([]plot.Point{{x, q3}, {x, aHigh}},
		[]plot.Point{{x - cap, aHigh}, {x + cap, aHigh}},
		[]plot.Point{{x, q1}, {x, aLow}},
		[]plot.Point{{x - cap, aLow}, {x + cap, aLow}})
	da.StrokeLines(b.WhiskerStyle, whisks...)

	for _, out := range b.Outside {
		y := trY(b.Value(out))
		da.DrawGlyph(b.GlyphStyle, plot.Point{x, y})
	}
}

// DataRange returns the minimum and maximum x
// and y values, implementing the plot.DataRanger
// interface.
func (b *BoxPlot) DataRange() (float64, float64, float64, float64) {
	return b.Location, b.Location, b.Min, b.Max
}

// GlyphBoxes returns a slice of GlyphBoxes for the
// points and for the median line of the boxplot,
// implementing the plot.GlyphBoxer interface
func (b *BoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
	bs := make([]plot.GlyphBox, len(b.Outside)+1)
	for i, out := range b.Outside {
		bs[i].X = plt.X.Norm(b.Location)
		bs[i].Y = plt.Y.Norm(b.Value(out))
		bs[i].Rect = b.GlyphStyle.Rect()
	}
	bs[len(bs)-1].X = plt.X.Norm(b.Location)
	bs[len(bs)-1].Y = plt.Y.Norm(b.Median)
	bs[len(bs)-1].Rect = plot.Rect{
		Min:  plot.Point{X: -(b.Width/2 + b.BoxStyle.Width/2)},
		Size: plot.Point{X: b.Width + b.BoxStyle.Width},
	}
	return bs
}

// OutsideLabels returns a *Labels that will plot
// a label for each of the outside points.  The
// labels are assumed to correspond to the
// points used to create the box plot.
func (b *BoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
	strs := make([]string, len(b.Outside))
	for i, out := range b.Outside {
		strs[i] = labels.Label(out)
	}
	o := boxPlotOutsideLabels{b, strs}
	ls, err := NewLabels(o)
	if err != nil {
		return nil, err
	}
	ls.XOffset += b.GlyphStyle.Radius / 2
	ls.YOffset += b.GlyphStyle.Radius / 2
	return ls, nil
}

type boxPlotOutsideLabels struct {
	box    *BoxPlot
	labels []string
}

func (o boxPlotOutsideLabels) Len() int {
	return len(o.box.Outside)
}

func (o boxPlotOutsideLabels) XY(i int) (float64, float64) {
	return o.box.Location, o.box.Value(o.box.Outside[i])
}

func (o boxPlotOutsideLabels) Label(i int) string {
	return o.labels[i]
}

// HorizBoxPlot is like a regular BoxPlot, however,
// it draws horizontally instead of Vertically.
type HorizBoxPlot struct{ *BoxPlot }

// MakeHorizBoxPlot returns a HorizBoxPlot,
// plotting the values in a horizontal box plot
// centered along a fixed location of the y axis.
func MakeHorizBoxPlot(w vg.Length, loc float64, vs Values) HorizBoxPlot {
	return HorizBoxPlot{NewBoxPlot(w, loc, vs)}
}

func (b HorizBoxPlot) Plot(da plot.DrawArea, plt *plot.Plot) {
	trX, trY := plt.Transforms(&da)
	y := trY(b.Location)
	if !da.ContainsY(y) {
		return
	}

	med := trX(b.Median)
	q1 := trX(b.Quartile1)
	q3 := trX(b.Quartile3)
	aLow := trX(b.AdjLow)
	aHigh := trX(b.AdjHigh)

	box := da.ClipLinesX([]plot.Point{
		{q1, y - b.Width/2},
		{q3, y - b.Width/2},
		{q3, y + b.Width/2},
		{q1, y + b.Width/2},
		{q1, y - b.Width/2 - b.BoxStyle.Width/2},
	})
	da.StrokeLines(b.BoxStyle, box...)

	medLine := da.ClipLinesX([]plot.Point{
		{med, y - b.Width/2},
		{med, y + b.Width/2},
	})
	da.StrokeLines(b.MedianStyle, medLine...)

	cap := b.CapWidth / 2
	whisks := da.ClipLinesX([]plot.Point{{q3, y}, {aHigh, y}},
		[]plot.Point{{aHigh, y - cap}, {aHigh, y + cap}},
		[]plot.Point{{q1, y}, {aLow, y}},
		[]plot.Point{{aLow, y - cap}, {aLow, y + cap}})
	da.StrokeLines(b.WhiskerStyle, whisks...)

	for _, out := range b.Outside {
		x := trX(b.Value(out))
		da.DrawGlyph(b.GlyphStyle, plot.Point{x, y})
	}
}

// DataRange returns the minimum and maximum x
// and y values, implementing the plot.DataRanger
// interface.
func (b HorizBoxPlot) DataRange() (float64, float64, float64, float64) {
	return b.Min, b.Max, b.Location, b.Location
}

// GlyphBoxes returns a slice of GlyphBoxes for the
// points and for the median line of the boxplot,
// implementing the plot.GlyphBoxer interface
func (b HorizBoxPlot) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
	bs := make([]plot.GlyphBox, len(b.Outside)+1)
	for i, out := range b.Outside {
		bs[i].X = plt.X.Norm(b.Value(out))
		bs[i].Y = plt.Y.Norm(b.Location)
		bs[i].Rect = b.GlyphStyle.Rect()
	}
	bs[len(bs)-1].X = plt.X.Norm(b.Median)
	bs[len(bs)-1].Y = plt.Y.Norm(b.Location)
	bs[len(bs)-1].Rect = plot.Rect{
		Min:  plot.Point{Y: -(b.Width/2 + b.BoxStyle.Width/2)},
		Size: plot.Point{Y: b.Width + b.BoxStyle.Width},
	}
	return bs
}

// OutsideLabels returns a *Labels that will plot
// a label for each of the outside points.  The
// labels are assumed to correspond to the
// points used to create the box plot.
func (b *HorizBoxPlot) OutsideLabels(labels Labeller) (*Labels, error) {
	strs := make([]string, len(b.Outside))
	for i, out := range b.Outside {
		strs[i] = labels.Label(out)
	}
	o := horizBoxPlotOutsideLabels{
		boxPlotOutsideLabels{b.BoxPlot, strs},
	}
	ls, err := NewLabels(o)
	if err != nil {
		return nil, err
	}
	ls.XOffset += b.GlyphStyle.Radius / 2
	ls.YOffset += b.GlyphStyle.Radius / 2
	return ls, nil
}

type horizBoxPlotOutsideLabels struct {
	boxPlotOutsideLabels
}

func (o horizBoxPlotOutsideLabels) XY(i int) (float64, float64) {
	return o.box.Value(o.box.Outside[i]), o.box.Location
}
<|endoftext|>


<commit_before>package plugin

import (
	"strings"
	"testing"
	. "github.com/smartystreets/goconvey/convey"

	"github.com/j6n/noye/noye"
)

func TestCommand(t *testing.T) {
	// helper for less typing
	// it builds a simple message and matches it to the command
	match := func(cmd *Command, s ...string) bool {
		return cmd.Match(noye.Message{"museun", "#museun", strings.Join(s, " ")})
	}

	Convey("Command", t, func() {
		Convey("should match a simple command", func() {
			cmd := &Command{Command: "hello"}
			So(match(cmd, "hello"), ShouldBeTrue)
			So(match(cmd, "something"), ShouldBeFalse)
		})

		Convey("should match a simple respond", func() {
			cmd := &Command{Respond: true, Command: "something"}
			So(match(cmd, "noye: something"), ShouldBeTrue)
			So(match(cmd, "noye: do something"), ShouldBeFalse)
		})

		Convey("should match multiple parts", func() {
			cmd := &Command{Command: "foo", Each: true,
				Matcher: func(s string) (bool, string) { return len(s) == 3, "" },
			}
			So(match(cmd, "foo bar baz"), ShouldBeTrue)
			So(match(cmd, "foo foo foobar"), ShouldBeFalse)
			So(match(cmd, "noye: test this out"), ShouldBeFalse)
		})

		Convey("should match respond with mulitple parts", func() {
			cmd := &Command{Command: "foo", Each: true, Respond: true,
				Matcher: func(s string) (bool, string) { return len(s) == 3, "" },
			}
			So(match(cmd, "noye: foo bar baz"), ShouldBeTrue)
			So(match(cmd, "noye: foo bar asdf"), ShouldBeFalse)
			So(match(cmd, "foo bar asdf"), ShouldBeFalse)
			So(match(cmd, "foo bar baz"), ShouldBeFalse)
			So(match(cmd, "noye: bar foo"), ShouldBeFalse)
		})

		Convey("should match simple with a result", func() {
			cmd := &Command{Command: "foo", Matcher: func(s string) (bool, string) {
				t.Log(">", s)
				if s == "test" {
					return true, "bar"
				}
				return false, ""
			}}

			So(match(cmd, "foo test"), ShouldBeTrue)
			So(cmd.Results()[0], ShouldEqual, "bar")
		})
	})
}
<commit_msg>tested results against regex and multiple<commit_after>package plugin

import (
	"regexp"
	"strings"
	"testing"
	. "github.com/smartystreets/goconvey/convey"

	"github.com/j6n/noye/noye"
)

func TestCommand(t *testing.T) {
	// helper for less typing
	// it builds a simple message and matches it to the command
	match := func(cmd *Command, s ...string) bool {
		return cmd.Match(noye.Message{"museun", "#museun", strings.Join(s, " ")})
	}

	Convey("Command", t, func() {
		Convey("should match a simple command", func() {
			cmd := &Command{Command: "hello"}
			So(match(cmd, "hello"), ShouldBeTrue)
			So(match(cmd, "something"), ShouldBeFalse)
		})

		Convey("should match a simple respond", func() {
			cmd := &Command{Respond: true, Command: "something"}
			So(match(cmd, "noye: something"), ShouldBeTrue)
			So(match(cmd, "noye: do something"), ShouldBeFalse)
		})

		Convey("should match multiple parts", func() {
			cmd := &Command{Command: "foo", Each: true,
				Matcher: func(s string) (bool, string) { return len(s) == 3, "" },
			}
			So(match(cmd, "foo bar baz"), ShouldBeTrue)
			So(match(cmd, "foo foo foobar"), ShouldBeFalse)
			So(match(cmd, "noye: test this out"), ShouldBeFalse)
		})

		Convey("should match respond with mulitple parts", func() {
			cmd := &Command{Command: "foo", Each: true, Respond: true,
				Matcher: func(s string) (bool, string) { return len(s) == 3, "" },
			}
			So(match(cmd, "noye: foo bar baz"), ShouldBeTrue)
			So(match(cmd, "noye: foo bar asdf"), ShouldBeFalse)
			So(match(cmd, "foo bar asdf"), ShouldBeFalse)
			So(match(cmd, "foo bar baz"), ShouldBeFalse)
			So(match(cmd, "noye: bar foo"), ShouldBeFalse)
		})

		Convey("should match simple with a result", func() {
			cmd := &Command{Command: "foo", Matcher: func(s string) (bool, string) {
				t.Log(">", s)
				if s == "test" {
					return true, "bar"
				}
				return false, ""
			}}

			So(match(cmd, "foo test"), ShouldBeTrue)

			res := cmd.Results()
			So(res, ShouldNotBeNil)
			So(len(res), ShouldEqual, 1)
			So(res[0], ShouldEqual, "bar")
		})

		Convey("should match multiple with results", func() {
			cmd := &Command{Command: "foo", Each: true, Matcher: func(s string) (bool, string) {
				ok, _ := regexp.MatchString("[0-9]", s)
				if ok {
					return ok, s
				}

				return false, ""
			}}

			So(match(cmd, "foo 1 0 0 4"), ShouldBeTrue)

			res := cmd.Results()
			So(res, ShouldNotBeNil)
			So(len(res), ShouldEqual, 4)
			So(res, ShouldResemble, []string{"1", "0", "0", "4"})
		})
	})
}
<|endoftext|>


<commit_before>package plugin

import (
	"bytes"
	"errors"
	"io"
	"log"
	"os/exec"
	"strings"
	"sync"
	"time"
)

// This is a slice of the "managed" clients which are cleaned up when
// calling Cleanup
var managedClients = make([]*client, 0, 5)

type client struct {
	StartTimeout time.Duration

	cmd         *exec.Cmd
	exited      bool
	doneLogging bool
}

// This makes sure all the managed subprocesses are killed and properly
// logged. This should be called before the parent process running the
// plugins exits.
//
// This must only be called _once_.
func CleanupClients() {
	// Kill all the managed clients in parallel and use a WaitGroup
	// to wait for them all to finish up.
	var wg sync.WaitGroup
	for _, client := range managedClients {
		wg.Add(1)

		go func() {
			client.Kill()
			wg.Done()
		}()
	}

	log.Println("waiting for all plugin processes to complete...")
	wg.Wait()
}

// Creates a new plugin client which manages the lifecycle of an external
// plugin and gets the address for the RPC connection.
//
// The client must be cleaned up at some point by calling Kill(). If
// the client is a managed client (created with NewManagedClient) you
// can just call CleanupClients at the end of your program and they will
// be properly cleaned.
func NewClient(cmd *exec.Cmd) *client {
	return &client{
		1 * time.Minute,
		cmd,
		false,
		false,
	}
}

// Creates a new client that is managed, meaning it'll automatically be
// cleaned up when CleanupClients() is called at some point. Please see
// the documentation for CleanupClients() for more information on how
// managed clients work.
func NewManagedClient(cmd *exec.Cmd) (result *client) {
	result = NewClient(cmd)
	managedClients = append(managedClients, result)
	return
}

// Tells whether or not the underlying process has exited.
func (c *client) Exited() bool {
	return c.exited
}

// Starts the underlying subprocess, communicating with it to negotiate
// a port for RPC connections, and returning the address to connect via RPC.
//
// This method is safe to call multiple times. Subsequent calls have no effect.
// Once a client has been started once, it cannot be started again, even if
// it was killed.
func (c *client) Start() (address string, err error) {
	// TODO: Make only run once
	// TODO: Mutex

	env := []string{
		"PACKER_PLUGIN_MIN_PORT=10000",
		"PACKER_PLUGIN_MAX_PORT=25000",
	}

	stdout := new(bytes.Buffer)
	stderr := new(bytes.Buffer)
	c.cmd.Env = append(c.cmd.Env, env...)
	c.cmd.Stderr = stderr
	c.cmd.Stdout = stdout
	err = c.cmd.Start()
	if err != nil {
		return
	}

	// Make sure the command is properly cleaned up if there is an error
	defer func() {
		r := recover()

		if err != nil || r != nil {
			c.cmd.Process.Kill()
		}

		if r != nil {
			panic(r)
		}
	}()

	// Start goroutine to wait for process to exit
	go func() {
		c.cmd.Wait()
		log.Printf("%s: plugin process exited\n", c.cmd.Path)
		c.exited = true
	}()

	// Start goroutine that logs the stderr
	go c.logStderr(stderr)

	// Some channels for the next step
	timeout := time.After(c.StartTimeout)

	// Start looking for the address
	for done := false; !done; {
		select {
		case <-timeout:
			err = errors.New("timeout while waiting for plugin to start")
			done = true
		default:
		}

		if err == nil && c.Exited() {
			err = errors.New("plugin exited before we could connect")
			done = true
		}

		if line, lerr := stdout.ReadBytes('\n'); lerr == nil {
			// Trim the address and reset the err since we were able
			// to read some sort of address.
			address = strings.TrimSpace(string(line))
			err = nil
			break
		}

		// If error is nil from previously, return now
		if err != nil {
			return
		}

		// Wait a bit
		time.Sleep(10 * time.Millisecond)
	}

	return
}

// End the executing subprocess (if it is running) and perform any cleanup
// tasks necessary such as capturing any remaining logs and so on.
//
// This method blocks until the process successfully exits.
//
// This method can safely be called multiple times.
func (c *client) Kill() {
	if c.cmd.Process == nil {
		return
	}

	c.cmd.Process.Kill()

	// Wait for the client to finish logging so we have a complete log
	done := make(chan bool)
	go func() {
		for !c.doneLogging {
			time.Sleep(10 * time.Millisecond)
		}

		done <- true
	}()

	<-done
}

func (c *client) logStderr(buf *bytes.Buffer) {
	for done := false; !done; {
		if c.Exited() {
			done = true
		}

		var err error
		for err != io.EOF {
			var line string
			line, err = buf.ReadString('\n')
			if line != "" {
				log.Printf("%s: %s", c.cmd.Path, line)
			}
		}

		time.Sleep(10 * time.Millisecond)
	}

	// Flag that we've completed logging for others
	c.doneLogging = true
}
<commit_msg>packer/plugin: Preserve parent ENV when executing client<commit_after>package plugin

import (
	"bytes"
	"errors"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"
)

// This is a slice of the "managed" clients which are cleaned up when
// calling Cleanup
var managedClients = make([]*client, 0, 5)

type client struct {
	StartTimeout time.Duration

	cmd         *exec.Cmd
	exited      bool
	doneLogging bool
}

// This makes sure all the managed subprocesses are killed and properly
// logged. This should be called before the parent process running the
// plugins exits.
//
// This must only be called _once_.
func CleanupClients() {
	// Kill all the managed clients in parallel and use a WaitGroup
	// to wait for them all to finish up.
	var wg sync.WaitGroup
	for _, client := range managedClients {
		wg.Add(1)

		go func() {
			client.Kill()
			wg.Done()
		}()
	}

	log.Println("waiting for all plugin processes to complete...")
	wg.Wait()
}

// Creates a new plugin client which manages the lifecycle of an external
// plugin and gets the address for the RPC connection.
//
// The client must be cleaned up at some point by calling Kill(). If
// the client is a managed client (created with NewManagedClient) you
// can just call CleanupClients at the end of your program and they will
// be properly cleaned.
func NewClient(cmd *exec.Cmd) *client {
	return &client{
		1 * time.Minute,
		cmd,
		false,
		false,
	}
}

// Creates a new client that is managed, meaning it'll automatically be
// cleaned up when CleanupClients() is called at some point. Please see
// the documentation for CleanupClients() for more information on how
// managed clients work.
func NewManagedClient(cmd *exec.Cmd) (result *client) {
	result = NewClient(cmd)
	managedClients = append(managedClients, result)
	return
}

// Tells whether or not the underlying process has exited.
func (c *client) Exited() bool {
	return c.exited
}

// Starts the underlying subprocess, communicating with it to negotiate
// a port for RPC connections, and returning the address to connect via RPC.
//
// This method is safe to call multiple times. Subsequent calls have no effect.
// Once a client has been started once, it cannot be started again, even if
// it was killed.
func (c *client) Start() (address string, err error) {
	// TODO: Make only run once
	// TODO: Mutex

	env := []string{
		"PACKER_PLUGIN_MIN_PORT=10000",
		"PACKER_PLUGIN_MAX_PORT=25000",
	}

	stdout := new(bytes.Buffer)
	stderr := new(bytes.Buffer)
	c.cmd.Env = append(c.cmd.Env, os.Environ()...)
	c.cmd.Env = append(c.cmd.Env, env...)
	c.cmd.Stderr = stderr
	c.cmd.Stdout = stdout
	err = c.cmd.Start()
	if err != nil {
		return
	}

	// Make sure the command is properly cleaned up if there is an error
	defer func() {
		r := recover()

		if err != nil || r != nil {
			c.cmd.Process.Kill()
		}

		if r != nil {
			panic(r)
		}
	}()

	// Start goroutine to wait for process to exit
	go func() {
		c.cmd.Wait()
		log.Printf("%s: plugin process exited\n", c.cmd.Path)
		c.exited = true
	}()

	// Start goroutine that logs the stderr
	go c.logStderr(stderr)

	// Some channels for the next step
	timeout := time.After(c.StartTimeout)

	// Start looking for the address
	for done := false; !done; {
		select {
		case <-timeout:
			err = errors.New("timeout while waiting for plugin to start")
			done = true
		default:
		}

		if err == nil && c.Exited() {
			err = errors.New("plugin exited before we could connect")
			done = true
		}

		if line, lerr := stdout.ReadBytes('\n'); lerr == nil {
			// Trim the address and reset the err since we were able
			// to read some sort of address.
			address = strings.TrimSpace(string(line))
			err = nil
			break
		}

		// If error is nil from previously, return now
		if err != nil {
			return
		}

		// Wait a bit
		time.Sleep(10 * time.Millisecond)
	}

	return
}

// End the executing subprocess (if it is running) and perform any cleanup
// tasks necessary such as capturing any remaining logs and so on.
//
// This method blocks until the process successfully exits.
//
// This method can safely be called multiple times.
func (c *client) Kill() {
	if c.cmd.Process == nil {
		return
	}

	c.cmd.Process.Kill()

	// Wait for the client to finish logging so we have a complete log
	done := make(chan bool)
	go func() {
		for !c.doneLogging {
			time.Sleep(10 * time.Millisecond)
		}

		done <- true
	}()

	<-done
}

func (c *client) logStderr(buf *bytes.Buffer) {
	for done := false; !done; {
		if c.Exited() {
			done = true
		}

		var err error
		for err != io.EOF {
			var line string
			line, err = buf.ReadString('\n')
			if line != "" {
				log.Printf("%s: %s", c.cmd.Path, line)
			}
		}

		time.Sleep(10 * time.Millisecond)
	}

	// Flag that we've completed logging for others
	c.doneLogging = true
}
<|endoftext|>


<commit_before>package packethandler

import (
	"bytes"
	"fmt"
	"git.zxq.co/ripple/go-bancho/inbound"
	"git.zxq.co/ripple/go-bancho/packethandler/logindata"
	"time"
)

const deliverTimeout = time.Millisecond * 15

// Handle takes an input and writes data to an output. Not very hard.
func Handle(input []byte, output *[]byte, token string) (string, error) {
	sendBackToken := false

	defer func() {
		c := recover()
		if c != nil {
			fmt.Println("ERROR!!!!!!!11!")
			fmt.Println(c)
		}
	}()

	// The user wants to login
	if token == "" {
		sendBackToken = true
		d, err := logindata.Unmarshal(input)
		if err != nil {
			return "", err
		}
		token, err = Login(d)
		if err != nil {
			return token, err
		}
	} else {

		inputReader := bytes.NewReader(input)
		for {
			// Find a new packet from input
			pack, err := inbound.GetPacket(inputReader)
			if err != nil {
				fmt.Println(err)
			}
			if !pack.Initialised {
				break
			}
			fmt.Printf("Inbound packet: %d - %s", pack.ID, pack.Content)
		}
	}

	// Make up response, putting together all the accumulated packets.
	for {
		packet := Tokens[token].Pop()
		if packet == nil {
			break
		}
		*output = append(*output, packet.Content...)
	}
	fmt.Printf("% x\n", *output)

	if sendBackToken {
		return token, nil
	}
	return "", nil
}
<commit_msg>Remove unnecessary defaultTimeout<commit_after>package packethandler

import (
	"bytes"
	"fmt"
	"git.zxq.co/ripple/go-bancho/inbound"
	"git.zxq.co/ripple/go-bancho/packethandler/logindata"
)

// Handle takes an input and writes data to an output. Not very hard.
func Handle(input []byte, output *[]byte, token string) (string, error) {
	sendBackToken := false

	defer func() {
		c := recover()
		if c != nil {
			fmt.Println("ERROR!!!!!!!11!")
			fmt.Println(c)
		}
	}()

	// The user wants to login
	if token == "" {
		sendBackToken = true
		d, err := logindata.Unmarshal(input)
		if err != nil {
			return "", err
		}
		token, err = Login(d)
		if err != nil {
			return token, err
		}
	} else {

		inputReader := bytes.NewReader(input)
		for {
			// Find a new packet from input
			pack, err := inbound.GetPacket(inputReader)
			if err != nil {
				fmt.Println(err)
			}
			if !pack.Initialised {
				break
			}
			fmt.Printf("Inbound packet: %d - %s", pack.ID, pack.Content)
		}
	}

	// Make up response, putting together all the accumulated packets.
	for {
		packet := Tokens[token].Pop()
		if packet == nil {
			break
		}
		*output = append(*output, packet.Content...)
	}
	fmt.Printf("% x\n", *output)

	if sendBackToken {
		return token, nil
	}
	return "", nil
}
<|endoftext|>