<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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.
*/

// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	isup             = flag.Bool("isup", false, "Check to see if the e2e cluster is up, then exit.")
	build            = flag.Bool("build", false, "If true, build a new release. Otherwise, use whatever is there.")
	version          = flag.String("version", "", "The version to be tested (including the leading 'v'). An empty string defaults to the local build, but it can be set to any release (e.g. v0.4.4, v0.6.0).")
	up               = flag.Bool("up", false, "If true, start the the e2e cluster. If cluster is already up, recreate it.")
	push             = flag.Bool("push", false, "If true, push to e2e cluster. Has no effect if -up is true.")
	pushup           = flag.Bool("pushup", false, "If true, push to e2e cluster if it's up, otherwise start the e2e cluster.")
	down             = flag.Bool("down", false, "If true, tear down the cluster before exiting.")
	test             = flag.Bool("test", false, "Run Ginkgo tests.")
	root             = flag.String("root", absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))), "Root directory of kubernetes repository.")
	verbose          = flag.Bool("v", false, "If true, print all command output.")
	checkVersionSkew = flag.Bool("check_version_skew", true, ""+
		"By default, verify that client and server have exact version match. "+
		"You can explicitly set to false if you're, e.g., testing client changes "+
		"for which the server version doesn't make a difference.")

	ctlCmd = flag.String("ctl", "", "If nonempty, pass this as an argument, and call kubectl. Implies -v. (-test, -cfg, -ctl are mutually exclusive)")
)

const (
	serverTarName   = "kubernetes-server-linux-amd64.tar.gz"
	saltTarName     = "kubernetes-salt.tar.gz"
	downloadDirName = "_output/downloads"
	tarDirName      = "server"
	tempDirName     = "upgrade-e2e-temp-dir"
	minMinionCount  = 2
)

var (
	signals = make(chan os.Signal, 100)
	// Root directory of the specified cluster version, rather than of where
	// this script is being run from.
	versionRoot = *root
)

func absOrDie(path string) string {
	out, err := filepath.Abs(path)
	if err != nil {
		panic(err)
	}
	return out
}

type TestResult struct {
	Pass int
	Fail int
}

type ResultsByTest map[string]TestResult

func main() {
	flag.Parse()
	signal.Notify(signals, os.Interrupt)

	if *isup {
		status := 1
		if IsUp() {
			status = 0
			log.Printf("Cluster is UP")
		} else {
			log.Printf("Cluster is DOWN")
		}
		os.Exit(status)
	}

	if *build {
		// The build-release script needs stdin to ask the user whether
		// it's OK to download the docker image.
		cmd := exec.Command(path.Join(*root, "hack/e2e-internal/build-release.sh"))
		cmd.Stdin = os.Stdin
		if !finishRunning("build-release", cmd) {
			log.Fatal("Error building. Aborting.")
		}
	}

	if *version != "" {
		// If the desired version isn't available already, do whatever's needed
		// to make it available. Once done, update the root directory for client
		// tools to be the root of the release directory so that the given
		// release's tools will be used. We can't use this new root for
		// everything because it likely doesn't have the hack/ directory in it.
		if newVersionRoot, err := PrepareVersion(*version); err != nil {
			log.Fatalf("Error preparing a binary of version %s: %s. Aborting.", *version, err)
		} else {
			versionRoot = newVersionRoot
			os.Setenv("KUBE_VERSION_ROOT", newVersionRoot)
		}
	}

	os.Setenv("KUBECTL", versionRoot+`/cluster/kubectl.sh`+kubectlArgs())

	if *pushup {
		if IsUp() {
			log.Printf("e2e cluster is up, pushing.")
			*up = false
			*push = true
		} else {
			log.Printf("e2e cluster is down, creating.")
			*up = true
			*push = false
		}
	}
	if *up {
		if !Up() {
			log.Fatal("Error starting e2e cluster. Aborting.")
		}
	} else if *push {
		if !finishRunning("push", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-push.sh"))) {
			log.Fatal("Error pushing e2e cluster. Aborting.")
		}
	}

	success := true
	switch {
	case *ctlCmd != "":
		ctlArgs := strings.Fields(*ctlCmd)
		os.Setenv("KUBE_CONFIG_FILE", "config-test.sh")
		success = finishRunning("'kubectl "+*ctlCmd+"'", exec.Command(path.Join(versionRoot, "cluster/kubectl.sh"), ctlArgs...))
	case *test:
		success = Test()
	}

	if *down {
		TearDown()
	}

	if !success {
		os.Exit(1)
	}
}

func TearDown() bool {
	return finishRunning("teardown", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-down.sh")))
}

// Up brings an e2e cluster up, recreating it if one is already running.
func Up() bool {
	if IsUp() {
		log.Printf("e2e cluster already running; will teardown")
		if res := TearDown(); !res {
			return false
		}
	}

	return finishRunning("up", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-up.sh")))
}

// Ensure that the cluster is large engough to run the e2e tests.
func ValidateClusterSize() {
	// Check that there are at least 3 minions running
	res, stdout, _ := finishRunningWithOutputs("validate cluster size", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-watch-events.sh")))
	if !res {
		log.Fatal("Could not get nodes to validate cluster size")
	}

	numNodes, err := strconv.Atoi(strings.TrimSpace(stdout))
	if err != nil {
		log.Fatalf("Could not count number of nodes to validate cluster size (%s)", err)
	}

	if numNodes < minMinionCount {
		log.Fatalf("Cluster size (%d) is too small to run e2e tests.  %d Minions are required.", numNodes, minMinionCount)
	}
}

// Is the e2e cluster up?
func IsUp() bool {
	return finishRunning("get status", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-status.sh")))
}

// PrepareVersion makes sure that the specified release version is locally
// available and ready to be used by kube-up or kube-push. Returns the director
// path of the release.
func PrepareVersion(version string) (string, error) {
	if version == "" {
		// Assume that the build flag already handled building a local binary.
		return *root, nil
	}

	// If the version isn't a local build, try fetching the release from Google
	// Cloud Storage.
	downloadDir := filepath.Join(*root, downloadDirName)
	if err := os.MkdirAll(downloadDir, 0755); err != nil {
		return "", err
	}
	localReleaseDir := filepath.Join(downloadDir, version)
	if err := os.MkdirAll(localReleaseDir, 0755); err != nil {
		return "", err
	}

	remoteReleaseTar := fmt.Sprintf("https://storage.googleapis.com/kubernetes-release/release/%s/kubernetes.tar.gz", version)
	localReleaseTar := filepath.Join(downloadDir, fmt.Sprintf("kubernetes-%s.tar.gz", version))
	if _, err := os.Stat(localReleaseTar); os.IsNotExist(err) {
		out, err := os.Create(localReleaseTar)
		if err != nil {
			return "", err
		}
		resp, err := http.Get(remoteReleaseTar)
		if err != nil {
			out.Close()
			return "", err
		}
		defer resp.Body.Close()
		io.Copy(out, resp.Body)
		if err != nil {
			out.Close()
			return "", err
		}
		out.Close()
	}
	if !finishRunning("untarRelease", exec.Command("tar", "-C", localReleaseDir, "-zxf", localReleaseTar, "--strip-components=1")) {
		log.Fatal("Failed to untar release. Aborting.")
	}
	// Now that we have the binaries saved locally, use the path to the untarred
	// directory as the "root" path for future operations.
	return localReleaseDir, nil
}

// Fisher-Yates shuffle using the given RNG r
func shuffleStrings(strings []string, r *rand.Rand) {
	for i := len(strings) - 1; i > 0; i-- {
		j := r.Intn(i + 1)
		strings[i], strings[j] = strings[j], strings[i]
	}
}

func Test() bool {
	defer runBashUntil("watchEvents", exec.Command(filepath.Join(*root, "hack/e2e-internal/e2e-watch-events.sh")))()

	if !IsUp() {
		log.Fatal("Testing requested, but e2e cluster not up!")
	}

	ValidateClusterSize()

	return finishRunning("Ginkgo tests", exec.Command(filepath.Join(*root, "hack/ginkgo-e2e.sh")))
}

// All nonsense below is temporary until we have go versions of these things.

// call the returned anonymous function to stop.
func runBashUntil(stepName string, cmd *exec.Cmd) func() {
	log.Printf("Running in background: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	cmd.Stdout, cmd.Stderr = stdout, stderr
	if err := cmd.Start(); err != nil {
		log.Printf("Unable to start '%v': '%v'", stepName, err)
		return func() {}
	}
	return func() {
		cmd.Process.Signal(os.Interrupt)
		headerprefix := stepName + " "
		lineprefix := "  "
		printBashOutputs(headerprefix, lineprefix, string(stdout.Bytes()), string(stderr.Bytes()), false)
	}
}

func finishRunningWithOutputs(stepName string, cmd *exec.Cmd) (bool, string, string) {
	log.Printf("Running: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	if *verbose {
		cmd.Stdout = io.MultiWriter(os.Stdout, stdout)
		cmd.Stderr = io.MultiWriter(os.Stderr, stderr)
	} else {
		cmd.Stdout = stdout
		cmd.Stderr = stderr
	}

	done := make(chan struct{})
	defer close(done)
	go func() {
		for {
			select {
			case <-done:
				return
			case s := <-signals:
				cmd.Process.Signal(s)
			}
		}
	}()

	if err := cmd.Run(); err != nil {
		log.Printf("Error running %v: %v", stepName, err)
		return false, string(stdout.Bytes()), string(stderr.Bytes())
	}
	return true, string(stdout.Bytes()), string(stderr.Bytes())
}

func finishRunning(stepName string, cmd *exec.Cmd) bool {
	result, _, _ := finishRunningWithOutputs(stepName, cmd)
	return result
}

func printBashOutputs(headerprefix, lineprefix, stdout, stderr string, escape bool) {
	// The |'s (plus appropriate prefixing) are to make this look
	// "YAMLish" to the Jenkins TAP plugin:
	//   https://wiki.jenkins-ci.org/display/JENKINS/TAP+Plugin
	if stdout != "" {
		fmt.Printf("%vstdout: |\n", headerprefix)
		if escape {
			stdout = escapeOutput(stdout)
		}
		printPrefixedLines(lineprefix, stdout)
	}
	if stderr != "" {
		fmt.Printf("%vstderr: |\n", headerprefix)
		if escape {
			stderr = escapeOutput(stderr)
		}
		printPrefixedLines(lineprefix, stderr)
	}
}

// Escape stdout/stderr so the Jenkins YAMLish parser doesn't barf on
// it. This escaping is crude (it masks all colons as something humans
// will hopefully see as a colon, for instance), but it should get the
// job done without pulling in a whole YAML package.
func escapeOutput(s string) (out string) {
	for _, r := range s {
		switch {
		case r == '\n':
			out += string(r)
		case !strconv.IsPrint(r):
			out += " "
		case r == ':':
			out += "\ua789" // "꞉", modifier letter colon
		default:
			out += string(r)
		}
	}
	return
}

func printPrefixedLines(prefix, s string) {
	for _, line := range strings.Split(s, "\n") {
		fmt.Printf("%v%v\n", prefix, line)
	}
}

// returns either "", or a list of args intended for appending with the
// kubectl command (begining with a space).
func kubectlArgs() string {
	if *checkVersionSkew {
		return " --match-server-version"
	}
	return ""
}
<commit_msg>Clean up output handling in hack/e2e.go<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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.
*/

// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	isup             = flag.Bool("isup", false, "Check to see if the e2e cluster is up, then exit.")
	build            = flag.Bool("build", false, "If true, build a new release. Otherwise, use whatever is there.")
	version          = flag.String("version", "", "The version to be tested (including the leading 'v'). An empty string defaults to the local build, but it can be set to any release (e.g. v0.4.4, v0.6.0).")
	up               = flag.Bool("up", false, "If true, start the the e2e cluster. If cluster is already up, recreate it.")
	push             = flag.Bool("push", false, "If true, push to e2e cluster. Has no effect if -up is true.")
	pushup           = flag.Bool("pushup", false, "If true, push to e2e cluster if it's up, otherwise start the e2e cluster.")
	down             = flag.Bool("down", false, "If true, tear down the cluster before exiting.")
	test             = flag.Bool("test", false, "Run Ginkgo tests.")
	root             = flag.String("root", absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))), "Root directory of kubernetes repository.")
	verbose          = flag.Bool("v", false, "If true, print all command output.")
	checkVersionSkew = flag.Bool("check_version_skew", true, ""+
		"By default, verify that client and server have exact version match. "+
		"You can explicitly set to false if you're, e.g., testing client changes "+
		"for which the server version doesn't make a difference.")

	ctlCmd = flag.String("ctl", "", "If nonempty, pass this as an argument, and call kubectl. Implies -v. (-test, -cfg, -ctl are mutually exclusive)")
)

const (
	serverTarName   = "kubernetes-server-linux-amd64.tar.gz"
	saltTarName     = "kubernetes-salt.tar.gz"
	downloadDirName = "_output/downloads"
	tarDirName      = "server"
	tempDirName     = "upgrade-e2e-temp-dir"
	minMinionCount  = 2
)

var (
	signals = make(chan os.Signal, 100)
	// Root directory of the specified cluster version, rather than of where
	// this script is being run from.
	versionRoot = *root
)

func absOrDie(path string) string {
	out, err := filepath.Abs(path)
	if err != nil {
		panic(err)
	}
	return out
}

type TestResult struct {
	Pass int
	Fail int
}

type ResultsByTest map[string]TestResult

func main() {
	flag.Parse()
	signal.Notify(signals, os.Interrupt)

	if *isup {
		status := 1
		if IsUp() {
			status = 0
			log.Printf("Cluster is UP")
		} else {
			log.Printf("Cluster is DOWN")
		}
		os.Exit(status)
	}

	if *build {
		// The build-release script needs stdin to ask the user whether
		// it's OK to download the docker image.
		cmd := exec.Command(path.Join(*root, "hack/e2e-internal/build-release.sh"))
		cmd.Stdin = os.Stdin
		if !finishRunning("build-release", cmd) {
			log.Fatal("Error building. Aborting.")
		}
	}

	if *version != "" {
		// If the desired version isn't available already, do whatever's needed
		// to make it available. Once done, update the root directory for client
		// tools to be the root of the release directory so that the given
		// release's tools will be used. We can't use this new root for
		// everything because it likely doesn't have the hack/ directory in it.
		if newVersionRoot, err := PrepareVersion(*version); err != nil {
			log.Fatalf("Error preparing a binary of version %s: %s. Aborting.", *version, err)
		} else {
			versionRoot = newVersionRoot
			os.Setenv("KUBE_VERSION_ROOT", newVersionRoot)
		}
	}

	os.Setenv("KUBECTL", versionRoot+`/cluster/kubectl.sh`+kubectlArgs())

	if *pushup {
		if IsUp() {
			log.Printf("e2e cluster is up, pushing.")
			*up = false
			*push = true
		} else {
			log.Printf("e2e cluster is down, creating.")
			*up = true
			*push = false
		}
	}
	if *up {
		if !Up() {
			log.Fatal("Error starting e2e cluster. Aborting.")
		}
	} else if *push {
		if !finishRunning("push", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-push.sh"))) {
			log.Fatal("Error pushing e2e cluster. Aborting.")
		}
	}

	success := true
	switch {
	case *ctlCmd != "":
		ctlArgs := strings.Fields(*ctlCmd)
		os.Setenv("KUBE_CONFIG_FILE", "config-test.sh")
		success = finishRunning("'kubectl "+*ctlCmd+"'", exec.Command(path.Join(versionRoot, "cluster/kubectl.sh"), ctlArgs...))
	case *test:
		success = Test()
	}

	if *down {
		TearDown()
	}

	if !success {
		os.Exit(1)
	}
}

func TearDown() bool {
	return finishRunning("teardown", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-down.sh")))
}

// Up brings an e2e cluster up, recreating it if one is already running.
func Up() bool {
	if IsUp() {
		log.Printf("e2e cluster already running; will teardown")
		if res := TearDown(); !res {
			return false
		}
	}

	return finishRunning("up", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-up.sh")))
}

// Ensure that the cluster is large engough to run the e2e tests.
func ValidateClusterSize() {
	// Check that there are at least 3 minions running
	res, stdout, _ := finishRunningWithOutputs("validate cluster size", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-watch-events.sh")))
	if !res {
		log.Fatal("Could not get nodes to validate cluster size")
	}

	numNodes, err := strconv.Atoi(strings.TrimSpace(stdout))
	if err != nil {
		log.Fatalf("Could not count number of nodes to validate cluster size (%s)", err)
	}

	if numNodes < minMinionCount {
		log.Fatalf("Cluster size (%d) is too small to run e2e tests.  %d Minions are required.", numNodes, minMinionCount)
	}
}

// Is the e2e cluster up?
func IsUp() bool {
	return finishRunning("get status", exec.Command(path.Join(*root, "hack/e2e-internal/e2e-status.sh")))
}

// PrepareVersion makes sure that the specified release version is locally
// available and ready to be used by kube-up or kube-push. Returns the director
// path of the release.
func PrepareVersion(version string) (string, error) {
	if version == "" {
		// Assume that the build flag already handled building a local binary.
		return *root, nil
	}

	// If the version isn't a local build, try fetching the release from Google
	// Cloud Storage.
	downloadDir := filepath.Join(*root, downloadDirName)
	if err := os.MkdirAll(downloadDir, 0755); err != nil {
		return "", err
	}
	localReleaseDir := filepath.Join(downloadDir, version)
	if err := os.MkdirAll(localReleaseDir, 0755); err != nil {
		return "", err
	}

	remoteReleaseTar := fmt.Sprintf("https://storage.googleapis.com/kubernetes-release/release/%s/kubernetes.tar.gz", version)
	localReleaseTar := filepath.Join(downloadDir, fmt.Sprintf("kubernetes-%s.tar.gz", version))
	if _, err := os.Stat(localReleaseTar); os.IsNotExist(err) {
		out, err := os.Create(localReleaseTar)
		if err != nil {
			return "", err
		}
		resp, err := http.Get(remoteReleaseTar)
		if err != nil {
			out.Close()
			return "", err
		}
		defer resp.Body.Close()
		io.Copy(out, resp.Body)
		if err != nil {
			out.Close()
			return "", err
		}
		out.Close()
	}
	if !finishRunning("untarRelease", exec.Command("tar", "-C", localReleaseDir, "-zxf", localReleaseTar, "--strip-components=1")) {
		log.Fatal("Failed to untar release. Aborting.")
	}
	// Now that we have the binaries saved locally, use the path to the untarred
	// directory as the "root" path for future operations.
	return localReleaseDir, nil
}

// Fisher-Yates shuffle using the given RNG r
func shuffleStrings(strings []string, r *rand.Rand) {
	for i := len(strings) - 1; i > 0; i-- {
		j := r.Intn(i + 1)
		strings[i], strings[j] = strings[j], strings[i]
	}
}

func Test() bool {
	defer runBashUntil("watchEvents", exec.Command(filepath.Join(*root, "hack/e2e-internal/e2e-watch-events.sh")))()

	if !IsUp() {
		log.Fatal("Testing requested, but e2e cluster not up!")
	}

	ValidateClusterSize()

	return finishRunning("Ginkgo tests", exec.Command(filepath.Join(*root, "hack/ginkgo-e2e.sh")))
}

// All nonsense below is temporary until we have go versions of these things.

// call the returned anonymous function to stop.
func runBashUntil(stepName string, cmd *exec.Cmd) func() {
	log.Printf("Running in background: %v", stepName)
	output := bytes.NewBuffer(nil)
	cmd.Stdout, cmd.Stderr = output, output
	if err := cmd.Start(); err != nil {
		log.Printf("Unable to start '%v': '%v'", stepName, err)
		return func() {}
	}
	return func() {
		cmd.Process.Signal(os.Interrupt)
		headerprefix := stepName + " "
		lineprefix := "  "
		printBashOutputs(headerprefix, lineprefix, string(output.Bytes()), false)
	}
}

func finishRunningWithOutputs(stepName string, cmd *exec.Cmd) (bool, string, string) {
	log.Printf("Running: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	if *verbose {
		cmd.Stdout = io.MultiWriter(os.Stdout, stdout)
		cmd.Stderr = io.MultiWriter(os.Stderr, stderr)
	} else {
		cmd.Stdout = stdout
		cmd.Stderr = stderr
	}

	done := make(chan struct{})
	defer close(done)
	go func() {
		for {
			select {
			case <-done:
				return
			case s := <-signals:
				cmd.Process.Signal(s)
			}
		}
	}()

	if err := cmd.Run(); err != nil {
		log.Printf("Error running %v: %v", stepName, err)
		return false, string(stdout.Bytes()), string(stderr.Bytes())
	}
	return true, string(stdout.Bytes()), string(stderr.Bytes())
}

func finishRunning(stepName string, cmd *exec.Cmd) bool {
	result, _, _ := finishRunningWithOutputs(stepName, cmd)
	return result
}

func printBashOutputs(headerprefix, lineprefix, output string, escape bool) {
	if output != "" {
		fmt.Printf("%voutput: |\n", headerprefix)
		printPrefixedLines(lineprefix, output)
	}
}

func printPrefixedLines(prefix, s string) {
	for _, line := range strings.Split(s, "\n") {
		fmt.Printf("%v%v\n", prefix, line)
	}
}

// returns either "", or a list of args intended for appending with the
// kubectl command (begining with a space).
func kubectlArgs() string {
	if *checkVersionSkew {
		return " --match-server-version"
	}
	return ""
}
<|endoftext|>


<commit_before>/*
Package handlers is a small library of utility http handlers useful for
building web applications. It includes a NotFoundHandler, an ErrorHandler,
and a FileHandler.

The NotFoundHandler and ErrorHandler provide a simple way to respond to the
client with custom 404 and 500 status pages: create your own application
specific error page templates and call one of handler's Serve methods with
the appropriate arguments.

These two handlers are intended to be used indirectly, from inside other
handlers where server errors or page not found errors occur and you need
to inform the client. However, both of these types implement the http.Handler
interface with a ServeHTTP method, which shows their default behaviour when
bound to a specific route.

The FileHandler provides similar functionality to the FileServer in the
net/http package, but with two differences: it will not show directory
listings for directories under its path, and it will respond to any request
for a non-existent file with the given NotFoundHandler.
*/
package handlers

import (
	"log"
	"os"
	"strings"
	"path/filepath"
	"net/http"
	"html/template"
)

// ErrorMessage holds the message passed to the error template. The template
// can access the message field with the {{.ErrorMessage}} tag.
type ErrorMessage struct {

	ErrorMessage string
}

// ErrorHandler serves error messages with the given template. The template
// can access the message served by the handler with the {{.ErrorMessage}} tag.
type ErrorHandler struct {

	template *template.Template
	defaultMessage string
	displayErrors bool
}

// NewErrorHandler returns a new ErrorHandler with the handler values initialised.
// The handler uses the given template to print an error message. The template
// must display {{.ErrorMessage}}. The default error message is set to message.
// The display argument controls whether error messages passed to the handler's
// ServeError function are shown to the user on the error page, or whether the
// default error message is shown instead. This allows detailed error messages
// to be printed to the screen during development, but turned off in production.
// The handler's AlwaysServeError method forces the display of a particular
// error message even if displayErrors is set to false.
func NewErrorHandler(template *template.Template, defaultMessage string, displayErrors bool) *ErrorHandler {

	return &ErrorHandler{

		template: template,
		defaultMessage: defaultMessage,
		displayErrors: displayErrors,
	}
}

// LoadErrorHandler is a convenience function that returns a new ErrorHandler
// using the template file specified by tpath. The function first loads the
// template and then creates the ErrorHandler using NewErrorHandler.
func LoadErrorHandler(templatePath string, defaultMessage string, displayErrors bool) *ErrorHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewErrorHandler(template, defaultMessage, displayErrors)
}

// ServeError serves the appropriate error message in the error template
// depending on the value of displayErrors. If displayErrors is true then
// the given message is shown, otherwise the default error message is shown.
func (h *ErrorHandler) ServeError(w http.ResponseWriter, message string) {

	var templateData *ErrorMessage

	if h.displayErrors {

		templateData = &ErrorMessage{message}

	} else {

		templateData = &ErrorMessage{h.defaultMessage}
	}

	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// AlwaysServeError serves the given error message in the error template.
// This method overrides the default error message, irrespective of whether
// displayErrors is false, and ensures that the given message is always shown.
func (h *ErrorHandler) AlwaysServeError(w http.ResponseWriter, message string) {

	templateData := &ErrorMessage{message}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// Serve HTTP serves the default error message in the error template.
func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &ErrorMessage{h.defaultMessage}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// NotFoundData holds the path passed to the handler's template. The template
// can access the message field with the {{.Path}} tag.
type NotFoundData struct {

	Path string
}

// NotFoundHandler serves a 404 with the given template. The template
// can access the path to the file not found with {{.Path}} tag.
type NotFoundHandler struct {

	template *template.Template
}

// NewNotFoundHandler returns a new NotFoundHandler with the handler values
// initialised. The handler uses the given template to print the path to the
// file not found with a 404. The template must display {{.Path}}.
func NewNotFoundHandler(template *template.Template) *NotFoundHandler {

	return &NotFoundHandler{

		template: template,
	}
}

// LoadNotFoundHandler is a convenience function that returns a new NotFoundHandler
// using the template file specified by tpath. The function first loads the
// template and then creates the NotFoundHandler using NewNotFoundHandler.
func LoadNotFoundHandler(templatePath string) *NotFoundHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewNotFoundHandler(template)
}

// Serve HTTP serves the path in the handler's template.
func (h *NotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &NotFoundData{r.URL.Path}
	w.WriteHeader(http.StatusNotFound)
	h.template.Execute(w, templateData)
	return
}

// FileHandler serves files requested under the given url path from the given
// directory. The url path should be the same as the path to which the handler
// is bound with htp.Handle. If the file is not found the handler serves a 404
// using the given notFoundHandler. The notFoundHandler can be any Handler, but
// its ServeHTTP method should return a 404. Unlike Go's built-in FileServer,
// FileHandler will not return directory listings for directories without an
// index.html and will instead respond with a 404.
type FileHandler struct {

	urlPath string
	directory string
	notFoundHandler http.Handler
}

// FileHandler returns a new FileHandler with the handler values initialised.
func NewFileHandler(urlPath string, directory string, notFoundHandler http.Handler) *FileHandler {

	return &FileHandler{

		urlPath: urlPath,
		directory: directory,
		notFoundHandler: notFoundHandler,
	}
}

// Serve HTTP serves the path in the handler's template.
func (h *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	const indexPage string = "index.html"

	var(
		requestPath string = r.URL.Path[len(h.urlPath)-1:]
		filePath string
	)

	// If the request path ends in "/" ...
	if strings.HasSuffix(r.URL.Path, "/") {

		// Set the target filepath to index.html
		filePath = h.directory + filepath.FromSlash(requestPath + indexPage)

	} else {

		// Otherwise set the target filepath to the named file
		filePath = h.directory + filepath.FromSlash(requestPath)
	}

	// Try to get file info
	finfo, err := os.Stat(filePath)

	// If Stat fails return a 404
	if err != nil {

		h.notFoundHandler.ServeHTTP(w, r)
		return
	}

	// Check the mode to ensure the target filepath is a file
	switch mode := finfo.Mode(); {

	// If the target file is a directory redirect to the path with a slash
	case mode.IsDir():

		http.Redirect(w, r, r.URL.Path + "/", http.StatusFound)

	// Otherwise serve the file
	case mode.IsRegular():

		http.ServeFile(w, r, filePath)
	}

	return
}
	<commit_msg>1.0.0<commit_after>/*
Package handlers is a small library of utility http handlers useful for
building web applications. It includes a NotFoundHandler, an ErrorHandler,
and a FileHandler.

The NotFoundHandler and ErrorHandler provide a simple way to respond to the
client with custom 404 and 500 status pages: create your own application
specific error page templates and call one of handler's Serve methods with
the appropriate arguments.

These two handlers are intended to be used indirectly, from inside other
handlers where server errors or page not found errors occur and you need
to inform the client. However, both of these types implement the http.Handler
interface with a ServeHTTP method, which shows their default behaviour when
bound to a specific route.

The FileHandler provides similar functionality to the FileServer in the
net/http package, but with two differences: it will not show directory
listings for directories under its path, and it will respond to any request
for a non-existent file with the given NotFoundHandler.
*/
package handlers

import (
	"log"
	"os"
	"strings"
	"path/filepath"
	"net/http"
	"html/template"
)

// ErrorMessage holds the message passed to the error template. The template
// can access the message field with the {{.ErrorMessage}} tag.
type ErrorMessage struct {

	ErrorMessage string
}

// ErrorHandler serves error messages with the given template. The template
// can access the message served by the handler with the {{.ErrorMessage}} tag.
type ErrorHandler struct {

	template *template.Template
	defaultMessage string
	displayErrors bool
}

// NewErrorHandler returns a new ErrorHandler with the handler values initialised.
// The handler uses the given template to print an error message. The template
// must display {{.ErrorMessage}}. The default error message is set to message.
// The display argument controls whether error messages passed to the handler's
// ServeError function are shown to the user on the error page, or whether the
// default error message is shown instead. This allows detailed error messages
// to be printed to the screen during development, but turned off in production.
// The handler's AlwaysServeError method forces the display of a particular
// error message even if displayErrors is set to false.
func NewErrorHandler(template *template.Template, defaultMessage string, displayErrors bool) *ErrorHandler {

	return &ErrorHandler{

		template: template,
		defaultMessage: defaultMessage,
		displayErrors: displayErrors,
	}
}

// LoadErrorHandler is a convenience function that returns a new ErrorHandler
// using the template file specified by tpath. The function first loads the
// template and then creates the ErrorHandler using NewErrorHandler.
func LoadErrorHandler(templatePath string, defaultMessage string, displayErrors bool) *ErrorHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewErrorHandler(template, defaultMessage, displayErrors)
}

// ServeError serves the appropriate error message in the error template
// depending on the value of displayErrors. If displayErrors is true then
// the given message is shown, otherwise the default error message is shown.
func (h *ErrorHandler) ServeError(w http.ResponseWriter, message string) {

	var templateData *ErrorMessage

	if h.displayErrors {

		templateData = &ErrorMessage{message}

	} else {

		templateData = &ErrorMessage{h.defaultMessage}
	}

	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// AlwaysServeError serves the given error message in the error template.
// This method overrides the default error message, irrespective of whether
// displayErrors is false, and ensures that the given message is always shown.
func (h *ErrorHandler) AlwaysServeError(w http.ResponseWriter, message string) {

	templateData := &ErrorMessage{message}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// Serve HTTP serves the default error message in the error template.
func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &ErrorMessage{h.defaultMessage}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// NotFoundData holds the path passed to the handler's template. The template
// can access the message field with the {{.Path}} tag.
type NotFoundData struct {

	Path string
}

// NotFoundHandler serves a 404 with the given template. The template
// can access the path to the file not found with {{.Path}} tag.
type NotFoundHandler struct {

	template *template.Template
}

// NewNotFoundHandler returns a new NotFoundHandler with the handler values
// initialised. The handler uses the given template to print the path to the
// file not found with a 404. The template must display {{.Path}}.
func NewNotFoundHandler(template *template.Template) *NotFoundHandler {

	return &NotFoundHandler{

		template: template,
	}
}

// LoadNotFoundHandler is a convenience function that returns a new NotFoundHandler
// using the template file specified by tpath. The function first loads the
// template and then creates the NotFoundHandler using NewNotFoundHandler.
func LoadNotFoundHandler(templatePath string) *NotFoundHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewNotFoundHandler(template)
}

// Serve HTTP serves the path in the handler's template.
func (h *NotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &NotFoundData{r.URL.Path}
	w.WriteHeader(http.StatusNotFound)
	h.template.Execute(w, templateData)
	return
}

// FileHandler serves files requested under the given url path from the given
// directory. The url path should be the same as the path to which the handler
// is bound with http.Handle. If the file is not found the handler serves a 404
// using the given notFoundHandler. The notFoundHandler can be any Handler, but
// its ServeHTTP method should return a 404. Unlike Go's built-in FileServer,
// FileHandler will not return directory listings for directories without an
// index.html and will instead respond with a 404.
type FileHandler struct {

	urlPath string
	directory string
	notFoundHandler http.Handler
}

// FileHandler returns a new FileHandler with the handler values initialised.
func NewFileHandler(urlPath string, directory string, notFoundHandler http.Handler) *FileHandler {

	return &FileHandler{

		urlPath: urlPath,
		directory: directory,
		notFoundHandler: notFoundHandler,
	}
}

// Serve HTTP serves the path in the handler's template.
func (h *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	const indexPage string = "index.html"

	var(
		requestPath string = r.URL.Path[len(h.urlPath)-1:]
		filePath string
	)

	// If the request path ends in "/" ...
	if strings.HasSuffix(r.URL.Path, "/") {

		// Set the target filepath to index.html
		filePath = h.directory + filepath.FromSlash(requestPath + indexPage)

	} else {

		// Otherwise set the target filepath to the named file
		filePath = h.directory + filepath.FromSlash(requestPath)
	}

	// Try to get file info
	finfo, err := os.Stat(filePath)

	// If Stat fails return a 404
	if err != nil {

		h.notFoundHandler.ServeHTTP(w, r)
		return
	}

	// Check the mode to ensure the target filepath is a file
	switch mode := finfo.Mode(); {

	// If the target file is a directory redirect to the path with a slash
	case mode.IsDir():

		http.Redirect(w, r, r.URL.Path + "/", http.StatusFound)

	// Otherwise serve the file
	case mode.IsRegular():

		http.ServeFile(w, r, filePath)
	}

	return
}
	<|endoftext|>


<commit_before>package core

import "net/http"

type handlersStack []func(*Context)

var handlers handlersStack

// Use adds a handler to the handlers stack.
func Use(h func(*Context)) {
	handlers = append(handlers, h)
}

func (h handlersStack) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Init a new context for the request.
	c := &Context{
		ResponseWriter: w,
		Request:        r,
		index:          -1, // Begin with -1 because the NextWriter will increment index before calling the first handler.
	}

	// Enter the handlers stack.
	// We use a binder to set the c.written flag on first write and break handlers chain.
	c.ResponseWriter = ResponseWriterBinder{
		Writer:         c.ResponseWriter,
		ResponseWriter: c.ResponseWriter,
		BeforeWrite:    func([]byte) { c.written = true },
	}

	// Use default headers.
	c.ResponseWriter.Header().Set("Connection", "keep-alive")
	c.ResponseWriter.Header().Set("Vary", "Accept-Encoding")

	c.Next()
}
<commit_msg>Set no-cache header by default<commit_after>package core

import "net/http"

type handlersStack []func(*Context)

var handlers handlersStack

// Use adds a handler to the handlers stack.
func Use(h func(*Context)) {
	handlers = append(handlers, h)
}

func (h handlersStack) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Init a new context for the request.
	c := &Context{
		ResponseWriter: w,
		Request:        r,
		index:          -1, // Begin with -1 because the NextWriter will increment index before calling the first handler.
	}

	// Enter the handlers stack.
	// We use a binder to set the c.written flag on first write and break handlers chain.
	c.ResponseWriter = ResponseWriterBinder{
		Writer:         c.ResponseWriter,
		ResponseWriter: c.ResponseWriter,
		BeforeWrite:    func([]byte) { c.written = true },
	}

	// Use default headers.
	c.ResponseWriter.Header().Set("Cache-Control", "no-cache")
	c.ResponseWriter.Header().Set("Connection", "keep-alive")
	c.ResponseWriter.Header().Set("Vary", "Accept-Encoding")

	c.Next()
}
<|endoftext|>


<commit_before>// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package girc

import (
	"strings"
	"time"
)

// registerHandlers sets up built-in callbacks/helpers, based on client
// configuration.
func (c *Client) registerHandlers() {
	c.debug.Print("registering built-in handlers")
	c.Callbacks.mu.Lock()

	// Built-in things that should always be supported.
	c.Callbacks.register(true, RPL_WELCOME, CallbackFunc(func(c *Client, e Event) {
		go handleConnect(c, e)
	}))
	c.Callbacks.register(true, PING, CallbackFunc(handlePING))

	if !c.config.DisableTracking {
		// Joins/parts/anything that may add/remove/rename users.
		c.Callbacks.register(true, JOIN, CallbackFunc(handleJOIN))
		c.Callbacks.register(true, PART, CallbackFunc(handlePART))
		c.Callbacks.register(true, KICK, CallbackFunc(handleKICK))
		c.Callbacks.register(true, QUIT, CallbackFunc(handleQUIT))
		c.Callbacks.register(true, NICK, CallbackFunc(handleNICK))
		c.Callbacks.register(true, RPL_NAMREPLY, CallbackFunc(handleNAMES))

		// Modes.
		c.Callbacks.register(true, MODE, CallbackFunc(handleMODE))
		c.Callbacks.register(true, RPL_CHANNELMODEIS, CallbackFunc(handleMODE))

		// WHO/WHOX responses.
		c.Callbacks.register(true, RPL_WHOREPLY, CallbackFunc(handleWHO))
		c.Callbacks.register(true, RPL_WHOSPCRPL, CallbackFunc(handleWHO))

		// Other misc. useful stuff.
		c.Callbacks.register(true, TOPIC, CallbackFunc(handleTOPIC))
		c.Callbacks.register(true, RPL_TOPIC, CallbackFunc(handleTOPIC))
		c.Callbacks.register(true, RPL_MYINFO, CallbackFunc(handleMYINFO))
		c.Callbacks.register(true, RPL_ISUPPORT, CallbackFunc(handleISUPPORT))
		c.Callbacks.register(true, RPL_MOTDSTART, CallbackFunc(handleMOTD))
		c.Callbacks.register(true, RPL_MOTD, CallbackFunc(handleMOTD))
	}

	// Nickname collisions.
	if !c.config.DisableNickCollision {
		c.Callbacks.register(true, ERR_NICKNAMEINUSE, CallbackFunc(nickCollisionHandler))
		c.Callbacks.register(true, ERR_NICKCOLLISION, CallbackFunc(nickCollisionHandler))
		c.Callbacks.register(true, ERR_UNAVAILRESOURCE, CallbackFunc(nickCollisionHandler))
	}

	// CAP IRCv3-specific tracking and functionality.
	if !c.config.DisableTracking && !c.config.DisableCapTracking {
		c.Callbacks.register(true, CAP, CallbackFunc(handleCAP))
		c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST))
		c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY))
		c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT))
		c.Callbacks.register(true, ALLEVENTS, CallbackFunc(handleTags))
	}

	c.Callbacks.mu.Unlock()
}

// handleConnect is a helper function which lets the client know that enough
// time has passed and now they can send commands.
//
// Should always run in separate thread due to blocking delay.
func handleConnect(c *Client, e Event) {
	// This should be the nick that the server gives us. 99% of the time, it's
	// the one we supplied during connection, but some networks will rename
	// users on connect.
	if len(e.Params) > 0 {
		c.state.nick = e.Params[0]
	}

	time.Sleep(2 * time.Second)

	c.Events <- &Event{Command: CONNECTED, Trailing: c.Server()}
}

// nickCollisionHandler helps prevent the client from having conflicting
// nicknames with another bot, user, etc.
func nickCollisionHandler(c *Client, e Event) {
	c.Nick(c.GetNick() + "_")
}

// handlePING helps respond to ping requests from the server.
func handlePING(c *Client, e Event) {
	c.Pong(e.Trailing)
}

// handleJOIN ensures that the state has updated users and channels.
func handleJOIN(c *Client, e Event) {
	if len(e.Params) < 1 {
		return
	}

	// Create the user in state. 2This will also verify the channel.
	c.state.mu.Lock()
	user := c.state.createUserIfNotExists(e.Params[0], e.Source.Name)
	c.state.mu.Unlock()
	if user == nil {
		return
	}

	// Assume extended-join (ircv3).
	if len(e.Params) == 2 {
		if e.Params[1] != "*" {
			user.Extras.Account = e.Params[1]
		}

		if len(e.Trailing) > 0 {
			user.Extras.Name = e.Trailing
		}
	}

	if e.Source.Name == c.GetNick() {
		// If it's us, don't just add our user to the list. Run a WHO which
		// will tell us who exactly is in the entire channel.
		c.Send(&Event{Command: WHO, Params: []string{e.Params[0], "%tacuhnr,1"}})

		// Also send a MODE to obtain the list of channel modes.
		c.Send(&Event{Command: MODE, Params: []string{e.Params[0]}})
		return
	}

	// Only WHO the user, which is more efficient.
	c.Send(&Event{Command: WHO, Params: []string{e.Source.Name, "%tacuhnr,1"}})
}

// handlePART ensures that the state is clean of old user and channel entries.
func handlePART(c *Client, e Event) {
	if len(e.Params) == 0 {
		return
	}

	if e.Source.Name == c.GetNick() {
		c.state.mu.Lock()
		c.state.deleteChannel(e.Params[0])
		c.state.mu.Unlock()
		return
	}

	c.state.mu.Lock()
	c.state.deleteUser(e.Source.Name)
	c.state.mu.Unlock()
}

// handleTOPIC handles incoming TOPIC events and keeps channel tracking info
// updated with the latest channel topic.
func handleTOPIC(c *Client, e Event) {
	var name string
	switch len(e.Params) {
	case 0:
		return
	case 1:
		name = e.Params[0]
	default:
		name = e.Params[len(e.Params)-1]
	}

	c.state.mu.Lock()
	channel := c.state.createChanIfNotExists(name)
	if channel == nil {
		c.state.mu.Unlock()
		return
	}

	channel.Topic = e.Trailing
	c.state.mu.Unlock()
}

// handlWHO updates our internal tracking of users/channels with WHO/WHOX
// information.
func handleWHO(c *Client, e Event) {
	var channel, ident, host, nick, account string

	// Assume WHOX related.
	if e.Command == RPL_WHOSPCRPL {
		if len(e.Params) != 7 {
			// Assume there was some form of error or invalid WHOX response.
			return
		}

		if e.Params[1] != "1" {
			// We should always be sending 1, and we should receive 1. If this
			// is anything but, then we didn't send the request and we can
			// ignore it.
			return
		}

		channel, ident, host, nick, account = e.Params[2], e.Params[3], e.Params[4], e.Params[5], e.Params[6]
	} else {
		channel, ident, host, nick = e.Params[1], e.Params[2], e.Params[3], e.Params[5]
	}

	c.state.mu.Lock()
	user := c.state.createUserIfNotExists(channel, nick)
	if user == nil {
		c.state.mu.Unlock()
		return
	}

	user.Host = host
	user.Ident = ident
	user.Extras.Name = e.Trailing

	if account != "0" {
		user.Extras.Account = account
	}

	c.state.mu.Unlock()
}

// handleKICK ensures that users are cleaned up after being kicked from the
// channel
func handleKICK(c *Client, e Event) {
	if len(e.Params) < 2 {
		// Needs at least channel and user.
		return
	}

	if e.Params[1] == c.GetNick() {
		c.state.mu.Lock()
		c.state.deleteChannel(e.Params[0])
		c.state.mu.Unlock()
		return
	}

	// Assume it's just another user.
	c.state.mu.Lock()
	c.state.deleteUser(e.Params[1])
	c.state.mu.Unlock()
}

// handleNICK ensures that users are renamed in state, or the client name is
// up to date.
func handleNICK(c *Client, e Event) {
	if len(e.Params) != 1 {
		// Something erronous was sent to us.
		return
	}

	c.state.mu.Lock()
	c.state.renameUser(e.Source.Name, e.Params[0])
	c.state.mu.Unlock()
}

func handleQUIT(c *Client, e Event) {
	c.state.mu.Lock()
	c.state.deleteUser(e.Source.Name)
	c.state.mu.Unlock()
}

// handleMYINFO handles incoming MYINFO events -- these are commonly used
// to tell us what the server name is, what version of software is being used
// as well as what channel and user modes are being used on the server.
func handleMYINFO(c *Client, e Event) {
	// Malformed or odd output. As this can differ strongly between networks,
	// just skip it.
	if len(e.Params) < 3 {
		return
	}

	c.state.mu.Lock()
	c.state.serverOptions["SERVER"] = e.Params[1]
	c.state.serverOptions["VERSION"] = e.Params[2]
	c.state.mu.Unlock()
}

// handleISUPPORT handles incoming RPL_ISUPPORT (also known as RPL_PROTOCTL)
// events. These commonly contain the server capabilities and limitations.
// For example, things like max channel name length, or nickname length.
func handleISUPPORT(c *Client, e Event) {
	// Must be a ISUPPORT-based message. 005 is also used for server bounce
	// related things, so this callback may be triggered during other
	// situations.

	// Also known as RPL_PROTOCTL.
	if !strings.HasSuffix(e.Trailing, "this server") {
		return
	}

	// Must have at least one configuration.
	if len(e.Params) < 2 {
		return
	}

	c.state.mu.Lock()
	// Skip the first parameter, as it's our nickname.
	for i := 1; i < len(e.Params); i++ {
		j := strings.IndexByte(e.Params[i], 0x3D) // =

		if j < 1 || (j+1) == len(e.Params[i]) {
			c.state.serverOptions[e.Params[i]] = ""
			continue
		}

		name := e.Params[i][0:j]
		val := e.Params[i][j+1:]
		c.state.serverOptions[name] = val
	}
	c.state.mu.Unlock()
}

// handleMOTD handles incoming MOTD messages and buffers them up for use with
// Client.ServerMOTD().
func handleMOTD(c *Client, e Event) {
	c.state.mu.Lock()

	// Beginning of the MOTD.
	if e.Command == RPL_MOTDSTART {
		c.state.motd = ""

		c.state.mu.Unlock()
		return
	}

	// Otherwise, assume we're getting sent the MOTD line-by-line.
	if len(c.state.motd) != 0 {
		e.Trailing = "\n" + e.Trailing
	}

	c.state.motd += e.Trailing

	c.state.mu.Unlock()
}

func handleNAMES(c *Client, e Event) {
	if len(e.Params) < 1 || !IsValidChannel(e.Params[len(e.Params)-1]) {
		return
	}

	parts := strings.Split(e.Trailing, " ")

	var host, ident, modes, nick string
	var ok bool

	c.state.mu.Lock()
	for i := 0; i < len(parts); i++ {
		modes, nick, ok = parseUserPrefix(parts[i])
		if !ok {
			continue
		}

		// If userhost-in-names.
		if strings.Contains(nick, "@") {
			s := ParseSource(nick)
			if s == nil {
				continue
			}

			host = s.Host
			nick = s.Name
			ident = s.Ident
		}

		if !IsValidNick(nick) {
			continue
		}

		user := c.state.createUserIfNotExists(e.Params[len(e.Params)-1], nick)
		if user == nil {
			continue
		}

		// Add necessary userhost-in-names data into the user.
		if host != "" {
			user.Host = host
		}
		if ident != "" {
			user.Ident = ident
		}

		// Don't append modes, overwrite them.
		user.Perms.set(modes, false)
	}
	c.state.mu.Unlock()
}
<commit_msg>better LastActive tracking for users<commit_after>// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package girc

import (
	"strings"
	"time"
)

// registerHandlers sets up built-in callbacks/helpers, based on client
// configuration.
func (c *Client) registerHandlers() {
	c.debug.Print("registering built-in handlers")
	c.Callbacks.mu.Lock()

	// Built-in things that should always be supported.
	c.Callbacks.register(true, RPL_WELCOME, CallbackFunc(func(c *Client, e Event) {
		go handleConnect(c, e)
	}))
	c.Callbacks.register(true, PING, CallbackFunc(handlePING))

	if !c.config.DisableTracking {
		// Joins/parts/anything that may add/remove/rename users.
		c.Callbacks.register(true, JOIN, CallbackFunc(handleJOIN))
		c.Callbacks.register(true, PART, CallbackFunc(handlePART))
		c.Callbacks.register(true, KICK, CallbackFunc(handleKICK))
		c.Callbacks.register(true, QUIT, CallbackFunc(handleQUIT))
		c.Callbacks.register(true, NICK, CallbackFunc(handleNICK))
		c.Callbacks.register(true, RPL_NAMREPLY, CallbackFunc(handleNAMES))

		// Modes.
		c.Callbacks.register(true, MODE, CallbackFunc(handleMODE))
		c.Callbacks.register(true, RPL_CHANNELMODEIS, CallbackFunc(handleMODE))

		// WHO/WHOX responses.
		c.Callbacks.register(true, RPL_WHOREPLY, CallbackFunc(handleWHO))
		c.Callbacks.register(true, RPL_WHOSPCRPL, CallbackFunc(handleWHO))

		// Other misc. useful stuff.
		c.Callbacks.register(true, TOPIC, CallbackFunc(handleTOPIC))
		c.Callbacks.register(true, RPL_TOPIC, CallbackFunc(handleTOPIC))
		c.Callbacks.register(true, RPL_MYINFO, CallbackFunc(handleMYINFO))
		c.Callbacks.register(true, RPL_ISUPPORT, CallbackFunc(handleISUPPORT))
		c.Callbacks.register(true, RPL_MOTDSTART, CallbackFunc(handleMOTD))
		c.Callbacks.register(true, RPL_MOTD, CallbackFunc(handleMOTD))

		// Keep users lastactive times up to date.
		c.Callbacks.register(true, PRIVMSG, CallbackFunc(updateLastActive))
		c.Callbacks.register(true, NOTICE, CallbackFunc(updateLastActive))
		c.Callbacks.register(true, TOPIC, CallbackFunc(updateLastActive))
		c.Callbacks.register(true, KICK, CallbackFunc(updateLastActive))
	}

	// Nickname collisions.
	if !c.config.DisableNickCollision {
		c.Callbacks.register(true, ERR_NICKNAMEINUSE, CallbackFunc(nickCollisionHandler))
		c.Callbacks.register(true, ERR_NICKCOLLISION, CallbackFunc(nickCollisionHandler))
		c.Callbacks.register(true, ERR_UNAVAILRESOURCE, CallbackFunc(nickCollisionHandler))
	}

	// CAP IRCv3-specific tracking and functionality.
	if !c.config.DisableTracking && !c.config.DisableCapTracking {
		c.Callbacks.register(true, CAP, CallbackFunc(handleCAP))
		c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST))
		c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY))
		c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT))
		c.Callbacks.register(true, ALLEVENTS, CallbackFunc(handleTags))
	}

	c.Callbacks.mu.Unlock()
}

// handleConnect is a helper function which lets the client know that enough
// time has passed and now they can send commands.
//
// Should always run in separate thread due to blocking delay.
func handleConnect(c *Client, e Event) {
	// This should be the nick that the server gives us. 99% of the time, it's
	// the one we supplied during connection, but some networks will rename
	// users on connect.
	if len(e.Params) > 0 {
		c.state.nick = e.Params[0]
	}

	time.Sleep(2 * time.Second)

	c.Events <- &Event{Command: CONNECTED, Trailing: c.Server()}
}

// nickCollisionHandler helps prevent the client from having conflicting
// nicknames with another bot, user, etc.
func nickCollisionHandler(c *Client, e Event) {
	c.Nick(c.GetNick() + "_")
}

// handlePING helps respond to ping requests from the server.
func handlePING(c *Client, e Event) {
	c.Pong(e.Trailing)
}

// handleJOIN ensures that the state has updated users and channels.
func handleJOIN(c *Client, e Event) {
	if len(e.Params) < 1 {
		return
	}

	// Create the user in state. 2This will also verify the channel.
	c.state.mu.Lock()
	user := c.state.createUserIfNotExists(e.Params[0], e.Source.Name)
	c.state.mu.Unlock()
	if user == nil {
		return
	}

	// Assume extended-join (ircv3).
	if len(e.Params) == 2 {
		if e.Params[1] != "*" {
			user.Extras.Account = e.Params[1]
		}

		if len(e.Trailing) > 0 {
			user.Extras.Name = e.Trailing
		}
	}

	if e.Source.Name == c.GetNick() {
		// If it's us, don't just add our user to the list. Run a WHO which
		// will tell us who exactly is in the entire channel.
		c.Send(&Event{Command: WHO, Params: []string{e.Params[0], "%tacuhnr,1"}})

		// Also send a MODE to obtain the list of channel modes.
		c.Send(&Event{Command: MODE, Params: []string{e.Params[0]}})
		return
	}

	// Only WHO the user, which is more efficient.
	c.Send(&Event{Command: WHO, Params: []string{e.Source.Name, "%tacuhnr,1"}})
}

// handlePART ensures that the state is clean of old user and channel entries.
func handlePART(c *Client, e Event) {
	if len(e.Params) == 0 {
		return
	}

	if e.Source.Name == c.GetNick() {
		c.state.mu.Lock()
		c.state.deleteChannel(e.Params[0])
		c.state.mu.Unlock()
		return
	}

	c.state.mu.Lock()
	c.state.deleteUser(e.Source.Name)
	c.state.mu.Unlock()
}

// handleTOPIC handles incoming TOPIC events and keeps channel tracking info
// updated with the latest channel topic.
func handleTOPIC(c *Client, e Event) {
	var name string
	switch len(e.Params) {
	case 0:
		return
	case 1:
		name = e.Params[0]
	default:
		name = e.Params[len(e.Params)-1]
	}

	c.state.mu.Lock()
	channel := c.state.createChanIfNotExists(name)
	if channel == nil {
		c.state.mu.Unlock()
		return
	}

	channel.Topic = e.Trailing
	c.state.mu.Unlock()
}

// handlWHO updates our internal tracking of users/channels with WHO/WHOX
// information.
func handleWHO(c *Client, e Event) {
	var channel, ident, host, nick, account string

	// Assume WHOX related.
	if e.Command == RPL_WHOSPCRPL {
		if len(e.Params) != 7 {
			// Assume there was some form of error or invalid WHOX response.
			return
		}

		if e.Params[1] != "1" {
			// We should always be sending 1, and we should receive 1. If this
			// is anything but, then we didn't send the request and we can
			// ignore it.
			return
		}

		channel, ident, host, nick, account = e.Params[2], e.Params[3], e.Params[4], e.Params[5], e.Params[6]
	} else {
		channel, ident, host, nick = e.Params[1], e.Params[2], e.Params[3], e.Params[5]
	}

	c.state.mu.Lock()
	user := c.state.createUserIfNotExists(channel, nick)
	if user == nil {
		c.state.mu.Unlock()
		return
	}

	user.Host = host
	user.Ident = ident
	user.Extras.Name = e.Trailing

	if account != "0" {
		user.Extras.Account = account
	}

	c.state.mu.Unlock()
}

// handleKICK ensures that users are cleaned up after being kicked from the
// channel
func handleKICK(c *Client, e Event) {
	if len(e.Params) < 2 {
		// Needs at least channel and user.
		return
	}

	if e.Params[1] == c.GetNick() {
		c.state.mu.Lock()
		c.state.deleteChannel(e.Params[0])
		c.state.mu.Unlock()
		return
	}

	// Assume it's just another user.
	c.state.mu.Lock()
	c.state.deleteUser(e.Params[1])
	c.state.mu.Unlock()
}

// handleNICK ensures that users are renamed in state, or the client name is
// up to date.
func handleNICK(c *Client, e Event) {
	if len(e.Params) != 1 {
		// Something erronous was sent to us.
		return
	}

	c.state.mu.Lock()
	// renameUser updates the LastActive time automatically.
	c.state.renameUser(e.Source.Name, e.Params[0])
	c.state.mu.Unlock()
}

func handleQUIT(c *Client, e Event) {
	c.state.mu.Lock()
	c.state.deleteUser(e.Source.Name)
	c.state.mu.Unlock()
}

// handleMYINFO handles incoming MYINFO events -- these are commonly used
// to tell us what the server name is, what version of software is being used
// as well as what channel and user modes are being used on the server.
func handleMYINFO(c *Client, e Event) {
	// Malformed or odd output. As this can differ strongly between networks,
	// just skip it.
	if len(e.Params) < 3 {
		return
	}

	c.state.mu.Lock()
	c.state.serverOptions["SERVER"] = e.Params[1]
	c.state.serverOptions["VERSION"] = e.Params[2]
	c.state.mu.Unlock()
}

// handleISUPPORT handles incoming RPL_ISUPPORT (also known as RPL_PROTOCTL)
// events. These commonly contain the server capabilities and limitations.
// For example, things like max channel name length, or nickname length.
func handleISUPPORT(c *Client, e Event) {
	// Must be a ISUPPORT-based message. 005 is also used for server bounce
	// related things, so this callback may be triggered during other
	// situations.

	// Also known as RPL_PROTOCTL.
	if !strings.HasSuffix(e.Trailing, "this server") {
		return
	}

	// Must have at least one configuration.
	if len(e.Params) < 2 {
		return
	}

	c.state.mu.Lock()
	// Skip the first parameter, as it's our nickname.
	for i := 1; i < len(e.Params); i++ {
		j := strings.IndexByte(e.Params[i], 0x3D) // =

		if j < 1 || (j+1) == len(e.Params[i]) {
			c.state.serverOptions[e.Params[i]] = ""
			continue
		}

		name := e.Params[i][0:j]
		val := e.Params[i][j+1:]
		c.state.serverOptions[name] = val
	}
	c.state.mu.Unlock()
}

// handleMOTD handles incoming MOTD messages and buffers them up for use with
// Client.ServerMOTD().
func handleMOTD(c *Client, e Event) {
	c.state.mu.Lock()

	// Beginning of the MOTD.
	if e.Command == RPL_MOTDSTART {
		c.state.motd = ""

		c.state.mu.Unlock()
		return
	}

	// Otherwise, assume we're getting sent the MOTD line-by-line.
	if len(c.state.motd) != 0 {
		e.Trailing = "\n" + e.Trailing
	}

	c.state.motd += e.Trailing

	c.state.mu.Unlock()
}

func handleNAMES(c *Client, e Event) {
	if len(e.Params) < 1 || !IsValidChannel(e.Params[len(e.Params)-1]) {
		return
	}

	parts := strings.Split(e.Trailing, " ")

	var host, ident, modes, nick string
	var ok bool

	c.state.mu.Lock()
	for i := 0; i < len(parts); i++ {
		modes, nick, ok = parseUserPrefix(parts[i])
		if !ok {
			continue
		}

		// If userhost-in-names.
		if strings.Contains(nick, "@") {
			s := ParseSource(nick)
			if s == nil {
				continue
			}

			host = s.Host
			nick = s.Name
			ident = s.Ident
		}

		if !IsValidNick(nick) {
			continue
		}

		user := c.state.createUserIfNotExists(e.Params[len(e.Params)-1], nick)
		if user == nil {
			continue
		}

		// Add necessary userhost-in-names data into the user.
		if host != "" {
			user.Host = host
		}
		if ident != "" {
			user.Ident = ident
		}

		// Don't append modes, overwrite them.
		user.Perms.set(modes, false)
	}
	c.state.mu.Unlock()
}

func updateLastActive(c *Client, e Event) {
	c.state.mu.Lock()
	// Update the users last active time, if they exist.
	users := c.state.lookupUsers("nick", e.Source.Name)
	for i := 0; i < len(users); i++ {
		users[i].LastActive = time.Now()
	}
	c.state.mu.Unlock()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	_ "net/url"
	_ "os"

	_ "github.com/codegangsta/negroni"
	_ "github.com/garyburd/redigo/redis"
	_ "gopkg.in/mgo.v2"
	_ "gopkg.in/mgo.v2/bson"
)

const (
	Kb = 1024
)

func SetupMux() *http.ServeMux {
	server_mux := http.NewServeMux()
	server_mux.HandleFunc("/", IndexHandler)
	server_mux.HandleFunc("/login", LoginHandler)
	server_mux.HandleFunc("/api/v1/user/exercises", SubmissionHandler)
	return server_mux
}

func IndexHandler(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "Hello There")
}

func LoginHandler(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "hi")
}

type submitResponse struct {
	Status string `json::"status"`
	Error  string `json::"error"`
}

type submitRequest struct {
	Email string `json::"email"`
	Key   string `json::"key"`
	Code  string `json::"code"`
}

func SubmissionHandler(res http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "POST":
		PostSubmissionHandler(res, req)
	default:
		res.WriteHeader(http.StatusBadRequest)
	}
}

func PostSubmissionHandler(res http.ResponseWriter, req *http.Request) {
	var sreq *submitRequest
	decoder := json.NewDecoder(req.Body)
	if err := decoder.Decode(&sreq); err != nil {
		log.Printf("Error parsing response: %v", err)
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	u, err := Users.FindUserByEmail(sreq.Email)
	if err != nil || sreq.Key != u.APIKey {
		res.WriteHeader(http.StatusForbidden)
		return
	}

	code := sreq.Code

	compressed_code, err := compressCode(code)
	if err != nil {
		log.Printf("Compressing code failure: %v\n", err)
		res.WriteHeader(http.StatusInternalServerError)
	}
	uncompress_code, err := uncompressCode(compressed_code)
	if err != nil {
		log.Printf("Uncompressing code failure: %v\n", err)
		res.WriteHeader(http.StatusInternalServerError)
	}

	fmt.Printf("Compressed Code: %v\n", compressed_code)
	fmt.Printf("Uncompressed Code: %v\n", uncompress_code)

	if len(compressed_code) > 20*Kb {
		log.Println("File is too big!")
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	_, err = Users.SubmitCode(u.Email, compressed_code)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sres := submitResponse{Status: "OK", Error: "None"}

	res.Header().Set("Access-Control-Allow-Origin", "*")
	res.Header().Set("Access-Control-Allow-Headers", "X-Requested-With")
	res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(http.StatusCreated)
	encoder := json.NewEncoder(res)
	if err := encoder.Encode(sres); err != nil {
		log.Printf("Error parsing response: %v", err)
	}
}

func compressCode(code string) (string, error) {
	var b bytes.Buffer
	gz := gzip.NewWriter(&b)
	defer gz.Close()

	_, err := gz.Write([]byte(code))
	if err != nil {
		return b.String(), err
	}

	return b.String(), nil
}

func uncompressCode(code string) (string, error) {
	b := bytes.NewBufferString(code)
	var uncompresed []byte
	gunz, err := gzip.NewReader(b)
	if err != nil {
		return "", err
	}
	defer gunz.Close()

	if _, err := gunz.Read(uncompresed); err != nil {
		return string(uncompresed), err
	}

	return string(uncompresed), nil
}
<commit_msg>gziping code<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	_ "net/url"
	_ "os"

	_ "github.com/codegangsta/negroni"
	_ "github.com/garyburd/redigo/redis"
	_ "gopkg.in/mgo.v2"
	_ "gopkg.in/mgo.v2/bson"
)

const (
	Kb = 1024
)

func SetupMux() *http.ServeMux {
	server_mux := http.NewServeMux()
	server_mux.HandleFunc("/", IndexHandler)
	server_mux.HandleFunc("/login", LoginHandler)
	server_mux.HandleFunc("/api/v1/user/exercises", SubmissionHandler)
	return server_mux
}

func IndexHandler(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "Hello There")
}

func LoginHandler(res http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(res, "hi")
}

type submitResponse struct {
	Status string `json::"status"`
	Error  string `json::"error"`
}

type submitRequest struct {
	Email string `json::"email"`
	Key   string `json::"key"`
	Code  string `json::"code"`
}

func SubmissionHandler(res http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "POST":
		PostSubmissionHandler(res, req)
	default:
		res.WriteHeader(http.StatusBadRequest)
	}
}

func PostSubmissionHandler(res http.ResponseWriter, req *http.Request) {
	var sreq *submitRequest
	decoder := json.NewDecoder(req.Body)
	if err := decoder.Decode(&sreq); err != nil {
		log.Printf("Error parsing response: %v", err)
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	u, err := Users.FindUserByEmail(sreq.Email)
	if err != nil || sreq.Key != u.APIKey {
		res.WriteHeader(http.StatusForbidden)
		return
	}

	code := sreq.Code

	compressed_code, err := compressCode(code)
	if err != nil {
		log.Printf("Compressing code failure: %v\n", err)
		res.WriteHeader(http.StatusInternalServerError)
	}
	uncompressed_code, err := uncompressCode(compressed_code)
	if err != nil {
		log.Printf("Uncompressing code failure: %v\n", err)
		res.WriteHeader(http.StatusInternalServerError)
	}

	fmt.Printf("Compressed Code: %v\n", compressed_code)
	fmt.Printf("Uncompressed Code: %v\n", uncompressed_code)

	if len(compressed_code) > 20*Kb {
		log.Println("File is too big!")
		res.WriteHeader(http.StatusBadRequest)
		return
	}

	_, err = Users.SubmitCode(u.Email, compressed_code)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	sres := submitResponse{Status: "OK", Error: "None"}

	res.Header().Set("Access-Control-Allow-Origin", "*")
	res.Header().Set("Access-Control-Allow-Headers", "X-Requested-With")
	res.Header().Set("Content-Type", "application/json")
	res.WriteHeader(http.StatusCreated)
	encoder := json.NewEncoder(res)
	if err := encoder.Encode(sres); err != nil {
		log.Printf("Error parsing response: %v", err)
	}
}

func compressCode(code string) (string, error) {
	var b bytes.Buffer
	gz := gzip.NewWriter(&b)
	defer gz.Close()

	_, err := gz.Write([]byte(code))
	if err != nil {
		return b.String(), err
	}

	return b.String(), nil
}

func uncompressCode(code string) (string, error) {
	b := bytes.NewBufferString(code)
	var uncompresed []byte
	gunz, err := gzip.NewReader(&b)
	if err != nil {
		return "", err
	}
	defer gunz.Close()

	if _, err := gunz.Read(uncompresed); err != nil {
		return "", err
	}

	return "", nil
}
<|endoftext|>


<commit_before>package steamscreenshots

import (
	"fmt"
	"html/template"
	"net/http"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
)

type StringSliceNoCase []string

func (p StringSliceNoCase) Len() int           { return len(p) }
func (p StringSliceNoCase) Less(i, j int) bool { return strings.ToLower(p[i]) < strings.ToLower(p[j]) }
func (p StringSliceNoCase) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func SortKeysByValue(m map[string]string) []string {
	vals := []string{}
	for _, v := range m {
		vals = append(vals, v)
	}

	sort.Strings(vals)
	sorted := []string{}
	for _, s := range vals {
		for k, v := range m {
			if v == s {
				sorted = append(sorted, k)
			}
		}
	}

	return sorted
}

func (s *Server) handler_main(w http.ResponseWriter, r *http.Request) {
	// Uncomment this for debugging HTML stuff.
	//if err := init_templates(); err != nil {
	//    fmt.Fprintf(w, "Error reloading templates: %s", err)
	//    return
	//}

	//root, err := discover()
	//if err != nil {
	//    fmt.Fprintf(w, "Error discovering: %s", err)
	//    return
	//}

	//keys := GetKeys(root)
	s.dataLock.Lock()
	keys := GetKeys(s.dataTree)
	s.dataLock.Unlock()

	// Game page
	if r.URL.Path != "/" {
		trimmed := strings.Trim(r.URL.Path, "/")
		if SliceContains(keys, trimmed) {
			imageMeta := s.ImageCache.GetMetadata(trimmed)

			files := []string{}
			for _, m := range imageMeta {
				files = append(files, m.Src)
			}

			sort.Strings(files)
			pretty, err := s.getGameName(trimmed)
			if err != nil {
				fmt.Printf("Error getting name for %s: %s\n", trimmed, err)
			}

			d := TemplateData{}
			d.Title = pretty
			d.Header = map[string]string{
				"Text":  pretty,
				"Count": fmt.Sprintf("%d", len(files)),
			}
			d.Body = []map[string]template.JS{}
			d.ImageMetadata = imageMeta

			for idx, filename := range files {
				base := filepath.Base(filename)
				clearclass := ""
				if idx%3 == 0 {
					clearclass = " clearme"
					//fmt.Printf("Clearme on %q\n", base)
				}

				d.Body = append(d.Body, map[string]template.JS{
					"ImageTarget":  template.JS("/img/" + trimmed + "/" + base),
					"ThumbnailSrc": template.JS("/thumb/" + trimmed + "/" + base),
					"Text":         template.JS(base),
					"Clear":        template.JS(clearclass),
					"Idx":          template.JS(fmt.Sprintf("%d", idx)),
				})
			}

			err = renderTemplate(w, "list", &d)
			if err != nil {
				fmt.Println(err)
			}
		}

		// Main page
	} else {
		gameNames := map[string]string{}

		d := TemplateData{}
		d.Body = []map[string]template.JS{}
		for _, k := range keys {
			pretty, err := s.getGameName(k)
			if err != nil {
				fmt.Printf("Error getting name for %s: %s\n", k, err)
			}
			gameNames[pretty] = k
		}

		gameKeys := []string{}
		for k, _ := range gameNames {
			gameKeys = append(gameKeys, k)
		}

		sort.Sort(StringSliceNoCase(gameKeys))

		for idx, pretty := range gameKeys {
			clearclass := ""
			if idx%3 == 0 {
				clearclass = " clearme"
			}
			appid := gameNames[pretty]
			d.Body = append(d.Body, map[string]template.JS{
				"Target": template.JS("/" + appid + "/"),
				"Pretty": template.JS(pretty),
				"Count":  template.JS(fmt.Sprintf("%d", s.ImageCache.Count(appid))),
				"Clear":  template.JS(clearclass),
			})
		}

		err := renderTemplate(w, "main", &d)
		if err != nil {
			fmt.Println(err)
		}
	}
}

func (s *Server) handler_thumb(w http.ResponseWriter, r *http.Request) {
	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(split) != 3 {
		fmt.Printf(w, "[split error] %s\n", split)
		http.NotFound(w, r)
		return
	}

	if split[1] == ".." || split[1] == "." || split[2] == ".." || split[2] == "." {
		fmt.Printf("Dots in handler_thumb() url: %q\n", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	fullPath := filepath.Join(
		s.settings.RemoteDirectory,
		split[1],
		"screenshots",
		"thumbnails",
		split[2])

	http.ServeFile(w, r, fullPath)
}

func (s *Server) handler_image(w http.ResponseWriter, r *http.Request) {
	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(split) != 3 {
		fmt.Printf("[split error] %s\n", split)
		http.NotFound(w, r)
		return
	}

	if split[1] == ".." || split[1] == "." || split[2] == ".." || split[2] == "." {
		fmt.Printf("Dots in handler_image() url: %q\n", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	fullPath := filepath.Join(
		s.settings.RemoteDirectory,
		split[1],
		"screenshots",
		split[2])

	http.ServeFile(w, r, fullPath)
}

func (s *Server) handler_banner(w http.ResponseWriter, r *http.Request) {
	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")

	if len(split) != 2 {
		fmt.Printf(w, "[split error] %s\n", split)
		http.NotFound(w, r)
		return
	}

	// FIXME: does this need to be sanitized like handler_image()?
	//if split[1] == ".." || split[1] == "." || split[2] == ".." || split[2] == "." {
	//	fmt.Printf("Dots in handler_banner() url: %q\n", r.URL.Path)
	//	http.NotFound(w, r)
	//	return
	//}

	appidbase := split[1]
	if idx := strings.LastIndex(appidbase, "."); idx > -1 {
		appidbase = appidbase[:idx]
	}

	appid, err := strconv.ParseUint(appidbase, 10, 64)
	if err != nil {
		fmt.Printf("[handle_banner] Invalid appid: %s\n", split[1])
		http.ServeFile(w, r, "banners/unknown.jpg")
		return
	}

	fullPath := fmt.Sprintf("banners/%d.jpg", appid)
	if ex := exists(fullPath); ex {
		http.ServeFile(w, r, fullPath)
	} else {
		bannerPath, err := s.getGameBanner(appid)
		if err != nil {
			fmt.Printf("[handle_banner] Unable to get banner: %s\n", err)
			http.ServeFile(w, r, "banners/unknown.jpg")
			return
		}

		http.ServeFile(w, r, bannerPath)
	}
}

func (s *Server) handler_static(w http.ResponseWriter, r *http.Request) {
	if strings.HasSuffix(r.URL.Path, "/") {
		fmt.Printf("[handler_static] attempted to get directory: %s\n", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")

	// The three-length paths are for the PhotoSwipe gallery.
	if len(split) != 2 && len(split) != 3 {
		fmt.Printf("[handler_static] split error: %s\n", split)
		return
	}

	fullPath := fmt.Sprintf("static/%s", split[1])
	if len(split) == 3 {
		fullPath = fmt.Sprintf("%s/%s", fullPath, split[2])
	}

	if ex := exists(fullPath); ex {
		http.ServeFile(w, r, fullPath)
	} else {
		fmt.Printf("[handler_static] 404 on file %q\n", fullPath)
		http.NotFound(w, r)
	}
}

func (s *Server) handler_debug(w http.ResponseWriter, r *http.Request) {
	d := TemplateData{}
	d.Body = []map[string]template.JS{}

	if len(gitCommit) == 0 {
		gitCommit = "Missing commit hash"
	}

	if len(version) == 0 {
		version = "Missing version info"
	}

	tmp := []string{
		fmt.Sprintf("Last scan: %s", time.Since(s.lastScan)),
		fmt.Sprintf("Uptime: %s", time.Since(s.startTime)),
		fmt.Sprintf("Game cache count: %d", s.Games.Length()),
		fmt.Sprintf("Game count: %d", s.ImageCache.Length()),
		fmt.Sprintf("Version: %s", version),
		fmt.Sprintf("Commit: %s", gitCommit),
	}

	for _, s := range tmp {
		d.Body = append(d.Body, map[string]template.JS{
			"Data": template.JS(s),
		})
	}

	err := renderTemplate(w, "debug", &d)
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>Fix build error<commit_after>package steamscreenshots

import (
	"fmt"
	"html/template"
	"net/http"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
)

type StringSliceNoCase []string

func (p StringSliceNoCase) Len() int           { return len(p) }
func (p StringSliceNoCase) Less(i, j int) bool { return strings.ToLower(p[i]) < strings.ToLower(p[j]) }
func (p StringSliceNoCase) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func SortKeysByValue(m map[string]string) []string {
	vals := []string{}
	for _, v := range m {
		vals = append(vals, v)
	}

	sort.Strings(vals)
	sorted := []string{}
	for _, s := range vals {
		for k, v := range m {
			if v == s {
				sorted = append(sorted, k)
			}
		}
	}

	return sorted
}

func (s *Server) handler_main(w http.ResponseWriter, r *http.Request) {
	// Uncomment this for debugging HTML stuff.
	//if err := init_templates(); err != nil {
	//    fmt.Fprintf(w, "Error reloading templates: %s", err)
	//    return
	//}

	//root, err := discover()
	//if err != nil {
	//    fmt.Fprintf(w, "Error discovering: %s", err)
	//    return
	//}

	//keys := GetKeys(root)
	s.dataLock.Lock()
	keys := GetKeys(s.dataTree)
	s.dataLock.Unlock()

	// Game page
	if r.URL.Path != "/" {
		trimmed := strings.Trim(r.URL.Path, "/")
		if SliceContains(keys, trimmed) {
			imageMeta := s.ImageCache.GetMetadata(trimmed)

			files := []string{}
			for _, m := range imageMeta {
				files = append(files, m.Src)
			}

			sort.Strings(files)
			pretty, err := s.getGameName(trimmed)
			if err != nil {
				fmt.Printf("Error getting name for %s: %s\n", trimmed, err)
			}

			d := TemplateData{}
			d.Title = pretty
			d.Header = map[string]string{
				"Text":  pretty,
				"Count": fmt.Sprintf("%d", len(files)),
			}
			d.Body = []map[string]template.JS{}
			d.ImageMetadata = imageMeta

			for idx, filename := range files {
				base := filepath.Base(filename)
				clearclass := ""
				if idx%3 == 0 {
					clearclass = " clearme"
					//fmt.Printf("Clearme on %q\n", base)
				}

				d.Body = append(d.Body, map[string]template.JS{
					"ImageTarget":  template.JS("/img/" + trimmed + "/" + base),
					"ThumbnailSrc": template.JS("/thumb/" + trimmed + "/" + base),
					"Text":         template.JS(base),
					"Clear":        template.JS(clearclass),
					"Idx":          template.JS(fmt.Sprintf("%d", idx)),
				})
			}

			err = renderTemplate(w, "list", &d)
			if err != nil {
				fmt.Println(err)
			}
		}

		// Main page
	} else {
		gameNames := map[string]string{}

		d := TemplateData{}
		d.Body = []map[string]template.JS{}
		for _, k := range keys {
			pretty, err := s.getGameName(k)
			if err != nil {
				fmt.Printf("Error getting name for %s: %s\n", k, err)
			}
			gameNames[pretty] = k
		}

		gameKeys := []string{}
		for k, _ := range gameNames {
			gameKeys = append(gameKeys, k)
		}

		sort.Sort(StringSliceNoCase(gameKeys))

		for idx, pretty := range gameKeys {
			clearclass := ""
			if idx%3 == 0 {
				clearclass = " clearme"
			}
			appid := gameNames[pretty]
			d.Body = append(d.Body, map[string]template.JS{
				"Target": template.JS("/" + appid + "/"),
				"Pretty": template.JS(pretty),
				"Count":  template.JS(fmt.Sprintf("%d", s.ImageCache.Count(appid))),
				"Clear":  template.JS(clearclass),
			})
		}

		err := renderTemplate(w, "main", &d)
		if err != nil {
			fmt.Println(err)
		}
	}
}

func (s *Server) handler_thumb(w http.ResponseWriter, r *http.Request) {
	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(split) != 3 {
		fmt.Fprintf(w, "[split error] %s\n", split)
		http.NotFound(w, r)
		return
	}

	if split[1] == ".." || split[1] == "." || split[2] == ".." || split[2] == "." {
		fmt.Printf("Dots in handler_thumb() url: %q\n", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	fullPath := filepath.Join(
		s.settings.RemoteDirectory,
		split[1],
		"screenshots",
		"thumbnails",
		split[2])

	http.ServeFile(w, r, fullPath)
}

func (s *Server) handler_image(w http.ResponseWriter, r *http.Request) {
	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(split) != 3 {
		fmt.Printf("[split error] %s\n", split)
		http.NotFound(w, r)
		return
	}

	if split[1] == ".." || split[1] == "." || split[2] == ".." || split[2] == "." {
		fmt.Printf("Dots in handler_image() url: %q\n", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	fullPath := filepath.Join(
		s.settings.RemoteDirectory,
		split[1],
		"screenshots",
		split[2])

	http.ServeFile(w, r, fullPath)
}

func (s *Server) handler_banner(w http.ResponseWriter, r *http.Request) {
	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")

	if len(split) != 2 {
		fmt.Fprintf(w, "[split error] %s\n", split)
		http.NotFound(w, r)
		return
	}

	// FIXME: does this need to be sanitized like handler_image()?
	//if split[1] == ".." || split[1] == "." || split[2] == ".." || split[2] == "." {
	//	fmt.Printf("Dots in handler_banner() url: %q\n", r.URL.Path)
	//	http.NotFound(w, r)
	//	return
	//}

	appidbase := split[1]
	if idx := strings.LastIndex(appidbase, "."); idx > -1 {
		appidbase = appidbase[:idx]
	}

	appid, err := strconv.ParseUint(appidbase, 10, 64)
	if err != nil {
		fmt.Printf("[handle_banner] Invalid appid: %s\n", split[1])
		http.ServeFile(w, r, "banners/unknown.jpg")
		return
	}

	fullPath := fmt.Sprintf("banners/%d.jpg", appid)
	if ex := exists(fullPath); ex {
		http.ServeFile(w, r, fullPath)
	} else {
		bannerPath, err := s.getGameBanner(appid)
		if err != nil {
			fmt.Printf("[handle_banner] Unable to get banner: %s\n", err)
			http.ServeFile(w, r, "banners/unknown.jpg")
			return
		}

		http.ServeFile(w, r, bannerPath)
	}
}

func (s *Server) handler_static(w http.ResponseWriter, r *http.Request) {
	if strings.HasSuffix(r.URL.Path, "/") {
		fmt.Printf("[handler_static] attempted to get directory: %s\n", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	split := strings.Split(strings.Trim(r.URL.Path, "/"), "/")

	// The three-length paths are for the PhotoSwipe gallery.
	if len(split) != 2 && len(split) != 3 {
		fmt.Printf("[handler_static] split error: %s\n", split)
		return
	}

	fullPath := fmt.Sprintf("static/%s", split[1])
	if len(split) == 3 {
		fullPath = fmt.Sprintf("%s/%s", fullPath, split[2])
	}

	if ex := exists(fullPath); ex {
		http.ServeFile(w, r, fullPath)
	} else {
		fmt.Printf("[handler_static] 404 on file %q\n", fullPath)
		http.NotFound(w, r)
	}
}

func (s *Server) handler_debug(w http.ResponseWriter, r *http.Request) {
	d := TemplateData{}
	d.Body = []map[string]template.JS{}

	if len(gitCommit) == 0 {
		gitCommit = "Missing commit hash"
	}

	if len(version) == 0 {
		version = "Missing version info"
	}

	tmp := []string{
		fmt.Sprintf("Last scan: %s", time.Since(s.lastScan)),
		fmt.Sprintf("Uptime: %s", time.Since(s.startTime)),
		fmt.Sprintf("Game cache count: %d", s.Games.Length()),
		fmt.Sprintf("Game count: %d", s.ImageCache.Length()),
		fmt.Sprintf("Version: %s", version),
		fmt.Sprintf("Commit: %s", gitCommit),
	}

	for _, s := range tmp {
		d.Body = append(d.Body, map[string]template.JS{
			"Data": template.JS(s),
		})
	}

	err := renderTemplate(w, "debug", &d)
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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.
*/

// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
package main

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
)

var (
	isup             = flag.Bool("isup", false, "Check to see if the e2e cluster is up, then exit.")
	build            = flag.Bool("build", false, "If true, build a new release. Otherwise, use whatever is there.")
	up               = flag.Bool("up", false, "If true, start the the e2e cluster. If cluster is already up, recreate it.")
	push             = flag.Bool("push", false, "If true, push to e2e cluster. Has no effect if -up is true.")
	down             = flag.Bool("down", false, "If true, tear down the cluster before exiting.")
	test             = flag.Bool("test", false, "Run all tests in hack/e2e-suite.")
	tests            = flag.String("tests", "", "Run only tests in hack/e2e-suite matching this glob. Ignored if -test is set.")
	root             = flag.String("root", absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))), "Root directory of kubernetes repository.")
	verbose          = flag.Bool("v", false, "If true, print all command output.")
	trace_bash       = flag.Bool("trace-bash", false, "If true, pass -x to bash to trace all bash commands")
	checkVersionSkew = flag.Bool("check_version_skew", true, ""+
		"By default, verify that client and server have exact version match. "+
		"You can explicitly set to false if you're, e.g., testing client changes "+
		"for which the server version doesn't make a difference.")

	cfgCmd = flag.String("cfg", "", "If nonempty, pass this as an argument, and call kubecfg. Implies -v.")
	ctlCmd = flag.String("ctl", "", "If nonempty, pass this as an argument, and call kubectl. Implies -v. (-test, -cfg, -ctl are mutually exclusive)")
)

var signals = make(chan os.Signal, 100)

func absOrDie(path string) string {
	out, err := filepath.Abs(path)
	if err != nil {
		panic(err)
	}
	return out
}

func main() {
	flag.Parse()
	signal.Notify(signals, os.Interrupt)

	if *test {
		*tests = "*"
	}

	if *isup {
		status := 1
		if runBash("get status", `$KUBECFG -server_version`) {
			status = 0
			log.Printf("Cluster is UP")
		} else {
			log.Printf("Cluster is DOWN")
		}
		os.Exit(status)
	}

	if *build {
		if !runBash("build-release", `test-build-release`) {
			log.Fatal("Error building. Aborting.")
		}
	}

	if *up {
		if !Up() {
			log.Fatal("Error starting e2e cluster. Aborting.")
		}
	} else if *push {
		if !runBash("push", path.Join(*root, "/cluster/kube-push.sh")) {
			log.Fatal("Error pushing e2e cluster. Aborting.")
		}
	}

	failure := false
	switch {
	case *cfgCmd != "":
		failure = !runBash("'kubecfg "+*cfgCmd+"'", "$KUBECFG "+*cfgCmd)
	case *ctlCmd != "":
		failure = !runBash("'kubectl "+*ctlCmd+"'", "$KUBECTL "+*ctlCmd)
	case *tests != "":
		failed, passed := Test()
		log.Printf("Passed tests: %v", passed)
		log.Printf("Failed tests: %v", failed)
		failure = len(failed) > 0
	}

	if *down {
		TearDown()
	}

	if failure {
		os.Exit(1)
	}
}

func TearDown() {
	runBash("teardown", "test-teardown")
}

func Up() bool {
	if !tryUp() {
		log.Printf("kube-up failed; will tear down and retry. (Possibly your cluster was in some partially created state?)")
		TearDown()
		return tryUp()
	}
	return true
}

func tryUp() bool {
	return runBash("up", path.Join(*root, "/cluster/kube-up.sh; test-setup;"))
}

func Test() (failed, passed []string) {
	defer runBashUntil("watchEvents", "$KUBECTL --watch-only get events")()
	// run tests!
	dir, err := os.Open(filepath.Join(*root, "hack", "e2e-suite"))
	if err != nil {
		log.Fatal("Couldn't open e2e-suite dir")
	}
	defer dir.Close()
	names, err := dir.Readdirnames(0)
	if err != nil {
		log.Fatal("Couldn't read names in e2e-suite dir")
	}

	for i := range names {
		name := names[i]
		if name == "." || name == ".." {
			continue
		}
		if match, err := path.Match(*tests, name); !match && err == nil {
			continue
		}
		absName := filepath.Join(*root, "hack", "e2e-suite", name)
		log.Printf("%v matches %v. Starting test.", name, *tests)
		if runBash(name, absName) {
			log.Printf("%v passed", name)
			passed = append(passed, name)
		} else {
			log.Printf("%v failed", name)
			failed = append(failed, name)
		}
	}

	return
}

// All nonsense below is temporary until we have go versions of these things.

func runBash(stepName, bashFragment string) bool {
	cmd := exec.Command("bash", "-s")
	if *trace_bash {
		cmd.Args = append(cmd.Args, "-x")
	}
	cmd.Stdin = strings.NewReader(bashWrap(bashFragment))
	return finishRunning(stepName, cmd)
}

// call the returned anonymous function to stop.
func runBashUntil(stepName, bashFragment string) func() {
	cmd := exec.Command("bash", "-s")
	cmd.Stdin = strings.NewReader(bashWrap(bashFragment))
	log.Printf("Running in background: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	cmd.Stdout, cmd.Stderr = stdout, stderr
	if err := cmd.Start(); err != nil {
		log.Printf("Unable to start '%v': '%v'", stepName, err)
		return func() {}
	}
	return func() {
		cmd.Process.Signal(os.Interrupt)
		fmt.Printf("%v stdout:\n------\n%v\n------\n", stepName, string(stdout.Bytes()))
		fmt.Printf("%v stderr:\n------\n%v\n------\n", stepName, string(stderr.Bytes()))
	}
}

func run(stepName, cmdPath string) bool {
	return finishRunning(stepName, exec.Command(filepath.Join(*root, cmdPath)))
}

func finishRunning(stepName string, cmd *exec.Cmd) bool {
	log.Printf("Running: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	if *verbose {
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	} else {
		cmd.Stdout = stdout
		cmd.Stderr = stderr
	}

	done := make(chan struct{})
	defer close(done)
	go func() {
		for {
			select {
			case <-done:
				return
			case s := <-signals:
				cmd.Process.Signal(s)
			}
		}
	}()

	if err := cmd.Run(); err != nil {
		log.Printf("Error running %v: %v", stepName, err)
		if !*verbose {
			fmt.Printf("stdout:\n------\n%v\n------\n", string(stdout.Bytes()))
			fmt.Printf("stderr:\n------\n%v\n------\n", string(stderr.Bytes()))
		}
		return false
	}
	return true
}

// returns either "", or a list of args intended for appending with the
// kubecfg or kubectl commands (begining with a space).
func kubecfgArgs() string {
	if *checkVersionSkew {
		return " -expect_version_match"
	}
	return ""
}

// returns either "", or a list of args intended for appending with the
// kubectl command (begining with a space).
func kubectlArgs() string {
	if *checkVersionSkew {
		return " --match-server-version"
	}
	return ""
}

func bashWrap(cmd string) string {
	return `
set -o errexit
set -o nounset
set -o pipefail

export KUBE_CONFIG_FILE="config-test.sh"

# TODO(jbeda): This will break on usage if there is a space in
# ${KUBE_ROOT}.  Covert to an array?  Or an exported function?
export KUBECFG="` + *root + `/cluster/kubecfg.sh` + kubecfgArgs() + `"
export KUBECTL="` + *root + `/cluster/kubectl.sh` + kubectlArgs() + `"

source "` + *root + `/cluster/kube-env.sh"
source "` + *root + `/cluster/${KUBERNETES_PROVIDER}/util.sh"

prepare-e2e

` + cmd + `
`
}
<commit_msg>Check if cluster is up before e2e test without -up<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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.
*/

// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
package main

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
)

var (
	isup             = flag.Bool("isup", false, "Check to see if the e2e cluster is up, then exit.")
	build            = flag.Bool("build", false, "If true, build a new release. Otherwise, use whatever is there.")
	up               = flag.Bool("up", false, "If true, start the the e2e cluster. If cluster is already up, recreate it.")
	push             = flag.Bool("push", false, "If true, push to e2e cluster. Has no effect if -up is true.")
	down             = flag.Bool("down", false, "If true, tear down the cluster before exiting.")
	test             = flag.Bool("test", false, "Run all tests in hack/e2e-suite.")
	tests            = flag.String("tests", "", "Run only tests in hack/e2e-suite matching this glob. Ignored if -test is set.")
	root             = flag.String("root", absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))), "Root directory of kubernetes repository.")
	verbose          = flag.Bool("v", false, "If true, print all command output.")
	trace_bash       = flag.Bool("trace-bash", false, "If true, pass -x to bash to trace all bash commands")
	checkVersionSkew = flag.Bool("check_version_skew", true, ""+
		"By default, verify that client and server have exact version match. "+
		"You can explicitly set to false if you're, e.g., testing client changes "+
		"for which the server version doesn't make a difference.")

	cfgCmd = flag.String("cfg", "", "If nonempty, pass this as an argument, and call kubecfg. Implies -v.")
	ctlCmd = flag.String("ctl", "", "If nonempty, pass this as an argument, and call kubectl. Implies -v. (-test, -cfg, -ctl are mutually exclusive)")
)

var signals = make(chan os.Signal, 100)

func absOrDie(path string) string {
	out, err := filepath.Abs(path)
	if err != nil {
		panic(err)
	}
	return out
}

func main() {
	flag.Parse()
	signal.Notify(signals, os.Interrupt)

	if *test {
		*tests = "*"
	}

	if *isup {
		status := 1
		if IsUp() {
			status = 0
			log.Printf("Cluster is UP")
		} else {
			log.Printf("Cluster is DOWN")
		}
		os.Exit(status)
	}

	if *build {
		if !runBash("build-release", `test-build-release`) {
			log.Fatal("Error building. Aborting.")
		}
	}

	if *up {
		if !Up() {
			log.Fatal("Error starting e2e cluster. Aborting.")
		}
	} else if *push {
		if !runBash("push", path.Join(*root, "/cluster/kube-push.sh")) {
			log.Fatal("Error pushing e2e cluster. Aborting.")
		}
	}

	failure := false
	switch {
	case *cfgCmd != "":
		failure = !runBash("'kubecfg "+*cfgCmd+"'", "$KUBECFG "+*cfgCmd)
	case *ctlCmd != "":
		failure = !runBash("'kubectl "+*ctlCmd+"'", "$KUBECTL "+*ctlCmd)
	case *tests != "":
		failed, passed := Test()
		log.Printf("Passed tests: %v", passed)
		log.Printf("Failed tests: %v", failed)
		failure = len(failed) > 0
	}

	if *down {
		TearDown()
	}

	if failure {
		os.Exit(1)
	}
}

func TearDown() {
	runBash("teardown", "test-teardown")
}

func Up() bool {
	if !tryUp() {
		log.Printf("kube-up failed; will tear down and retry. (Possibly your cluster was in some partially created state?)")
		TearDown()
		return tryUp()
	}
	return true
}

// Is the e2e cluster up?
func IsUp() bool {
	return runBash("get status", `$KUBECFG -server_version`)
}

func tryUp() bool {
	return runBash("up", path.Join(*root, "/cluster/kube-up.sh; test-setup;"))
}

func Test() (failed, passed []string) {
	defer runBashUntil("watchEvents", "$KUBECTL --watch-only get events")()

	if !IsUp() {
		log.Fatal("Testing requested, but e2e cluster not up!")
	}

	// run tests!
	dir, err := os.Open(filepath.Join(*root, "hack", "e2e-suite"))
	if err != nil {
		log.Fatal("Couldn't open e2e-suite dir")
	}
	defer dir.Close()
	names, err := dir.Readdirnames(0)
	if err != nil {
		log.Fatal("Couldn't read names in e2e-suite dir")
	}

	for i := range names {
		name := names[i]
		if name == "." || name == ".." {
			continue
		}
		if match, err := path.Match(*tests, name); !match && err == nil {
			continue
		}
		absName := filepath.Join(*root, "hack", "e2e-suite", name)
		log.Printf("%v matches %v. Starting test.", name, *tests)
		if runBash(name, absName) {
			log.Printf("%v passed", name)
			passed = append(passed, name)
		} else {
			log.Printf("%v failed", name)
			failed = append(failed, name)
		}
	}

	return
}

// All nonsense below is temporary until we have go versions of these things.

func runBash(stepName, bashFragment string) bool {
	cmd := exec.Command("bash", "-s")
	if *trace_bash {
		cmd.Args = append(cmd.Args, "-x")
	}
	cmd.Stdin = strings.NewReader(bashWrap(bashFragment))
	return finishRunning(stepName, cmd)
}

// call the returned anonymous function to stop.
func runBashUntil(stepName, bashFragment string) func() {
	cmd := exec.Command("bash", "-s")
	cmd.Stdin = strings.NewReader(bashWrap(bashFragment))
	log.Printf("Running in background: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	cmd.Stdout, cmd.Stderr = stdout, stderr
	if err := cmd.Start(); err != nil {
		log.Printf("Unable to start '%v': '%v'", stepName, err)
		return func() {}
	}
	return func() {
		cmd.Process.Signal(os.Interrupt)
		fmt.Printf("%v stdout:\n------\n%v\n------\n", stepName, string(stdout.Bytes()))
		fmt.Printf("%v stderr:\n------\n%v\n------\n", stepName, string(stderr.Bytes()))
	}
}

func run(stepName, cmdPath string) bool {
	return finishRunning(stepName, exec.Command(filepath.Join(*root, cmdPath)))
}

func finishRunning(stepName string, cmd *exec.Cmd) bool {
	log.Printf("Running: %v", stepName)
	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
	if *verbose {
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	} else {
		cmd.Stdout = stdout
		cmd.Stderr = stderr
	}

	done := make(chan struct{})
	defer close(done)
	go func() {
		for {
			select {
			case <-done:
				return
			case s := <-signals:
				cmd.Process.Signal(s)
			}
		}
	}()

	if err := cmd.Run(); err != nil {
		log.Printf("Error running %v: %v", stepName, err)
		if !*verbose {
			fmt.Printf("stdout:\n------\n%v\n------\n", string(stdout.Bytes()))
			fmt.Printf("stderr:\n------\n%v\n------\n", string(stderr.Bytes()))
		}
		return false
	}
	return true
}

// returns either "", or a list of args intended for appending with the
// kubecfg or kubectl commands (begining with a space).
func kubecfgArgs() string {
	if *checkVersionSkew {
		return " -expect_version_match"
	}
	return ""
}

// returns either "", or a list of args intended for appending with the
// kubectl command (begining with a space).
func kubectlArgs() string {
	if *checkVersionSkew {
		return " --match-server-version"
	}
	return ""
}

func bashWrap(cmd string) string {
	return `
set -o errexit
set -o nounset
set -o pipefail

export KUBE_CONFIG_FILE="config-test.sh"

# TODO(jbeda): This will break on usage if there is a space in
# ${KUBE_ROOT}.  Covert to an array?  Or an exported function?
export KUBECFG="` + *root + `/cluster/kubecfg.sh` + kubecfgArgs() + `"
export KUBECTL="` + *root + `/cluster/kubectl.sh` + kubectlArgs() + `"

source "` + *root + `/cluster/kube-env.sh"
source "` + *root + `/cluster/${KUBERNETES_PROVIDER}/util.sh"

prepare-e2e

` + cmd + `
`
}
<|endoftext|>