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

package main

import (
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/api"
	"github.com/globocom/tsuru/api/auth"
	"github.com/globocom/tsuru/api/service/consumption"
	service_provision "github.com/globocom/tsuru/api/service/provision"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/db"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	_ "github.com/globocom/tsuru/provision/juju"
	stdlog "log"
	"log/syslog"
	"net/http"
	"os"
)

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func main() {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		fatal(err)
	}
	defer db.Session.Close()
	fmt.Printf("Connected to MongoDB server at %s.\n", connString)
	fmt.Printf("Using the database %q.\n\n", dbName)

	m := pat.New()

	m.Get("/services/instances", AuthorizationRequiredHandler(consumption.ServicesInstancesHandler))
	m.Post("/services/instances", AuthorizationRequiredHandler(consumption.CreateInstanceHandler))
	m.Put("/services/instances/:instance/:app", AuthorizationRequiredHandler(api.BindHandler))
	m.Del("/services/instances/:instance/:app", AuthorizationRequiredHandler(api.UnbindHandler))
	m.Del("/services/c/instances/:name", AuthorizationRequiredHandler(consumption.RemoveServiceInstanceHandler))
	m.Get("/services/instances/:instance/status", AuthorizationRequiredHandler(consumption.ServiceInstanceStatusHandler))

	m.Get("/services", AuthorizationRequiredHandler(service_provision.ServicesHandler))
	m.Post("/services", AuthorizationRequiredHandler(service_provision.CreateHandler))
	m.Put("/services", AuthorizationRequiredHandler(service_provision.UpdateHandler))
	m.Del("/services/:name", AuthorizationRequiredHandler(service_provision.DeleteHandler))
	m.Get("/services/:name", AuthorizationRequiredHandler(consumption.ServiceInfoHandler))
	m.Get("/services/c/:name/doc", AuthorizationRequiredHandler(consumption.Doc))
	m.Get("/services/:name/doc", AuthorizationRequiredHandler(service_provision.GetDocHandler))
	m.Put("/services/:name/doc", AuthorizationRequiredHandler(service_provision.AddDocHandler))
	m.Put("/services/:service/:team", AuthorizationRequiredHandler(service_provision.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", AuthorizationRequiredHandler(service_provision.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", AuthorizationRequiredHandler(api.AppDelete))
	m.Get("/apps/:name/repository/clone", Handler(api.CloneRepositoryHandler))
	m.Get("/apps/:name/avaliable", Handler(api.AppIsAvaliableHandler))
	m.Get("/apps/:name", AuthorizationRequiredHandler(api.AppInfo))
	m.Post("/apps/:name/run", AuthorizationRequiredHandler(api.RunCommand))
	m.Get("/apps/:name/restart", AuthorizationRequiredHandler(api.RestartHandler))
	m.Get("/apps/:name/env", AuthorizationRequiredHandler(api.GetEnv))
	m.Post("/apps/:name/env", AuthorizationRequiredHandler(api.SetEnv))
	m.Del("/apps/:name/env", AuthorizationRequiredHandler(api.UnsetEnv))
	m.Get("/apps", AuthorizationRequiredHandler(api.AppList))
	m.Post("/apps", AuthorizationRequiredHandler(api.CreateAppHandler))
	m.Put("/apps/:app/:team", AuthorizationRequiredHandler(api.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", AuthorizationRequiredHandler(api.RevokeAccessFromTeamHandler))
	m.Get("/apps/:name/log", AuthorizationRequiredHandler(api.AppLog))
	m.Post("/apps/:name/log", Handler(api.AddLogHandler))
	m.Put("/apps/:name/units", AuthorizationRequiredHandler(api.AddUnitsHandler))

	m.Post("/users", Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", Handler(auth.Login))
	m.Put("/users/password", AuthorizationRequiredHandler(auth.ChangePassword))
	m.Del("/users", AuthorizationRequiredHandler(auth.RemoveUser))
	m.Post("/users/keys", AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Get("/teams", AuthorizationRequiredHandler(auth.ListTeams))
	m.Post("/teams", AuthorizationRequiredHandler(auth.CreateTeam))
	m.Del("/teams/:name", AuthorizationRequiredHandler(auth.RemoveTeam))
	m.Put("/teams/:team/:user", AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	if !*dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		listen, err := config.GetString("listen")
		if err != nil {
			fatal(err)
		}
		fmt.Printf("tsuru HTTP server listening at %s...\n", listen)
		fatal(http.ListenAndServe(listen, m))
	}
}
<commit_msg>api/webserver: fix url mapping for AddUnit handler<commit_after>// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/api"
	"github.com/globocom/tsuru/api/auth"
	"github.com/globocom/tsuru/api/service/consumption"
	service_provision "github.com/globocom/tsuru/api/service/provision"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/db"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	_ "github.com/globocom/tsuru/provision/juju"
	stdlog "log"
	"log/syslog"
	"net/http"
	"os"
)

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func main() {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		fatal(err)
	}
	defer db.Session.Close()
	fmt.Printf("Connected to MongoDB server at %s.\n", connString)
	fmt.Printf("Using the database %q.\n\n", dbName)

	m := pat.New()

	m.Get("/services/instances", AuthorizationRequiredHandler(consumption.ServicesInstancesHandler))
	m.Post("/services/instances", AuthorizationRequiredHandler(consumption.CreateInstanceHandler))
	m.Put("/services/instances/:instance/:app", AuthorizationRequiredHandler(api.BindHandler))
	m.Del("/services/instances/:instance/:app", AuthorizationRequiredHandler(api.UnbindHandler))
	m.Del("/services/c/instances/:name", AuthorizationRequiredHandler(consumption.RemoveServiceInstanceHandler))
	m.Get("/services/instances/:instance/status", AuthorizationRequiredHandler(consumption.ServiceInstanceStatusHandler))

	m.Get("/services", AuthorizationRequiredHandler(service_provision.ServicesHandler))
	m.Post("/services", AuthorizationRequiredHandler(service_provision.CreateHandler))
	m.Put("/services", AuthorizationRequiredHandler(service_provision.UpdateHandler))
	m.Del("/services/:name", AuthorizationRequiredHandler(service_provision.DeleteHandler))
	m.Get("/services/:name", AuthorizationRequiredHandler(consumption.ServiceInfoHandler))
	m.Get("/services/c/:name/doc", AuthorizationRequiredHandler(consumption.Doc))
	m.Get("/services/:name/doc", AuthorizationRequiredHandler(service_provision.GetDocHandler))
	m.Put("/services/:name/doc", AuthorizationRequiredHandler(service_provision.AddDocHandler))
	m.Put("/services/:service/:team", AuthorizationRequiredHandler(service_provision.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", AuthorizationRequiredHandler(service_provision.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", AuthorizationRequiredHandler(api.AppDelete))
	m.Get("/apps/:name/repository/clone", Handler(api.CloneRepositoryHandler))
	m.Get("/apps/:name/avaliable", Handler(api.AppIsAvaliableHandler))
	m.Get("/apps/:name", AuthorizationRequiredHandler(api.AppInfo))
	m.Post("/apps/:name/run", AuthorizationRequiredHandler(api.RunCommand))
	m.Get("/apps/:name/restart", AuthorizationRequiredHandler(api.RestartHandler))
	m.Get("/apps/:name/env", AuthorizationRequiredHandler(api.GetEnv))
	m.Post("/apps/:name/env", AuthorizationRequiredHandler(api.SetEnv))
	m.Del("/apps/:name/env", AuthorizationRequiredHandler(api.UnsetEnv))
	m.Get("/apps", AuthorizationRequiredHandler(api.AppList))
	m.Post("/apps", AuthorizationRequiredHandler(api.CreateAppHandler))
	m.Put("/apps/:name/units", AuthorizationRequiredHandler(api.AddUnitsHandler))
	m.Put("/apps/:app/:team", AuthorizationRequiredHandler(api.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", AuthorizationRequiredHandler(api.RevokeAccessFromTeamHandler))
	m.Get("/apps/:name/log", AuthorizationRequiredHandler(api.AppLog))
	m.Post("/apps/:name/log", Handler(api.AddLogHandler))

	m.Post("/users", Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", Handler(auth.Login))
	m.Put("/users/password", AuthorizationRequiredHandler(auth.ChangePassword))
	m.Del("/users", AuthorizationRequiredHandler(auth.RemoveUser))
	m.Post("/users/keys", AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Get("/teams", AuthorizationRequiredHandler(auth.ListTeams))
	m.Post("/teams", AuthorizationRequiredHandler(auth.CreateTeam))
	m.Del("/teams/:name", AuthorizationRequiredHandler(auth.RemoveTeam))
	m.Put("/teams/:team/:user", AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	if !*dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		listen, err := config.GetString("listen")
		if err != nil {
			fatal(err)
		}
		fmt.Printf("tsuru HTTP server listening at %s...\n", listen)
		fatal(http.ListenAndServe(listen, m))
	}
}
<|endoftext|>


<commit_before>package errors

import "errors"

// The implementation bellow is heavily influenced by go-kit's log context.

// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = errors.New("(MISSING)")

// With returns a new error with keyvals context appended to it.
// If the wrapped error is already a contextual error created by With or WithPrefix
// keyvals is appended to the existing context, but a new error is returned.
func With(err error, keyvals ...interface{}) error {
	if len(keyvals) == 0 {
		return err
	}

	var kvs []interface{}

	if c, ok := err.(*contextualError); ok {
		err = c.err
		kvs = c.keyvals
	} else if c, ok := err.(ContextualError); ok {
		kvs = c.Context()
	}

	kvs = append(kvs, keyvals...)

	if len(kvs)%2 != 0 {
		kvs = append(kvs, ErrMissingValue)
	}
	return &contextualError{
		err: err,
		// Limiting the capacity of the stored keyvals ensures that a new
		// backing array is created if the slice must grow in With.
		// Using the extra capacity without copying risks a data race.
		keyvals: kvs[:len(kvs):len(kvs)],
	}
}

// WithPrefix returns a new error with keyvals context appended to it.
// If the wrapped error is already a contextual error created by With or WithPrefix
// keyvals is prepended to the existing context, but a new error is returned.
func WithPrefix(err error, keyvals ...interface{}) error {
	if len(keyvals) == 0 {
		return err
	}

	var prevkvs []interface{}

	if c, ok := err.(*contextualError); ok {
		err = c.err
		prevkvs = c.keyvals
	} else if c, ok := err.(ContextualError); ok {
		prevkvs = c.Context()
	}

	n := len(prevkvs) + len(keyvals)
	if len(keyvals)%2 != 0 {
		n++
	}

	kvs := make([]interface{}, 0, n)
	kvs = append(kvs, keyvals...)

	if len(kvs)%2 != 0 {
		kvs = append(kvs, ErrMissingValue)
	}

	kvs = append(kvs, prevkvs...)

	return &contextualError{
		err:     err,
		keyvals: kvs,
	}
}

// contextualError is the ContextualError implementation returned by With.
//
// It wraps an error and a holds keyvals as the context.
type contextualError struct {
	err     error
	keyvals []interface{}
}

// Error calls the underlying error and returns it's message.
func (e *contextualError) Error() string {
	return e.err.Error()
}

// Context returns the appended keyvals.
func (e *contextualError) Context() []interface{} {
	return e.keyvals
}

// Cause returns the underlying error.
//
// This method fulfills the causer interface described in github.com/pkg/errors.
func (e *contextualError) Cause() error {
	return e.err
}
<commit_msg>Avoid unnecessary package import<commit_after>package errors

// The implementation bellow is heavily influenced by go-kit's log context.

// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = New("(MISSING)")

// With returns a new error with keyvals context appended to it.
// If the wrapped error is already a contextual error created by With or WithPrefix
// keyvals is appended to the existing context, but a new error is returned.
func With(err error, keyvals ...interface{}) error {
	if len(keyvals) == 0 {
		return err
	}

	var kvs []interface{}

	if c, ok := err.(*contextualError); ok {
		err = c.err
		kvs = c.keyvals
	} else if c, ok := err.(ContextualError); ok {
		kvs = c.Context()
	}

	kvs = append(kvs, keyvals...)

	if len(kvs)%2 != 0 {
		kvs = append(kvs, ErrMissingValue)
	}
	return &contextualError{
		err: err,
		// Limiting the capacity of the stored keyvals ensures that a new
		// backing array is created if the slice must grow in With.
		// Using the extra capacity without copying risks a data race.
		keyvals: kvs[:len(kvs):len(kvs)],
	}
}

// WithPrefix returns a new error with keyvals context appended to it.
// If the wrapped error is already a contextual error created by With or WithPrefix
// keyvals is prepended to the existing context, but a new error is returned.
func WithPrefix(err error, keyvals ...interface{}) error {
	if len(keyvals) == 0 {
		return err
	}

	var prevkvs []interface{}

	if c, ok := err.(*contextualError); ok {
		err = c.err
		prevkvs = c.keyvals
	} else if c, ok := err.(ContextualError); ok {
		prevkvs = c.Context()
	}

	n := len(prevkvs) + len(keyvals)
	if len(keyvals)%2 != 0 {
		n++
	}

	kvs := make([]interface{}, 0, n)
	kvs = append(kvs, keyvals...)

	if len(kvs)%2 != 0 {
		kvs = append(kvs, ErrMissingValue)
	}

	kvs = append(kvs, prevkvs...)

	return &contextualError{
		err:     err,
		keyvals: kvs,
	}
}

// contextualError is the ContextualError implementation returned by With.
//
// It wraps an error and a holds keyvals as the context.
type contextualError struct {
	err     error
	keyvals []interface{}
}

// Error calls the underlying error and returns it's message.
func (e *contextualError) Error() string {
	return e.err.Error()
}

// Context returns the appended keyvals.
func (e *contextualError) Context() []interface{} {
	return e.keyvals
}

// Cause returns the underlying error.
//
// This method fulfills the causer interface described in github.com/pkg/errors.
func (e *contextualError) Cause() error {
	return e.err
}
<|endoftext|>


<commit_before>package kite

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"koding/newKite/kd/util"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type Install struct{}

func NewInstall() *Install {
	return &Install{}
}

func (*Install) Definition() string {
	return "Install kite from Koding repository"
}

const S3URL = "http://koding-kites.s3.amazonaws.com/"

func (*Install) Exec(args []string) error {
	// Parse kite name
	if len(args) != 1 {
		return errors.New("You should give a kite name")
	}

	kiteFullName := args[0]
	kiteName, kiteVersion, err := splitVersion(kiteFullName, true)
	if err != nil {
		kiteName, kiteVersion = kiteFullName, "latest"
	}

	// Make download request
	fmt.Println("Downloading...")
	targz, err := requestPackage(kiteName, kiteVersion)
	if err != nil {
		return err
	}
	defer targz.Close()

	// Extract gzip
	gz, err := gzip.NewReader(targz)
	if err != nil {
		return err
	}
	defer gz.Close()

	// Extract tar
	tempKitePath, err := ioutil.TempDir("", "kd-kite-install-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempKitePath)

	err = extractTar(gz, tempKitePath)
	if err != nil {
		return err
	}

	foundName, _, bundlePath, err := validatePackage(tempKitePath)
	if err != nil {
		return err
	}

	if foundName != kiteName {
		return fmt.Errorf("Invalid package: Bundle name does not match with package name: %s != %s",
			foundName, kiteName)
	}

	err = installBundle(bundlePath)
	if err != nil {
		return err
	}

	fmt.Println("Installed successfully:", kiteFullName)
	return nil
}

// requestPackage makes a request to the kite repository and returns
// a io.ReadCloser. The caller must close the returned io.ReadCloser.
func requestPackage(kiteName, kiteVersion string) (io.ReadCloser, error) {
	kiteURL := S3URL + kiteName + "-" + kiteVersion + ".kite.tar.gz"

	res, err := http.Get(kiteURL)
	if err != nil {
		return nil, err
	}

	if res.StatusCode == 404 {
		res.Body.Close()
		return nil, errors.New("Package is not found on the server.")
	}

	if res.StatusCode != 200 {
		res.Body.Close()
		return nil, fmt.Errorf("Unexpected response from server: %d", res.StatusCode)
	}

	return res.Body, nil
}

// extractTar reads from the io.Reader and writes the files into the directory.
func extractTar(r io.Reader, dir string) error {
	first := true // true if we are on the first entry of tarball
	tr := tar.NewReader(r)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return err
		}

		if first {
			first = false
			kiteName := strings.TrimSuffix(hdr.Name, ".kite/")

			installed, err := isInstalled(kiteName)
			if err != nil {
				return err
			}

			if installed {
				return fmt.Errorf("Already installed: %s", kiteName)
			}
		}

		path := filepath.Join(dir, hdr.Name)

		// TODO make the binary under /bin executable

		if hdr.FileInfo().IsDir() {
			os.MkdirAll(path, 0700)
		} else {
			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
			if err != nil {
				return err
			}

			if _, err := io.Copy(f, tr); err != nil {
				return err
			}
		}
	}
	return nil
}

// validatePackage returns the package name, version and bundle path.
func validatePackage(tempKitePath string) (string, string, string, error) {
	dirs, err := ioutil.ReadDir(tempKitePath)
	if err != nil {
		return "", "", "", err
	}

	if len(dirs) != 1 {
		return "", "", "", errors.New("Invalid package: Package must contain only one directory.")
	}

	bundleName := dirs[0].Name() // Example: asdf-1.2.3.kite
	if !strings.HasSuffix(bundleName, ".kite") {
		return "", "", "", errors.New("Invalid package: Direcory name must end with \".kite\".")
	}

	fullName := strings.TrimSuffix(bundleName, ".kite") // Example: asdf-1.2.3
	kiteName, version, err := splitVersion(fullName, false)
	if err != nil {
		return "", "", "", errors.New("Invalid package: No version number in Kite bundle")
	}

	return kiteName, version, filepath.Join(tempKitePath, bundleName), nil
}

// installBundle moves the .kite bundle into ~/kd/kites.
func installBundle(bundlePath string) error {
	kitesPath := filepath.Join(util.GetKdPath(), "kites")
	os.MkdirAll(kitesPath, 0700)

	bundleName := filepath.Base(bundlePath)
	kitePath := filepath.Join(kitesPath, bundleName)
	return os.Rename(bundlePath, kitePath)
}

// splitVersion takes a name like "asdf-1.2.3" and
// returns the name "asdf" and version "1.2.3" seperately.
// If allowLatest is true, then the version must not be numeric and can be "latest".
func splitVersion(fullname string, allowLatest bool) (name, version string, err error) {
	notFound := errors.New("name does not contain a version number")

	parts := strings.Split(fullname, "-")
	n := len(parts)
	if n < 2 {
		return "", "", notFound
	}

	name = strings.Join(parts[:n-1], "-")
	version = parts[n-1]

	if allowLatest && version == "latest" {
		return name, version, nil
	}

	versionParts := strings.Split(version, ".")
	for _, v := range versionParts {
		if _, err := strconv.Atoi(v); err != nil {
			return "", "", notFound
		}
	}

	return name, version, nil
}
<commit_msg>improve install output<commit_after>package kite

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"koding/newKite/kd/util"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type Install struct{}

func NewInstall() *Install {
	return &Install{}
}

func (*Install) Definition() string {
	return "Install kite from Koding repository"
}

const S3URL = "http://koding-kites.s3.amazonaws.com/"

func (*Install) Exec(args []string) error {
	// Parse kite name
	if len(args) != 1 {
		return errors.New("You should give a kite name")
	}

	kiteFullName := args[0]
	kiteName, kiteVersion, err := splitVersion(kiteFullName, true)
	if err != nil {
		kiteName, kiteVersion = kiteFullName, "latest"
	}

	// Make download request
	fmt.Println("Downloading...")
	targz, err := requestPackage(kiteName, kiteVersion)
	if err != nil {
		return err
	}
	defer targz.Close()

	// Extract gzip
	gz, err := gzip.NewReader(targz)
	if err != nil {
		return err
	}
	defer gz.Close()

	// Extract tar
	tempKitePath, err := ioutil.TempDir("", "kd-kite-install-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempKitePath)

	err = extractTar(gz, tempKitePath)
	if err != nil {
		return err
	}

	foundName, foundVersion, bundlePath, err := validatePackage(tempKitePath)
	if err != nil {
		return err
	}

	if foundName != kiteName {
		return fmt.Errorf("Invalid package: Bundle name does not match with package name: %s != %s",
			foundName, kiteName)
	}

	err = installBundle(bundlePath)
	if err != nil {
		return err
	}

	fmt.Println("Installed successfully:", foundName+"-"+foundVersion)
	return nil
}

// requestPackage makes a request to the kite repository and returns
// a io.ReadCloser. The caller must close the returned io.ReadCloser.
func requestPackage(kiteName, kiteVersion string) (io.ReadCloser, error) {
	kiteURL := S3URL + kiteName + "-" + kiteVersion + ".kite.tar.gz"

	res, err := http.Get(kiteURL)
	if err != nil {
		return nil, err
	}

	if res.StatusCode == 404 {
		res.Body.Close()
		return nil, errors.New("Package is not found on the server.")
	}

	if res.StatusCode != 200 {
		res.Body.Close()
		return nil, fmt.Errorf("Unexpected response from server: %d", res.StatusCode)
	}

	return res.Body, nil
}

// extractTar reads from the io.Reader and writes the files into the directory.
func extractTar(r io.Reader, dir string) error {
	first := true // true if we are on the first entry of tarball
	tr := tar.NewReader(r)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return err
		}

		if first {
			first = false
			kiteName := strings.TrimSuffix(hdr.Name, ".kite/")

			installed, err := isInstalled(kiteName)
			if err != nil {
				return err
			}

			if installed {
				return fmt.Errorf("Already installed: %s", kiteName)
			}
		}

		path := filepath.Join(dir, hdr.Name)

		// TODO make the binary under /bin executable

		if hdr.FileInfo().IsDir() {
			os.MkdirAll(path, 0700)
		} else {
			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
			if err != nil {
				return err
			}

			if _, err := io.Copy(f, tr); err != nil {
				return err
			}
		}
	}
	return nil
}

// validatePackage returns the package name, version and bundle path.
func validatePackage(tempKitePath string) (string, string, string, error) {
	dirs, err := ioutil.ReadDir(tempKitePath)
	if err != nil {
		return "", "", "", err
	}

	if len(dirs) != 1 {
		return "", "", "", errors.New("Invalid package: Package must contain only one directory.")
	}

	bundleName := dirs[0].Name() // Example: asdf-1.2.3.kite
	if !strings.HasSuffix(bundleName, ".kite") {
		return "", "", "", errors.New("Invalid package: Direcory name must end with \".kite\".")
	}

	fullName := strings.TrimSuffix(bundleName, ".kite") // Example: asdf-1.2.3
	kiteName, version, err := splitVersion(fullName, false)
	if err != nil {
		return "", "", "", errors.New("Invalid package: No version number in Kite bundle")
	}

	return kiteName, version, filepath.Join(tempKitePath, bundleName), nil
}

// installBundle moves the .kite bundle into ~/kd/kites.
func installBundle(bundlePath string) error {
	kitesPath := filepath.Join(util.GetKdPath(), "kites")
	os.MkdirAll(kitesPath, 0700)

	bundleName := filepath.Base(bundlePath)
	kitePath := filepath.Join(kitesPath, bundleName)
	return os.Rename(bundlePath, kitePath)
}

// splitVersion takes a name like "asdf-1.2.3" and
// returns the name "asdf" and version "1.2.3" seperately.
// If allowLatest is true, then the version must not be numeric and can be "latest".
func splitVersion(fullname string, allowLatest bool) (name, version string, err error) {
	notFound := errors.New("name does not contain a version number")

	parts := strings.Split(fullname, "-")
	n := len(parts)
	if n < 2 {
		return "", "", notFound
	}

	name = strings.Join(parts[:n-1], "-")
	version = parts[n-1]

	if allowLatest && version == "latest" {
		return name, version, nil
	}

	versionParts := strings.Split(version, ".")
	for _, v := range versionParts {
		if _, err := strconv.Atoi(v); err != nil {
			return "", "", notFound
		}
	}

	return name, version, nil
}
<|endoftext|>


<commit_before>package module

import (
	"fmt"
	"github.com/briandowns/openweathermap"
	"github.com/davidscholberg/irkbot/lib/configure"
	"github.com/davidscholberg/irkbot/lib/message"
	"os"
	"strings"
)

func HelpWeather() []string {
	s := "weather <location> - display current weather for the given location (only <city> or <city,country> searches are supported)"
	return []string{s}
}

func Weather(cfg *configure.Config, in *message.InboundMsg, actions *Actions) {
	if !strings.HasPrefix(in.Src, "#") {
		actions.Say("weather searches not allowed in PMs")
		return
	}

	if len(in.MsgArgs) < 2 {
		actions.Say(fmt.Sprintf("%s: please specify a location (<city> or <city,country>)", in.Event.Nick))
		return
	}

	msg := strings.Join(in.MsgArgs[1:], " ")
	//fetch API key from config
	apiKey := cfg.Modules["weather"]["api_key"]

	w, err := openweathermap.NewCurrent("c", "en", apiKey)
	if err != nil {
		actions.Say("error initializing weather search :(")
		fmt.Fprintln(os.Stderr, err)
		return
	}

	err = w.CurrentByName(msg)
	if err != nil {
		actions.Say("No results returned. Only <city> or <city,country> searches are supported.")
		fmt.Fprintln(os.Stderr, err)
		return
	}

	conditions := ""
	for i, condition := range w.Weather {
		if i > 0 {
			conditions += ", "
		}
		conditions += condition.Description
	}

	actions.Say(
		fmt.Sprintf(
			"current weather for %s, %s: %.2f°C, %d%% humidity, %s",
			w.Name,
			w.Sys.Country,
			w.Main.Temp,
			w.Main.Humidity,
			conditions,
		),
	)
}
<commit_msg>weather: added wind speed and direction<commit_after>package module

import (
	"fmt"
	"github.com/briandowns/openweathermap"
	"github.com/davidscholberg/irkbot/lib/configure"
	"github.com/davidscholberg/irkbot/lib/message"
	"math"
	"os"
	"strings"
)

func HelpWeather() []string {
	s := "weather <location> - display current weather for the given location (only <city> or <city,country> searches are supported)"
	return []string{s}
}

func Weather(cfg *configure.Config, in *message.InboundMsg, actions *Actions) {
	if !strings.HasPrefix(in.Src, "#") {
		actions.Say("weather searches not allowed in PMs")
		return
	}

	if len(in.MsgArgs) < 2 {
		actions.Say(fmt.Sprintf("%s: please specify a location (<city> or <city,country>)", in.Event.Nick))
		return
	}

	msg := strings.Join(in.MsgArgs[1:], " ")
	//fetch API key from config
	apiKey := cfg.Modules["weather"]["api_key"]

	w, err := openweathermap.NewCurrent("c", "en", apiKey)
	if err != nil {
		actions.Say("error initializing weather search :(")
		fmt.Fprintln(os.Stderr, err)
		return
	}

	err = w.CurrentByName(msg)
	if err != nil {
		actions.Say("No results returned. Only <city> or <city,country> searches are supported.")
		fmt.Fprintln(os.Stderr, err)
		return
	}

	conditions := ""
	for i, condition := range w.Weather {
		if i > 0 {
			conditions += ", "
		}
		conditions += condition.Description
	}

	actions.Say(
		fmt.Sprintf(
			"current weather for %s, %s: %.2f°C, %d%% humidity, wind %s at %.2fm/s, %s",
			w.Name,
			w.Sys.Country,
			w.Main.Temp,
			w.Main.Humidity,
			degreeToCompassDir(w.Wind.Deg),
			w.Wind.Speed,
			conditions,
		),
	)
}

func degreeToCompassDir(degree float64) string {
	compassDirs := [8]string{"N", "NE", "E", "SE", "S", "SW", "W", "NW"}
	return compassDirs[int(math.Floor((degree+22.5)/45))%8]
}
<|endoftext|>


<commit_before>package protocol

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

// This is sent into the function
// All HTTP request headers should be set in env
type JSONIO struct {
	Headers    http.Header `json:"headers,omitempty"`
	Body       string      `json:"body"`
	StatusCode int         `json:"status_code,omitempty"`
}

// JSONProtocol converts stdin/stdout streams from HTTP into JSON format.
type JSONProtocol struct {
	in  io.Writer
	out io.Reader
}

func (p *JSONProtocol) IsStreamable() bool {
	return true
}

func (h *JSONProtocol) Dispatch(w io.Writer, req *http.Request) error {
	var body bytes.Buffer
	if req.Body != nil {
		var dest io.Writer = &body

		// TODO copy w/ ctx
		_, err := io.Copy(dest, req.Body)
		if err != nil {
			return respondWithError(
				w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
		}
		defer req.Body.Close()
	}
	err := json.NewEncoder(h.in).Encode(&JSONIO{
		Headers: req.Header,
		Body:    body.String(),
	})
	if err != nil {
		// this shouldn't happen
		return respondWithError(
			w, fmt.Errorf("error marshalling JSONInput: %s", err.Error()))
	}

	jout := new(JSONIO)
	dec := json.NewDecoder(h.out)
	if err := dec.Decode(jout); err != nil {
		return respondWithError(
			w, fmt.Errorf("unable to decode JSON response object: %s", err.Error()))
	}
	if rw, ok := w.(http.ResponseWriter); ok {
		// this has to be done for pulling out:
		// - status code
		// - body
		rw.WriteHeader(jout.StatusCode)
		_, err = rw.Write([]byte(jout.Body)) // TODO timeout
		if err != nil {
			return respondWithError(
				w, fmt.Errorf("unable to write JSON response object: %s", err.Error()))
		}
	} else {
		// logs can just copy the full thing in there, headers and all.
		err = json.NewEncoder(w).Encode(jout)
		if err != nil {
			return respondWithError(
				w, fmt.Errorf("error writing function response: %s", err.Error()))
		}
	}
	return nil
}

func respondWithError(w io.Writer, err error) error {
	errMsg := []byte(err.Error())
	statusCode := http.StatusInternalServerError
	if rw, ok := w.(http.ResponseWriter); ok {
		rw.WriteHeader(statusCode)
		rw.Write(errMsg)
	} else {
		// logs can just copy the full thing in there, headers and all.
		w.Write(errMsg)
	}

	return err
}
<commit_msg>Trying to avoid buffers and write directly to pipe<commit_after>package protocol

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

// This is sent into the function
// All HTTP request headers should be set in env
type JSONIO struct {
	Headers    http.Header `json:"headers,omitempty"`
	Body       string      `json:"body"`
	StatusCode int         `json:"status_code,omitempty"`
}

// JSONProtocol converts stdin/stdout streams from HTTP into JSON format.
type JSONProtocol struct {
	in  io.Writer
	out io.Reader
}

func (p *JSONProtocol) IsStreamable() bool {
	return true
}

func (h *JSONProtocol) Dispatch(w io.Writer, req *http.Request) error {
	_, err := io.WriteString(h.in, `{`)
	if err != nil {
		// this shouldn't happen
		return respondWithError(
			w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
	}
	if req.Body != nil {
		_, err := io.WriteString(h.in, `"body":"`)
		if err != nil {
			// this shouldn't happen
			return respondWithError(
				w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
		}
		_, err = io.CopyN(h.in, req.Body, req.ContentLength)
		if err != nil {
			// this shouldn't happen
			return respondWithError(
				w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
		}
		_, err = io.WriteString(h.in, `",`)
		if err != nil {
			// this shouldn't happen
			return respondWithError(
				w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
		}
		defer req.Body.Close()
	}
	_, err = io.WriteString(h.in, `"headers:"`)
	if err != nil {
		// this shouldn't happen
		return respondWithError(
			w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
	}
	err = json.NewEncoder(h.in).Encode(req.Header)
	if err != nil {
		// this shouldn't happen
		return respondWithError(
			w, fmt.Errorf("error marshalling JSONInput: %s", err.Error()))
	}
	_, err = io.WriteString(h.in, `"}`)
	if err != nil {
		// this shouldn't happen
		return respondWithError(
			w, fmt.Errorf("error reader JSON object from request body: %s", err.Error()))
	}

	jout := new(JSONIO)
	dec := json.NewDecoder(h.out)
	if err := dec.Decode(jout); err != nil {
		return respondWithError(
			w, fmt.Errorf("unable to decode JSON response object: %s", err.Error()))
	}
	if rw, ok := w.(http.ResponseWriter); ok {
		// this has to be done for pulling out:
		// - status code
		// - body
		rw.WriteHeader(jout.StatusCode)
		_, err = rw.Write([]byte(jout.Body)) // TODO timeout
		if err != nil {
			return respondWithError(
				w, fmt.Errorf("unable to write JSON response object: %s", err.Error()))
		}
	} else {
		// logs can just copy the full thing in there, headers and all.
		err = json.NewEncoder(w).Encode(jout)
		if err != nil {
			return respondWithError(
				w, fmt.Errorf("error writing function response: %s", err.Error()))
		}
	}
	return nil
}

func respondWithError(w io.Writer, err error) error {
	errMsg := []byte(err.Error())
	statusCode := http.StatusInternalServerError
	if rw, ok := w.(http.ResponseWriter); ok {
		rw.WriteHeader(statusCode)
		rw.Write(errMsg)
	} else {
		// logs can just copy the full thing in there, headers and all.
		w.Write(errMsg)
	}

	return err
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"

	"github.com/hashicorp/terraform/terraform"
)

type ResourceProvider struct {
}

func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
	errs := c.CheckSet([]string{
		"access_key",
		"secret_key",
	})

	return nil, errs
}

func (p *ResourceProvider) Configure(*terraform.ResourceConfig) error {
	return nil
}

func (p *ResourceProvider) Apply(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff) (*terraform.ResourceState, error) {
	result := &terraform.ResourceState{
		ID: "foo",
	}
	result = result.MergeDiff(d)

	return result, nil
}

func (p *ResourceProvider) Diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
	b := diffMap.Get(s.Type)
	if b == nil {
		return nil, fmt.Errorf("Unknown type: %s", s.Type)
	}

	return b.Diff(s, c)
}

func (p *ResourceProvider) Resources() []terraform.ResourceType {
	return []terraform.ResourceType{
		terraform.ResourceType{
			Name: "aws_instance",
		},
	}
}
<commit_msg>providers/aws: adhere to interface<commit_after>package aws

import (
	"fmt"

	"github.com/hashicorp/terraform/terraform"
)

type ResourceProvider struct {
}

func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
	errs := c.CheckSet([]string{
		"access_key",
		"secret_key",
	})

	return nil, errs
}

func (p *ResourceProvider) Configure(*terraform.ResourceConfig) error {
	return nil
}

func (p *ResourceProvider) Apply(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff) (*terraform.ResourceState, error) {
	result := &terraform.ResourceState{
		ID: "foo",
	}
	result = result.MergeDiff(d)

	return result, nil
}

func (p *ResourceProvider) Diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
	b := diffMap.Get(s.Type)
	if b == nil {
		return nil, fmt.Errorf("Unknown type: %s", s.Type)
	}

	return b.Diff(s, c)
}

func (p *ResourceProvider) Refresh(
	s *terraform.ResourceState) (*terraform.ResourceState, error) {
	return s, nil
}

func (p *ResourceProvider) Resources() []terraform.ResourceType {
	return []terraform.ResourceType{
		terraform.ResourceType{
			Name: "aws_instance",
		},
	}
}
<|endoftext|>


<commit_before>package gateway

import (
	"path/filepath"
	"strings"
	"testing"

	"github.com/TykTechnologies/tyk/config"
)

func createGetHandler() *WebHookHandler {
	eventHandlerConf := config.WebHookHandlerConf{
		TargetPath:   TestHttpGet,
		Method:       "GET",
		EventTimeout: 10,
		TemplatePath: "../templates/default_webhook.json",
		HeaderList:   map[string]string{"x-tyk-test": "TEST"},
	}
	ev := &WebHookHandler{}
	if err := ev.Init(eventHandlerConf); err != nil {
		panic(err)
	}
	return ev
}

func TestNewValid(t *testing.T) {
	h := &WebHookHandler{}
	err := h.Init(map[string]interface{}{
		"method":        "POST",
		"target_path":   testHttpPost,
		"template_path": "../templates/default_webhook.json",
		"header_map":    map[string]string{"X-Tyk-Test-Header": "Tyk v1.BANANA"},
		"event_timeout": 10,
	})
	if err != nil {
		t.Error("Webhook Handler should have created valid configuration")
	}
}

func TestNewInvalid(t *testing.T) {
	h := &WebHookHandler{}
	err := h.Init(map[string]interface{}{
		"method":        123,
		"target_path":   testHttpPost,
		"template_path": "../templates/default_webhook.json",
		"header_map":    map[string]string{"X-Tyk-Test-Header": "Tyk v1.BANANA"},
		"event_timeout": 10,
	})
	if err == nil {
		t.Error("Webhook Handler should have failed")
	}
}

func TestChecksum(t *testing.T) {
	rBody := `{
		"event": "QuotaExceeded",
		"message": "Key Quota Limit Exceeded",
		"path": "/about-lonelycoder/",
		"origin": "",
		"key": "4321",
		"timestamp": 2014-11-27 12:52:05.944549825 +0000 GMT
	}`

	hook := createGetHandler()
	checksum, err := hook.Checksum(rBody)

	if err != nil {
		t.Error("Checksum should not have failed with good objet and body")
	}

	if checksum != "62a6b4fa9b45cd372b871764296fb3a5" {
		t.Error("Checksum is incorrect")
		t.Error(checksum)
	}
}

func TestBuildRequest(t *testing.T) {
	hook := createGetHandler()

	rBody := `{
		"event": "QuotaExceeded",
		"message": "Key Quota Limit Exceeded",
		"path": "/about-lonelycoder/",
		"origin": "",
		"key": "4321",
		"timestamp": 2014-11-27 12:52:05.944549825 +0000 GMT
	}`

	req, err := hook.BuildRequest(rBody)
	if err != nil {
		t.Error("Request should have built cleanly.")
	}
	if req.Method != "GET" {
		t.Error("Method hould be GET")
	}

	if got := req.Header.Get("User-Agent"); got != "Tyk-Hookshot" {
		t.Error("Header User Agent is not correct!")
	}

	if got := req.Header.Get("Content-Type"); got != "application/json" {
		t.Error("Header Content-Type is not correct!")
	}
}

func TestCreateBody(t *testing.T) {
	em := config.EventMessage{
		Type:      EventQuotaExceeded,
		TimeStamp: "0",
	}

	hook := createGetHandler()
	body, err := hook.CreateBody(em)
	if err != nil {
		t.Error("Create body failed with error! ", err)
	}

	expectedBody := `"event": "QuotaExceeded"`
	if !strings.Contains(body, expectedBody) {
		t.Error("Body incorrect, is: ", body)
	}
}

func TestGet(t *testing.T) {
	eventHandler := createGetHandler()

	eventMessage := config.EventMessage{
		Type: EventKeyExpired,
		Meta: EventKeyFailureMeta{
			EventMetaDefault: EventMetaDefault{Message: "THIS IS A TEST"},
			Path:             "/banana",
			Origin:           "tyk.io",
			Key:              "123456789",
		},
	}
	body, _ := eventHandler.CreateBody(eventMessage)

	checksum, _ := eventHandler.Checksum(body)
	eventHandler.HandleEvent(eventMessage)

	if wasFired := eventHandler.WasHookFired(checksum); !wasFired {
		t.Error("Checksum should have matched, event did not fire!")
	}

}

func TestPost(t *testing.T) {
	eventHandlerConf := config.WebHookHandlerConf{
		TargetPath:   "`+testHttpPost+`",
		Method:       "POST",
		EventTimeout: 10,
		TemplatePath: "templates/default_webhook.json",
		HeaderList:   map[string]string{"x-tyk-test": "TEST POST"},
	}

	eventHandler := &WebHookHandler{}
	if err := eventHandler.Init(eventHandlerConf); err != nil {
		t.Fatal(err)
	}

	eventMessage := config.EventMessage{
		Type: EventKeyExpired,
		Meta: EventKeyFailureMeta{
			EventMetaDefault: EventMetaDefault{Message: "THIS IS A TEST"},
			Path:             "/banana",
			Origin:           "tyk.io",
			Key:              "123456789",
		},
	}

	body, _ := eventHandler.CreateBody(eventMessage)

	checksum, _ := eventHandler.Checksum(body)
	eventHandler.HandleEvent(eventMessage)

	if wasFired := eventHandler.WasHookFired(checksum); !wasFired {
		t.Error("Checksum should have matched, event did not fire!")
	}
}

func TestNewCustomTemplate(t *testing.T) {
	tests := []struct {
		name           string
		missingDefault bool
		templatePath   string
		wantErr        bool
	}{
		{"UseDefault", false, "", false},
		{"FallbackToDefault", false, "missing_webhook.json", false},
		{"UseCustom", false, "templates/breaker_webhook.json", false},
		{"MissingDefault", true, "", true},
		{"MissingDefaultFallback", true, "missing_webhook.json", true},
		{"MissingDefaultNotNeeded", true, "../templates/breaker_webhook.json", false},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if tc.missingDefault {
				globalConf := config.Global()
				old := globalConf.TemplatePath
				globalConf.TemplatePath = "missing-dir"
				config.SetGlobal(globalConf)
				defer func() {
					globalConf.TemplatePath = old
					config.SetGlobal(globalConf)
				}()
			}
			h := &WebHookHandler{}
			err := h.Init(map[string]interface{}{
				"target_path":   testHttpPost,
				"template_path": tc.templatePath,
			})
			if tc.wantErr && err == nil {
				t.Fatalf("wanted error, got nil")
			} else if !tc.wantErr && err != nil {
				t.Fatalf("didn't want error, got: %v", err)
			}
			if err == nil && h.template == nil {
				t.Fatalf("didn't get an error but template is nil")
			}
		})
	}
}

func TestWebhookContentTypeHeader(t *testing.T) {
	globalConf := config.Global()
	templatePath := globalConf.TemplatePath

	tests := []struct {
		Name                string
		TemplatePath        string
		InputHeaders        map[string]string
		ExpectedContentType string
	}{
		{"MissingTemplatePath", "", nil, "application/json"},
		{"MissingTemplatePath/CustomHeaders", "", map[string]string{"Content-Type": "application/xml"}, "application/xml"},
		{"InvalidTemplatePath", "randomPath", nil, "application/json"},
		{"InvalidTemplatePath/CustomHeaders", "randomPath", map[string]string{"Content-Type": "application/xml"}, "application/xml"},
		{"CustomTemplate", filepath.Join(templatePath, "breaker_webhook.json"), nil, ""},
		{"CustomTemplate/CustomHeaders", filepath.Join(templatePath, "breaker_webhook.json"), map[string]string{"Content-Type": "application/json"}, "application/json"},
	}

	for _, ts := range tests {
		t.Run(ts.Name, func(t *testing.T) {
			conf := config.WebHookHandlerConf{
				TemplatePath: ts.TemplatePath,
				HeaderList:   ts.InputHeaders,
			}

			hook := &WebHookHandler{}
			if err := hook.Init(conf); err != nil {
				t.Fatal("Webhook Init failed with err ", err)
			}

			req, err := hook.BuildRequest("")
			if err != nil {
				t.Fatal("Failed to build request with error ", err)
			}

			if req.Header.Get("Content-Type") != ts.ExpectedContentType {
				t.Fatalf("Expect Content-Type %s. Got %s", ts.ExpectedContentType, req.Header.Get("Content-Type"))
			}
		})
	}

}
<commit_msg>Fix webhook tests<commit_after>package gateway

import (
	"path/filepath"
	"strings"
	"testing"

	"github.com/TykTechnologies/tyk/config"
)

func createGetHandler() *WebHookHandler {
	eventHandlerConf := config.WebHookHandlerConf{
		TargetPath:   TestHttpGet,
		Method:       "GET",
		EventTimeout: 10,
		TemplatePath: "../templates/default_webhook.json",
		HeaderList:   map[string]string{"x-tyk-test": "TEST"},
	}
	ev := &WebHookHandler{}
	if err := ev.Init(eventHandlerConf); err != nil {
		panic(err)
	}
	return ev
}

func TestNewValid(t *testing.T) {
	h := &WebHookHandler{}
	err := h.Init(map[string]interface{}{
		"method":        "POST",
		"target_path":   testHttpPost,
		"template_path": "../templates/default_webhook.json",
		"header_map":    map[string]string{"X-Tyk-Test-Header": "Tyk v1.BANANA"},
		"event_timeout": 10,
	})
	if err != nil {
		t.Error("Webhook Handler should have created valid configuration")
	}
}

func TestNewInvalid(t *testing.T) {
	h := &WebHookHandler{}
	err := h.Init(map[string]interface{}{
		"method":        123,
		"target_path":   testHttpPost,
		"template_path": "../templates/default_webhook.json",
		"header_map":    map[string]string{"X-Tyk-Test-Header": "Tyk v1.BANANA"},
		"event_timeout": 10,
	})
	if err == nil {
		t.Error("Webhook Handler should have failed")
	}
}

func TestChecksum(t *testing.T) {
	rBody := `{
		"event": "QuotaExceeded",
		"message": "Key Quota Limit Exceeded",
		"path": "/about-lonelycoder/",
		"origin": "",
		"key": "4321",
		"timestamp": 2014-11-27 12:52:05.944549825 +0000 GMT
	}`

	hook := createGetHandler()
	checksum, err := hook.Checksum(rBody)

	if err != nil {
		t.Error("Checksum should not have failed with good objet and body")
	}

	if checksum != "62a6b4fa9b45cd372b871764296fb3a5" {
		t.Error("Checksum is incorrect")
		t.Error(checksum)
	}
}

func TestBuildRequest(t *testing.T) {
	hook := createGetHandler()

	rBody := `{
		"event": "QuotaExceeded",
		"message": "Key Quota Limit Exceeded",
		"path": "/about-lonelycoder/",
		"origin": "",
		"key": "4321",
		"timestamp": 2014-11-27 12:52:05.944549825 +0000 GMT
	}`

	req, err := hook.BuildRequest(rBody)
	if err != nil {
		t.Error("Request should have built cleanly.")
	}
	if req.Method != "GET" {
		t.Error("Method hould be GET")
	}

	if got := req.Header.Get("User-Agent"); got != "Tyk-Hookshot" {
		t.Error("Header User Agent is not correct!")
	}

	if got := req.Header.Get("Content-Type"); got != "application/json" {
		t.Error("Header Content-Type is not correct!")
	}
}

func TestCreateBody(t *testing.T) {
	em := config.EventMessage{
		Type:      EventQuotaExceeded,
		TimeStamp: "0",
	}

	hook := createGetHandler()
	body, err := hook.CreateBody(em)
	if err != nil {
		t.Error("Create body failed with error! ", err)
	}

	expectedBody := `"event": "QuotaExceeded"`
	if !strings.Contains(body, expectedBody) {
		t.Error("Body incorrect, is: ", body)
	}
}

func TestGet(t *testing.T) {
	eventHandler := createGetHandler()

	eventMessage := config.EventMessage{
		Type: EventKeyExpired,
		Meta: EventKeyFailureMeta{
			EventMetaDefault: EventMetaDefault{Message: "THIS IS A TEST"},
			Path:             "/banana",
			Origin:           "tyk.io",
			Key:              "123456789",
		},
	}
	body, _ := eventHandler.CreateBody(eventMessage)

	checksum, _ := eventHandler.Checksum(body)
	eventHandler.HandleEvent(eventMessage)

	if wasFired := eventHandler.WasHookFired(checksum); !wasFired {
		t.Error("Checksum should have matched, event did not fire!")
	}

}

func TestPost(t *testing.T) {
	eventHandlerConf := config.WebHookHandlerConf{
		TargetPath:   "`+testHttpPost+`",
		Method:       "POST",
		EventTimeout: 10,
		TemplatePath: "templates/default_webhook.json",
		HeaderList:   map[string]string{"x-tyk-test": "TEST POST"},
	}

	eventHandler := &WebHookHandler{}
	if err := eventHandler.Init(eventHandlerConf); err != nil {
		t.Fatal(err)
	}

	eventMessage := config.EventMessage{
		Type: EventKeyExpired,
		Meta: EventKeyFailureMeta{
			EventMetaDefault: EventMetaDefault{Message: "THIS IS A TEST"},
			Path:             "/banana",
			Origin:           "tyk.io",
			Key:              "123456789",
		},
	}

	body, _ := eventHandler.CreateBody(eventMessage)

	checksum, _ := eventHandler.Checksum(body)
	eventHandler.HandleEvent(eventMessage)

	if wasFired := eventHandler.WasHookFired(checksum); !wasFired {
		t.Error("Checksum should have matched, event did not fire!")
	}
}

func TestNewCustomTemplate(t *testing.T) {
	tests := []struct {
		name           string
		missingDefault bool
		templatePath   string
		wantErr        bool
	}{
		{"UseDefault", false, "", false},
		{"FallbackToDefault", false, "missing_webhook.json", false},
		{"UseCustom", false, "templates/breaker_webhook.json", false},
		{"MissingDefault", true, "", true},
		{"MissingDefaultFallback", true, "missing_webhook.json", true},
		{"MissingDefaultNotNeeded", true, "../templates/breaker_webhook.json", false},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if tc.missingDefault {
				globalConf := config.Global()
				old := globalConf.TemplatePath
				globalConf.TemplatePath = "missing-dir"
				config.SetGlobal(globalConf)
				defer func() {
					globalConf.TemplatePath = old
					config.SetGlobal(globalConf)
				}()
			}
			h := &WebHookHandler{}
			err := h.Init(map[string]interface{}{
				"target_path":   testHttpPost,
				"template_path": tc.templatePath,
			})
			if tc.wantErr && err == nil {
				t.Fatalf("wanted error, got nil")
			} else if !tc.wantErr && err != nil {
				t.Fatalf("didn't want error, got: %v", err)
			}
			if err == nil && h.template == nil {
				t.Fatalf("didn't get an error but template is nil")
			}
		})
	}
}

func TestWebhookContentTypeHeader(t *testing.T) {
	globalConf := config.Global()
	templatePath := globalConf.TemplatePath

	tests := []struct {
		Name                string
		TemplatePath        string
		InputHeaders        map[string]string
		ExpectedContentType string
	}{
		{"MissingTemplatePath", "", nil, "application/json"},
		{"MissingTemplatePath/CustomHeaders", "", map[string]string{"Content-Type": "application/xml"}, "application/xml"},
		{"InvalidTemplatePath", "randomPath", nil, "application/json"},
		{"InvalidTemplatePath/CustomHeaders", "randomPath", map[string]string{"Content-Type": "application/xml"}, "application/xml"},
		{"CustomTemplate", filepath.Join(templatePath, "transform_test.tmpl"), nil, ""},
		{"CustomTemplate/CustomHeaders", filepath.Join(templatePath, "breaker_webhook.json"), map[string]string{"Content-Type": "application/xml"}, "application/xml"},
	}

	for _, ts := range tests {
		t.Run(ts.Name, func(t *testing.T) {
			conf := config.WebHookHandlerConf{
				TemplatePath: ts.TemplatePath,
				HeaderList:   ts.InputHeaders,
			}

			hook := &WebHookHandler{}
			if err := hook.Init(conf); err != nil {
				t.Fatal("Webhook Init failed with err ", err)
			}

			req, err := hook.BuildRequest("")
			if err != nil {
				t.Fatal("Failed to build request with error ", err)
			}

			if req.Header.Get("Content-Type") != ts.ExpectedContentType {
				t.Fatalf("Expect Content-Type %s. Got %s", ts.ExpectedContentType, req.Header.Get("Content-Type"))
			}
		})
	}

}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google 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 blobref

import (
	"crypto/sha1"
	"fmt"
	"hash"
	"io"
	"regexp"
)

var kBlobRefPattern *regexp.Regexp = regexp.MustCompile(`^([a-z0-9]+)-([a-f0-9]+)$`)

var supportedDigests = map[string]func() hash.Hash{
	"sha1": func() hash.Hash {
		return sha1.New()
	},
}

// BlobRef is an immutable reference to a blob.
type BlobRef struct {
	hashName string
	digest   string

	strValue string // "<hashname>-<digest>"
}

// SizedBlobRef is like a BlobRef but includes because it includes a
// potentially mutable 'Size', this should be used as a stack value,
// not a *SizedBlobRef.
type SizedBlobRef struct {
	*BlobRef
	Size int64
}

type ReadSeekCloser interface {
	io.Reader
	io.Seeker
	io.Closer
}

func (b *BlobRef) HashName() string {
	return b.hashName
}

func (b *BlobRef) Digest() string {
	return b.digest
}

func (b *BlobRef) String() string {
	if b == nil {
		return "<nil-BlobRef>"
	}
	return b.strValue
}

func (o *BlobRef) Equals(other *BlobRef) bool {
	return o.hashName == other.hashName && o.digest == other.digest
}

func (o *BlobRef) Hash() hash.Hash {
	fn, ok := supportedDigests[o.hashName]
	if !ok {
		return nil
	}
	return fn()
}

func (o *BlobRef) HashMatches(h hash.Hash) bool {
	return fmt.Sprintf("%x", h.Sum()) == o.digest
}

func (o *BlobRef) IsSupported() bool {
	_, ok := supportedDigests[o.hashName]
	return ok
}

func (o *BlobRef) Sum32() uint32 {
	var h32 uint32
	n, err := fmt.Sscanf(o.digest[len(o.digest)-8:], "%8x", &h32)
	if err != nil {
		panic(err)
	}
	if n != 1 {
		panic("sum32")
	}
	return h32
}

var kExpectedDigestSize = map[string]int{
	"md5":  32,
	"sha1": 40,
}

func newBlob(hashName, digest string) *BlobRef {
	strValue := fmt.Sprintf("%s-%s", hashName, digest)
	return &BlobRef{strValue[0:len(hashName)],
		strValue[len(hashName)+1:],
		strValue}
}

func blobIfValid(hashname, digest string) *BlobRef {
	expectedSize := kExpectedDigestSize[hashname]
	if expectedSize != 0 && len(digest) != expectedSize {
		return nil
	}
	return newBlob(hashname, digest)
}

func FromHash(name string, h hash.Hash) *BlobRef {
	return newBlob(name, fmt.Sprintf("%x", h.Sum()))
}

// FromPattern takes a pattern and if it matches 's' with two exactly two valid
// submatches, returns a BlobRef, else returns nil.
func FromPattern(r *regexp.Regexp, s string) *BlobRef {
	matches := r.FindStringSubmatch(s)
	if len(matches) != 3 {
		return nil
	}
	return blobIfValid(matches[1], matches[2])
}

func Parse(ref string) *BlobRef {
	return FromPattern(kBlobRefPattern, ref)
}

func MustParse(ref string) *BlobRef {
	br := Parse(ref)
	if br == nil {
		panic("Failed to parse blobref: " + ref)
	}
	return br
}

// May return nil in list positions where the blobref could not be parsed.
func ParseMulti(refs []string) (parsed []*BlobRef) {
	parsed = make([]*BlobRef, 0, len(refs))
	for _, ref := range refs {
		parsed = append(parsed, Parse(ref))
	}
	return
}
<commit_msg>blobref: add Sha1FromString helper and SizedBlobRef Equal/String<commit_after>/*
Copyright 2011 Google 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 blobref

import (
	"crypto/sha1"
	"fmt"
	"hash"
	"io"
	"regexp"
)

var kBlobRefPattern *regexp.Regexp = regexp.MustCompile(`^([a-z0-9]+)-([a-f0-9]+)$`)

var supportedDigests = map[string]func() hash.Hash{
	"sha1": func() hash.Hash {
		return sha1.New()
	},
}

// BlobRef is an immutable reference to a blob.
type BlobRef struct {
	hashName string
	digest   string

	strValue string // "<hashname>-<digest>"
}

// SizedBlobRef is like a BlobRef but includes because it includes a
// potentially mutable 'Size', this should be used as a stack value,
// not a *SizedBlobRef.
type SizedBlobRef struct {
	*BlobRef
	Size int64
}

func (sb *SizedBlobRef) Equal(o SizedBlobRef) bool {
	return sb.Size == o.Size && sb.BlobRef.String() == o.BlobRef.String()
}

func (sb *SizedBlobRef) String() string {
	return fmt.Sprintf("[%s %d bytes]", sb.BlobRef.String(), sb.Size)
}

type ReadSeekCloser interface {
	io.Reader
	io.Seeker
	io.Closer
}

func (b *BlobRef) HashName() string {
	return b.hashName
}

func (b *BlobRef) Digest() string {
	return b.digest
}

func (b *BlobRef) String() string {
	if b == nil {
		return "<nil-BlobRef>"
	}
	return b.strValue
}

func (o *BlobRef) Equals(other *BlobRef) bool {
	return o.hashName == other.hashName && o.digest == other.digest
}

func (o *BlobRef) Hash() hash.Hash {
	fn, ok := supportedDigests[o.hashName]
	if !ok {
		return nil
	}
	return fn()
}

func (o *BlobRef) HashMatches(h hash.Hash) bool {
	return fmt.Sprintf("%x", h.Sum()) == o.digest
}

func (o *BlobRef) IsSupported() bool {
	_, ok := supportedDigests[o.hashName]
	return ok
}

func (o *BlobRef) Sum32() uint32 {
	var h32 uint32
	n, err := fmt.Sscanf(o.digest[len(o.digest)-8:], "%8x", &h32)
	if err != nil {
		panic(err)
	}
	if n != 1 {
		panic("sum32")
	}
	return h32
}

var kExpectedDigestSize = map[string]int{
	"md5":  32,
	"sha1": 40,
}

func newBlob(hashName, digest string) *BlobRef {
	strValue := fmt.Sprintf("%s-%s", hashName, digest)
	return &BlobRef{strValue[0:len(hashName)],
		strValue[len(hashName)+1:],
		strValue}
}

func blobIfValid(hashname, digest string) *BlobRef {
	expectedSize := kExpectedDigestSize[hashname]
	if expectedSize != 0 && len(digest) != expectedSize {
		return nil
	}
	return newBlob(hashname, digest)
}

func FromHash(hashfunc string, h hash.Hash) *BlobRef {
	return newBlob(hashfunc, fmt.Sprintf("%x", h.Sum()))
}

func Sha1FromString(s string) *BlobRef {
	s1 := sha1.New()
	s1.Write([]byte(s))
	return FromHash("sha1", s1)
}

// FromPattern takes a pattern and if it matches 's' with two exactly two valid
// submatches, returns a BlobRef, else returns nil.
func FromPattern(r *regexp.Regexp, s string) *BlobRef {
	matches := r.FindStringSubmatch(s)
	if len(matches) != 3 {
		return nil
	}
	return blobIfValid(matches[1], matches[2])
}

func Parse(ref string) *BlobRef {
	return FromPattern(kBlobRefPattern, ref)
}

func MustParse(ref string) *BlobRef {
	br := Parse(ref)
	if br == nil {
		panic("Failed to parse blobref: " + ref)
	}
	return br
}

// May return nil in list positions where the blobref could not be parsed.
func ParseMulti(refs []string) (parsed []*BlobRef) {
	parsed = make([]*BlobRef, 0, len(refs))
	for _, ref := range refs {
		parsed = append(parsed, Parse(ref))
	}
	return
}
<|endoftext|>


<commit_before>package feeder

import (
	"errors"
	"fmt"
	"math"
	"socialapi/config"
	socialmodels "socialapi/models"
	"socialapi/workers/helper"
	"socialapi/workers/sitemap/common"
	"socialapi/workers/sitemap/models"
	"time"

	"github.com/koding/logging"
	"github.com/koding/redis"
	"github.com/streadway/amqp"
)

type Controller struct {
	log            logging.Logger
	redisConn      *redis.RedisSession
	updateInterval time.Duration
}

var ErrIgnore = errors.New("ignore")

const (
	DefaultInterval   = 30 * time.Minute
	MaxItemSizeInFile = 1000
)

func (f *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	f.log.Error("an error occurred deleting realtime event", err)
	delivery.Ack(false)
	return false
}

func New(log logging.Logger) *Controller {
	conf := *config.MustGet()
	conf.Redis.DB = conf.Sitemap.RedisDB
	// TODO later on seperate config structs could be better for each helper
	redisConn := helper.MustInitRedisConn(&conf)

	c := &Controller{
		log:            log,
		redisConn:      redisConn,
		updateInterval: common.GetInterval(),
	}

	return c
}

func (f *Controller) MessageAdded(cm *socialmodels.ChannelMessage) error {
	return f.queueChannelMessage(cm, models.STATUS_UPDATE)
}

func (f *Controller) MessageUpdated(cm *socialmodels.ChannelMessage) error {
	return f.queueChannelMessage(cm, models.STATUS_UPDATE)
}

func (f *Controller) MessageDeleted(cm *socialmodels.ChannelMessage) error {
	return f.queueChannelMessage(cm, models.STATUS_DELETE)
}

// queueChannelMessage updates account sitemap file if message's initial channel is public.
// Also adds post's url to the sitemap
func (f *Controller) queueChannelMessage(cm *socialmodels.ChannelMessage, status string) error {
	if err := validateChannelMessage(cm); err != nil {
		if err == ErrIgnore {
			return nil
		}

		return err
	}

	// add post's url to the sitemap
	_, err := f.queueItem(newItemByChannelMessage(cm, status))
	if err != nil {
		return err
	}

	return nil
}

func (f *Controller) ChannelMessageListUpdated(c *socialmodels.ChannelMessageList) error {
	return f.queueChannelMessageList(c, models.STATUS_UPDATE)
}

func (f *Controller) ChannelMessageListAdded(c *socialmodels.ChannelMessageList) error {
	return f.queueChannelMessageList(c, models.STATUS_UPDATE)
}

func (f *Controller) ChannelMessageListDeleted(c *socialmodels.ChannelMessageList) error {
	return f.queueChannelMessageList(c, models.STATUS_DELETE)
}

func (f *Controller) queueChannelMessageList(c *socialmodels.ChannelMessageList, status string) error {
	ch, err := socialmodels.ChannelById(c.ChannelId)
	if err != nil {
		return nil
	}

	// Even validateChannel returns just ErrIgnore now, for preventing
	// potential future errors, we are checking for err existence here
	if err := validateChannel(ch); err != nil {
		if err == ErrIgnore {
			return nil
		}

		return err
	}

	_, err = f.queueItem(newItemByChannel(ch, status))

	return err
}

func validateChannelMessage(cm *socialmodels.ChannelMessage) error {
	// TODO if it is reply update parent message
	if cm.TypeConstant != socialmodels.ChannelMessage_TYPE_POST {
		return ErrIgnore
	}

	ch, err := socialmodels.ChannelById(cm.InitialChannelId)
	if err != nil {
		return err
	}

	// it could be a message in a private group
	if ch.PrivacyConstant == socialmodels.Channel_PRIVACY_PRIVATE {
		return ErrIgnore
	}

	return nil
}

func validateChannel(c *socialmodels.Channel) error {
	// for now we are only adding topics, but later on we could add groups here
	if c.TypeConstant != socialmodels.Channel_TYPE_TOPIC &&
		c.TypeConstant != socialmodels.Channel_TYPE_GROUP {
		return ErrIgnore
	}

	if c.PrivacyConstant == socialmodels.Channel_PRIVACY_PRIVATE {
		return ErrIgnore
	}

	return nil
}

func newItemByChannelMessage(cm *socialmodels.ChannelMessage, status string) *models.SitemapItem {
	return &models.SitemapItem{
		Id:           cm.Id,
		TypeConstant: models.TYPE_CHANNEL_MESSAGE,
		Slug:         fmt.Sprintf("%s/%s", "Post", cm.Slug),
		Status:       status,
	}
}

func newItemByChannel(c *socialmodels.Channel, status string) *models.SitemapItem {
	slug := "Public"
	switch c.TypeConstant {
	case socialmodels.Channel_TYPE_TOPIC:
		slug = fmt.Sprintf("Topic/%s", c.Name)
	case socialmodels.Channel_TYPE_GROUP:
		// TODO implement when group routes are defined
	}

	return &models.SitemapItem{
		Id:           c.Id,
		TypeConstant: models.TYPE_CHANNEL,
		Slug:         slug,
		Status:       status,
	}
}

// queueItem push an item to cache and returns related file name
func (f *Controller) queueItem(i *models.SitemapItem) (string, error) {
	// fetch file name
	n := f.fetchFileName(i)

	if err := f.updateFileNameCache(n); err != nil {
		return "", err
	}

	if err := f.updateFileItemCache(n, i); err != nil {
		return "", err
	}

	return n, nil
}

func (f *Controller) updateFileNameCache(fileName string) error {
	key := common.PrepareNextFileNameSetCacheKey(int(f.updateInterval.Minutes()))
	if _, err := f.redisConn.AddSetMembers(key, fileName); err != nil {
		return err
	}

	return nil
}

func (f *Controller) updateFileItemCache(fileName string, i *models.SitemapItem) error {
	// prepare cache key
	key := common.PrepareNextFileCacheKey(fileName, int(f.updateInterval.Minutes()))
	value := i.PrepareSetValue()
	if _, err := f.redisConn.AddSetMembers(key, value); err != nil {
		return err
	}

	return nil
}

func (f *Controller) fetchFileName(i *models.SitemapItem) string {
	switch i.TypeConstant {
	case models.TYPE_CHANNEL_MESSAGE:
		return fetchChannelMessageName(i.Id)
	case models.TYPE_CHANNEL:
		return fetchChannelName(i.Id)
	}

	return ""
}

func fetchChannelMessageName(id int64) string {
	remainder := math.Mod(float64(id), float64(MaxItemSizeInFile))
	return fmt.Sprintf("channel_message_%d", int64(remainder))
}

func fetchChannelName(id int64) string {
	remainder := math.Mod(float64(id), float64(MaxItemSizeInFile))
	return fmt.Sprintf("channel_%d", int64(remainder))
}
<commit_msg>sitemap: panic when there is an unexpected message type<commit_after>package feeder

import (
	"errors"
	"fmt"
	"math"
	"socialapi/config"
	socialmodels "socialapi/models"
	"socialapi/workers/helper"
	"socialapi/workers/sitemap/common"
	"socialapi/workers/sitemap/models"
	"time"

	"github.com/koding/logging"
	"github.com/koding/redis"
	"github.com/streadway/amqp"
)

type Controller struct {
	log            logging.Logger
	redisConn      *redis.RedisSession
	updateInterval time.Duration
}

var (
	ErrIgnore      = errors.New("ignore")
	ErrInvalidType = errors.New("invalid type")
)

const (
	DefaultInterval   = 30 * time.Minute
	MaxItemSizeInFile = 1000
)

func (f *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	f.log.Error("an error occurred deleting realtime event", err)
	delivery.Ack(false)
	return false
}

func New(log logging.Logger) *Controller {
	conf := *config.MustGet()
	conf.Redis.DB = conf.Sitemap.RedisDB
	// TODO later on seperate config structs could be better for each helper
	redisConn := helper.MustInitRedisConn(&conf)

	c := &Controller{
		log:            log,
		redisConn:      redisConn,
		updateInterval: common.GetInterval(),
	}

	return c
}

func (f *Controller) MessageAdded(cm *socialmodels.ChannelMessage) error {
	return f.queueChannelMessage(cm, models.STATUS_UPDATE)
}

func (f *Controller) MessageUpdated(cm *socialmodels.ChannelMessage) error {
	return f.queueChannelMessage(cm, models.STATUS_UPDATE)
}

func (f *Controller) MessageDeleted(cm *socialmodels.ChannelMessage) error {
	return f.queueChannelMessage(cm, models.STATUS_DELETE)
}

// queueChannelMessage updates account sitemap file if message's initial channel is public.
// Also adds post's url to the sitemap
func (f *Controller) queueChannelMessage(cm *socialmodels.ChannelMessage, status string) error {
	if err := validateChannelMessage(cm); err != nil {
		if err == ErrIgnore {
			return nil
		}

		return err
	}

	// add post's url to the sitemap
	_, err := f.queueItem(newItemByChannelMessage(cm, status))
	if err != nil {
		return err
	}

	return nil
}

func (f *Controller) ChannelMessageListUpdated(c *socialmodels.ChannelMessageList) error {
	return f.queueChannelMessageList(c, models.STATUS_UPDATE)
}

func (f *Controller) ChannelMessageListAdded(c *socialmodels.ChannelMessageList) error {
	return f.queueChannelMessageList(c, models.STATUS_UPDATE)
}

func (f *Controller) ChannelMessageListDeleted(c *socialmodels.ChannelMessageList) error {
	return f.queueChannelMessageList(c, models.STATUS_DELETE)
}

func (f *Controller) queueChannelMessageList(c *socialmodels.ChannelMessageList, status string) error {
	ch, err := socialmodels.ChannelById(c.ChannelId)
	if err != nil {
		return nil
	}

	// Even validateChannel returns just ErrIgnore now, for preventing
	// potential future errors, we are checking for err existence here
	if err := validateChannel(ch); err != nil {
		if err == ErrIgnore {
			return nil
		}

		return err
	}

	_, err = f.queueItem(newItemByChannel(ch, status))

	return err
}

func validateChannelMessage(cm *socialmodels.ChannelMessage) error {
	// TODO if it is reply update parent message
	if cm.TypeConstant != socialmodels.ChannelMessage_TYPE_POST {
		return ErrIgnore
	}

	ch, err := socialmodels.ChannelById(cm.InitialChannelId)
	if err != nil {
		return err
	}

	// it could be a message in a private group
	if ch.PrivacyConstant == socialmodels.Channel_PRIVACY_PRIVATE {
		return ErrIgnore
	}

	return nil
}

func validateChannel(c *socialmodels.Channel) error {
	// for now we are only adding topics, but later on we could add groups here
	if c.TypeConstant != socialmodels.Channel_TYPE_TOPIC &&
		c.TypeConstant != socialmodels.Channel_TYPE_GROUP {
		return ErrIgnore
	}

	if c.PrivacyConstant == socialmodels.Channel_PRIVACY_PRIVATE {
		return ErrIgnore
	}

	return nil
}

func newItemByChannelMessage(cm *socialmodels.ChannelMessage, status string) *models.SitemapItem {
	return &models.SitemapItem{
		Id:           cm.Id,
		TypeConstant: models.TYPE_CHANNEL_MESSAGE,
		Slug:         fmt.Sprintf("%s/%s", "Post", cm.Slug),
		Status:       status,
	}
}

func newItemByChannel(c *socialmodels.Channel, status string) *models.SitemapItem {
	slug := "Public"
	switch c.TypeConstant {
	case socialmodels.Channel_TYPE_TOPIC:
		slug = fmt.Sprintf("Topic/%s", c.Name)
	case socialmodels.Channel_TYPE_GROUP:
		// TODO implement when group routes are defined
	}

	return &models.SitemapItem{
		Id:           c.Id,
		TypeConstant: models.TYPE_CHANNEL,
		Slug:         slug,
		Status:       status,
	}
}

// queueItem push an item to cache and returns related file name
func (f *Controller) queueItem(i *models.SitemapItem) (string, error) {
	// fetch file name
	n := f.fetchFileName(i)

	if err := f.updateFileNameCache(n); err != nil {
		return "", err
	}

	if err := f.updateFileItemCache(n, i); err != nil {
		return "", err
	}

	return n, nil
}

func (f *Controller) updateFileNameCache(fileName string) error {
	key := common.PrepareNextFileNameSetCacheKey(int(f.updateInterval.Minutes()))
	if _, err := f.redisConn.AddSetMembers(key, fileName); err != nil {
		return err
	}

	return nil
}

func (f *Controller) updateFileItemCache(fileName string, i *models.SitemapItem) error {
	// prepare cache key
	key := common.PrepareNextFileCacheKey(fileName, int(f.updateInterval.Minutes()))
	value := i.PrepareSetValue()
	if _, err := f.redisConn.AddSetMembers(key, value); err != nil {
		return err
	}

	return nil
}

func (f *Controller) fetchFileName(i *models.SitemapItem) string {
	switch i.TypeConstant {
	case models.TYPE_CHANNEL_MESSAGE:
		return fetchChannelMessageName(i.Id)
	case models.TYPE_CHANNEL:
		return fetchChannelName(i.Id)
	default:
		panic(ErrInvalidType)
	}
}

func fetchChannelMessageName(id int64) string {
	remainder := math.Mod(float64(id), float64(MaxItemSizeInFile))
	return fmt.Sprintf("channel_message_%d", int64(remainder))
}

func fetchChannelName(id int64) string {
	remainder := math.Mod(float64(id), float64(MaxItemSizeInFile))
	return fmt.Sprintf("channel_%d", int64(remainder))
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"."
	"flag"
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/service"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/log"
	stdlog "log"
	"log/syslog"
	"net/http"
)

func main() {
	var err error
	log.Target, err = syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		panic(err)
	}
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		log.Panic(err.Error())
	}
	connString, err := config.GetString("database:host")
	if err != nil {
		panic(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		panic(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		log.Panic(err.Error())
	}
	defer db.Session.Close()
	m := pat.New()

	m.Post("/services", webserver.AuthorizationRequiredHandler(service.CreateHandler))
	m.Get("/services", webserver.AuthorizationRequiredHandler(service.ServicesHandler))
	m.Get("/services/types", webserver.Handler(service.ServiceTypesHandler))
	m.Del("/services/:name", webserver.AuthorizationRequiredHandler(service.DeleteHandler))
	m.Post("/services/bind", webserver.AuthorizationRequiredHandler(service.BindHandler))
	m.Post("/services/unbind", webserver.AuthorizationRequiredHandler(service.UnbindHandler))
	m.Put("/services/:service/:team", webserver.AuthorizationRequiredHandler(service.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", webserver.AuthorizationRequiredHandler(service.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", webserver.AuthorizationRequiredHandler(app.AppDelete))
	m.Get("/apps/:name/clone", webserver.Handler(app.CloneRepositoryHandler))
	m.Get("/apps/:name", webserver.AuthorizationRequiredHandler(app.AppInfo))
	m.Get("/apps", webserver.AuthorizationRequiredHandler(app.AppList))
	m.Post("/apps", webserver.AuthorizationRequiredHandler(app.CreateAppHandler))
	m.Put("/apps/:app/:team", webserver.AuthorizationRequiredHandler(app.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", webserver.AuthorizationRequiredHandler(app.RevokeAccessFromTeamHandler))

	m.Post("/users", webserver.Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", webserver.Handler(auth.Login))
	m.Post("/users/keys", webserver.AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", webserver.AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Post("/teams", webserver.AuthorizationRequiredHandler(auth.CreateTeam))
	m.Put("/teams/:team/:user", webserver.AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", webserver.AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	listen, err := config.GetString("listen")
	if err != nil {
		panic(err)
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(listen, m))
	}
}
<commit_msg>api/webserver: starting repository agent<commit_after>// +build ignore

package main

import (
	"."
	"flag"
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/service"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	stdlog "log"
	"log/syslog"
	"net/http"
)

func main() {
	var err error
	log.Target, err = syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		panic(err)
	}
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		log.Panic(err.Error())
	}
	connString, err := config.GetString("database:host")
	if err != nil {
		panic(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		panic(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		log.Panic(err.Error())
	}
	defer db.Session.Close()

	repository.RunAgent()
	m := pat.New()

	m.Post("/services", webserver.AuthorizationRequiredHandler(service.CreateHandler))
	m.Get("/services", webserver.AuthorizationRequiredHandler(service.ServicesHandler))
	m.Get("/services/types", webserver.Handler(service.ServiceTypesHandler))
	m.Del("/services/:name", webserver.AuthorizationRequiredHandler(service.DeleteHandler))
	m.Post("/services/bind", webserver.AuthorizationRequiredHandler(service.BindHandler))
	m.Post("/services/unbind", webserver.AuthorizationRequiredHandler(service.UnbindHandler))
	m.Put("/services/:service/:team", webserver.AuthorizationRequiredHandler(service.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", webserver.AuthorizationRequiredHandler(service.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", webserver.AuthorizationRequiredHandler(app.AppDelete))
	m.Get("/apps/:name/clone", webserver.Handler(app.CloneRepositoryHandler))
	m.Get("/apps/:name", webserver.AuthorizationRequiredHandler(app.AppInfo))
	m.Get("/apps", webserver.AuthorizationRequiredHandler(app.AppList))
	m.Post("/apps", webserver.AuthorizationRequiredHandler(app.CreateAppHandler))
	m.Put("/apps/:app/:team", webserver.AuthorizationRequiredHandler(app.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", webserver.AuthorizationRequiredHandler(app.RevokeAccessFromTeamHandler))

	m.Post("/users", webserver.Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", webserver.Handler(auth.Login))
	m.Post("/users/keys", webserver.AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", webserver.AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Post("/teams", webserver.AuthorizationRequiredHandler(auth.CreateTeam))
	m.Put("/teams/:team/:user", webserver.AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", webserver.AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	listen, err := config.GetString("listen")
	if err != nil {
		panic(err)
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(listen, m))
	}
}
<|endoftext|>


<commit_before>package errs

import (
	"fmt"
	"testing"
)

func TestNumError(t *testing.T) {
	testCases := []struct {
		err error
		str string
	}{
		{err: Num(0), str: "Unknown error (0)"},
		{err: ErrNullPointer, str: "Null reference instance"},
		{err: ErrArmorText, str: "Cannot find OpenPGP armor boundary"},
		{err: ErrInvalidWhence, str: "Invalid whence"},
		{err: ErrInvalidOffset, str: "Invalid offset"},
		{err: Num(5), str: "Unknown error (5)"},
	}

	for _, tc := range testCases {
		errStr := tc.err.Error()
		if errStr != tc.str {
			t.Errorf("\"%v\" != \"%v\"", errStr, tc.str)
		}
		fmt.Printf("Info(TestNumError): %+v\n", tc.err)
	}
}

func TestNumErrorEquality(t *testing.T) {
	testCases := []struct {
		err1 error
		err2 error
		res  bool
	}{
		{err1: ErrNullPointer, err2: ErrNullPointer, res: true},
		{err1: ErrNullPointer, err2: Wrap(ErrNullPointer, "wrapping error"), res: false},
		{err1: ErrNullPointer, err2: nil, res: false},
		{err1: ErrNullPointer, err2: Num(0), res: false},
	}

	for _, tc := range testCases {
		res := Is(tc.err1, tc.err2)
		if res != tc.res {
			t.Errorf("\"%v\" == \"%v\" ? %v, want %v", tc.err1, tc.err2, res, tc.res)
		}
	}
}

func TestWrapError(t *testing.T) {
	testCases := []struct {
		err error
		msg string
		str string
	}{
		{err: ErrNullPointer, msg: "wrapping error", str: "wrapping error: Null reference instance"},
	}

	for _, tc := range testCases {
		we := Wrap(tc.err, tc.msg)
		if we.Error() != tc.str {
			t.Errorf("wrapError.Error() == \"%v\", want \"%v\"", we.Error(), tc.str)
		}
		fmt.Printf("Info(TestWrapError): %+v\n", we)
	}
}

func TestWrapNilError(t *testing.T) {
	if we := Wrap(nil, "null error"); we != nil {
		t.Errorf("Wrap(nil) == \"%v\", want nil.", we)
	}
}

func TestWrapfError(t *testing.T) {
	testCases := []struct {
		err error
		msg string
		str string
	}{
		{err: ErrNullPointer, msg: "wrapping error", str: "wrapping error: Null reference instance"},
	}

	for _, tc := range testCases {
		we := Wrapf(tc.err, "%v", tc.msg)
		if we.Error() != tc.str {
			t.Errorf("wrapError.Error() == \"%v\", want \"%v\"", we.Error(), tc.str)
		}
		fmt.Printf("Info(TestWrapfError): %+v\n", we)
	}
}

func TestWrapfNilError(t *testing.T) {
	if we := Wrapf(nil, "%v", "null error"); we != nil {
		t.Errorf("Wrapf(nil) == \"%v\", want nil.", we)
	}
}

func TestWrapErrorEquality(t *testing.T) {
	testCases := []struct {
		err1 error
		err2 error
		res  bool
	}{
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: ErrNullPointer, res: true},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: nil, res: false},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: Num(0), res: false},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: Wrap(Num(0), "wrapping error"), res: false},
	}

	for _, tc := range testCases {
		res := Is(tc.err1, tc.err2)
		if res != tc.res {
			t.Errorf("\"%v\" == \"%v\" ? %v, want %v", tc.err1, tc.err2, res, tc.res)
		}
	}
}

/* Copyright 2019 Spiegel
*
* 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.
 */
<commit_msg>Testing<commit_after>package errs

import (
	"fmt"
	"testing"
)

func TestNumError(t *testing.T) {
	testCases := []struct {
		err error
		str string
	}{
		{err: Num(0), str: "Unknown error (0)"},
		{err: ErrNullPointer, str: "Null reference instance"},
		{err: ErrArmorText, str: "Cannot find OpenPGP armor boundary"},
		{err: ErrInvalidWhence, str: "Invalid whence"},
		{err: ErrInvalidOffset, str: "Invalid offset"},
		{err: Num(5), str: "Unknown error (5)"},
	}

	for _, tc := range testCases {
		errStr := tc.err.Error()
		if errStr != tc.str {
			t.Errorf("\"%v\" != \"%v\"", errStr, tc.str)
		}
		fmt.Printf("Info(TestNumError): %+v\n", tc.err)
	}
}

func TestNumErrorEquality(t *testing.T) {
	testCases := []struct {
		err1 error
		err2 error
		res  bool
	}{
		{err1: nil, err2: nil, res: true},
		{err1: nil, err2: ErrNullPointer, res: false},
		{err1: ErrNullPointer, err2: ErrNullPointer, res: true},
		{err1: ErrNullPointer, err2: Wrap(ErrNullPointer, "wrapping error"), res: false},
		{err1: ErrNullPointer, err2: Num(0), res: false},
		{err1: ErrNullPointer, err2: nil, res: false},
	}

	for _, tc := range testCases {
		res := Is(tc.err1, tc.err2)
		if res != tc.res {
			t.Errorf("\"%v\" == \"%v\" ? %v, want %v", tc.err1, tc.err2, res, tc.res)
		}
	}
}

func TestWrapError(t *testing.T) {
	testCases := []struct {
		err error
		msg string
		str string
	}{
		{err: ErrNullPointer, msg: "wrapping error", str: "wrapping error: Null reference instance"},
	}

	for _, tc := range testCases {
		we := Wrap(tc.err, tc.msg)
		if we.Error() != tc.str {
			t.Errorf("wrapError.Error() == \"%v\", want \"%v\"", we.Error(), tc.str)
		}
		fmt.Printf("Info(TestWrapError): %+v\n", we)
	}
}

func TestWrapNilError(t *testing.T) {
	if we := Wrap(nil, "null error"); we != nil {
		t.Errorf("Wrap(nil) == \"%v\", want nil.", we)
	}
}

func TestWrapfError(t *testing.T) {
	testCases := []struct {
		err error
		msg string
		str string
	}{
		{err: ErrNullPointer, msg: "wrapping error", str: "wrapping error: Null reference instance"},
	}

	for _, tc := range testCases {
		we := Wrapf(tc.err, "%v", tc.msg)
		if we.Error() != tc.str {
			t.Errorf("wrapError.Error() == \"%v\", want \"%v\"", we.Error(), tc.str)
		}
		fmt.Printf("Info(TestWrapfError): %+v\n", we)
	}
}

func TestWrapfNilError(t *testing.T) {
	if we := Wrapf(nil, "%v", "null error"); we != nil {
		t.Errorf("Wrapf(nil) == \"%v\", want nil.", we)
	}
}

func TestWrapErrorEquality(t *testing.T) {
	testCases := []struct {
		err1 error
		err2 error
		res  bool
	}{
		{err1: nil, err2: Wrap(ErrNullPointer, "wrapping error"), res: false},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: ErrNullPointer, res: true},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: Wrap(Num(0), "wrapping error"), res: false},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: Num(0), res: false},
		{err1: Wrap(ErrNullPointer, "wrapping error"), err2: nil, res: false},
	}

	for _, tc := range testCases {
		res := Is(tc.err1, tc.err2)
		if res != tc.res {
			t.Errorf("\"%v\" == \"%v\" ? %v, want %v", tc.err1, tc.err2, res, tc.res)
		}
	}
}

/* Copyright 2019 Spiegel
*
* 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.
 */
<|endoftext|>