<commit_before>package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"

	flags "github.com/jessevdk/go-flags"
	"github.com/monochromegane/terminal"
	pt "github.com/monochromegane/the_platinum_searcher"
)

const version = "1.7.6"

var opts pt.Option

func init() {
	if cpu := runtime.NumCPU(); cpu == 1 {
		runtime.GOMAXPROCS(2)
	} else {
		runtime.GOMAXPROCS(cpu)
	}
}

func main() {
	opts.Color = opts.SetEnableColor
	opts.NoColor = opts.SetDisableColor
	opts.EnableColor = true
	opts.SkipVcsIgnore = opts.SkipVcsIgnores

	parser := flags.NewParser(&opts, flags.Default)
	parser.Name = "pt"
	parser.Usage = "[OPTIONS] PATTERN [PATH]"

	args, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}

	if opts.Version {
		fmt.Printf("%s\n", version)
		os.Exit(0)
	}

	if len(args) == 0 && opts.FilesWithRegexp == "" {
		parser.WriteHelp(os.Stdout)
		os.Exit(1)
	}

	opts.SearchStream = false
	if len(args) == 1 {
		fi, err := os.Stdin.Stat()
		if runtime.GOOS == "windows" {
			if err == nil {
				opts.SearchStream = true
				opts.NoGroup = true
			}
		} else {
			if err != nil {
				os.Exit(1)
			}

			mode := fi.Mode()
			if (mode&os.ModeNamedPipe != 0) || mode.IsRegular() {
				opts.SearchStream = true
				opts.NoGroup = true
			}
		}
	}

	var roots = []string{"."}
	if len(args) >= 2 {
		roots = []string{}
		for _, root := range args[1:] {
			root = strings.TrimRight(root, "\"")
			_, err := os.Lstat(root)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s\n", err)
				os.Exit(1)
			}
			roots = append(roots, root)
		}
	}

	opts.Proc = runtime.NumCPU()

	if !terminal.IsTerminal(os.Stdout) {
		if !opts.ForceColor {
			opts.EnableColor = false
		}
		opts.NoGroup = true
	}

	if opts.Context > 0 {
		opts.Before = opts.Context
		opts.After = opts.Context
	}

	pattern := ""
	if len(args) > 0 {
		pattern = args[0]
	}

	if opts.WordRegexp {
		opts.Regexp = true
		pattern = "\\b" + pattern + "\\b"
	}

	start := time.Now()

	searcher := pt.PlatinumSearcher{roots, pattern, &opts}
	err = searcher.Search()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}
	if opts.Stats {
		elapsed := time.Since(start)
		fmt.Printf("%d Files Searched\n", pt.FilesSearched)
		fmt.Printf("%s Elapsed\n", elapsed)
	}

	if pt.FileMatchCount == 0 && pt.MatchCount == 0 {
		os.Exit(1)
	}
}
<commit_msg>Bumped version to 1.7.7.<commit_after>package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"

	flags "github.com/jessevdk/go-flags"
	"github.com/monochromegane/terminal"
	pt "github.com/monochromegane/the_platinum_searcher"
)

const version = "1.7.7"

var opts pt.Option

func init() {
	if cpu := runtime.NumCPU(); cpu == 1 {
		runtime.GOMAXPROCS(2)
	} else {
		runtime.GOMAXPROCS(cpu)
	}
}

func main() {
	opts.Color = opts.SetEnableColor
	opts.NoColor = opts.SetDisableColor
	opts.EnableColor = true
	opts.SkipVcsIgnore = opts.SkipVcsIgnores

	parser := flags.NewParser(&opts, flags.Default)
	parser.Name = "pt"
	parser.Usage = "[OPTIONS] PATTERN [PATH]"

	args, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}

	if opts.Version {
		fmt.Printf("%s\n", version)
		os.Exit(0)
	}

	if len(args) == 0 && opts.FilesWithRegexp == "" {
		parser.WriteHelp(os.Stdout)
		os.Exit(1)
	}

	opts.SearchStream = false
	if len(args) == 1 {
		fi, err := os.Stdin.Stat()
		if runtime.GOOS == "windows" {
			if err == nil {
				opts.SearchStream = true
				opts.NoGroup = true
			}
		} else {
			if err != nil {
				os.Exit(1)
			}

			mode := fi.Mode()
			if (mode&os.ModeNamedPipe != 0) || mode.IsRegular() {
				opts.SearchStream = true
				opts.NoGroup = true
			}
		}
	}

	var roots = []string{"."}
	if len(args) >= 2 {
		roots = []string{}
		for _, root := range args[1:] {
			root = strings.TrimRight(root, "\"")
			_, err := os.Lstat(root)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s\n", err)
				os.Exit(1)
			}
			roots = append(roots, root)
		}
	}

	opts.Proc = runtime.NumCPU()

	if !terminal.IsTerminal(os.Stdout) {
		if !opts.ForceColor {
			opts.EnableColor = false
		}
		opts.NoGroup = true
	}

	if opts.Context > 0 {
		opts.Before = opts.Context
		opts.After = opts.Context
	}

	pattern := ""
	if len(args) > 0 {
		pattern = args[0]
	}

	if opts.WordRegexp {
		opts.Regexp = true
		pattern = "\\b" + pattern + "\\b"
	}

	start := time.Now()

	searcher := pt.PlatinumSearcher{roots, pattern, &opts}
	err = searcher.Search()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}
	if opts.Stats {
		elapsed := time.Since(start)
		fmt.Printf("%d Files Searched\n", pt.FilesSearched)
		fmt.Printf("%s Elapsed\n", elapsed)
	}

	if pt.FileMatchCount == 0 && pt.MatchCount == 0 {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"

	"github.com/spf13/cobra"

	"github.com/grafana/tanka/pkg/spec/v1alpha1"
)

// initCmd creates a new application
func initCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "init",
		Short: "Create the directory structure",
		Args:  cobra.NoArgs,
	}

	force := cmd.Flags().BoolP("force", "f", false, "ignore the working directory not being empty")
	installK8sLibFlag := cmd.Flags().Bool("k8s", true, "set to false to skip installation of k.libsonnet")

	cmd.Run = func(cmd *cobra.Command, args []string) {
		failed := false

		files, err := ioutil.ReadDir(".")
		if err != nil {
			log.Fatalln("Error listing files:", err)
		}
		if len(files) > 0 && !*force {
			log.Fatalln("Error: directory not empty. Use `-f` to force")
		}

		if err := writeNewFile("jsonnetfile.json", "{}"); err != nil {
			log.Fatalln("Error creating `jsonnetfile.json`:", err)
		}

		if err := os.Mkdir("vendor", os.ModePerm); err != nil {
			log.Fatalln("Error creating `vendor/` folder:", err)
		}

		if err := os.Mkdir("lib", os.ModePerm); err != nil {
			log.Fatalln("Error creating `vendor/` folder:", err)
		}

		cfg := v1alpha1.New()
		if err := addEnv("environments/default", cfg); err != nil {
			log.Fatalln(err)
		}

		if *installK8sLibFlag {
			if err := installK8sLib(); err != nil {
				// This is not fatal, as most of Tanka will work anyways
				log.Println("Installing k.libsonnet:", err)
				failed = true
			}
		}

		fmt.Println("Directory structure set up! Remember to configure the API endpoint:\n`tk env set environments/default --server=127.0.0.1:6443`")
		if failed {
			log.Println("Errors occured while initializing the project. Check the above logs for details.")
		}
	}
	return cmd
}

func installK8sLib() error {
	if _, err := exec.LookPath("jb"); err != nil {
		return errors.New("jsonnet-bundler not found in $PATH. Follow https://tanka.dev/install#jsonnet-bundler for installation instructions.")
	}

	// TODO: use the jb packages for this once refactored there
	const klibsonnetJsonnetfile = `{
"dependencies": [
    {
      "source": {
        "git": {
          "remote": "https://github.com/grafana/jsonnet-libs",
          "subdir": "ksonnet-util"
        }
      },
      "version": "master"
    },
    {
      "source": {
        "git": {
          "remote": "https://github.com/ksonnet/ksonnet-lib",
          "subdir": "ksonnet.beta.4"
        }
      },
      "version": "master"
    }
  ]
}
`

	if err := writeNewFile("lib/k.libsonnet", `import "k.libsonnet"`); err != nil {
		return err
	}

	if err := ioutil.WriteFile("jsonnetfile.json", []byte(klibsonnetJsonnetfile), 0644); err != nil {
		return err
	}

	cmd := exec.Command("jb", "install")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()
}

// writeNewFile writes the content to a file if it does not exist
func writeNewFile(name, content string) error {
	if _, err := os.Stat(name); os.IsNotExist(err) {
		return ioutil.WriteFile(name, []byte(content), 0644)
	}
	return nil
}
<commit_msg>fix(cli): correct k.libsonnet import path in init (#141)<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"

	"github.com/spf13/cobra"

	"github.com/grafana/tanka/pkg/spec/v1alpha1"
)

// initCmd creates a new application
func initCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "init",
		Short: "Create the directory structure",
		Args:  cobra.NoArgs,
	}

	force := cmd.Flags().BoolP("force", "f", false, "ignore the working directory not being empty")
	installK8sLibFlag := cmd.Flags().Bool("k8s", true, "set to false to skip installation of k.libsonnet")

	cmd.Run = func(cmd *cobra.Command, args []string) {
		failed := false

		files, err := ioutil.ReadDir(".")
		if err != nil {
			log.Fatalln("Error listing files:", err)
		}
		if len(files) > 0 && !*force {
			log.Fatalln("Error: directory not empty. Use `-f` to force")
		}

		if err := writeNewFile("jsonnetfile.json", "{}"); err != nil {
			log.Fatalln("Error creating `jsonnetfile.json`:", err)
		}

		if err := os.Mkdir("vendor", os.ModePerm); err != nil {
			log.Fatalln("Error creating `vendor/` folder:", err)
		}

		if err := os.Mkdir("lib", os.ModePerm); err != nil {
			log.Fatalln("Error creating `vendor/` folder:", err)
		}

		cfg := v1alpha1.New()
		if err := addEnv("environments/default", cfg); err != nil {
			log.Fatalln(err)
		}

		if *installK8sLibFlag {
			if err := installK8sLib(); err != nil {
				// This is not fatal, as most of Tanka will work anyways
				log.Println("Installing k.libsonnet:", err)
				failed = true
			}
		}

		fmt.Println("Directory structure set up! Remember to configure the API endpoint:\n`tk env set environments/default --server=127.0.0.1:6443`")
		if failed {
			log.Println("Errors occured while initializing the project. Check the above logs for details.")
		}
	}
	return cmd
}

func installK8sLib() error {
	if _, err := exec.LookPath("jb"); err != nil {
		return errors.New("jsonnet-bundler not found in $PATH. Follow https://tanka.dev/install#jsonnet-bundler for installation instructions.")
	}

	// TODO: use the jb packages for this once refactored there
	const klibsonnetJsonnetfile = `{
"dependencies": [
    {
      "source": {
        "git": {
          "remote": "https://github.com/grafana/jsonnet-libs",
          "subdir": "ksonnet-util"
        }
      },
      "version": "master"
    },
    {
      "source": {
        "git": {
          "remote": "https://github.com/ksonnet/ksonnet-lib",
          "subdir": "ksonnet.beta.4"
        }
      },
      "version": "master"
    }
  ]
}
`

	if err := writeNewFile("lib/k.libsonnet", `import "ksonnet.beta.4/k.libsonnet"`); err != nil {
		return err
	}

	if err := ioutil.WriteFile("jsonnetfile.json", []byte(klibsonnetJsonnetfile), 0644); err != nil {
		return err
	}

	cmd := exec.Command("jb", "install")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()
}

// writeNewFile writes the content to a file if it does not exist
func writeNewFile(name, content string) error {
	if _, err := os.Stat(name); os.IsNotExist(err) {
		return ioutil.WriteFile(name, []byte(content), 0644)
	}
	return nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	ver "github.com/hashicorp/go-version"
)

var (
	version    = "v3.0.0"
	prerelease = "develop" // blank if full release
)

func buildVersion() string {
	verStr := version
	if prerelease != "" {
		verStr = fmt.Sprintf("%s-%s", version, prerelease)
	}
	// make sure we fail fast (panic) if bad version - this will get caught in CI tests
	ver.Must(ver.NewVersion(verStr))
	return verStr
}
<commit_msg>Set prerelease to beta<commit_after>package cmd

import (
	"fmt"

	ver "github.com/hashicorp/go-version"
)

var (
	version    = "v3.0.0"
	prerelease = "beta" // blank if full release
)

func buildVersion() string {
	verStr := version
	if prerelease != "" {
		verStr = fmt.Sprintf("%s-%s", version, prerelease)
	}
	// make sure we fail fast (panic) if bad version - this will get caught in CI tests
	ver.Must(ver.NewVersion(verStr))
	return verStr
}
<|endoftext|>


<commit_before>package installer

import (
	"testing"

	. "github.com/VonC/godbg"
	. "github.com/smartystreets/goconvey/convey"
)

type testInstaller struct{ i Inst }

type testPrg struct{ name string }

func (tp *testPrg) Name() string { return tp.name }

func (ti testInstaller) IsInstalled() bool {
	ti.i.IsInstalled()
	return true
}
func (ti testInstaller) HasFailed() bool {
	ti.i.HasFailed()
	return false
}
func TestMain(t *testing.T) {

	Convey("For a given installer", t, func() {
		SetBuffers(nil)
		p := &testPrg{name: "prg1"}
		inst1 := New(p)
		So(inst1.(*inst).p.Name(), ShouldEqual, "prg1")
		inst1 = &testInstaller{i: inst1}
		Convey("an installer can test if the program is already installed", func() {
			So(inst1.IsInstalled(), ShouldBeTrue)
		})
		Convey("an installer can test if the program has failed to install", func() {
			So(inst1.HasFailed(), ShouldBeFalse)
		})
	})

}
<commit_msg>Basic Test for Install()<commit_after>package installer

import (
	"testing"

	. "github.com/VonC/godbg"
	. "github.com/smartystreets/goconvey/convey"
)

type testInstaller struct{ i Inst }

type testPrg struct{ name string }

func (tp *testPrg) Name() string { return tp.name }

func (ti *testInstaller) IsInstalled() bool {
	ti.i.IsInstalled()
	return true
}
func (ti *testInstaller) HasFailed() bool {
	ti.i.HasFailed()
	return false
}
func (ti *testInstaller) Install() error {
	ti.i.Install()
	return nil
}
func TestMain(t *testing.T) {

	Convey("For a given installer", t, func() {
		SetBuffers(nil)
		p := &testPrg{name: "prg1"}
		inst1 := New(p)
		So(inst1.(*inst).p.Name(), ShouldEqual, "prg1")
		inst1 = &testInstaller{i: inst1}
		Convey("an installer can test if the program is already installed", func() {
			So(inst1.IsInstalled(), ShouldBeTrue)
		})
		Convey("an installer can test if the program has failed to install", func() {
			So(inst1.HasFailed(), ShouldBeFalse)
		})
		Convey("an installer can launch the installation of a program ", func() {
			So(inst1.Install(), ShouldBeNil)
		})
	})

}
<|endoftext|>


<commit_before>// A circuit breaker
package circuitry

import (
	"sync"
	"time"
)

// CircuitBreaker represents a circuit breaker
type CircuitBreaker struct {
	FailCounter  int
	FailMax      int
	ResetTimeout time.Duration
	state        circuitState
	lock         *sync.Mutex
}

// Create a new circuit breaker with failMax failures and a resetTimeout timeout
func Breaker(failMax int, resetTimeout time.Duration) *CircuitBreaker {
	b := new(CircuitBreaker)
	b.FailCounter = 0
	b.FailMax = failMax
	b.ResetTimeout = resetTimeout
	b.lock = new(sync.Mutex)
	b.state = &closedCircuit{b}
	return b
}

// Reports if the circuit is closed
func (b *CircuitBreaker) IsClosed() bool {
	return b.state.BeforeCall()
}

// Reports if the circuit is open
func (b *CircuitBreaker) IsOpen() bool {
	return !b.state.BeforeCall()
}

// Pass error to the to the circuit breaker
func (b *CircuitBreaker) Error(err error) {
	if err == nil {
		b.state.HandleSuccess()
	} else {
		b.state.HandleFailure()
	}
}

// Close the circuit
func (b *CircuitBreaker) Close() {
	b.lock.Lock()
	b.FailCounter = 0
	b.state = &closedCircuit{b}
	b.lock.Unlock()
}

// Open the circuit
func (b *CircuitBreaker) Open() {
	b.lock.Lock()
	b.state = &openCircuit{time.Now(), b}
	b.lock.Unlock()
}

// Half-open the circuit
func (b *CircuitBreaker) HalfOpen() {
	b.lock.Lock()
	b.state = &halfopenCircuit{b}
	b.lock.Unlock()
}
<commit_msg>better mutex code<commit_after>// A circuit breaker
package circuitry

import (
	"sync"
	"time"
)

// CircuitBreaker represents a circuit breaker
type CircuitBreaker struct {
	FailCounter  int
	FailMax      int
	ResetTimeout time.Duration
	state        circuitState
	sync.Mutex
}

// Create a new circuit breaker with failMax failures and a resetTimeout timeout
func Breaker(failMax int, resetTimeout time.Duration) *CircuitBreaker {
	b := new(CircuitBreaker)
	b.FailCounter = 0
	b.FailMax = failMax
	b.ResetTimeout = resetTimeout
	b.state = &closedCircuit{b}
	return b
}

// Reports if the circuit is closed
func (b *CircuitBreaker) IsClosed() bool {
	return b.state.BeforeCall()
}

// Reports if the circuit is open
func (b *CircuitBreaker) IsOpen() bool {
	return !b.state.BeforeCall()
}

// Pass error to the to the circuit breaker
func (b *CircuitBreaker) Error(err error) {
	if err == nil {
		b.state.HandleSuccess()
	} else {
		b.state.HandleFailure()
	}
}

// Close the circuit
func (b *CircuitBreaker) Close() {
	b.Lock()
	b.FailCounter = 0
	b.state = &closedCircuit{b}
	b.Unlock()
}

// Open the circuit
func (b *CircuitBreaker) Open() {
	b.Lock()
	b.state = &openCircuit{time.Now(), b}
	b.Unlock()
}

// Half-open the circuit
func (b *CircuitBreaker) HalfOpen() {
	b.Lock()
	b.state = &halfopenCircuit{b}
	b.Unlock()
}
<|endoftext|>


<commit_before>// Copyright 2015 Jeff Martinez. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE.txt file
// or at http://opensource.org/licenses/MIT

package cleanexit

import (
	"os"
	"os/signal"
)

// Standard command line tool code for 'successful termination'.
const EXIT_SUCCESS = 0

/*
	Sets up a separate goroutine to listen for ctrl-c.
	As soon as a ctrl-c is entered in the terminal, the program
	is terminated. An program exit code of 0 is returned.

	Use SetUpExitOnCtrlC to allow for a custom function to run
	right before program termination.
*/
func SetUpSimpleExitOnCtrlC() {
	SetUpExitOnCtrlC(func() {})
}

/*
	Similar to SetUpSimpleExitOnCtrlC except you can pass in a
	function to run right before the program terminates.

	Can be used for cleanup, or to print a nice exit message. I've
	used this to hide the "^C" that is printed to a terminal when a
	user hits ctrl-c. You can do this by printing a couple \b
	characters followed by spaces to stdout. Like this:

	fmt.Printf("\b\b  \n")

	The \b characters are the equivalent of hitting the back arrow
	key. Wrap that in a function, pass it in to SetUpExitOnCtrlC and
	you're set.

	You can use your own os.Exit(...) within the cleanup function
	to signify non-successful exit if necessary.
*/
func SetUpExitOnCtrlC(cleanup func()) {
	const NUM_PARALLEL_SIGNALS_TO_PROCESS = 1

	killChannel := make(chan os.Signal, NUM_PARALLEL_SIGNALS_TO_PROCESS)
	signal.Notify(killChannel, os.Interrupt, os.Kill)

	go func() {
		<-killChannel
		cleanup()
		os.Exit(EXIT_SUCCESS)
	}()
}
<commit_msg>fix comment typo<commit_after>// Copyright 2015 Jeff Martinez. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE.txt file
// or at http://opensource.org/licenses/MIT

package cleanexit

import (
	"os"
	"os/signal"
)

// Standard command line tool code for 'successful termination'.
const EXIT_SUCCESS = 0

/*
	Sets up a separate goroutine to listen for ctrl-c.
	As soon as a ctrl-c is entered in the terminal, the program
	is terminated. An program exit code of 0 is returned.

	Use SetUpExitOnCtrlC to allow for a custom function to run
	right before program termination.
*/
func SetUpSimpleExitOnCtrlC() {
	SetUpExitOnCtrlC(func() {})
}

/*
	Similar to SetUpSimpleExitOnCtrlC except you can pass in a
	function to run right before the program terminates.

	Can be used for clean up, or to print a nice exit message. I've
	used this to hide the "^C" that is printed to a terminal when a
	user hits ctrl-c. You can do this by printing a couple \b
	characters followed by spaces to stdout. Like this:

	fmt.Printf("\b\b  \n")

	The \b characters are the equivalent of hitting the back arrow
	key. Wrap that in a function, pass it in to SetUpExitOnCtrlC and
	you're set.

	You can use your own os.Exit(...) within the cleanup function
	to signify non-successful exit if necessary.
*/
func SetUpExitOnCtrlC(cleanup func()) {
	const NUM_PARALLEL_SIGNALS_TO_PROCESS = 1

	killChannel := make(chan os.Signal, NUM_PARALLEL_SIGNALS_TO_PROCESS)
	signal.Notify(killChannel, os.Interrupt, os.Kill)

	go func() {
		<-killChannel
		cleanup()
		os.Exit(EXIT_SUCCESS)
	}()
}
<|endoftext|>


<commit_before>package dbmigrate

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/gocql/gocql"
)

// Database interface needs to be inmplemented to migrate a new type of database

type Database interface {
	CreateMigrationsTable() error
	HasMigrated(filename string) (bool, error)
	Migrate(filename string, migration string) error
}

// CassandraDatabase migrates Cassandra databases

type CassandraDatabase struct {
	readerSession *gocql.Session
	writerSession *gocql.Session
}

func (cassandra *CassandraDatabase) CreateMigrationsTable() error {
	err := cassandra.writerSession.Query(`
		CREATE TABLE migrations (
			name TEXT,
			created_at TIMEUUID,
			PRIMARY KEY (name)
		);
	`).Exec()
	if err != nil {
		if !strings.Contains(err.Error(), "Cannot add already existing column family") {
			return err
		}
	}
	return nil
}

func (cassandra *CassandraDatabase) HasMigrated(filename string) (bool, error) {
	var count int
	iter := cassandra.readerSession.Query(`
		SELECT COUNT(*) FROM migrations WHERE name = ?
	`, filename).Iter()
	if !iter.Scan(&count) {
		return false, iter.Close()
	}
	if err := iter.Close(); err != nil {
		return false, err
	}
	return count > 0, nil
}

func (cassandra *CassandraDatabase) Migrate(filename string, migration string) error {
	/*
		_, err := postgres.database.Exec(migration)
		if err != nil {
			return err
		}
		_, err = postgres.database.Exec("insert into migrations(name, created_at) values($1, current_timestamp)", filename)
		return err
	*/
	return nil
}

func NewCassandraDatabase(readerSession *gocql.Session, writerSession *gocql.Session) *CassandraDatabase {
	return &CassandraDatabase{
		readerSession: readerSession,
		writerSession: writerSession,
	}
}

// PostgresDatabase migrates Postgresql databases

type PostgresDatabase struct {
	database *sql.DB
}

func (postgres *PostgresDatabase) CreateMigrationsTable() error {
	_, err := postgres.database.Exec(`
		create table if not exists migrations(id serial, name text not null, created_at timestamp with time zone not null)
	`)
	if err != nil {
		return err
	}
	_, err = postgres.database.Exec("create unique index idx_migrations_name on migrations(name)")
	if err != nil {
		if !strings.Contains(err.Error(), "already exists") {
			return err
		}
	}
	return nil
}

func (postgres *PostgresDatabase) HasMigrated(filename string) (bool, error) {
	var count int
	err := postgres.database.QueryRow("select count(1) from migrations where name = $1", filename).Scan(&count)
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func (postgres *PostgresDatabase) Migrate(filename string, migration string) error {
	_, err := postgres.database.Exec(migration)
	if err != nil {
		return err
	}
	_, err = postgres.database.Exec("insert into migrations(name, created_at) values($1, current_timestamp)", filename)
	return err
}

func NewPostgresDatabase(db *sql.DB) *PostgresDatabase {
	return &PostgresDatabase{database: db}
}

// By default, apply Postgresql migrations, as in older versions
func Run(db *sql.DB, migrationsFolder string) error {
	postgres := NewPostgresDatabase(db)
	return ApplyMigrations(postgres, migrationsFolder)
}

// Run applies migrations from migrationsFolder to database.
func ApplyMigrations(database Database, migrationsFolder string) error {
	// Initialize migrations table, if it does not exist yet
	if err := database.CreateMigrationsTable(); err != nil {
		return err
	}

	// Scan migration file names in migrations folder
	d, err := os.Open(migrationsFolder)
	if err != nil {
		return err
	}
	dir, err := d.Readdir(-1)
	if err != nil {
		return err
	}

	// Run migrations
	sqlFiles := make([]string, 0)
	for _, f := range dir {
		ext := filepath.Ext(f.Name())
		if ".sql" == ext || ".cql" == ext {
			sqlFiles = append(sqlFiles, f.Name())
		}
	}
	sort.Strings(sqlFiles)
	for _, filename := range sqlFiles {
		// if exists in migrations table, leave it
		// else execute sql
		migrated, err := database.HasMigrated(filename)
		if err != nil {
			return err
		}
		if migrated {
			continue
		}
		b, err := ioutil.ReadFile(filepath.Join(migrationsFolder, filename))
		if err != nil {
			return err
		}
		migration := string(b)
		if len(migration) == 0 {
			continue // empty file
		}
		err = database.Migrate(filename, migration)
		if err != nil {
			return err
		}
		fmt.Println("Migrated", filename)
	}

	return nil
}
<commit_msg>Run c* migrations<commit_after>package dbmigrate

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/gocql/gocql"
)

// Database interface needs to be inmplemented to migrate a new type of database

type Database interface {
	CreateMigrationsTable() error
	HasMigrated(filename string) (bool, error)
	Migrate(filename string, migration string) error
}

// CassandraDatabase migrates Cassandra databases

type CassandraDatabase struct {
	readerSession *gocql.Session
	writerSession *gocql.Session
}

func (cassandra *CassandraDatabase) CreateMigrationsTable() error {
	err := cassandra.writerSession.Query(`
		CREATE TABLE migrations (
			name TEXT,
			created_at TIMEUUID,
			PRIMARY KEY (name)
		);
	`).Exec()
	if err != nil {
		if !strings.Contains(err.Error(), "Cannot add already existing column family") {
			return err
		}
	}
	return nil
}

func (cassandra *CassandraDatabase) HasMigrated(filename string) (bool, error) {
	var count int
	iter := cassandra.readerSession.Query(`
		SELECT COUNT(*) FROM migrations WHERE name = ?
	`, filename).Iter()
	if !iter.Scan(&count) {
		return false, iter.Close()
	}
	if err := iter.Close(); err != nil {
		return false, err
	}
	return count > 0, nil
}

func (cassandra *CassandraDatabase) Migrate(filename string, migration string) error {
	if err := cassandra.writerSession.Query(migration).Exec(); err != nil {
		return err
	}
	return cassandra.writerSession.Query(`
		INSERT INTO migrations(name, created_at)
		VALUES(?, NOW())
	`, filename).Exec()
}

func NewCassandraDatabase(readerSession *gocql.Session, writerSession *gocql.Session) *CassandraDatabase {
	return &CassandraDatabase{
		readerSession: readerSession,
		writerSession: writerSession,
	}
}

// PostgresDatabase migrates Postgresql databases

type PostgresDatabase struct {
	database *sql.DB
}

func (postgres *PostgresDatabase) CreateMigrationsTable() error {
	_, err := postgres.database.Exec(`
		create table if not exists migrations(id serial, name text not null, created_at timestamp with time zone not null)
	`)
	if err != nil {
		return err
	}
	_, err = postgres.database.Exec("create unique index idx_migrations_name on migrations(name)")
	if err != nil {
		if !strings.Contains(err.Error(), "already exists") {
			return err
		}
	}
	return nil
}

func (postgres *PostgresDatabase) HasMigrated(filename string) (bool, error) {
	var count int
	err := postgres.database.QueryRow("select count(1) from migrations where name = $1", filename).Scan(&count)
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func (postgres *PostgresDatabase) Migrate(filename string, migration string) error {
	_, err := postgres.database.Exec(migration)
	if err != nil {
		return err
	}
	_, err = postgres.database.Exec("insert into migrations(name, created_at) values($1, current_timestamp)", filename)
	return err
}

func NewPostgresDatabase(db *sql.DB) *PostgresDatabase {
	return &PostgresDatabase{database: db}
}

// By default, apply Postgresql migrations, as in older versions
func Run(db *sql.DB, migrationsFolder string) error {
	postgres := NewPostgresDatabase(db)
	return ApplyMigrations(postgres, migrationsFolder)
}

// Run applies migrations from migrationsFolder to database.
func ApplyMigrations(database Database, migrationsFolder string) error {
	// Initialize migrations table, if it does not exist yet
	if err := database.CreateMigrationsTable(); err != nil {
		return err
	}

	// Scan migration file names in migrations folder
	d, err := os.Open(migrationsFolder)
	if err != nil {
		return err
	}
	dir, err := d.Readdir(-1)
	if err != nil {
		return err
	}

	// Run migrations
	sqlFiles := make([]string, 0)
	for _, f := range dir {
		ext := filepath.Ext(f.Name())
		if ".sql" == ext || ".cql" == ext {
			sqlFiles = append(sqlFiles, f.Name())
		}
	}
	sort.Strings(sqlFiles)
	for _, filename := range sqlFiles {
		// if exists in migrations table, leave it
		// else execute sql
		migrated, err := database.HasMigrated(filename)
		if err != nil {
			return err
		}
		if migrated {
			continue
		}
		b, err := ioutil.ReadFile(filepath.Join(migrationsFolder, filename))
		if err != nil {
			return err
		}
		migration := string(b)
		if len(migration) == 0 {
			continue // empty file
		}
		err = database.Migrate(filename, migration)
		if err != nil {
			return err
		}
		fmt.Println("Migrated", filename)
	}

	return nil
}
<|endoftext|>


<commit_before>package acceptance_test

import (
	"os"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/onsi/gomega/gexec"
)

var _ = Describe("trace logging for the plugin", func() {
	var (
		orgName   string
		spaceName string
	)

	BeforeEach(func() {
		prefix := testConfig.Prefix
		orgName = prefix + "org"
		Expect(cf.Cf("create-org", orgName).Wait(Timeout_Push)).To(gexec.Exit(0))
		Expect(cf.Cf("target", "-o", orgName).Wait(Timeout_Push)).To(gexec.Exit(0))

		spaceName = prefix + "space"
		Expect(cf.Cf("create-space", spaceName).Wait(Timeout_Push)).To(gexec.Exit(0))
		Expect(cf.Cf("target", "-o", orgName, "-s", spaceName).Wait(Timeout_Push)).To(gexec.Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete-org", orgName, "-f").Wait(Timeout_Push)).To(gexec.Exit(0))
	})

	Context("when tracing is disabled", func() {
		It("does not log the HTTP request or response", func() {
			listAccess := cf.Cf("list-access")
			Expect(listAccess.Wait(Timeout_Push)).To(gexec.Exit(0))
			Expect(string(listAccess.Out.Contents())).NotTo(ContainSubstring("GET /networking/v0/external/policies"))
		})
	})

	Context("when tracing is enabled", func() {
		BeforeEach(func() {
			Expect(os.Setenv("CF_TRACE", "true")).To(Succeed())
		})

		AfterEach(func() {
			Expect(os.Unsetenv("CF_TRACE")).To(Succeed())
		})

		It("logs the HTTP request and responses to the policy server", func() {
			listAccess := cf.Cf("list-access")
			Expect(listAccess.Wait(Timeout_Push)).To(gexec.Exit(0))
			Expect(string(listAccess.Out.Contents())).To(ContainSubstring("GET /networking/v0/external/policies"))
		})

		It("does not print private data", func() {
			listAccess := cf.Cf("list-access")
			Expect(listAccess.Wait(Timeout_Push)).To(gexec.Exit(0))
			Expect(string(listAccess.Out.Contents())).ToNot(ContainSubstring("bearer"))
		})
	})
})
<commit_msg>Fix cli plugin acceptance test to have unique org name<commit_after>package acceptance_test

import (
	"fmt"
	"os"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/onsi/gomega/gexec"
)

var _ = Describe("trace logging for the plugin", func() {
	var (
		orgName   string
		spaceName string
	)

	BeforeEach(func() {
		prefix := testConfig.Prefix
		orgName = fmt.Sprintf("%scli-plugin-org-%d", prefix, GinkgoParallelNode())

		Expect(cf.Cf("create-org", orgName).Wait(Timeout_Push)).To(gexec.Exit(0))
		Expect(cf.Cf("target", "-o", orgName).Wait(Timeout_Push)).To(gexec.Exit(0))

		spaceName = prefix + "space"
		Expect(cf.Cf("create-space", spaceName).Wait(Timeout_Push)).To(gexec.Exit(0))
		Expect(cf.Cf("target", "-o", orgName, "-s", spaceName).Wait(Timeout_Push)).To(gexec.Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete-org", orgName, "-f").Wait(Timeout_Push)).To(gexec.Exit(0))
	})

	Describe("when tracing is disabled", func() {
		It("does not log the HTTP request or response", func() {
			listAccess := cf.Cf("list-access")
			Expect(listAccess.Wait(Timeout_Push)).To(gexec.Exit(0))
			Expect(string(listAccess.Out.Contents())).NotTo(ContainSubstring("GET /networking/v0/external/policies"))
		})
	})

	Describe("when tracing is enabled", func() {
		BeforeEach(func() {
			Expect(os.Setenv("CF_TRACE", "true")).To(Succeed())
		})

		AfterEach(func() {
			Expect(os.Unsetenv("CF_TRACE")).To(Succeed())
		})

		It("logs the HTTP request and responses to the policy server", func() {
			listAccess := cf.Cf("list-access")
			Expect(listAccess.Wait(Timeout_Push)).To(gexec.Exit(0))

			By("printing trace info", func() {
				Expect(string(listAccess.Out.Contents())).To(ContainSubstring("GET /networking/v0/external/policies"))
			})

			By("not printing private data", func() {
				Expect(string(listAccess.Out.Contents())).ToNot(ContainSubstring("bearer"))
			})
		})
	})
})
<|endoftext|>


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

package backend

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/fsouza/fake-gcs-server/internal/checksum"
)

// storageFS is an implementation of the backend storage that stores data on disk
//
// The layout is the following:
//
// - rootDir
//   |- bucket1
//   \- bucket2
//     |- object1
//     \- object2
//
// Bucket and object names are url path escaped, so there's no special meaning of forward slashes.
type storageFS struct {
	rootDir string
	mtx     sync.RWMutex
}

// NewStorageFS creates an instance of the filesystem-backed storage backend.
func NewStorageFS(objects []Object, rootDir string) (Storage, error) {
	if !strings.HasSuffix(rootDir, "/") {
		rootDir += "/"
	}
	err := os.MkdirAll(rootDir, 0o700)
	if err != nil {
		return nil, err
	}
	s := &storageFS{rootDir: rootDir}
	for _, o := range objects {
		_, err := s.CreateObject(o)
		if err != nil {
			return nil, err
		}
	}
	return s, nil
}

// CreateBucket creates a bucket in the fs backend. A bucket is a folder in the
// root directory.
func (s *storageFS) CreateBucket(name string, versioningEnabled bool) error {
	if versioningEnabled {
		return errors.New("not implemented: fs storage type does not support versioning yet")
	}
	s.mtx.Lock()
	defer s.mtx.Unlock()
	return s.createBucket(name)
}

func (s *storageFS) createBucket(name string) error {
	return os.MkdirAll(filepath.Join(s.rootDir, url.PathEscape(name)), 0o700)
}

// ListBuckets returns a list of buckets from the list of directories in the
// root directory.
func (s *storageFS) ListBuckets() ([]Bucket, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	infos, err := ioutil.ReadDir(s.rootDir)
	if err != nil {
		return nil, err
	}
	buckets := []Bucket{}
	for _, info := range infos {
		if info.IsDir() {
			unescaped, err := url.PathUnescape(info.Name())
			if err != nil {
				return nil, fmt.Errorf("failed to unescape object name %s: %w", info.Name(), err)
			}
			buckets = append(buckets, Bucket{Name: unescaped})
		}
	}
	return buckets, nil
}

func timespecToTime(ts syscall.Timespec) time.Time {
	return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}

// GetBucket returns information about the given bucket, or an error if it
// doesn't exist.
func (s *storageFS) GetBucket(name string) (Bucket, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	dirInfo, err := os.Stat(filepath.Join(s.rootDir, url.PathEscape(name)))
	if err != nil {
		return Bucket{}, err
	}
	return Bucket{Name: name, VersioningEnabled: false, TimeCreated: timespecToTime(createTimeFromFileInfo(dirInfo))}, err
}

// DeleteBucket removes the bucket from the backend.
func (s *storageFS) DeleteBucket(name string) error {
	objs, err := s.ListObjects(name, "", false)
	if err != nil {
		return BucketNotFound
	}
	if len(objs) > 0 {
		return BucketNotEmpty
	}

	s.mtx.Lock()
	defer s.mtx.Unlock()
	return os.RemoveAll(filepath.Join(s.rootDir, url.PathEscape(name)))
}

// CreateObject stores an object as a regular file in the disk.
func (s *storageFS) CreateObject(obj Object) (Object, error) {
	if obj.Generation > 0 {
		return Object{}, errors.New("not implemented: fs storage type does not support objects generation yet")
	}
	s.mtx.Lock()
	defer s.mtx.Unlock()
	err := s.createBucket(obj.BucketName)
	if err != nil {
		return Object{}, err
	}
	encoded, err := json.Marshal(obj)
	if err != nil {
		return Object{}, err
	}
	return obj, ioutil.WriteFile(filepath.Join(s.rootDir, url.PathEscape(obj.BucketName), url.PathEscape(obj.Name)), encoded, 0o600)
}

// ListObjects lists the objects in a given bucket with a given prefix and
// delimeter.
func (s *storageFS) ListObjects(bucketName string, prefix string, versions bool) ([]ObjectAttrs, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()

	infos, err := ioutil.ReadDir(path.Join(s.rootDir, url.PathEscape(bucketName)))
	if err != nil {
		return nil, err
	}
	objects := []ObjectAttrs{}
	for _, info := range infos {
		unescaped, err := url.PathUnescape(info.Name())
		if err != nil {
			return nil, fmt.Errorf("failed to unescape object name %s: %w", info.Name(), err)
		}
		if prefix != "" && !strings.HasPrefix(unescaped, prefix) {
			continue
		}
		object, err := s.getObject(bucketName, unescaped)
		if err != nil {
			return nil, err
		}
		object.Size = int64(len(object.Content))
		objects = append(objects, object.ObjectAttrs)
	}
	return objects, nil
}

// GetObject get an object by bucket and name.
func (s *storageFS) GetObject(bucketName, objectName string) (Object, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	return s.getObject(bucketName, objectName)
}

// GetObjectWithGeneration retrieves an specific version of the object. Not
// implemented for this backend.
func (s *storageFS) GetObjectWithGeneration(bucketName, objectName string, generation int64) (Object, error) {
	return Object{}, errors.New("not implemented: fs storage type does not support versioning yet")
}

func (s *storageFS) getObject(bucketName, objectName string) (Object, error) {
	encoded, err := ioutil.ReadFile(filepath.Join(s.rootDir, url.PathEscape(bucketName), url.PathEscape(objectName)))
	if err != nil {
		return Object{}, err
	}
	var obj Object
	err = json.Unmarshal(encoded, &obj)
	if err != nil {
		return Object{}, err
	}
	obj.Name = filepath.ToSlash(objectName)
	obj.BucketName = bucketName
	obj.Size = int64(len(obj.Content))
	return obj, nil
}

// DeleteObject deletes an object by bucket and name.
func (s *storageFS) DeleteObject(bucketName, objectName string) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	if objectName == "" {
		return errors.New("can't delete object with empty name")
	}
	return os.Remove(filepath.Join(s.rootDir, url.PathEscape(bucketName), url.PathEscape(objectName)))
}

// PatchObject patches the given object metadata.
func (s *storageFS) PatchObject(bucketName, objectName string, metadata map[string]string) (Object, error) {
	obj, err := s.GetObject(bucketName, objectName)
	if err != nil {
		return Object{}, err
	}
	if obj.Metadata == nil {
		obj.Metadata = map[string]string{}
	}
	for k, v := range metadata {
		obj.Metadata[k] = v
	}
	s.CreateObject(obj) // recreate object
	return obj, nil
}

func (s *storageFS) ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string) (Object, error) {
	var data []byte
	for _, n := range objectNames {
		obj, err := s.GetObject(bucketName, n)
		if err != nil {
			return Object{}, err
		}
		data = append(data, obj.Content...)
	}

	dest, err := s.GetObject(bucketName, destinationName)
	if err != nil {
		oattrs := ObjectAttrs{
			BucketName:  bucketName,
			Name:        destinationName,
			ContentType: contentType,
			Created:     time.Now().String(),
		}
		dest = Object{
			ObjectAttrs: oattrs,
		}
	}

	dest.Content = data
	dest.Crc32c = checksum.EncodedCrc32cChecksum(data)
	dest.Md5Hash = checksum.EncodedMd5Hash(data)
	dest.Metadata = metadata

	result, err := s.CreateObject(dest)
	if err != nil {
		return result, err
	}

	return result, nil
}
<commit_msg>Use path/filepath instead of path (#595)<commit_after>// Copyright 2018 Francisco Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package backend

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/fsouza/fake-gcs-server/internal/checksum"
)

// storageFS is an implementation of the backend storage that stores data on disk
//
// The layout is the following:
//
// - rootDir
//   |- bucket1
//   \- bucket2
//     |- object1
//     \- object2
//
// Bucket and object names are url path escaped, so there's no special meaning of forward slashes.
type storageFS struct {
	rootDir string
	mtx     sync.RWMutex
}

// NewStorageFS creates an instance of the filesystem-backed storage backend.
func NewStorageFS(objects []Object, rootDir string) (Storage, error) {
	if !strings.HasSuffix(rootDir, "/") {
		rootDir += "/"
	}
	err := os.MkdirAll(rootDir, 0o700)
	if err != nil {
		return nil, err
	}
	s := &storageFS{rootDir: rootDir}
	for _, o := range objects {
		_, err := s.CreateObject(o)
		if err != nil {
			return nil, err
		}
	}
	return s, nil
}

// CreateBucket creates a bucket in the fs backend. A bucket is a folder in the
// root directory.
func (s *storageFS) CreateBucket(name string, versioningEnabled bool) error {
	if versioningEnabled {
		return errors.New("not implemented: fs storage type does not support versioning yet")
	}
	s.mtx.Lock()
	defer s.mtx.Unlock()
	return s.createBucket(name)
}

func (s *storageFS) createBucket(name string) error {
	return os.MkdirAll(filepath.Join(s.rootDir, url.PathEscape(name)), 0o700)
}

// ListBuckets returns a list of buckets from the list of directories in the
// root directory.
func (s *storageFS) ListBuckets() ([]Bucket, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	infos, err := ioutil.ReadDir(s.rootDir)
	if err != nil {
		return nil, err
	}
	buckets := []Bucket{}
	for _, info := range infos {
		if info.IsDir() {
			unescaped, err := url.PathUnescape(info.Name())
			if err != nil {
				return nil, fmt.Errorf("failed to unescape object name %s: %w", info.Name(), err)
			}
			buckets = append(buckets, Bucket{Name: unescaped})
		}
	}
	return buckets, nil
}

func timespecToTime(ts syscall.Timespec) time.Time {
	return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}

// GetBucket returns information about the given bucket, or an error if it
// doesn't exist.
func (s *storageFS) GetBucket(name string) (Bucket, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	dirInfo, err := os.Stat(filepath.Join(s.rootDir, url.PathEscape(name)))
	if err != nil {
		return Bucket{}, err
	}
	return Bucket{Name: name, VersioningEnabled: false, TimeCreated: timespecToTime(createTimeFromFileInfo(dirInfo))}, err
}

// DeleteBucket removes the bucket from the backend.
func (s *storageFS) DeleteBucket(name string) error {
	objs, err := s.ListObjects(name, "", false)
	if err != nil {
		return BucketNotFound
	}
	if len(objs) > 0 {
		return BucketNotEmpty
	}

	s.mtx.Lock()
	defer s.mtx.Unlock()
	return os.RemoveAll(filepath.Join(s.rootDir, url.PathEscape(name)))
}

// CreateObject stores an object as a regular file in the disk.
func (s *storageFS) CreateObject(obj Object) (Object, error) {
	if obj.Generation > 0 {
		return Object{}, errors.New("not implemented: fs storage type does not support objects generation yet")
	}
	s.mtx.Lock()
	defer s.mtx.Unlock()
	err := s.createBucket(obj.BucketName)
	if err != nil {
		return Object{}, err
	}
	encoded, err := json.Marshal(obj)
	if err != nil {
		return Object{}, err
	}
	return obj, ioutil.WriteFile(filepath.Join(s.rootDir, url.PathEscape(obj.BucketName), url.PathEscape(obj.Name)), encoded, 0o600)
}

// ListObjects lists the objects in a given bucket with a given prefix and
// delimeter.
func (s *storageFS) ListObjects(bucketName string, prefix string, versions bool) ([]ObjectAttrs, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()

	infos, err := ioutil.ReadDir(filepath.Join(s.rootDir, url.PathEscape(bucketName)))
	if err != nil {
		return nil, err
	}
	objects := []ObjectAttrs{}
	for _, info := range infos {
		unescaped, err := url.PathUnescape(info.Name())
		if err != nil {
			return nil, fmt.Errorf("failed to unescape object name %s: %w", info.Name(), err)
		}
		if prefix != "" && !strings.HasPrefix(unescaped, prefix) {
			continue
		}
		object, err := s.getObject(bucketName, unescaped)
		if err != nil {
			return nil, err
		}
		object.Size = int64(len(object.Content))
		objects = append(objects, object.ObjectAttrs)
	}
	return objects, nil
}

// GetObject get an object by bucket and name.
func (s *storageFS) GetObject(bucketName, objectName string) (Object, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	return s.getObject(bucketName, objectName)
}

// GetObjectWithGeneration retrieves an specific version of the object. Not
// implemented for this backend.
func (s *storageFS) GetObjectWithGeneration(bucketName, objectName string, generation int64) (Object, error) {
	return Object{}, errors.New("not implemented: fs storage type does not support versioning yet")
}

func (s *storageFS) getObject(bucketName, objectName string) (Object, error) {
	encoded, err := ioutil.ReadFile(filepath.Join(s.rootDir, url.PathEscape(bucketName), url.PathEscape(objectName)))
	if err != nil {
		return Object{}, err
	}
	var obj Object
	err = json.Unmarshal(encoded, &obj)
	if err != nil {
		return Object{}, err
	}
	obj.Name = filepath.ToSlash(objectName)
	obj.BucketName = bucketName
	obj.Size = int64(len(obj.Content))
	return obj, nil
}

// DeleteObject deletes an object by bucket and name.
func (s *storageFS) DeleteObject(bucketName, objectName string) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	if objectName == "" {
		return errors.New("can't delete object with empty name")
	}
	return os.Remove(filepath.Join(s.rootDir, url.PathEscape(bucketName), url.PathEscape(objectName)))
}

// PatchObject patches the given object metadata.
func (s *storageFS) PatchObject(bucketName, objectName string, metadata map[string]string) (Object, error) {
	obj, err := s.GetObject(bucketName, objectName)
	if err != nil {
		return Object{}, err
	}
	if obj.Metadata == nil {
		obj.Metadata = map[string]string{}
	}
	for k, v := range metadata {
		obj.Metadata[k] = v
	}
	s.CreateObject(obj) // recreate object
	return obj, nil
}

func (s *storageFS) ComposeObject(bucketName string, objectNames []string, destinationName string, metadata map[string]string, contentType string) (Object, error) {
	var data []byte
	for _, n := range objectNames {
		obj, err := s.GetObject(bucketName, n)
		if err != nil {
			return Object{}, err
		}
		data = append(data, obj.Content...)
	}

	dest, err := s.GetObject(bucketName, destinationName)
	if err != nil {
		oattrs := ObjectAttrs{
			BucketName:  bucketName,
			Name:        destinationName,
			ContentType: contentType,
			Created:     time.Now().String(),
		}
		dest = Object{
			ObjectAttrs: oattrs,
		}
	}

	dest.Content = data
	dest.Crc32c = checksum.EncodedCrc32cChecksum(data)
	dest.Md5Hash = checksum.EncodedMd5Hash(data)
	dest.Metadata = metadata

	result, err := s.CreateObject(dest)
	if err != nil {
		return result, err
	}

	return result, nil
}
<|endoftext|>


<commit_before>package core

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/scaleway/scaleway-cli/internal/human"
	sdk "github.com/scaleway/scaleway-sdk-go/scw"
)

func init() {
	human.RegisterMarshalerFunc((*sdk.ResponseError)(nil), sdkResponseErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.InvalidArgumentsError)(nil), sdkInvalidArgumentsErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.QuotasExceededError)(nil), sdkQuotasExceededErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.TransientStateError)(nil), sdkTransientStateErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.ResourceNotFoundError)(nil), sdkResourceNotFoundErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.OutOfStockError)(nil), sdkOutOfStockErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.ResourceExpiredError)(nil), sdkResourceExpiredHumanMarshallFunc())
}

// CliError is an all-in-one error structure that can be used in commands to return useful errors to the user.
// CliError implements JSON and human marshaller for a smooth experience.
type CliError struct {
	Err     error
	Details string
	Hint    string
}

func (s *CliError) Error() string {
	return s.Err.Error()
}

func (s *CliError) MarshalHuman() (string, error) {
	sections := []string(nil)
	if s.Err != nil {
		str, err := human.Marshal(s.Err, nil)
		if err != nil {
			return "", err
		}
		sections = append(sections, str)
	}

	if s.Details != "" {
		str, err := human.Marshal(s.Details, &human.MarshalOpt{Title: "Details"})
		if err != nil {
			return "", err
		}
		sections = append(sections, str)
	}

	if s.Hint != "" {
		str, err := human.Marshal(s.Hint, &human.MarshalOpt{Title: "Hint"})
		if err != nil {
			return "", err
		}
		sections = append(sections, str)
	}

	return strings.Join(sections, "\n\n"), nil
}

func (s *CliError) MarshalJSON() ([]byte, error) {
	type tmpRes struct {
		Error   string `json:"error"`
		Details string `json:"details"`
		Hint    string `json:"hint"`
	}
	return json.Marshal(&tmpRes{
		Error:   s.Err.Error(),
		Details: s.Details,
		Hint:    s.Hint,
	})
}

func sdkResponseErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		responseError := i.(*sdk.ResponseError)

		return human.Marshal(&CliError{
			Err: fmt.Errorf(responseError.Message),
		}, opt)
	}
}

func sdkInvalidArgumentsErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		invalidArgumentsError := i.(*sdk.InvalidArgumentsError)
		reasonsMap := map[string]string{
			"unknown":    "is invalid for unexpected reason",
			"required":   "is required",
			"format":     "is wrongly formatted",
			"constraint": "does not respect constraint",
		}

		arguments := make([]string, len(invalidArgumentsError.Details))
		reasons := make([]string, len(invalidArgumentsError.Details))
		hints := make([]string, len(invalidArgumentsError.Details))
		for i, d := range invalidArgumentsError.Details {
			arguments[i] = "'" + d.ArgumentName + "'"
			reasons[i] = "- " + d.ArgumentName + " " + reasonsMap[d.Reason]
			hints[i] = d.HelpMessage
		}

		return human.Marshal(&CliError{
			Err:     fmt.Errorf("invalid arguments %v", strings.Join(arguments, ", ")),
			Details: strings.Join(reasons, "\n"),
			Hint:    strings.Join(hints, "\n"),
		}, opt)
	}
}

func sdkQuotasExceededErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		quotasExceededError := i.(*sdk.QuotasExceededError)

		invalidArgs := make([]string, len(quotasExceededError.Details))
		resources := make([]string, len(quotasExceededError.Details))
		for i, d := range quotasExceededError.Details {
			invalidArgs[i] = fmt.Sprintf("- %s has reached its quota (%d/%d)", d.Resource, d.Current, d.Current)
			resources[i] = fmt.Sprintf("'%v'", d.Resource)
		}

		return human.Marshal(&CliError{
			Err:     fmt.Errorf("quota exceeded for resources %v", strings.Join(resources, ", ")),
			Details: strings.Join(invalidArgs, "\n"),
			Hint:    "Quotas are defined by organization. You should either delete unused resources or contact support to obtain bigger quotas.",
		}, opt)
	}
}

func sdkTransientStateErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		transientStateError := i.(*sdk.TransientStateError)

		return human.Marshal(&CliError{
			Err: fmt.Errorf("transient state error for resource '%v'", transientStateError.Resource),
			Details: fmt.Sprintf("resource %s with ID %s is in a transient state '%s'",
				transientStateError.Resource,
				transientStateError.ResourceID,
				transientStateError.CurrentState),
		}, opt)
	}
}

func sdkResourceNotFoundErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		resourceNotFoundError := i.(*sdk.ResourceNotFoundError)

		return human.Marshal(&CliError{
			Err: fmt.Errorf("cannot find resource '%v' with ID '%v'", resourceNotFoundError.Resource, resourceNotFoundError.ResourceID),
		}, opt)
	}
}

func sdkOutOfStockErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		outOfStockError := i.(*sdk.OutOfStockError)

		return human.Marshal(&CliError{
			Err:  fmt.Errorf("resource out of stock '%v'", outOfStockError.Resource),
			Hint: "Try again later :-)",
		}, opt)
	}
}

func sdkResourceExpiredHumanMarshallFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		resourceExpiredError := i.(*sdk.ResourceExpiredError)

		var hint string
		switch resourceName := resourceExpiredError.Resource; resourceName {
		case "account_token":
			hint = "Try to generate a new token here https://console.scaleway.com/account/credentials"
		default:
			hint = "Try to re-create the expired resource"
		}

		return human.Marshal(&CliError{
			Err:  fmt.Errorf("resource %s with ID %s expired since %s", resourceExpiredError.Resource, resourceExpiredError.ResourceID, resourceExpiredError.ExpiredSince.String()),
			Hint: hint,
		}, opt)
	}
}
<commit_msg>fix: remove default hint on resource exired error (#622)<commit_after>package core

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/scaleway/scaleway-cli/internal/human"
	sdk "github.com/scaleway/scaleway-sdk-go/scw"
)

func init() {
	human.RegisterMarshalerFunc((*sdk.ResponseError)(nil), sdkResponseErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.InvalidArgumentsError)(nil), sdkInvalidArgumentsErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.QuotasExceededError)(nil), sdkQuotasExceededErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.TransientStateError)(nil), sdkTransientStateErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.ResourceNotFoundError)(nil), sdkResourceNotFoundErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.OutOfStockError)(nil), sdkOutOfStockErrorHumanMarshallerFunc())
	human.RegisterMarshalerFunc((*sdk.ResourceExpiredError)(nil), sdkResourceExpiredHumanMarshallFunc())
}

// CliError is an all-in-one error structure that can be used in commands to return useful errors to the user.
// CliError implements JSON and human marshaller for a smooth experience.
type CliError struct {
	Err     error
	Details string
	Hint    string
}

func (s *CliError) Error() string {
	return s.Err.Error()
}

func (s *CliError) MarshalHuman() (string, error) {
	sections := []string(nil)
	if s.Err != nil {
		str, err := human.Marshal(s.Err, nil)
		if err != nil {
			return "", err
		}
		sections = append(sections, str)
	}

	if s.Details != "" {
		str, err := human.Marshal(s.Details, &human.MarshalOpt{Title: "Details"})
		if err != nil {
			return "", err
		}
		sections = append(sections, str)
	}

	if s.Hint != "" {
		str, err := human.Marshal(s.Hint, &human.MarshalOpt{Title: "Hint"})
		if err != nil {
			return "", err
		}
		sections = append(sections, str)
	}

	return strings.Join(sections, "\n\n"), nil
}

func (s *CliError) MarshalJSON() ([]byte, error) {
	type tmpRes struct {
		Error   string `json:"error"`
		Details string `json:"details"`
		Hint    string `json:"hint"`
	}
	return json.Marshal(&tmpRes{
		Error:   s.Err.Error(),
		Details: s.Details,
		Hint:    s.Hint,
	})
}

func sdkResponseErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		responseError := i.(*sdk.ResponseError)

		return human.Marshal(&CliError{
			Err: fmt.Errorf(responseError.Message),
		}, opt)
	}
}

func sdkInvalidArgumentsErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		invalidArgumentsError := i.(*sdk.InvalidArgumentsError)
		reasonsMap := map[string]string{
			"unknown":    "is invalid for unexpected reason",
			"required":   "is required",
			"format":     "is wrongly formatted",
			"constraint": "does not respect constraint",
		}

		arguments := make([]string, len(invalidArgumentsError.Details))
		reasons := make([]string, len(invalidArgumentsError.Details))
		hints := make([]string, len(invalidArgumentsError.Details))
		for i, d := range invalidArgumentsError.Details {
			arguments[i] = "'" + d.ArgumentName + "'"
			reasons[i] = "- " + d.ArgumentName + " " + reasonsMap[d.Reason]
			hints[i] = d.HelpMessage
		}

		return human.Marshal(&CliError{
			Err:     fmt.Errorf("invalid arguments %v", strings.Join(arguments, ", ")),
			Details: strings.Join(reasons, "\n"),
			Hint:    strings.Join(hints, "\n"),
		}, opt)
	}
}

func sdkQuotasExceededErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		quotasExceededError := i.(*sdk.QuotasExceededError)

		invalidArgs := make([]string, len(quotasExceededError.Details))
		resources := make([]string, len(quotasExceededError.Details))
		for i, d := range quotasExceededError.Details {
			invalidArgs[i] = fmt.Sprintf("- %s has reached its quota (%d/%d)", d.Resource, d.Current, d.Current)
			resources[i] = fmt.Sprintf("'%v'", d.Resource)
		}

		return human.Marshal(&CliError{
			Err:     fmt.Errorf("quota exceeded for resources %v", strings.Join(resources, ", ")),
			Details: strings.Join(invalidArgs, "\n"),
			Hint:    "Quotas are defined by organization. You should either delete unused resources or contact support to obtain bigger quotas.",
		}, opt)
	}
}

func sdkTransientStateErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		transientStateError := i.(*sdk.TransientStateError)

		return human.Marshal(&CliError{
			Err: fmt.Errorf("transient state error for resource '%v'", transientStateError.Resource),
			Details: fmt.Sprintf("resource %s with ID %s is in a transient state '%s'",
				transientStateError.Resource,
				transientStateError.ResourceID,
				transientStateError.CurrentState),
		}, opt)
	}
}

func sdkResourceNotFoundErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		resourceNotFoundError := i.(*sdk.ResourceNotFoundError)

		return human.Marshal(&CliError{
			Err: fmt.Errorf("cannot find resource '%v' with ID '%v'", resourceNotFoundError.Resource, resourceNotFoundError.ResourceID),
		}, opt)
	}
}

func sdkOutOfStockErrorHumanMarshallerFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		outOfStockError := i.(*sdk.OutOfStockError)

		return human.Marshal(&CliError{
			Err:  fmt.Errorf("resource out of stock '%v'", outOfStockError.Resource),
			Hint: "Try again later :-)",
		}, opt)
	}
}

func sdkResourceExpiredHumanMarshallFunc() human.MarshalerFunc {
	return func(i interface{}, opt *human.MarshalOpt) (string, error) {
		resourceExpiredError := i.(*sdk.ResourceExpiredError)

		var hint string
		switch resourceName := resourceExpiredError.Resource; resourceName {
		case "account_token":
			hint = "Try to generate a new token here https://console.scaleway.com/account/credentials"
		}

		return human.Marshal(&CliError{
			Err:  fmt.Errorf("resource %s with ID %s expired since %s", resourceExpiredError.Resource, resourceExpiredError.ResourceID, resourceExpiredError.ExpiredSince.String()),
			Hint: hint,
		}, opt)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package entry

import (
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strings"

	"github.com/issue9/mux/internal/method"
)

// 所有 Entry 实现的公用部分。
type base struct {
	patternString string

	// 是否包含通配符
	wildcard bool

	// 请求方法及其对应的 Handler
	handlers map[string]http.Handler

	// 缓存的 OPTIONS 请求头的 allow 报头内容，每次更新 handlers 时更新。
	optionsAllow string

	// 固定 optionsAllow 不再修改，
	// 调用 SetAllow() 进行强制修改之后为 true。
	fixedOptionsAllow bool

	// 固定 handlers[http.MethodOptions] 不再修改，
	// 显示地调用 Add(http.MethodOptions,...) 进行赋值之后为 true。
	fixedOptionsHandler bool
}

func newBase(pattern string) *base {
	ret := &base{
		patternString: pattern,
		handlers:      make(map[string]http.Handler, len(method.Supported)),
		wildcard:      strings.HasSuffix(pattern, "/*"),
	}

	// 添加默认的 OPTIONS 请求内容
	ret.handlers[http.MethodOptions] = http.HandlerFunc(ret.optionsServeHTTP)
	ret.optionsAllow = ret.getOptionsAllow()

	return ret
}

func (i *base) pattern() string {
	return i.patternString
}

// Entry.Add()
func (i *base) Add(h http.Handler, methods ...string) error {
	if len(methods) == 0 {
		methods = method.Default
	}

	for _, m := range methods {
		if !method.IsSupported(m) {
			return fmt.Errorf("不支持的请求方法 %v", m)
		}

		if err := i.add(h, m); err != nil {
			return err
		}
	}

	return nil
}

func (i *base) add(h http.Handler, method string) error {
	if method == http.MethodOptions { // 强制修改 OPTIONS 方法的处理方式
		if i.fixedOptionsHandler { // 被强制修改过，不能再受理。
			return errors.New("该请求方法 OPTIONS 已经存在") // 与以下的错误提示相同
		}

		i.handlers[http.MethodOptions] = h
		i.fixedOptionsHandler = true
		return nil
	}

	// 非 OPTIONS 请求
	if _, found := i.handlers[method]; found {
		return fmt.Errorf("该请求方法 %v 已经存在", method)
	}
	i.handlers[method] = h

	// 重新生成 optionsAllow 字符串
	if !i.fixedOptionsAllow {
		i.optionsAllow = i.getOptionsAllow()
	}
	return nil
}

func (i *base) optionsServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", i.optionsAllow)
}

func (i *base) getOptionsAllow() string {
	methods := make([]string, 0, len(i.handlers))
	for method := range i.handlers {
		methods = append(methods, method)
	}

	sort.Strings(methods) // 防止每次从 map 中读取的顺序都不一样
	return strings.Join(methods, ", ")
}

// Entry.Remove()
func (i *base) Remove(methods ...string) bool {
	for _, method := range methods {
		delete(i.handlers, method)
		if method == http.MethodOptions { // 不恢复方法，只恢复了 fixedOptionsHandler
			i.fixedOptionsHandler = false
		}
	}

	// 删完了
	if len(i.handlers) == 0 {
		i.optionsAllow = ""
		return true
	}

	// 只有一个 OPTIONS 了，且未经外界强制修改，则将其也一并删除。
	if len(i.handlers) == 1 && i.handlers[http.MethodOptions] != nil {
		if !i.fixedOptionsAllow && !i.fixedOptionsHandler {
			delete(i.handlers, http.MethodOptions)
			i.optionsAllow = ""
			return true
		}
	}

	if !i.fixedOptionsAllow {
		i.optionsAllow = i.getOptionsAllow()
	}
	return false
}

// Entry.SetAllow()
func (i *base) SetAllow(optionsAllow string) {
	i.optionsAllow = optionsAllow
	i.fixedOptionsAllow = true
}

// Entry.Handler()
func (i *base) Handler(method string) http.Handler {
	return i.handlers[method]
}
<commit_msg>[internal/entry] 更明确的变量名<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package entry

import (
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strings"

	"github.com/issue9/mux/internal/method"
)

// 所有 Entry 实现的公用部分。
type base struct {
	patternString string

	// 是否包含通配符
	wildcard bool

	// 请求方法及其对应的 Handler
	handlers map[string]http.Handler

	// 缓存的 OPTIONS 请求头的 allow 报头内容，每次更新 handlers 时更新。
	optionsAllow string

	// 固定 optionsAllow 不再修改，
	// 调用 SetAllow() 进行强制修改之后为 true。
	fixedOptionsAllow bool

	// 固定 handlers[http.MethodOptions] 不再修改，
	// 显示地调用 Add(http.MethodOptions,...) 进行赋值之后为 true。
	fixedOptionsHandler bool
}

func newBase(pattern string) *base {
	ret := &base{
		patternString: pattern,
		handlers:      make(map[string]http.Handler, len(method.Supported)),
		wildcard:      strings.HasSuffix(pattern, "/*"),
	}

	// 添加默认的 OPTIONS 请求内容
	ret.handlers[http.MethodOptions] = http.HandlerFunc(ret.optionsServeHTTP)
	ret.optionsAllow = ret.getOptionsAllow()

	return ret
}

func (b *base) pattern() string {
	return b.patternString
}

// Entry.add()
func (b *base) add(h http.Handler, methods ...string) error {
	if len(methods) == 0 {
		methods = method.Default
	}

	for _, m := range methods {
		if !method.IsSupported(m) {
			return fmt.Errorf("不支持的请求方法 %v", m)
		}

		if err := b.addSingle(h, m); err != nil {
			return err
		}
	}

	return nil
}

func (b *base) addSingle(h http.Handler, method string) error {
	if method == http.MethodOptions { // 强制修改 OPTIONS 方法的处理方式
		if b.fixedOptionsHandler { // 被强制修改过，不能再受理。
			return errors.New("该请求方法 OPTIONS 已经存在") // 与以下的错误提示相同
		}

		b.handlers[http.MethodOptions] = h
		b.fixedOptionsHandler = true
		return nil
	}

	// 非 OPTIONS 请求
	if _, found := b.handlers[method]; found {
		return fmt.Errorf("该请求方法 %v 已经存在", method)
	}
	b.handlers[method] = h

	// 重新生成 optionsAllow 字符串
	if !b.fixedOptionsAllow {
		b.optionsAllow = b.getOptionsAllow()
	}
	return nil
}

func (b *base) optionsServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", b.optionsAllow)
}

func (b *base) getOptionsAllow() string {
	methods := make([]string, 0, len(b.handlers))
	for method := range b.handlers {
		methods = append(methods, method)
	}

	sort.Strings(methods) // 防止每次从 map 中读取的顺序都不一样
	return strings.Join(methods, ", ")
}

// Entry.Remove()
func (b *base) Remove(methods ...string) bool {
	for _, method := range methods {
		delete(b.handlers, method)
		if method == http.MethodOptions { // 不恢复方法，只恢复了 fixedOptionsHandler
			b.fixedOptionsHandler = false
		}
	}

	// 删完了
	if len(b.handlers) == 0 {
		b.optionsAllow = ""
		return true
	}

	// 只有一个 OPTIONS 了，且未经外界强制修改，则将其也一并删除。
	if len(b.handlers) == 1 && b.handlers[http.MethodOptions] != nil {
		if !b.fixedOptionsAllow && !b.fixedOptionsHandler {
			delete(b.handlers, http.MethodOptions)
			b.optionsAllow = ""
			return true
		}
	}

	if !b.fixedOptionsAllow {
		b.optionsAllow = b.getOptionsAllow()
	}
	return false
}

// Entry.SetAllow()
func (b *base) SetAllow(optionsAllow string) {
	b.optionsAllow = optionsAllow
	b.fixedOptionsAllow = true
}

// Entry.Handler()
func (b *base) Handler(method string) http.Handler {
	return b.handlers[method]
}
<|endoftext|>


<commit_before>package opentsdb

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type Logger interface {
	Debug(i ...interface{})
}

type DefaultLogger struct {
}

func (logger *DefaultLogger) Debug(i ...interface{}) {
	return
}

var logger Logger

func init() {
	logger = &DefaultLogger{}
}

// OpenTSDB request parameters.
type OpenTSDBRequestParams struct {
	Host    string                         // Host to query.
	Start   string                         // Time point when to start query.
	End     string                         // Time point to end query (optional).
	Metrics []*OpenTSDBMetricConfiguration // Configuration of the metrics to request.
}

// OpenTSDB metric query parameters and configuration for result
// interpration.
type OpenTSDBMetricConfiguration struct {
	Unit      string                // TODO: required?
	Filter    func(float64) float64 // Function used to map metric values.
	Aggregate string                // Aggregation of matching metrics
	Rate      string                // Mark metric as rate or downsample.
	Metric    string                // Metric to query for.
	TagFilter string                // Filter on tags (comma separated string with <tag>=<value> pairs.
}

// Mapping from the metric identifier to the according configuration
// used to parse and handle the results.
type OpenTSDBMetricConfigurations map[string]*OpenTSDBMetricConfiguration

// Parse a single line of the result returned by OpenTSDB in ASCII mode.
func parseLogEventLine(line string, mCfg OpenTSDBMetricConfigurations) (*MetricValue, error) {
	parts := strings.SplitN(line, " ", 4)
	if len(parts) != 4 {
		logger.Debug("failed to parse line:", line)
		return nil, errors.New("failed to parse line")
	}

	key, tags := parts[0], parts[3]

	timestamp, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		logger.Debug("failed to parse timestamp:", parts[1])
		return nil, err
	}

	value, err := strconv.ParseFloat(parts[2], 64)
	if err != nil {
		logger.Debug("failed to parse value:", parts[2])
		return nil, err
	}

	if mCfg[key].Filter != nil {
		value = mCfg[key].Filter(value)
	}

	return &MetricValue{
		Key:   key,
		Value: value,
		Time:  time.Unix(timestamp, 0),
		Tags:  tags,
	}, nil
}

// Parse the content of the ASCII based OpenTSDB response.
func parseResponseFromOpenTSDB(content io.ReadCloser, mCfg OpenTSDBMetricConfigurations) (MetricsTree, error) {
	scanner := bufio.NewScanner(content)
	mt := NewMetricsTree()
	for scanner.Scan() {
		if mv, e := parseLogEventLine(scanner.Text(), mCfg); e == nil {
			if e = mt.AddMetricValue(mv); e != nil {
				return nil, e
			}
		} else {
			return nil, e
		}
	}
	return mt, nil
}

func createQueryURL(attrs *OpenTSDBRequestParams) string {
	values := url.Values{}
	values.Add("start", attrs.Start)
	if attrs.End != "" {
		values.Add("end", attrs.End)
	}

	for _, m := range attrs.Metrics {
		metric := m.Aggregate
		if m.Rate != "" {
			metric += ":" + m.Rate
		}
		metric += ":" + m.Metric
		metric += "{" + m.TagFilter + "}"
		values.Add("m", metric)
	}

	return "http://" + attrs.Host + ":4242/q?ascii&" + values.Encode()
}

func createMetricConfigurations(attrs *OpenTSDBRequestParams) (OpenTSDBMetricConfigurations, error) {
	mCfg := make(OpenTSDBMetricConfigurations)

	for _, m := range attrs.Metrics {
		if _, ok := mCfg[m.Metric]; ok {
			return nil, errors.New("Each metric only allowed once!")
		}
		mCfg[m.Metric] = m
	}
	return mCfg, nil
}

// Request data from OpenTSDB in ASCII format.
func GetOpenTSDBData(attrs *OpenTSDBRequestParams) (MetricsTree, error) {
	url := createQueryURL(attrs)
	logger.Debug("Request URL is ", url)

	mCfg, err := createMetricConfigurations(attrs)
	if err != nil {
		return nil, err
	}

	logger.Debug("Starting request to OpenTSDB")
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, errors.New(fmt.Sprintf("Request to OpenTSDB failed with %s", resp.Status))
	}
	defer resp.Body.Close()
	logger.Debug("Finished request to OpenTSDB")

	logger.Debug("Starting to parse the response from OpenTSDB")
	mt, e := parseResponseFromOpenTSDB(resp.Body, mCfg)
	logger.Debug("Finsihed parsing the response from OpenTSDB")

	return mt, e
}
<commit_msg>remove OpenTSDB prefix<commit_after>package opentsdb

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type Logger interface {
	Debug(i ...interface{})
}

type DefaultLogger struct {
}

func (logger *DefaultLogger) Debug(i ...interface{}) {
	return
}

var logger Logger

func init() {
	logger = &DefaultLogger{}
}

// OpenTSDB request parameters.
type RequestParams struct {
	Host    string                         // Host to query.
	Start   string                         // Time point when to start query.
	End     string                         // Time point to end query (optional).
	Metrics []*MetricConfiguration // Configuration of the metrics to request.
}

// OpenTSDB metric query parameters and configuration for result
// interpration.
type MetricConfiguration struct {
	Unit      string                // TODO: required?
	Filter    func(float64) float64 // Function used to map metric values.
	Aggregate string                // Aggregation of matching metrics
	Rate      string                // Mark metric as rate or downsample.
	Metric    string                // Metric to query for.
	TagFilter string                // Filter on tags (comma separated string with <tag>=<value> pairs.
}

// Mapping from the metric identifier to the according configuration
// used to parse and handle the results.
type MetricConfigurations map[string]*MetricConfiguration

// Parse a single line of the result returned by OpenTSDB in ASCII mode.
func parseLogEventLine(line string, mCfg MetricConfigurations) (*MetricValue, error) {
	parts := strings.SplitN(line, " ", 4)
	if len(parts) != 4 {
		logger.Debug("failed to parse line:", line)
		return nil, errors.New("failed to parse line")
	}

	key, tags := parts[0], parts[3]

	timestamp, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		logger.Debug("failed to parse timestamp:", parts[1])
		return nil, err
	}

	value, err := strconv.ParseFloat(parts[2], 64)
	if err != nil {
		logger.Debug("failed to parse value:", parts[2])
		return nil, err
	}

	if mCfg[key].Filter != nil {
		value = mCfg[key].Filter(value)
	}

	return &MetricValue{
		Key:   key,
		Value: value,
		Time:  time.Unix(timestamp, 0),
		Tags:  tags,
	}, nil
}

// Parse the content of the ASCII based OpenTSDB response.
func parseResponse(content io.ReadCloser, mCfg MetricConfigurations) (MetricsTree, error) {
	scanner := bufio.NewScanner(content)
	mt := NewMetricsTree()
	for scanner.Scan() {
		if mv, e := parseLogEventLine(scanner.Text(), mCfg); e == nil {
			if e = mt.AddMetricValue(mv); e != nil {
				return nil, e
			}
		} else {
			return nil, e
		}
	}
	return mt, nil
}

func createQueryURL(attrs *RequestParams) string {
	values := url.Values{}
	values.Add("start", attrs.Start)
	if attrs.End != "" {
		values.Add("end", attrs.End)
	}

	for _, m := range attrs.Metrics {
		metric := m.Aggregate
		if m.Rate != "" {
			metric += ":" + m.Rate
		}
		metric += ":" + m.Metric
		metric += "{" + m.TagFilter + "}"
		values.Add("m", metric)
	}

	return "http://" + attrs.Host + ":4242/q?ascii&" + values.Encode()
}

func createMetricConfigurations(attrs *RequestParams) (MetricConfigurations, error) {
	mCfg := make(MetricConfigurations)

	for _, m := range attrs.Metrics {
		if _, ok := mCfg[m.Metric]; ok {
			return nil, errors.New("Each metric only allowed once!")
		}
		mCfg[m.Metric] = m
	}
	return mCfg, nil
}

// Request data from OpenTSDB in ASCII format.
func GetData(attrs *RequestParams) (MetricsTree, error) {
	url := createQueryURL(attrs)
	logger.Debug("Request URL is ", url)

	mCfg, err := createMetricConfigurations(attrs)
	if err != nil {
		return nil, err
	}

	logger.Debug("Starting request to OpenTSDB")
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, errors.New(fmt.Sprintf("Request to OpenTSDB failed with %s", resp.Status))
	}
	defer resp.Body.Close()
	logger.Debug("Finished request to OpenTSDB")

	logger.Debug("Starting to parse the response from OpenTSDB")
	mt, e := parseResponse(resp.Body, mCfg)
	logger.Debug("Finsihed parsing the response from OpenTSDB")

	return mt, e
}
<|endoftext|>


<commit_before>package vmess

import (
	"bytes"
	"crypto/rand"
	"io/ioutil"
	"testing"

	"github.com/v2ray/v2ray-core"
	v2hash "github.com/v2ray/v2ray-core/hash"
	v2math "github.com/v2ray/v2ray-core/math"
	v2net "github.com/v2ray/v2ray-core/net"
	"github.com/v2ray/v2ray-core/testing/mocks"
	"github.com/v2ray/v2ray-core/testing/unit"
)

func TestVMessSerialization(t *testing.T) {
	assert := unit.Assert(t)

	userId, err := core.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51")
	if err != nil {
		t.Fatal(err)
	}

	userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int), make(map[string]int64)}
	userSet.AddUser(core.User{userId})

	request := new(VMessRequest)
	request.Version = byte(0x01)
	request.UserId = userId

	_, err = rand.Read(request.RequestIV[:])
	if err != nil {
		t.Fatal(err)
	}

	_, err = rand.Read(request.RequestKey[:])
	if err != nil {
		t.Fatal(err)
	}

	_, err = rand.Read(request.ResponseHeader[:])
	if err != nil {
		t.Fatal(err)
	}

	request.Command = byte(0x01)
	request.Address = v2net.DomainAddress("v2ray.com", 80)

	buffer := bytes.NewBuffer(make([]byte, 0, 300))
	mockTime := int64(1823730)
	requestWriter := NewVMessRequestWriter(v2hash.NewTimeHash(v2hash.HMACHash{}), func(base int64, delta int) int64 { return mockTime })
	err = requestWriter.Write(buffer, request)
	if err != nil {
		t.Fatal(err)
	}

	userSet.UserHashes[string(buffer.Bytes()[:16])] = 0
	userSet.Timestamps[string(buffer.Bytes()[:16])] = mockTime

	requestReader := NewVMessRequestReader(&userSet)
	actualRequest, err := requestReader.Read(buffer)
	if err != nil {
		t.Fatal(err)
	}

	assert.Byte(actualRequest.Version).Named("Version").Equals(byte(0x01))
	assert.String(actualRequest.UserId.String).Named("UserId").Equals(request.UserId.String)
	assert.Bytes(actualRequest.RequestIV[:]).Named("RequestIV").Equals(request.RequestIV[:])
	assert.Bytes(actualRequest.RequestKey[:]).Named("RequestKey").Equals(request.RequestKey[:])
	assert.Bytes(actualRequest.ResponseHeader[:]).Named("ResponseHeader").Equals(request.ResponseHeader[:])
	assert.Byte(actualRequest.Command).Named("Command").Equals(request.Command)
	assert.String(actualRequest.Address.String()).Named("Address").Equals(request.Address.String())
}

func BenchmarkVMessRequestWriting(b *testing.B) {
	userId, _ := core.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51")
	userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int), make(map[string]int64)}
	userSet.AddUser(core.User{userId})

	request := new(VMessRequest)
	request.Version = byte(0x01)
	request.UserId = userId

	rand.Read(request.RequestIV[:])
	rand.Read(request.RequestKey[:])
	rand.Read(request.ResponseHeader[:])

	request.Command = byte(0x01)
	request.Address = v2net.DomainAddress("v2ray.com", 80)

	requestWriter := NewVMessRequestWriter(v2hash.NewTimeHash(v2hash.HMACHash{}), v2math.GenerateRandomInt64InRange)
	for i := 0; i < b.N; i++ {
		requestWriter.Write(ioutil.Discard, request)
	}
}
<commit_msg>fix test break<commit_after>package vmess

import (
	"bytes"
	"crypto/rand"
	"testing"

	"github.com/v2ray/v2ray-core"
	v2hash "github.com/v2ray/v2ray-core/hash"
	v2math "github.com/v2ray/v2ray-core/math"
	v2net "github.com/v2ray/v2ray-core/net"
	"github.com/v2ray/v2ray-core/testing/mocks"
	"github.com/v2ray/v2ray-core/testing/unit"
)

func TestVMessSerialization(t *testing.T) {
	assert := unit.Assert(t)

	userId, err := core.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51")
	if err != nil {
		t.Fatal(err)
	}

	userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int), make(map[string]int64)}
	userSet.AddUser(core.User{userId})

	request := new(VMessRequest)
	request.Version = byte(0x01)
	request.UserId = userId

	_, err = rand.Read(request.RequestIV[:])
	if err != nil {
		t.Fatal(err)
	}

	_, err = rand.Read(request.RequestKey[:])
	if err != nil {
		t.Fatal(err)
	}

	_, err = rand.Read(request.ResponseHeader[:])
	if err != nil {
		t.Fatal(err)
	}

	request.Command = byte(0x01)
	request.Address = v2net.DomainAddress("v2ray.com", 80)

	mockTime := int64(1823730)
	buffer, err := request.ToBytes(v2hash.NewTimeHash(v2hash.HMACHash{}), func(base int64, delta int) int64 { return mockTime })
	if err != nil {
		t.Fatal(err)
	}

	userSet.UserHashes[string(buffer[:16])] = 0
	userSet.Timestamps[string(buffer[:16])] = mockTime

	requestReader := NewVMessRequestReader(&userSet)
	actualRequest, err := requestReader.Read(bytes.NewReader(buffer))
	if err != nil {
		t.Fatal(err)
	}

	assert.Byte(actualRequest.Version).Named("Version").Equals(byte(0x01))
	assert.String(actualRequest.UserId.String).Named("UserId").Equals(request.UserId.String)
	assert.Bytes(actualRequest.RequestIV[:]).Named("RequestIV").Equals(request.RequestIV[:])
	assert.Bytes(actualRequest.RequestKey[:]).Named("RequestKey").Equals(request.RequestKey[:])
	assert.Bytes(actualRequest.ResponseHeader[:]).Named("ResponseHeader").Equals(request.ResponseHeader[:])
	assert.Byte(actualRequest.Command).Named("Command").Equals(request.Command)
	assert.String(actualRequest.Address.String()).Named("Address").Equals(request.Address.String())
}

func BenchmarkVMessRequestWriting(b *testing.B) {
	userId, _ := core.NewID("2b2966ac-16aa-4fbf-8d81-c5f172a3da51")
	userSet := mocks.MockUserSet{[]core.ID{}, make(map[string]int), make(map[string]int64)}
	userSet.AddUser(core.User{userId})

	request := new(VMessRequest)
	request.Version = byte(0x01)
	request.UserId = userId

	rand.Read(request.RequestIV[:])
	rand.Read(request.RequestKey[:])
	rand.Read(request.ResponseHeader[:])

	request.Command = byte(0x01)
	request.Address = v2net.DomainAddress("v2ray.com", 80)

	for i := 0; i < b.N; i++ {
		request.ToBytes(v2hash.NewTimeHash(v2hash.HMACHash{}), v2math.GenerateRandomInt64InRange)
	}
}
<|endoftext|>


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

package main

import (
    "fmt"
    jwt "github.com/dgrijalva/jwt-go"
    "io/ioutil"
    "net/http"
    "time"
)

// openssl genrsa -out demo.rsa 1024 # the 1024 is the size of the key we are generating
// openssl rsa -in demo.rsa -pubout > demo.rsa.pub
var (
    privateKey []byte
    publicKey  []byte
)

func init() {
    privateKey, _ = ioutil.ReadFile("keys/demo.rsa")
    publicKey, _ = ioutil.ReadFile("keys/demo.rsa.pub")
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
    token := jwt.New(jwt.GetSigningMethod("RS256"))
    token.Claims["ID"] = "This is my super fake ID"
    token.Claims["exp"] = time.Now().Unix() + 30
    tokenString, _ := token.SignedString(privateKey)

    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, `{"token": %s}`, tokenString)
}

func jwtHandler(w http.ResponseWriter, r *http.Request) {
    token, err := jwt.ParseFromRequest(r, func(token *jwt.Token) (interface{}, error) {
        return publicKey, nil
    })

    fmt.Println(err)
    fmt.Println(token)

    if err != nil {
        http.Error(w, "Bad request", 500)
    }

	if token.Valid {
        fmt.Fprintf(w, "Yo man! ")
	} else {
        fmt.Fprintf(w, "Bad ! ")
	}
}

func authHandler(w http.ResponseWriter, r *http.Request) {
    login, password := r.FormValue("login"), r.FormValue("password")

    if login == "foo" && password == "secret" {
        fmt.Fprintf(w, "Connect\n")
		// jwtHandler(w, r)
    } else {
        fmt.Fprintf(w, "Connect\n")
    }

    fmt.Fprintf(w, "Server start...\n")
    fmt.Fprintf(w, login)
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/login", http.HandlerFunc(handleLogin))
    mux.Handle("/api", http.HandlerFunc(authHandler))
    http.ListenAndServe(":8080", mux)
}
<commit_msg>Update main.go<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
    "fmt"
    jwt "github.com/dgrijalva/jwt-go"
    "io/ioutil"
    "net/http"
    "time"
)

// openssl genrsa -out demo.rsa 1024 # the 1024 is the size of the key we are generating
// openssl rsa -in demo.rsa -pubout > demo.rsa.pub
var (
    privateKey []byte
    publicKey  []byte
)

func init() {
    privateKey, _ = ioutil.ReadFile("keys/demo.rsa")
    publicKey, _ = ioutil.ReadFile("keys/demo.rsa.pub")
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
    token := jwt.New(jwt.GetSigningMethod("RS256"))
    token.Claims["ID"] = "This is my super fake ID"
    token.Claims["exp"] = time.Now().Unix() + 30
    tokenString, _ := token.SignedString(privateKey)

    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, `{"token": %s}`, tokenString)
}

func jwtHandler(w http.ResponseWriter, r *http.Request) {
    token, err := jwt.ParseFromRequest(r, func(token *jwt.Token) (interface{}, error) {
        return publicKey, nil
    })

    fmt.Println(err)
    fmt.Println(token)

    if err != nil {
        http.Error(w, "Bad request", 500)
    }

    if token.Valid {
        fmt.Fprintf(w, "Yo man! ")
    } else {
        fmt.Fprintf(w, "Bad ! ")
    }
}

func authHandler(w http.ResponseWriter, r *http.Request) {
    login, password := r.FormValue("login"), r.FormValue("password")

    if login == "foo" && password == "secret" {
        fmt.Fprintf(w, "Connect\n")
		// jwtHandler(w, r)
    } else {
        fmt.Fprintf(w, "Connect\n")
    }

    fmt.Fprintf(w, "Server start...\n")
    fmt.Fprintf(w, login)
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/login", http.HandlerFunc(handleLogin))
    mux.Handle("/api", http.HandlerFunc(authHandler))
    http.ListenAndServe(":8080", mux)
}
<|endoftext|>


<commit_before>// Copyright © 2014 Daniele Tricoli <eriol@mornie.org>.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package db

import (
	"database/sql"
	"io/ioutil"
	"os"
	"path"
	"testing"

	_ "github.com/mattn/go-sqlite3"
)

// Insert some data for testing.
func fixture(databaseName string) {
	db, _ := sql.Open("sqlite3", databaseName)
	defer db.Close()

	db.Exec("INSERT INTO people VALUES (1, 'the fox')")
	db.Exec("INSERT INTO alias VALUES (1, 'volpe')")
	db.Exec("INSERT INTO channels VALUES (1, '#test')")
	db.Exec("INSERT INTO channels VALUES (2, '#test2')")
	db.Exec("INSERT INTO channels VALUES (3, '#test3')")
	db.Exec("INSERT INTO channels VALUES (4, '#test4')")
	db.Exec("INSERT INTO quotes VALUES (1, 1, 'Hatee-hatee-hatee-ho!')")
	db.Exec("INSERT INTO quotes VALUES (2, 1, 'Chacha-chacha-chacha-chow!')")
	db.Exec("INSERT INTO quotes VALUES (3, 1, 'A-oo-oo-oo-ooo!')")
	db.Exec("INSERT INTO quotes VALUES (4, 1, 'Wa-pa-pa-pa-pa-pa-pow!')")
	db.Exec("INSERT INTO quotes_acl VALUES (1, 1)")
	db.Exec("INSERT INTO quotes_acl VALUES (2, 2)")
	db.Exec("INSERT INTO quotes_acl VALUES (3, 4)")
	db.Exec("INSERT INTO quotes_acl VALUES (4, 4)")
}

func TestQuote(t *testing.T) {
	var s Store
	var quote string
	var id int

	dirName, err := ioutil.TempDir("", "perpetua")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dirName)

	databaseName := path.Join(dirName, "perpetua.sqlite3")

	err = s.Open(databaseName)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	fixture(databaseName)

	id = s.getPerson("the fox")
	if id != 1 {
		t.Fatal("Person id does not match!")
	}

	id = s.getChannel("#test2")
	if id != 2 {
		t.Fatal("Channel id does not match!")
	}

	// GetQuote tests

	quote = s.GetQuote("the fox", "#test")
	if quote != "Hatee-hatee-hatee-ho!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuote("volpe", "#test")
	if quote != "Hatee-hatee-hatee-ho!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuote("the fox", "#test2")
	if quote != "Chacha-chacha-chacha-chow!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuote("the fox", "#test3")
	if quote != "" {
		t.Fatal("Quote does not match!")
	}

	// GetQuoteAbout tests
	// Argument search is case insensitive
	quote = s.GetQuoteAbout("the fox", "wa", "#test4")
	if quote != "Wa-pa-pa-pa-pa-pa-pow!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuoteAbout("the fox", "OOO", "#test4")
	if quote != "A-oo-oo-oo-ooo!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuoteAbout("volpe", "OoO", "#test4")
	if quote != "A-oo-oo-oo-ooo!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuoteAbout("the fox", "wa", "#test")
	if quote != "" {
		t.Fatal("Quote does not match!")
	}
}
<commit_msg>Add test for case insensitive person search<commit_after>// Copyright © 2014 Daniele Tricoli <eriol@mornie.org>.
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package db

import (
	"database/sql"
	"io/ioutil"
	"os"
	"path"
	"testing"

	_ "github.com/mattn/go-sqlite3"
)

// Insert some data for testing.
func fixture(databaseName string) {
	db, _ := sql.Open("sqlite3", databaseName)
	defer db.Close()

	db.Exec("INSERT INTO people VALUES (1, 'the fox')")
	db.Exec("INSERT INTO alias VALUES (1, 'volpe')")
	db.Exec("INSERT INTO channels VALUES (1, '#test')")
	db.Exec("INSERT INTO channels VALUES (2, '#test2')")
	db.Exec("INSERT INTO channels VALUES (3, '#test3')")
	db.Exec("INSERT INTO channels VALUES (4, '#test4')")
	db.Exec("INSERT INTO quotes VALUES (1, 1, 'Hatee-hatee-hatee-ho!')")
	db.Exec("INSERT INTO quotes VALUES (2, 1, 'Chacha-chacha-chacha-chow!')")
	db.Exec("INSERT INTO quotes VALUES (3, 1, 'A-oo-oo-oo-ooo!')")
	db.Exec("INSERT INTO quotes VALUES (4, 1, 'Wa-pa-pa-pa-pa-pa-pow!')")
	db.Exec("INSERT INTO quotes_acl VALUES (1, 1)")
	db.Exec("INSERT INTO quotes_acl VALUES (2, 2)")
	db.Exec("INSERT INTO quotes_acl VALUES (3, 4)")
	db.Exec("INSERT INTO quotes_acl VALUES (4, 4)")
}

func TestQuote(t *testing.T) {
	var s Store
	var quote string
	var id int

	dirName, err := ioutil.TempDir("", "perpetua")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dirName)

	databaseName := path.Join(dirName, "perpetua.sqlite3")

	err = s.Open(databaseName)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	fixture(databaseName)

	id = s.getPerson("the fox")
	if id != 1 {
		t.Fatal("Person id does not match!")
	}

	// Person search is case insensitive
	id = s.getPerson("ThE fOx")
	if id != 1 {
		t.Fatal("Person id does not match!")
	}

	id = s.getChannel("#test2")
	if id != 2 {
		t.Fatal("Channel id does not match!")
	}

	// GetQuote tests

	quote = s.GetQuote("the fox", "#test")
	if quote != "Hatee-hatee-hatee-ho!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuote("volpe", "#test")
	if quote != "Hatee-hatee-hatee-ho!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuote("the fox", "#test2")
	if quote != "Chacha-chacha-chacha-chow!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuote("the fox", "#test3")
	if quote != "" {
		t.Fatal("Quote does not match!")
	}

	// GetQuoteAbout tests
	// Argument search is case insensitive
	quote = s.GetQuoteAbout("the fox", "wa", "#test4")
	if quote != "Wa-pa-pa-pa-pa-pa-pow!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuoteAbout("the fox", "OOO", "#test4")
	if quote != "A-oo-oo-oo-ooo!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuoteAbout("volpe", "OoO", "#test4")
	if quote != "A-oo-oo-oo-ooo!" {
		t.Fatal("Quote does not match!")
	}

	quote = s.GetQuoteAbout("the fox", "wa", "#test")
	if quote != "" {
		t.Fatal("Quote does not match!")
	}
}
<|endoftext|>