<commit_before>package githttp

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"
)

type GitHttp struct {
	// Root directory to serve repos from
	ProjectRoot string

	// Path to git binary
	GitBinPath string

	// Access rules
	UploadPack  bool
	ReceivePack bool

	// Event handling functions
	EventHandler func(ev Event)
}

// Implement the http.Handler interface
func (g *GitHttp) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	g.requestHandler(w, r)
	return
}

// Shorthand constructor for most common scenario
func New(root string) *GitHttp {
	return &GitHttp{
		ProjectRoot: root,
		GitBinPath:  "/usr/bin/git",
		UploadPack:  true,
		ReceivePack: true,
	}
}

// Build root directory if doesn't exist
func (g *GitHttp) Init() (*GitHttp, error) {
	if err := os.MkdirAll(g.ProjectRoot, os.ModePerm); err != nil {
		return nil, err
	}
	return g, nil
}

// Publish event if EventHandler is set
func (g *GitHttp) event(e Event) {
	if g.EventHandler != nil {
		g.EventHandler(e)
	} else {
		fmt.Printf("EVENT: %q\n", e)
	}
}

// Actual command handling functions

func (g *GitHttp) serviceRpc(hr HandlerReq) error {
	w, r, rpc, dir := hr.w, hr.r, hr.Rpc, hr.Dir

	access, err := g.hasAccess(r, dir, rpc, true)
	if err != nil {
		return err
	}

	if access == false {
		return &ErrorNoAccess{hr.Dir}
	}

	// Reader that decompresses if necessary
	reader, err := requestReader(r)
	if err != nil {
		return err
	}

	// Reader that scans for events
	rpcReader := &RpcReader{
		Reader: reader,
		Rpc:    rpc,
	}

	// Set content type
	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))

	args := []string{rpc, "--stateless-rpc", "."}
	cmd := exec.Command(g.GitBinPath, args...)
	cmd.Dir = dir
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	defer stdin.Close()

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	defer stdout.Close()

	err = cmd.Start()
	if err != nil {
		return err
	}

	// Scan's git command's output for errors
	gitReader := &GitReader{
		Reader: stdout,
	}

	// Copy input to git binary
	io.Copy(stdin, rpcReader)

	// Write git binary's output to http response
	io.Copy(w, gitReader)

	// Wait till command has completed
	mainError := cmd.Wait()

	if mainError == nil {
		mainError = gitReader.GitError
	}

	// Fire events
	for _, e := range rpcReader.Events {
		// Set directory to current repo
		e.Dir = dir
		e.Request = hr.r
		e.Error = mainError

		// Fire event
		g.event(e)
	}

	// May be nil if all is good
	return mainError
}

func (g *GitHttp) getInfoRefs(hr HandlerReq) error {
	w, r, dir := hr.w, hr.r, hr.Dir
	service_name := getServiceType(r)
	access, err := g.hasAccess(r, dir, service_name, false)
	if err != nil {
		return err
	}

	if !access {
		g.updateServerInfo(dir)
		hdrNocache(w)
		return sendFile("text/plain; charset=utf-8", hr)
	}

	args := []string{service_name, "--stateless-rpc", "--advertise-refs", "."}
	refs, err := g.gitCommand(dir, args...)
	if err != nil {
		return err
	}

	hdrNocache(w)
	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service_name))
	w.WriteHeader(http.StatusOK)
	w.Write(packetWrite("# service=git-" + service_name + "\n"))
	w.Write(packetFlush())
	w.Write(refs)

	return nil
}

func (g *GitHttp) getInfoPacks(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("text/plain; charset=utf-8", hr)
}

func (g *GitHttp) getLooseObject(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("application/x-git-loose-object", hr)
}

func (g *GitHttp) getPackFile(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("application/x-git-packed-objects", hr)
}

func (g *GitHttp) getIdxFile(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("application/x-git-packed-objects-toc", hr)
}

func (g *GitHttp) getTextFile(hr HandlerReq) error {
	hdrNocache(hr.w)
	return sendFile("text/plain", hr)
}

// Logic helping functions

func sendFile(content_type string, hr HandlerReq) error {
	w, r := hr.w, hr.r
	req_file := path.Join(hr.Dir, hr.File)

	f, err := os.Stat(req_file)
	if err != nil {
		return err
	}

	w.Header().Set("Content-Type", content_type)
	w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
	w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
	http.ServeFile(w, r, req_file)

	return nil
}

func (g *GitHttp) getGitDir(file_path string) (string, error) {
	root := g.ProjectRoot

	if root == "" {
		cwd, err := os.Getwd()

		if err != nil {
			return "", err
		}

		root = cwd
	}

	f := path.Join(root, file_path)
	if _, err := os.Stat(f); os.IsNotExist(err) {
		return "", err
	}

	return f, nil
}

func (g *GitHttp) hasAccess(r *http.Request, dir string, rpc string, check_content_type bool) (bool, error) {
	if check_content_type {
		if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
			return false, nil
		}
	}

	if !(rpc == "upload-pack" || rpc == "receive-pack") {
		return false, nil
	}
	if rpc == "receive-pack" {
		return g.ReceivePack, nil
	}
	if rpc == "upload-pack" {
		return g.UploadPack, nil
	}

	return g.getConfigSetting(rpc, dir)
}

func (g *GitHttp) getConfigSetting(service_name string, dir string) (bool, error) {
	service_name = strings.Replace(service_name, "-", "", -1)
	setting, err := g.getGitConfig("http."+service_name, dir)
	if err != nil {
		return false, nil
	}

	if service_name == "uploadpack" {
		return setting != "false", nil
	}

	return setting == "true", nil
}

func (g *GitHttp) getGitConfig(config_name string, dir string) (string, error) {
	args := []string{"config", config_name}
	out, err := g.gitCommand(dir, args...)
	if err != nil {
		return "", err
	}
	return string(out)[0 : len(out)-1], nil
}

func (g *GitHttp) updateServerInfo(dir string) ([]byte, error) {
	args := []string{"update-server-info"}
	return g.gitCommand(dir, args...)
}

func (g *GitHttp) gitCommand(dir string, args ...string) ([]byte, error) {
	command := exec.Command(g.GitBinPath, args...)
	command.Dir = dir

	return command.Output()
}
<commit_msg>Improve: Close requestReader<commit_after>package githttp

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"
)

type GitHttp struct {
	// Root directory to serve repos from
	ProjectRoot string

	// Path to git binary
	GitBinPath string

	// Access rules
	UploadPack  bool
	ReceivePack bool

	// Event handling functions
	EventHandler func(ev Event)
}

// Implement the http.Handler interface
func (g *GitHttp) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	g.requestHandler(w, r)
	return
}

// Shorthand constructor for most common scenario
func New(root string) *GitHttp {
	return &GitHttp{
		ProjectRoot: root,
		GitBinPath:  "/usr/bin/git",
		UploadPack:  true,
		ReceivePack: true,
	}
}

// Build root directory if doesn't exist
func (g *GitHttp) Init() (*GitHttp, error) {
	if err := os.MkdirAll(g.ProjectRoot, os.ModePerm); err != nil {
		return nil, err
	}
	return g, nil
}

// Publish event if EventHandler is set
func (g *GitHttp) event(e Event) {
	if g.EventHandler != nil {
		g.EventHandler(e)
	} else {
		fmt.Printf("EVENT: %q\n", e)
	}
}

// Actual command handling functions

func (g *GitHttp) serviceRpc(hr HandlerReq) error {
	w, r, rpc, dir := hr.w, hr.r, hr.Rpc, hr.Dir

	access, err := g.hasAccess(r, dir, rpc, true)
	if err != nil {
		return err
	}

	if access == false {
		return &ErrorNoAccess{hr.Dir}
	}

	// Reader that decompresses if necessary
	reader, err := requestReader(r)
	if err != nil {
		return err
	}
	defer reader.Close()

	// Reader that scans for events
	rpcReader := &RpcReader{
		Reader: reader,
		Rpc:    rpc,
	}

	// Set content type
	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))

	args := []string{rpc, "--stateless-rpc", "."}
	cmd := exec.Command(g.GitBinPath, args...)
	cmd.Dir = dir
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	defer stdin.Close()

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	defer stdout.Close()

	err = cmd.Start()
	if err != nil {
		return err
	}

	// Scan's git command's output for errors
	gitReader := &GitReader{
		Reader: stdout,
	}

	// Copy input to git binary
	io.Copy(stdin, rpcReader)

	// Write git binary's output to http response
	io.Copy(w, gitReader)

	// Wait till command has completed
	mainError := cmd.Wait()

	if mainError == nil {
		mainError = gitReader.GitError
	}

	// Fire events
	for _, e := range rpcReader.Events {
		// Set directory to current repo
		e.Dir = dir
		e.Request = hr.r
		e.Error = mainError

		// Fire event
		g.event(e)
	}

	// May be nil if all is good
	return mainError
}

func (g *GitHttp) getInfoRefs(hr HandlerReq) error {
	w, r, dir := hr.w, hr.r, hr.Dir
	service_name := getServiceType(r)
	access, err := g.hasAccess(r, dir, service_name, false)
	if err != nil {
		return err
	}

	if !access {
		g.updateServerInfo(dir)
		hdrNocache(w)
		return sendFile("text/plain; charset=utf-8", hr)
	}

	args := []string{service_name, "--stateless-rpc", "--advertise-refs", "."}
	refs, err := g.gitCommand(dir, args...)
	if err != nil {
		return err
	}

	hdrNocache(w)
	w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service_name))
	w.WriteHeader(http.StatusOK)
	w.Write(packetWrite("# service=git-" + service_name + "\n"))
	w.Write(packetFlush())
	w.Write(refs)

	return nil
}

func (g *GitHttp) getInfoPacks(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("text/plain; charset=utf-8", hr)
}

func (g *GitHttp) getLooseObject(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("application/x-git-loose-object", hr)
}

func (g *GitHttp) getPackFile(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("application/x-git-packed-objects", hr)
}

func (g *GitHttp) getIdxFile(hr HandlerReq) error {
	hdrCacheForever(hr.w)
	return sendFile("application/x-git-packed-objects-toc", hr)
}

func (g *GitHttp) getTextFile(hr HandlerReq) error {
	hdrNocache(hr.w)
	return sendFile("text/plain", hr)
}

// Logic helping functions

func sendFile(content_type string, hr HandlerReq) error {
	w, r := hr.w, hr.r
	req_file := path.Join(hr.Dir, hr.File)

	f, err := os.Stat(req_file)
	if err != nil {
		return err
	}

	w.Header().Set("Content-Type", content_type)
	w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
	w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
	http.ServeFile(w, r, req_file)

	return nil
}

func (g *GitHttp) getGitDir(file_path string) (string, error) {
	root := g.ProjectRoot

	if root == "" {
		cwd, err := os.Getwd()

		if err != nil {
			return "", err
		}

		root = cwd
	}

	f := path.Join(root, file_path)
	if _, err := os.Stat(f); os.IsNotExist(err) {
		return "", err
	}

	return f, nil
}

func (g *GitHttp) hasAccess(r *http.Request, dir string, rpc string, check_content_type bool) (bool, error) {
	if check_content_type {
		if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
			return false, nil
		}
	}

	if !(rpc == "upload-pack" || rpc == "receive-pack") {
		return false, nil
	}
	if rpc == "receive-pack" {
		return g.ReceivePack, nil
	}
	if rpc == "upload-pack" {
		return g.UploadPack, nil
	}

	return g.getConfigSetting(rpc, dir)
}

func (g *GitHttp) getConfigSetting(service_name string, dir string) (bool, error) {
	service_name = strings.Replace(service_name, "-", "", -1)
	setting, err := g.getGitConfig("http."+service_name, dir)
	if err != nil {
		return false, nil
	}

	if service_name == "uploadpack" {
		return setting != "false", nil
	}

	return setting == "true", nil
}

func (g *GitHttp) getGitConfig(config_name string, dir string) (string, error) {
	args := []string{"config", config_name}
	out, err := g.gitCommand(dir, args...)
	if err != nil {
		return "", err
	}
	return string(out)[0 : len(out)-1], nil
}

func (g *GitHttp) updateServerInfo(dir string) ([]byte, error) {
	args := []string{"update-server-info"}
	return g.gitCommand(dir, args...)
}

func (g *GitHttp) gitCommand(dir string, args ...string) ([]byte, error) {
	command := exec.Command(g.GitBinPath, args...)
	command.Dir = dir

	return command.Output()
}
<|endoftext|>


<commit_before>package gitmock

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/pkg/errors"
)

//go:generate go run _tools/gen.go
// New returns new GitMock
func New(opts ...string) (*GitMock, error) {
	git := "git"
	if len(opts) > 0 {
		git = opts[0]
	}

	cmd := exec.Command(git, "version")
	cmd.Env = append(os.Environ(), "LANG=C")
	var b bytes.Buffer
	cmd.Stdout = &b
	err := cmd.Run()
	if err != nil {
		return nil, errors.Wrap(err, "failed to create new GitMock")
	}
	gitVer := b.String()
	arr := strings.Fields(gitVer)
	if len(arr) != 3 || arr[0] != "git" || arr[1] != "version" {
		return nil, fmt.Errorf("output of `git version` looks strange: %s", gitVer)
	}
	ver, err := semver.NewVersion(arr[2])
	if err != nil {
		return nil, errors.Wrap(err, "git version looks strange")
	}
	c, _ := semver.NewConstraint(">= 1.8.5")
	if !c.Check(ver) {
		return nil, fmt.Errorf("git 1.8.5 or later required.")
	}

	user := ""
	err = exec.Command(git, "config", "user.name").Run()
	if err != nil {
		user = "gomock"
	}
	email := ""
	err = exec.Command(git, "config", "user.email").Run()
	if err != nil {
		email = "gomock@example.com"
	}

	return &GitMock{
		gitPath: git,
		user:    user,
		email:   email,
	}, nil
}

// GitMock is git mock repository
type GitMock struct {
	repoPath string
	gitPath  string
	user     string
	email    string
}

// RepoPath returns repository path
func (gm *GitMock) RepoPath() string {
	if gm.repoPath == "" {
		dir, err := ioutil.TempDir("", "gitmock")
		if err != nil {
			log.Fatal(err.Error())
		}
		gm.repoPath = dir
	}
	return gm.repoPath
}

func (gm *GitMock) gitProg() string {
	if gm.gitPath != "" {
		return gm.gitPath
	}
	return "git"
}

func (gm *GitMock) env() (ret []string) {
	if gm.user != "" {
		envs := []string{"GIT_AUTHOR_NAME", "GIT_COMMITER_NAME"}
		for _, v := range envs {
			if env := os.Getenv(v); env == "" {
				ret = append(ret, fmt.Sprintf("%s=%s", v, gm.user))
			}
		}
	}
	if gm.email != "" {
		envs := []string{"GIT_AUTHOR_EMAIL", "GIT_COMMITER_EMAIL"}
		for _, v := range envs {
			if env := os.Getenv(v); env == "" {
				ret = append(ret, fmt.Sprintf("%s=%s", v, gm.email))
			}
		}
	}
	return ret
}

// Do the git command
func (gm *GitMock) Do(args ...string) (string, string, error) {
	arg := []string{"-C", gm.RepoPath()}
	arg = append(arg, args...)
	cmd := exec.Command(gm.gitProg(), arg...)
	cmd.Env = append(os.Environ(), "LANG=C")
	env := gm.env()
	if len(env) > 0 {
		cmd.Env = append(cmd.Env, env...)
	}
	var bout, berr bytes.Buffer
	cmd.Stdout = &bout
	cmd.Stderr = &berr
	err := cmd.Run()
	return bout.String(), berr.String(), err
}

// PutFile put a file to repo
func (gm *GitMock) PutFile(file, content string) error {
	repo := gm.RepoPath()
	fpath := filepath.Join(repo, file)
	err := os.MkdirAll(filepath.Dir(fpath), 0755)
	if err != nil {
		return err
	}
	c := []byte(content)
	return ioutil.WriteFile(fpath, c, 0644)
}
<commit_msg>adjust comment<commit_after>package gitmock

//go:generate go run _tools/gen.go
import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/pkg/errors"
)

// New returns new GitMock
func New(opts ...string) (*GitMock, error) {
	git := "git"
	if len(opts) > 0 {
		git = opts[0]
	}

	cmd := exec.Command(git, "version")
	cmd.Env = append(os.Environ(), "LANG=C")
	var b bytes.Buffer
	cmd.Stdout = &b
	err := cmd.Run()
	if err != nil {
		return nil, errors.Wrap(err, "failed to create new GitMock")
	}
	gitVer := b.String()
	arr := strings.Fields(gitVer)
	if len(arr) != 3 || arr[0] != "git" || arr[1] != "version" {
		return nil, fmt.Errorf("output of `git version` looks strange: %s", gitVer)
	}
	ver, err := semver.NewVersion(arr[2])
	if err != nil {
		return nil, errors.Wrap(err, "git version looks strange")
	}
	c, _ := semver.NewConstraint(">= 1.8.5")
	if !c.Check(ver) {
		return nil, fmt.Errorf("git 1.8.5 or later required.")
	}

	user := ""
	err = exec.Command(git, "config", "user.name").Run()
	if err != nil {
		user = "gomock"
	}
	email := ""
	err = exec.Command(git, "config", "user.email").Run()
	if err != nil {
		email = "gomock@example.com"
	}

	return &GitMock{
		gitPath: git,
		user:    user,
		email:   email,
	}, nil
}

// GitMock is git mock repository
type GitMock struct {
	repoPath string
	gitPath  string
	user     string
	email    string
}

// RepoPath returns repository path
func (gm *GitMock) RepoPath() string {
	if gm.repoPath == "" {
		dir, err := ioutil.TempDir("", "gitmock")
		if err != nil {
			log.Fatal(err.Error())
		}
		gm.repoPath = dir
	}
	return gm.repoPath
}

func (gm *GitMock) gitProg() string {
	if gm.gitPath != "" {
		return gm.gitPath
	}
	return "git"
}

func (gm *GitMock) env() (ret []string) {
	if gm.user != "" {
		envs := []string{"GIT_AUTHOR_NAME", "GIT_COMMITER_NAME"}
		for _, v := range envs {
			if env := os.Getenv(v); env == "" {
				ret = append(ret, fmt.Sprintf("%s=%s", v, gm.user))
			}
		}
	}
	if gm.email != "" {
		envs := []string{"GIT_AUTHOR_EMAIL", "GIT_COMMITER_EMAIL"}
		for _, v := range envs {
			if env := os.Getenv(v); env == "" {
				ret = append(ret, fmt.Sprintf("%s=%s", v, gm.email))
			}
		}
	}
	return ret
}

// Do the git command
func (gm *GitMock) Do(args ...string) (string, string, error) {
	arg := []string{"-C", gm.RepoPath()}
	arg = append(arg, args...)
	cmd := exec.Command(gm.gitProg(), arg...)
	cmd.Env = append(os.Environ(), "LANG=C")
	env := gm.env()
	if len(env) > 0 {
		cmd.Env = append(cmd.Env, env...)
	}
	var bout, berr bytes.Buffer
	cmd.Stdout = &bout
	cmd.Stderr = &berr
	err := cmd.Run()
	return bout.String(), berr.String(), err
}

// PutFile put a file to repo
func (gm *GitMock) PutFile(file, content string) error {
	repo := gm.RepoPath()
	fpath := filepath.Join(repo, file)
	err := os.MkdirAll(filepath.Dir(fpath), 0755)
	if err != nil {
		return err
	}
	c := []byte(content)
	return ioutil.WriteFile(fpath, c, 0644)
}
<|endoftext|>


<commit_before>// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package fs

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

var errNoHome = errors.New("no home directory found - set $HOME (or the platform equivalent)")

func ExpandTilde(path string) (string, error) {
	if path == "~" {
		return getHomeDir()
	}

	path = filepath.FromSlash(path)
	if !strings.HasPrefix(path, fmt.Sprintf("~%c", PathSeparator)) {
		return path, nil
	}

	home, err := getHomeDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, path[2:]), nil
}

func getHomeDir() (string, error) {
	if runtime.GOOS == "windows" {
		// Legacy -- we prioritize this for historical reasons, whereas
		// os.UserHomeDir uses %USERPROFILE% always.
		home := filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
		if home != "" {
			return home, nil
		}
	}

	return os.UserHomeDir()
}

var windowsDisallowedCharacters = string([]rune{
	'<', '>', ':', '"', '|', '?', '*',
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
	11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31,
})

func WindowsInvalidFilename(name string) bool {
	// None of the path components should end in space
	for _, part := range strings.Split(name, `\`) {
		if len(part) == 0 {
			continue
		}
		if part[len(part)-1] == ' ' {
			// Names ending in space are not valid.
			return true
		}
	}

	// The path must not contain any disallowed characters
	return strings.ContainsAny(name, windowsDisallowedCharacters)
}

// IsParent compares paths purely lexicographically, meaning it returns false
// if path and parent aren't both absolute or relative.
func IsParent(path, parent string) bool {
	if parent == path {
		// Twice the same root on windows would not be caught at the end.
		return false
	}
	if filepath.IsAbs(path) != filepath.IsAbs(parent) {
		return false
	}
	if parent == "" || parent == "." {
		// The empty string is the parent of everything except the empty
		// string and ".". (Avoids panic in the last step.)
		return path != "" && path != "."
	}
	if parent == "/" {
		// The root is the parent of everything except itself, which would
		// not be caught below.
		return path != "/"
	}
	if parent[len(parent)-1] != PathSeparator {
		parent += string(PathSeparator)
	}
	return strings.HasPrefix(path, parent)
}

func CommonPrefix(first, second string) string {
	if filepath.IsAbs(first) != filepath.IsAbs(second) {
		// Whatever
		return ""
	}

	firstParts := strings.Split(filepath.Clean(first), string(PathSeparator))
	secondParts := strings.Split(filepath.Clean(second), string(PathSeparator))

	isAbs := filepath.IsAbs(first) && filepath.IsAbs(second)

	count := len(firstParts)
	if len(secondParts) < len(firstParts) {
		count = len(secondParts)
	}

	common := make([]string, 0, count)
	for i := 0; i < count; i++ {
		if firstParts[i] != secondParts[i] {
			break
		}
		common = append(common, firstParts[i])
	}

	if isAbs {
		if runtime.GOOS == "windows" && isVolumeNameOnly(common) {
			// Because strings.Split strips out path separators, if we're at the volume name, we end up without a separator
			// Wedge an empty element to be joined with.
			common = append(common, "")
		} else if len(common) == 1 {
			// If isAbs on non Windows, first element in both first and second is "", hence joining that returns nothing.
			return string(PathSeparator)
		}
	}

	// This should only be true on Windows when drive letters are different or when paths are relative.
	// In case of UNC paths we should end up with more than a single element hence joining is fine
	if len(common) == 0 {
		return ""
	}

	// This has to be strings.Join, because filepath.Join([]string{"", "", "?", "C:", "Audrius"}...) returns garbage
	result := strings.Join(common, string(PathSeparator))
	return filepath.Clean(result)
}

func isVolumeNameOnly(parts []string) bool {
	isNormalVolumeName := len(parts) == 1 && strings.HasSuffix(parts[0], ":")
	isUNCVolumeName := len(parts) == 4 && strings.HasSuffix(parts[3], ":")
	return isNormalVolumeName || isUNCVolumeName
}
<commit_msg>lib/model: Disallow reserved names, and names ending with period on Windows (fixes #7008) (#7010)<commit_after>// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package fs

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

var errNoHome = errors.New("no home directory found - set $HOME (or the platform equivalent)")

func ExpandTilde(path string) (string, error) {
	if path == "~" {
		return getHomeDir()
	}

	path = filepath.FromSlash(path)
	if !strings.HasPrefix(path, fmt.Sprintf("~%c", PathSeparator)) {
		return path, nil
	}

	home, err := getHomeDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, path[2:]), nil
}

func getHomeDir() (string, error) {
	if runtime.GOOS == "windows" {
		// Legacy -- we prioritize this for historical reasons, whereas
		// os.UserHomeDir uses %USERPROFILE% always.
		home := filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
		if home != "" {
			return home, nil
		}
	}

	return os.UserHomeDir()
}

var windowsDisallowedCharacters = string([]rune{
	'<', '>', ':', '"', '|', '?', '*',
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
	11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31,
})

func WindowsInvalidFilename(name string) bool {
	// None of the path components should end in space or period, or be a
	// reserved name.
	// (https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
	for _, part := range strings.Split(name, `\`) {
		if len(part) == 0 {
			continue
		}
		switch part[len(part)-1] {
		case ' ', '.':
			// Names ending in space or period are not valid.
			return true
		}
		switch part {
		case "CON", "PRN", "AUX", "NUL",
			"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
			"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9":
			// These reserved names are not valid.
			return true
		}
	}

	// The path must not contain any disallowed characters
	return strings.ContainsAny(name, windowsDisallowedCharacters)
}

// IsParent compares paths purely lexicographically, meaning it returns false
// if path and parent aren't both absolute or relative.
func IsParent(path, parent string) bool {
	if parent == path {
		// Twice the same root on windows would not be caught at the end.
		return false
	}
	if filepath.IsAbs(path) != filepath.IsAbs(parent) {
		return false
	}
	if parent == "" || parent == "." {
		// The empty string is the parent of everything except the empty
		// string and ".". (Avoids panic in the last step.)
		return path != "" && path != "."
	}
	if parent == "/" {
		// The root is the parent of everything except itself, which would
		// not be caught below.
		return path != "/"
	}
	if parent[len(parent)-1] != PathSeparator {
		parent += string(PathSeparator)
	}
	return strings.HasPrefix(path, parent)
}

func CommonPrefix(first, second string) string {
	if filepath.IsAbs(first) != filepath.IsAbs(second) {
		// Whatever
		return ""
	}

	firstParts := strings.Split(filepath.Clean(first), string(PathSeparator))
	secondParts := strings.Split(filepath.Clean(second), string(PathSeparator))

	isAbs := filepath.IsAbs(first) && filepath.IsAbs(second)

	count := len(firstParts)
	if len(secondParts) < len(firstParts) {
		count = len(secondParts)
	}

	common := make([]string, 0, count)
	for i := 0; i < count; i++ {
		if firstParts[i] != secondParts[i] {
			break
		}
		common = append(common, firstParts[i])
	}

	if isAbs {
		if runtime.GOOS == "windows" && isVolumeNameOnly(common) {
			// Because strings.Split strips out path separators, if we're at the volume name, we end up without a separator
			// Wedge an empty element to be joined with.
			common = append(common, "")
		} else if len(common) == 1 {
			// If isAbs on non Windows, first element in both first and second is "", hence joining that returns nothing.
			return string(PathSeparator)
		}
	}

	// This should only be true on Windows when drive letters are different or when paths are relative.
	// In case of UNC paths we should end up with more than a single element hence joining is fine
	if len(common) == 0 {
		return ""
	}

	// This has to be strings.Join, because filepath.Join([]string{"", "", "?", "C:", "Audrius"}...) returns garbage
	result := strings.Join(common, string(PathSeparator))
	return filepath.Clean(result)
}

func isVolumeNameOnly(parts []string) bool {
	isNormalVolumeName := len(parts) == 1 && strings.HasSuffix(parts[0], ":")
	isUNCVolumeName := len(parts) == 4 && strings.HasSuffix(parts[3], ":")
	return isNormalVolumeName || isUNCVolumeName
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Peter Mrekaj. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.txt file.

package search

// MinMax returns the min and max values from an.
// The time complexity is O(n). The O(1) additional space is needed.
func MinMax(an []int) (min, max int) {
	if len(an) == 0 {
		return 0, 0
	}

	min, max = an[0], an[0]
	for _, v := range an { // We start iterate again from index 0 to avoid array bounds checking and thus improve performance.
		if v < min {
			min = v
		} else if v > max {
			max = v
		}
	}
	return min, max
}

// compare returns a, b if a < b, otherwise b, a.
func compare(a, b int) (min, max int) {
	if a < b {
		return a, b
	}
	return b, a
}

// MinMaxAlt is an alternative function to MinMax for searching min and max in an.
// The time complexity is O(n). The O(1) additional space is needed.
// The time complexity hidden constant is more significant here than in the MaxMin.
func MinMaxAlt(an []int) (min, max int) {
	switch len(an) {
	case 0:
		return 0, 0
	case 1:
		return an[0], an[0]
	}

	min, max = compare(an[0], an[1])
	for i := 3; i < len(an); i += 2 { // Compare 3/2n - 2 times.
		nMin, nMax := compare(an[i-1], an[i])
		min, _ = compare(nMin, min)
		_, max = compare(nMax, max)
	}

	if len(an) != 0 { // We need to compare the last element if length of an is odd.
		v := an[len(an)-1]
		if v < min {
			min = v
		} else if v > max {
			max = v
		}
	}

	return min, max
}
<commit_msg>Fix unnecessary comparison at the end of search.MinMaxAlt function<commit_after>// Copyright (c) 2015, Peter Mrekaj. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.txt file.

package search

// MinMax returns the min and max values from an.
// The time complexity is O(n). The O(1) additional space is needed.
func MinMax(an []int) (min, max int) {
	if len(an) == 0 {
		return 0, 0
	}

	min, max = an[0], an[0]
	for _, v := range an { // We start iterate again from index 0 to avoid array bounds checking and thus improve performance.
		if v < min {
			min = v
		} else if v > max {
			max = v
		}
	}
	return min, max
}

// compare returns a, b if a < b, otherwise b, a.
func compare(a, b int) (min, max int) {
	if a < b {
		return a, b
	}
	return b, a
}

// MinMaxAlt is an alternative function to MinMax for searching min and max in an.
// The time complexity is O(n). The O(1) additional space is needed.
// The time complexity hidden constant is more significant here than in the MaxMin.
func MinMaxAlt(an []int) (min, max int) {
	switch len(an) {
	case 0:
		return 0, 0
	case 1:
		return an[0], an[0]
	}

	min, max = compare(an[0], an[1])
	for i := 3; i < len(an); i += 2 { // Compare 3/2n - 2 times.
		nMin, nMax := compare(an[i-1], an[i])
		min, _ = compare(nMin, min)
		_, max = compare(nMax, max)
	}

	if len(an)%2 != 0 { // We need to compare the last element if length of an is odd.
		v := an[len(an)-1]
		if v < min {
			min = v
		} else if v > max {
			max = v
		}
	}

	return min, max
}
<|endoftext|>


<commit_before>/*
 * Ferret
 * Copyright (c) 2016 Yieldbot, Inc.
 * For the full copyright and license information, please view the LICENSE.txt file.
 */

// Package search provides search interface and functionality
package search

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"reflect"
	"sort"
	"strconv"
	"time"

	prov "github.com/yieldbot/ferret/providers"
	"github.com/yieldbot/gocli"
	"golang.org/x/net/context"
)

var (
	goCommand     = "open"
	searchTimeout = "5000ms"
	providers     = make(map[string]Provider)
)

func init() {
	if e := os.Getenv("FERRET_GOTO_CMD"); e != "" {
		goCommand = e
	}
	if e := os.Getenv("FERRET_SEARCH_TIMEOUT"); e != "" {
		searchTimeout = e
	}

	prov.Register(Register)
}

// Provider represents a provider
type Provider struct {
	Name     string
	Title    string
	Enabled  bool
	Noui     bool
	Priority int64
	Doer
}

// Providers returns a sorted list of the names of the providers
func Providers() []string {
	l := []string{}
	for n := range providers {
		l = append(l, n)
	}
	sort.Strings(l)
	return l
}

// ProviderByName returns a provider by the given name
func ProviderByName(name string) (Provider, error) {
	p, ok := providers[name]
	if !ok {
		return p, errors.New("provider " + name + " couldn't be found")
	}
	return p, nil
}

// Doer is the interface that must be implemented by a search provider
type Doer interface {
	// Search makes a search
	Search(ctx context.Context, args map[string]interface{}) ([]map[string]interface{}, error)
}

// Register registers a search provider
func Register(provider interface{}) error {

	// Init provider
	p, ok := provider.(Doer)
	if !ok {
		return errors.New("invalid provider")
	}

	var name, title string
	var enabled, noui bool
	var priority int64

	// Get the value of the provider
	v := reflect.Indirect(reflect.ValueOf(p))
	// Iterate the provider fields
	for i := 0; i < v.NumField(); i++ {
		fn := v.Type().Field(i).Name
		ft := v.Field(i).Type().Name()

		if fn == "name" && ft == "string" {
			name = v.Field(i).String()
		} else if fn == "title" && ft == "string" {
			title = v.Field(i).String()
		} else if fn == "enabled" && ft == "bool" {
			enabled = v.Field(i).Bool()
		} else if fn == "noui" && ft == "bool" {
			noui = v.Field(i).Bool()
		} else if fn == "priority" && ft == "int64" {
			priority = v.Field(i).Int()
		}
	}
	if name == "" {
		return errors.New("invalid provider name")
	}
	if title == "" {
		title = name
	}

	// Init provider
	if _, ok := providers[name]; ok {
		return errors.New("search provider " + name + " is already registered")
	}
	np := Provider{
		Name:     name,
		Title:    title,
		Enabled:  enabled,
		Noui:     noui,
		Priority: priority,
		Doer:     p,
	}
	providers[name] = np

	return nil
}

// Do makes a search query by the given query
func Do(query Query) (Query, error) {

	// Provider
	provider, ok := providers[query.Provider]
	if !ok {
		query.HTTPStatus = http.StatusBadRequest
		return query, fmt.Errorf("invalid search provider. Possible search providers are %s", Providers())
	}

	// Keyword
	if query.Keyword == "" {
		query.HTTPStatus = http.StatusBadRequest
		return query, errors.New("missing keyword")
	}

	// Page
	if query.Page <= 0 {
		query.HTTPStatus = http.StatusBadRequest
		return query, errors.New("invalid page #. It should be greater than 0")
	}

	// Limit
	if query.Limit <= 0 {
		query.HTTPStatus = http.StatusBadRequest
		return query, errors.New("invalid limit. It should be greater than 0")
	}

	// Search
	query.Start = time.Now()
	ctx, cancel := context.WithTimeout(context.Background(), query.Timeout)
	defer cancel()
	sq := map[string]interface{}{"page": query.Page, "limit": query.Limit, "keyword": query.Keyword}
	sr, err := provider.Search(ctx, sq)
	if err != nil {
		if err == context.DeadlineExceeded {
			query.HTTPStatus = http.StatusGatewayTimeout
			return query, errors.New("timeout")
		} else if err == context.Canceled {
			query.HTTPStatus = http.StatusInternalServerError
			return query, errors.New("canceled")
		}
		query.HTTPStatus = http.StatusInternalServerError
		return query, errors.New("failed to search due to " + err.Error())
	}
	query.Elapsed = time.Since(query.Start)
	for _, srv := range sr {
		var d string
		if _, ok := srv["Description"]; ok {
			d = srv["Description"].(string)
		}

		var t time.Time
		if _, ok := srv["Date"]; ok {
			t = srv["Date"].(time.Time)
		}

		query.Results = append(query.Results, Result{
			Link:        srv["Link"].(string),
			Title:       srv["Title"].(string),
			Description: d,
			Date:        t,
			From:        provider.Title,
		})
	}

	// Goto
	if query.Goto != 0 {
		if query.Goto < 0 || query.Goto > len(query.Results) {
			return query, fmt.Errorf("invalid result # to go. It should be between 1 and %d", len(query.Results))
		}
		link := query.Results[query.Goto-1].Link
		if _, err = exec.Command(goCommand, link).Output(); err != nil {
			return query, fmt.Errorf("failed to go to %s due to %s. Check FERRET_GOTO_CMD environment variable", link, err.Error())
		}
		return query, nil
	}

	return query, nil
}

// PrintResults prints the given search results
func PrintResults(query Query, err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if query.Goto == 0 {
		t := gocli.Table{}
		t.AddRow(1, "#", "TITLE")
		for i, v := range query.Results {
			ts := ""
			if !v.Date.IsZero() {
				ts = fmt.Sprintf(" (%d-%02d-%02d)", v.Date.Year(), v.Date.Month(), v.Date.Day())
			}
			t.AddRow(i+2, fmt.Sprintf("%d", i+1), fmt.Sprintf("%s%s", v.Title, ts))
		}
		t.PrintData()
		fmt.Printf("\n%dms\n", int64(query.Elapsed/time.Millisecond))
	}
}

// ParsePage parses page from a given string
func ParsePage(page string) int {
	p := 1
	if page != "" {
		i, err := strconv.Atoi(page)
		if err == nil && i > 0 {
			p = i
		}
	}
	return p
}

// ParseGoto parses goto from a given string
func ParseGoto(gt string) int {
	g := 0
	if gt != "" {
		i, err := strconv.Atoi(gt)
		if err == nil && i > 0 {
			g = i
		}
	}
	return g
}

// ParseTimeout parses timeout from a given string
func ParseTimeout(timeout string) time.Duration {
	t := 5000 * time.Millisecond
	if timeout != "" {
		d, err := time.ParseDuration(timeout)
		if err == nil {
			t = d
		}
	} else {
		d, err := time.ParseDuration(searchTimeout)
		if err == nil {
			t = d
		}
	}
	return t
}

// ParseLimit parses limit from a given string
func ParseLimit(limit string) int {
	l := 10
	if limit != "" {
		i, err := strconv.Atoi(limit)
		if err == nil && i > 0 {
			l = i
		}
	}
	return l
}
<commit_msg>Rename Register function to ProviderRegister<commit_after>/*
 * Ferret
 * Copyright (c) 2016 Yieldbot, Inc.
 * For the full copyright and license information, please view the LICENSE.txt file.
 */

// Package search provides search interface and functionality
package search

import (
	"errors"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"reflect"
	"sort"
	"strconv"
	"time"

	prov "github.com/yieldbot/ferret/providers"
	"github.com/yieldbot/gocli"
	"golang.org/x/net/context"
)

var (
	goCommand     = "open"
	searchTimeout = "5000ms"
	providers     = make(map[string]Provider)
)

func init() {
	if e := os.Getenv("FERRET_GOTO_CMD"); e != "" {
		goCommand = e
	}
	if e := os.Getenv("FERRET_SEARCH_TIMEOUT"); e != "" {
		searchTimeout = e
	}

	prov.Register(ProviderRegister)
}

// Provider represents a provider
type Provider struct {
	Name     string
	Title    string
	Enabled  bool
	Noui     bool
	Priority int64
	Doer
}

// Providers returns a sorted list of the names of the providers
func Providers() []string {
	l := []string{}
	for n := range providers {
		l = append(l, n)
	}
	sort.Strings(l)
	return l
}

// ProviderByName returns a provider by the given name
func ProviderByName(name string) (Provider, error) {
	p, ok := providers[name]
	if !ok {
		return p, errors.New("provider " + name + " couldn't be found")
	}
	return p, nil
}

// ProviderRegister registers a search provider
func ProviderRegister(provider interface{}) error {

	// Init provider
	p, ok := provider.(Doer)
	if !ok {
		return errors.New("invalid provider")
	}

	var name, title string
	var enabled, noui bool
	var priority int64

	// Get the value of the provider
	v := reflect.Indirect(reflect.ValueOf(p))
	// Iterate the provider fields
	for i := 0; i < v.NumField(); i++ {
		fn := v.Type().Field(i).Name
		ft := v.Field(i).Type().Name()

		if fn == "name" && ft == "string" {
			name = v.Field(i).String()
		} else if fn == "title" && ft == "string" {
			title = v.Field(i).String()
		} else if fn == "enabled" && ft == "bool" {
			enabled = v.Field(i).Bool()
		} else if fn == "noui" && ft == "bool" {
			noui = v.Field(i).Bool()
		} else if fn == "priority" && ft == "int64" {
			priority = v.Field(i).Int()
		}
	}
	if name == "" {
		return errors.New("invalid provider name")
	}
	if title == "" {
		title = name
	}

	// Init provider
	if _, ok := providers[name]; ok {
		return errors.New("search provider " + name + " is already registered")
	}
	np := Provider{
		Name:     name,
		Title:    title,
		Enabled:  enabled,
		Noui:     noui,
		Priority: priority,
		Doer:     p,
	}
	providers[name] = np

	return nil
}

// Doer is the interface that must be implemented by a search provider
type Doer interface {
	// Search makes a search
	Search(ctx context.Context, args map[string]interface{}) ([]map[string]interface{}, error)
}

// Do makes a search query by the given query
func Do(query Query) (Query, error) {

	// Provider
	provider, ok := providers[query.Provider]
	if !ok {
		query.HTTPStatus = http.StatusBadRequest
		return query, fmt.Errorf("invalid search provider. Possible search providers are %s", Providers())
	}

	// Keyword
	if query.Keyword == "" {
		query.HTTPStatus = http.StatusBadRequest
		return query, errors.New("missing keyword")
	}

	// Page
	if query.Page <= 0 {
		query.HTTPStatus = http.StatusBadRequest
		return query, errors.New("invalid page #. It should be greater than 0")
	}

	// Limit
	if query.Limit <= 0 {
		query.HTTPStatus = http.StatusBadRequest
		return query, errors.New("invalid limit. It should be greater than 0")
	}

	// Search
	query.Start = time.Now()
	ctx, cancel := context.WithTimeout(context.Background(), query.Timeout)
	defer cancel()
	sq := map[string]interface{}{"page": query.Page, "limit": query.Limit, "keyword": query.Keyword}
	sr, err := provider.Search(ctx, sq)
	if err != nil {
		if err == context.DeadlineExceeded {
			query.HTTPStatus = http.StatusGatewayTimeout
			return query, errors.New("timeout")
		} else if err == context.Canceled {
			query.HTTPStatus = http.StatusInternalServerError
			return query, errors.New("canceled")
		}
		query.HTTPStatus = http.StatusInternalServerError
		return query, errors.New("failed to search due to " + err.Error())
	}
	query.Elapsed = time.Since(query.Start)
	for _, srv := range sr {
		var d string
		if _, ok := srv["Description"]; ok {
			d = srv["Description"].(string)
		}

		var t time.Time
		if _, ok := srv["Date"]; ok {
			t = srv["Date"].(time.Time)
		}

		query.Results = append(query.Results, Result{
			Link:        srv["Link"].(string),
			Title:       srv["Title"].(string),
			Description: d,
			Date:        t,
			From:        provider.Title,
		})
	}

	// Goto
	if query.Goto != 0 {
		if query.Goto < 0 || query.Goto > len(query.Results) {
			return query, fmt.Errorf("invalid result # to go. It should be between 1 and %d", len(query.Results))
		}
		link := query.Results[query.Goto-1].Link
		if _, err = exec.Command(goCommand, link).Output(); err != nil {
			return query, fmt.Errorf("failed to go to %s due to %s. Check FERRET_GOTO_CMD environment variable", link, err.Error())
		}
		return query, nil
	}

	return query, nil
}

// PrintResults prints the given search results
func PrintResults(query Query, err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if query.Goto == 0 {
		t := gocli.Table{}
		t.AddRow(1, "#", "TITLE")
		for i, v := range query.Results {
			ts := ""
			if !v.Date.IsZero() {
				ts = fmt.Sprintf(" (%d-%02d-%02d)", v.Date.Year(), v.Date.Month(), v.Date.Day())
			}
			t.AddRow(i+2, fmt.Sprintf("%d", i+1), fmt.Sprintf("%s%s", v.Title, ts))
		}
		t.PrintData()
		fmt.Printf("\n%dms\n", int64(query.Elapsed/time.Millisecond))
	}
}

// ParsePage parses page from a given string
func ParsePage(page string) int {
	p := 1
	if page != "" {
		i, err := strconv.Atoi(page)
		if err == nil && i > 0 {
			p = i
		}
	}
	return p
}

// ParseGoto parses goto from a given string
func ParseGoto(gt string) int {
	g := 0
	if gt != "" {
		i, err := strconv.Atoi(gt)
		if err == nil && i > 0 {
			g = i
		}
	}
	return g
}

// ParseTimeout parses timeout from a given string
func ParseTimeout(timeout string) time.Duration {
	t := 5000 * time.Millisecond
	if timeout != "" {
		d, err := time.ParseDuration(timeout)
		if err == nil {
			t = d
		}
	} else {
		d, err := time.ParseDuration(searchTimeout)
		if err == nil {
			t = d
		}
	}
	return t
}

// ParseLimit parses limit from a given string
func ParseLimit(limit string) int {
	l := 10
	if limit != "" {
		i, err := strconv.Atoi(limit)
		if err == nil && i > 0 {
			l = i
		}
	}
	return l
}
<|endoftext|>


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

package stats

import (
	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
)

// DBIndexer implements Indexer interface to use database's like search
type DBIndexer struct {
}

// Index repository status function
func (db *DBIndexer) Index(id int64) error {
	repo, err := models.GetRepositoryByID(id)
	if err != nil {
		return err
	}
	if repo.IsEmpty {
		return nil
	}

	status, err := repo.GetIndexerStatus(models.RepoIndexerTypeStats)
	if err != nil {
		return err
	}

	gitRepo, err := git.OpenRepository(repo.RepoPath())
	if err != nil {
		return err
	}
	defer gitRepo.Close()

	// Get latest commit for default branch
	commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
	if err != nil {
		if git.IsErrBranchNotExist(err) {
			log.Debug("Unable to get commit ID for defaultbranch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath())
			return nil
		}
		log.Error("Unable to get commit ID for defaultbranch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err)
		return err
	}

	// Do not recalculate stats if already calculated for this commit
	if status.CommitSha == commitID {
		return nil
	}

	// Calculate and save language statistics to database
	stats, err := gitRepo.GetLanguageStats(commitID)
	if err != nil {
		log.Error("Unable to get language stats for ID %s for defaultbranch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
		return err
	}
	return repo.UpdateLanguageStats(commitID, stats)
}

// Close dummy function
func (db *DBIndexer) Close() {
}
<commit_msg>If the default branch is not present do not report error on stats indexing (follow-up of #15546) (#15583)<commit_after>// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package stats

import (
	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
)

// DBIndexer implements Indexer interface to use database's like search
type DBIndexer struct {
}

// Index repository status function
func (db *DBIndexer) Index(id int64) error {
	repo, err := models.GetRepositoryByID(id)
	if err != nil {
		return err
	}
	if repo.IsEmpty {
		return nil
	}

	status, err := repo.GetIndexerStatus(models.RepoIndexerTypeStats)
	if err != nil {
		return err
	}

	gitRepo, err := git.OpenRepository(repo.RepoPath())
	if err != nil {
		return err
	}
	defer gitRepo.Close()

	// Get latest commit for default branch
	commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
	if err != nil {
		if git.IsErrBranchNotExist(err) || git.IsErrNotExist((err)) {
			log.Debug("Unable to get commit ID for defaultbranch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath())
			return nil
		}
		log.Error("Unable to get commit ID for defaultbranch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err)
		return err
	}

	// Do not recalculate stats if already calculated for this commit
	if status.CommitSha == commitID {
		return nil
	}

	// Calculate and save language statistics to database
	stats, err := gitRepo.GetLanguageStats(commitID)
	if err != nil {
		log.Error("Unable to get language stats for ID %s for defaultbranch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
		return err
	}
	return repo.UpdateLanguageStats(commitID, stats)
}

// Close dummy function
func (db *DBIndexer) Close() {
}
<|endoftext|>


<commit_before>package java

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/ezbuy/tgen/global"
	"github.com/samuel/go-thrift/parser"
)

func TestGenerate(t *testing.T) {
	// 1 read thrift files from folder 'cases'
	// 2 generate & output
	// 3 read generated files, compared with corresponding files in folder 'test'

	casedir, _ := filepath.Abs("./../../example/java")

	// create output dir
	outdir, _ := filepath.Abs("./output")
	// if err := os.MkdirAll(outdir, 0755); err != nil {
	// 	t.Errorf("failed to create output directory %s", outdir)
	// }

	testdir, _ := filepath.Abs("./../../example/java/ref")

	gen := &JavaGen{}
	p := &parser.Parser{}

	visitfunc := func(path string, info os.FileInfo, err error) error {
		if strings.HasPrefix(filepath.Base(path), ".") || filepath.Ext(path) != ".thrift" {
			return nil
		}

		global.InputFile = path

		parsedThrift, _, err := p.ParseFile(path)
		if err != nil {
			t.Errorf("parse error: %s\n", err.Error())
		}

		gen.Generate(outdir, parsedThrift)

		for f, thrift := range parsedThrift {
			if f != global.InputFile {
				continue
			}

			ns := thrift.Namespaces["java"]
			p := strings.Replace(ns, ".", "/", -1)

			for _, m := range thrift.Structs {
				name := m.Name + ".java"

				// jsonrpc

				outfile := filepath.Join(outdir, global.MODE_JSONRPC, p, name)
				testfile := filepath.Join(testdir, global.MODE_JSONRPC, p, name)

				fileCompare(t, outfile, testfile)

				// rest
				outfile = filepath.Join(outdir, global.MODE_REST, p, name)
				testfile = filepath.Join(testdir, global.MODE_REST, p, name)

				fileCompare(t, outfile, testfile)
			}

			for _, s := range thrift.Services {
				name := s.Name + "Service.java"

				// jsonrpc
				outfile := filepath.Join(outdir, global.MODE_JSONRPC, p, name)
				testfile := filepath.Join(testdir, global.MODE_JSONRPC, p, name)

				fileCompare(t, outfile, testfile)

				// rest
				outfile = filepath.Join(outdir, global.MODE_REST, p, name)
				testfile = filepath.Join(testdir, global.MODE_REST, p, name)

				fileCompare(t, outfile, testfile)
			}
		}

		return nil
	}

	if err := filepath.Walk(casedir, visitfunc); err != nil {
		t.Errorf("walk error: %s\n", err.Error())
	}

	// do some clean
	os.RemoveAll(outdir)
}

func fileCompare(t *testing.T, src string, dest string) {
	if !pathexists(src) {
		t.Error("generate error\n")
	} else if !pathexists(dest) {
		t.Errorf("no test file found [%s]\n", dest)
	} else {
		// compare the output file with the case
		srcdata, srcerr := ioutil.ReadFile(src)
		destdata, desterr := ioutil.ReadFile(dest)

		if srcerr != nil || desterr != nil {
			t.Error("compare error [reading]")
		} else if string(srcdata) != string(destdata) {
			t.Errorf("mismatch: [%s, %s]", src, dest)
		} else {
			t.Log("PASS")
		}
	}
}

func pathexists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}

	if os.IsNotExist(err) {
		return false
	}

	return false
}
<commit_msg>modify go test<commit_after>package java

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/ezbuy/tgen/global"
	"github.com/samuel/go-thrift/parser"
)

func TestGenerate(t *testing.T) {
	// 1 read thrift files from folder 'cases'
	// 2 generate & output
	// 3 read generated files, compared with corresponding files in folder 'test'

	casedir, _ := filepath.Abs("./../../example/java")

	// create output dir
	outdir, _ := filepath.Abs("./output")
	// if err := os.MkdirAll(outdir, 0755); err != nil {
	// 	t.Errorf("failed to create output directory %s", outdir)
	// }

	testdir, _ := filepath.Abs("./../../example/java/ref")

	gen := &JavaGen{}
	p := &parser.Parser{}

	visitfunc := func(path string, info os.FileInfo, err error) error {
		if strings.HasPrefix(filepath.Base(path), ".") || filepath.Ext(path) != ".thrift" {
			return nil
		}

		if !strings.HasSuffix(path, "ShipForMe.thrift") {
			return nil
		}

		global.InputFile = path

		parsedThrift, _, err := p.ParseFile(path)
		if err != nil {
			t.Errorf("parse error: %s\n", err.Error())
		}

		gen.Generate(outdir, parsedThrift)

		for f, thrift := range parsedThrift {
			if f != global.InputFile {
				continue
			}

			ns := thrift.Namespaces["java"]
			p := strings.Replace(ns, ".", "/", -1)

			for _, m := range thrift.Structs {
				name := m.Name + ".java"

				// jsonrpc

				outfile := filepath.Join(outdir, global.MODE_JSONRPC, p, name)
				testfile := filepath.Join(testdir, global.MODE_JSONRPC, p, name)

				fileCompare(t, outfile, testfile)

				// rest
				outfile = filepath.Join(outdir, global.MODE_REST, p, name)
				testfile = filepath.Join(testdir, global.MODE_REST, p, name)

				fileCompare(t, outfile, testfile)
			}

			for _, s := range thrift.Services {
				name := s.Name + "Service.java"

				// jsonrpc
				outfile := filepath.Join(outdir, global.MODE_JSONRPC, p, name)
				testfile := filepath.Join(testdir, global.MODE_JSONRPC, p, name)

				fileCompare(t, outfile, testfile)

				// rest
				outfile = filepath.Join(outdir, global.MODE_REST, p, name)
				testfile = filepath.Join(testdir, global.MODE_REST, p, name)

				fileCompare(t, outfile, testfile)
			}
		}

		return nil
	}

	if err := filepath.Walk(casedir, visitfunc); err != nil {
		t.Errorf("walk error: %s\n", err.Error())
	}

	// do some clean
	os.RemoveAll(outdir)
}

func fileCompare(t *testing.T, src string, dest string) {
	if !pathexists(src) {
		t.Error("generate error\n")
	} else if !pathexists(dest) {
		t.Errorf("no test file found [%s]\n", dest)
	} else {
		// compare the output file with the case
		srcdata, srcerr := ioutil.ReadFile(src)
		destdata, desterr := ioutil.ReadFile(dest)

		if srcerr != nil || desterr != nil {
			t.Error("compare error [reading]")
		} else if string(srcdata) != string(destdata) {
			t.Errorf("mismatch: [%s, %s]", src, dest)
		} else {
			t.Log("PASS")
		}
	}
}

func pathexists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}

	if os.IsNotExist(err) {
		return false
	}

	return false
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors 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.
*/

package chartutil

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"path"
	"strings"

	"github.com/ghodss/yaml"

	"github.com/BurntSushi/toml"
	"github.com/gobwas/glob"
	"github.com/golang/protobuf/ptypes/any"
)

// Files is a map of files in a chart that can be accessed from a template.
type Files map[string][]byte

// NewFiles creates a new Files from chart files.
// Given an []*any.Any (the format for files in a chart.Chart), extract a map of files.
func NewFiles(from []*any.Any) Files {
	files := map[string][]byte{}
	if from != nil {
		for _, f := range from {
			files[f.TypeUrl] = f.Value
		}
	}
	return files
}

// GetBytes gets a file by path.
//
// The returned data is raw. In a template context, this is identical to calling
// {{index .Files $path}}.
//
// This is intended to be accessed from within a template, so a missed key returns
// an empty []byte.
func (f Files) GetBytes(name string) []byte {
	v, ok := f[name]
	if !ok {
		return []byte{}
	}
	return v
}

// Get returns a string representation of the given file.
//
// Fetch the contents of a file as a string. It is designed to be called in a
// template.
//
//	{{.Files.Get "foo"}}
func (f Files) Get(name string) string {
	return string(f.GetBytes(name))
}

// Glob takes a glob pattern and returns another files object only containing
// matched  files.
//
// This is designed to be called from a template.
//
// {{ range $name, $content := .Files.Glob("foo/**") }}
// {{ $name }}: |
// {{ .Files.Get($name) | indent 4 }}{{ end }}
func (f Files) Glob(pattern string) Files {
	g, err := glob.Compile(pattern, '/')
	if err != nil {
		g, _ = glob.Compile("**")
	}

	nf := NewFiles(nil)
	for name, contents := range f {
		if g.Match(name) {
			nf[name] = contents
		}
	}

	return nf
}

// AsConfig turns a Files group and flattens it to a YAML map suitable for
// including in the 'data' section of a Kubernetes ConfigMap definition.
// Duplicate keys will be overwritten, so be aware that your file names
// (regardless of path) should be unique.
//
// This is designed to be called from a template, and will return empty string
// (via ToYaml function) if it cannot be serialized to YAML, or if the Files
// object is nil.
//
// The output will not be indented, so you will want to pipe this to the
// 'indent' template function.
//
//   data:
// {{ .Files.Glob("config/**").AsConfig() | indent 4 }}
func (f Files) AsConfig() string {
	if f == nil {
		return ""
	}

	m := map[string]string{}

	// Explicitly convert to strings, and file names
	for k, v := range f {
		m[path.Base(k)] = string(v)
	}

	return ToYaml(m)
}

// AsSecrets returns the base64-encoded value of a Files object suitable for
// including in the 'data' section of a Kubernetes Secret definition.
// Duplicate keys will be overwritten, so be aware that your file names
// (regardless of path) should be unique.
//
// This is designed to be called from a template, and will return empty string
// (via ToYaml function) if it cannot be serialized to YAML, or if the Files
// object is nil.
//
// The output will not be indented, so you will want to pipe this to the
// 'indent' template function.
//
//   data:
// {{ .Files.Glob("secrets/*").AsSecrets() }}
func (f Files) AsSecrets() string {
	if f == nil {
		return ""
	}

	m := map[string]string{}

	for k, v := range f {
		m[path.Base(k)] = base64.StdEncoding.EncodeToString(v)
	}

	return ToYaml(m)
}

// Lines returns each line of a named file (split by "\n") as a slice, so it can
// be ranged over in your templates.
//
// This is designed to be called from a template.
//
// {{ range .Files.Lines "foo/bar.html" }}
// {{ . }}{{ end }}
func (f Files) Lines(path string) []string {
	if f == nil || f[path] == nil {
		return []string{}
	}

	return strings.Split(string(f[path]), "\n")
}

// ToYaml takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func ToYaml(v interface{}) string {
	data, err := yaml.Marshal(v)
	if err != nil {
		// Swallow errors inside of a template.
		return ""
	}
	return string(data)
}

// FromYaml converts a YAML document into a map[string]interface{}.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func FromYaml(str string) map[string]interface{} {
	m := map[string]interface{}{}

	if err := yaml.Unmarshal([]byte(str), &m); err != nil {
		m["Error"] = err.Error()
	}
	return m
}

// ToToml takes an interface, marshals it to toml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func ToToml(v interface{}) string {
	b := bytes.NewBuffer(nil)
	e := toml.NewEncoder(b)
	err := e.Encode(v)
	if err != nil {
		return err.Error()
	}
	return b.String()
}

// ToJson takes an interface, marshals it to json, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func ToJson(v interface{}) string {
	data, err := json.Marshal(v)
	if err != nil {
		// Swallow errors inside of a template.
		return ""
	}
	return string(data)
}

// FromJson converts a YAML document into a map[string]interface{}.
//
// This is not a general-purpose JSON parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["error"] in the returned map.
func FromJson(str string) map[string]interface{} {
	m := map[string]interface{}{}

	if err := json.Unmarshal([]byte(str), &m); err != nil {
		m["Error"] = err.Error()
	}
	return m
}
<commit_msg>fix fromjson comment<commit_after>/*
Copyright 2016 The Kubernetes Authors 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.
*/

package chartutil

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"path"
	"strings"

	"github.com/ghodss/yaml"

	"github.com/BurntSushi/toml"
	"github.com/gobwas/glob"
	"github.com/golang/protobuf/ptypes/any"
)

// Files is a map of files in a chart that can be accessed from a template.
type Files map[string][]byte

// NewFiles creates a new Files from chart files.
// Given an []*any.Any (the format for files in a chart.Chart), extract a map of files.
func NewFiles(from []*any.Any) Files {
	files := map[string][]byte{}
	if from != nil {
		for _, f := range from {
			files[f.TypeUrl] = f.Value
		}
	}
	return files
}

// GetBytes gets a file by path.
//
// The returned data is raw. In a template context, this is identical to calling
// {{index .Files $path}}.
//
// This is intended to be accessed from within a template, so a missed key returns
// an empty []byte.
func (f Files) GetBytes(name string) []byte {
	v, ok := f[name]
	if !ok {
		return []byte{}
	}
	return v
}

// Get returns a string representation of the given file.
//
// Fetch the contents of a file as a string. It is designed to be called in a
// template.
//
//	{{.Files.Get "foo"}}
func (f Files) Get(name string) string {
	return string(f.GetBytes(name))
}

// Glob takes a glob pattern and returns another files object only containing
// matched  files.
//
// This is designed to be called from a template.
//
// {{ range $name, $content := .Files.Glob("foo/**") }}
// {{ $name }}: |
// {{ .Files.Get($name) | indent 4 }}{{ end }}
func (f Files) Glob(pattern string) Files {
	g, err := glob.Compile(pattern, '/')
	if err != nil {
		g, _ = glob.Compile("**")
	}

	nf := NewFiles(nil)
	for name, contents := range f {
		if g.Match(name) {
			nf[name] = contents
		}
	}

	return nf
}

// AsConfig turns a Files group and flattens it to a YAML map suitable for
// including in the 'data' section of a Kubernetes ConfigMap definition.
// Duplicate keys will be overwritten, so be aware that your file names
// (regardless of path) should be unique.
//
// This is designed to be called from a template, and will return empty string
// (via ToYaml function) if it cannot be serialized to YAML, or if the Files
// object is nil.
//
// The output will not be indented, so you will want to pipe this to the
// 'indent' template function.
//
//   data:
// {{ .Files.Glob("config/**").AsConfig() | indent 4 }}
func (f Files) AsConfig() string {
	if f == nil {
		return ""
	}

	m := map[string]string{}

	// Explicitly convert to strings, and file names
	for k, v := range f {
		m[path.Base(k)] = string(v)
	}

	return ToYaml(m)
}

// AsSecrets returns the base64-encoded value of a Files object suitable for
// including in the 'data' section of a Kubernetes Secret definition.
// Duplicate keys will be overwritten, so be aware that your file names
// (regardless of path) should be unique.
//
// This is designed to be called from a template, and will return empty string
// (via ToYaml function) if it cannot be serialized to YAML, or if the Files
// object is nil.
//
// The output will not be indented, so you will want to pipe this to the
// 'indent' template function.
//
//   data:
// {{ .Files.Glob("secrets/*").AsSecrets() }}
func (f Files) AsSecrets() string {
	if f == nil {
		return ""
	}

	m := map[string]string{}

	for k, v := range f {
		m[path.Base(k)] = base64.StdEncoding.EncodeToString(v)
	}

	return ToYaml(m)
}

// Lines returns each line of a named file (split by "\n") as a slice, so it can
// be ranged over in your templates.
//
// This is designed to be called from a template.
//
// {{ range .Files.Lines "foo/bar.html" }}
// {{ . }}{{ end }}
func (f Files) Lines(path string) []string {
	if f == nil || f[path] == nil {
		return []string{}
	}

	return strings.Split(string(f[path]), "\n")
}

// ToYaml takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func ToYaml(v interface{}) string {
	data, err := yaml.Marshal(v)
	if err != nil {
		// Swallow errors inside of a template.
		return ""
	}
	return string(data)
}

// FromYaml converts a YAML document into a map[string]interface{}.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func FromYaml(str string) map[string]interface{} {
	m := map[string]interface{}{}

	if err := yaml.Unmarshal([]byte(str), &m); err != nil {
		m["Error"] = err.Error()
	}
	return m
}

// ToToml takes an interface, marshals it to toml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func ToToml(v interface{}) string {
	b := bytes.NewBuffer(nil)
	e := toml.NewEncoder(b)
	err := e.Encode(v)
	if err != nil {
		return err.Error()
	}
	return b.String()
}

// ToJson takes an interface, marshals it to json, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func ToJson(v interface{}) string {
	data, err := json.Marshal(v)
	if err != nil {
		// Swallow errors inside of a template.
		return ""
	}
	return string(data)
}

// FromJson converts a YAML document into a map[string]interface{}.
//
// This is not a general-purpose JSON parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func FromJson(str string) map[string]interface{} {
	m := map[string]interface{}{}

	if err := json.Unmarshal([]byte(str), &m); err != nil {
		m["Error"] = err.Error()
	}
	return m
}
<|endoftext|>


<commit_before>package handler

import (
	"net/http"

	"github.com/fusor/ansible-service-broker/pkg/broker"
	"github.com/gorilla/mux"
	"github.com/pborman/uuid"
	"k8s.io/apimachinery/pkg/api/errors"
)

// TODO: implement asynchronous operations
// TODO: authentication / authorization

type handler struct {
	router mux.Router
	broker broker.Broker
}

func NewHandler(b broker.Broker) http.Handler {
	h := handler{broker: b}

	// TODO: handle X-Broker-API-Version header, currently poorly defined
	root := h.router.Headers("X-Broker-API-Version", "2.9").Subrouter()

	root.HandleFunc("/v2/bootstrap", h.bootstrap).Methods("POST")
	root.HandleFunc("/v2/catalog", h.catalog).Methods("GET")
	root.HandleFunc("/v2/service_instances/{instance_uuid}", h.provision).Methods("PUT")
	root.HandleFunc("/v2/service_instances/{instance_uuid}", h.update).Methods("PATCH")
	root.HandleFunc("/v2/service_instances/{instance_uuid}", h.deprovision).Methods("DELETE")
	root.HandleFunc("/v2/service_instances/{instance_uuid}/service_bindings/{binding_uuid}", h.bind).Methods("PUT")
	root.HandleFunc("/v2/service_instances/{instance_uuid}/service_bindings/{binding_uuid}", h.unbind).Methods("DELETE")

	return h
}

func (h handler) bootstrap(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	resp, err := h.broker.Bootstrap()
	writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.router.ServeHTTP(w, r)
}

func (h handler) catalog(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	resp, err := h.broker.Catalog()

	writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) provision(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	var req *broker.ProvisionRequest
	err := readRequest(r, &req)

	if err != nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "could not read request: " + err.Error()})
		return
	}

	resp, err := h.broker.Provision(instanceUUID, req)

	if errors.IsNotFound(err) || errors.IsInvalid(err) {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "instance not found: " + err.Error()})
	} else if errors.IsAlreadyExists(err) {
		writeResponse(w, http.StatusConflict, broker.ProvisionResponse{})
	} else {
		writeDefaultResponse(w, http.StatusCreated, resp, err)
	}
}

func (h handler) update(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	var req *broker.UpdateRequest
	if err := readRequest(r, &req); err != nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: err.Error()})
		return
	}

	resp, err := h.broker.Update(instanceUUID, req)

	writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) deprovision(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	resp, err := h.broker.Deprovision(instanceUUID)

	if errors.IsNotFound(err) {
		writeResponse(w, http.StatusGone, broker.DeprovisionResponse{})
	} else {
		writeDefaultResponse(w, http.StatusOK, resp, err)
	}
}

func (h handler) bind(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	bindingUUID := uuid.Parse(mux.Vars(r)["binding_uuid"])
	if bindingUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid binding_uuid"})
		return
	}

	var req *broker.BindRequest
	if err := readRequest(r, &req); err != nil {
		writeResponse(w, http.StatusInternalServerError, broker.ErrorResponse{Description: err.Error()})
		return
	}

	resp, err := h.broker.Bind(instanceUUID, bindingUUID, req)

	writeDefaultResponse(w, http.StatusCreated, resp, err)
}

func (h handler) unbind(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	bindingUUID := uuid.Parse(mux.Vars(r)["binding_uuid"])
	if bindingUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid binding_uuid"})
		return
	}

	err := h.broker.Unbind(instanceUUID, bindingUUID)

	if errors.IsNotFound(err) {
		writeResponse(w, http.StatusGone, struct{}{})
	} else {
		writeDefaultResponse(w, http.StatusOK, struct{}{}, err)
	}
	return
}
<commit_msg>Remove header restriction from router (#35)<commit_after>package handler

import (
	"net/http"

	"github.com/fusor/ansible-service-broker/pkg/broker"
	"github.com/gorilla/mux"
	"github.com/pborman/uuid"
	"k8s.io/apimachinery/pkg/api/errors"
)

// TODO: implement asynchronous operations
// TODO: authentication / authorization

type handler struct {
	router mux.Router
	broker broker.Broker
}

func NewHandler(b broker.Broker) http.Handler {
	h := handler{
		router: *mux.NewRouter(),
		broker: b,
	}

	// TODO: Reintroduce router restriction based on API version when settled upstream
	//root := h.router.Headers("X-Broker-API-Version", "2.9").Subrouter()

	h.router.HandleFunc("/v2/bootstrap", h.bootstrap).Methods("POST")
	h.router.HandleFunc("/v2/catalog", h.catalog).Methods("GET")
	h.router.HandleFunc("/v2/service_instances/{instance_uuid}", h.provision).Methods("PUT")
	h.router.HandleFunc("/v2/service_instances/{instance_uuid}", h.update).Methods("PATCH")
	h.router.HandleFunc("/v2/service_instances/{instance_uuid}", h.deprovision).Methods("DELETE")
	h.router.HandleFunc("/v2/service_instances/{instance_uuid}/service_bindings/{binding_uuid}", h.bind).Methods("PUT")
	h.router.HandleFunc("/v2/service_instances/{instance_uuid}/service_bindings/{binding_uuid}", h.unbind).Methods("DELETE")

	return h
}

func (h handler) bootstrap(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	resp, err := h.broker.Bootstrap()
	writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.router.ServeHTTP(w, r)
}

func (h handler) catalog(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	resp, err := h.broker.Catalog()

	writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) provision(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	var req *broker.ProvisionRequest
	err := readRequest(r, &req)

	if err != nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "could not read request: " + err.Error()})
		return
	}

	resp, err := h.broker.Provision(instanceUUID, req)

	if errors.IsNotFound(err) || errors.IsInvalid(err) {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "instance not found: " + err.Error()})
	} else if errors.IsAlreadyExists(err) {
		writeResponse(w, http.StatusConflict, broker.ProvisionResponse{})
	} else {
		writeDefaultResponse(w, http.StatusCreated, resp, err)
	}
}

func (h handler) update(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	var req *broker.UpdateRequest
	if err := readRequest(r, &req); err != nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: err.Error()})
		return
	}

	resp, err := h.broker.Update(instanceUUID, req)

	writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) deprovision(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	resp, err := h.broker.Deprovision(instanceUUID)

	if errors.IsNotFound(err) {
		writeResponse(w, http.StatusGone, broker.DeprovisionResponse{})
	} else {
		writeDefaultResponse(w, http.StatusOK, resp, err)
	}
}

func (h handler) bind(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	bindingUUID := uuid.Parse(mux.Vars(r)["binding_uuid"])
	if bindingUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid binding_uuid"})
		return
	}

	var req *broker.BindRequest
	if err := readRequest(r, &req); err != nil {
		writeResponse(w, http.StatusInternalServerError, broker.ErrorResponse{Description: err.Error()})
		return
	}

	resp, err := h.broker.Bind(instanceUUID, bindingUUID, req)

	writeDefaultResponse(w, http.StatusCreated, resp, err)
}

func (h handler) unbind(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	instanceUUID := uuid.Parse(mux.Vars(r)["instance_uuid"])
	if instanceUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid instance_uuid"})
		return
	}

	bindingUUID := uuid.Parse(mux.Vars(r)["binding_uuid"])
	if bindingUUID == nil {
		writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: "invalid binding_uuid"})
		return
	}

	err := h.broker.Unbind(instanceUUID, bindingUUID)

	if errors.IsNotFound(err) {
		writeResponse(w, http.StatusGone, struct{}{})
	} else {
		writeDefaultResponse(w, http.StatusOK, struct{}{}, err)
	}
	return
}
<|endoftext|>


<commit_before>package rancher

import (
	"context"
	"net/http"

	responsewriter "github.com/rancher/apiserver/pkg/middleware"
	"github.com/rancher/rancher/pkg/api/norman/customization/kontainerdriver"
	"github.com/rancher/rancher/pkg/api/norman/customization/podsecuritypolicytemplate"
	steveapi "github.com/rancher/rancher/pkg/api/steve"
	"github.com/rancher/rancher/pkg/api/steve/proxy"
	"github.com/rancher/rancher/pkg/auth"
	"github.com/rancher/rancher/pkg/auth/audit"
	"github.com/rancher/rancher/pkg/auth/requests"
	"github.com/rancher/rancher/pkg/controllers/dashboard"
	"github.com/rancher/rancher/pkg/controllers/dashboardapi"
	managementauth "github.com/rancher/rancher/pkg/controllers/management/auth"
	crds "github.com/rancher/rancher/pkg/crds/dashboard"
	dashboarddata "github.com/rancher/rancher/pkg/data/dashboard"
	"github.com/rancher/rancher/pkg/features"
	"github.com/rancher/rancher/pkg/multiclustermanager"
	"github.com/rancher/rancher/pkg/tls"
	"github.com/rancher/rancher/pkg/ui"
	"github.com/rancher/rancher/pkg/websocket"
	"github.com/rancher/rancher/pkg/wrangler"
	steveauth "github.com/rancher/steve/pkg/auth"
	steveserver "github.com/rancher/steve/pkg/server"
	"github.com/rancher/wrangler/pkg/k8scheck"
	"github.com/urfave/cli"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

type Options struct {
	ACMEDomains       cli.StringSlice
	AddLocal          string
	Embedded          bool
	BindHost          string
	HTTPListenPort    int
	HTTPSListenPort   int
	K8sMode           string
	Debug             bool
	Trace             bool
	NoCACerts         bool
	AuditLogPath      string
	AuditLogMaxage    int
	AuditLogMaxsize   int
	AuditLogMaxbackup int
	AuditLevel        int
	Agent             bool
	Features          string
}

type Rancher struct {
	Auth     steveauth.Middleware
	Handler  http.Handler
	Wrangler *wrangler.Context
	Steve    *steveserver.Server

	auditLog   *audit.LogWriter
	authServer *auth.Server
	opts       *Options
}

func New(ctx context.Context, clientConfg clientcmd.ClientConfig, opts *Options) (*Rancher, error) {
	var (
		authServer *auth.Server
	)

	if opts == nil {
		opts = &Options{}
	}

	restConfig, err := clientConfg.ClientConfig()
	if err != nil {
		return nil, err
	}

	restConfig, err = setupAndValidationRESTConfig(ctx, restConfig)
	if err != nil {
		return nil, err
	}

	lockID := "cattle-controllers"
	if opts.Agent {
		lockID = "cattle-agent-controllers"
	}

	wranglerContext, err := wrangler.NewContext(ctx, lockID, clientConfg, restConfig)
	if err != nil {
		return nil, err
	}
	wranglerContext.MultiClusterManager = newMCM(wranglerContext, opts)
	wranglerContext.Agent = opts.Agent

	podsecuritypolicytemplate.RegisterIndexers(wranglerContext)
	kontainerdriver.RegisterIndexers(wranglerContext)
	managementauth.RegisterWranglerIndexers(wranglerContext)

	if err := crds.Create(ctx, restConfig); err != nil {
		return nil, err
	}

	// Initialize Features as early as possible
	features.InitializeFeatures(wranglerContext.Mgmt.Feature(), opts.Features)

	if opts.Agent {
		authServer, err = auth.NewHeaderAuth()
		if err != nil {
			return nil, err
		}
		features.MCM.Disable()
		features.Fleet.Disable()
	} else if features.Auth.Enabled() {
		authServer, err = auth.NewServer(ctx, restConfig)
		if err != nil {
			return nil, err
		}
	} else {
		authServer, err = auth.NewAlwaysAdmin()
		if err != nil {
			return nil, err
		}
	}

	steve, err := steveserver.New(ctx, restConfig, &steveserver.Options{
		Controllers:     wranglerContext.Controllers,
		AccessSetLookup: wranglerContext.ASL,
		AuthMiddleware:  steveauth.ExistingContext,
		Next:            ui.New(wranglerContext.Mgmt.Preference().Cache()),
	})
	if err != nil {
		return nil, err
	}

	clusterProxy, err := proxy.NewProxyMiddleware(wranglerContext.K8s.AuthorizationV1().SubjectAccessReviews(),
		wranglerContext.MultiClusterManager,
		wranglerContext.Mgmt.Cluster().Cache(),
		localClusterEnabled(opts),
		steve,
	)
	if err != nil {
		return nil, err
	}

	additionalAPI, err := steveapi.AdditionalAPIs(ctx, wranglerContext, steve)
	if err != nil {
		return nil, err
	}

	auditLogWriter := audit.NewLogWriter(opts.AuditLogPath, opts.AuditLevel, opts.AuditLogMaxage, opts.AuditLogMaxbackup, opts.AuditLogMaxsize)
	auditFilter := audit.NewAuditLogMiddleware(auditLogWriter)

	return &Rancher{
		Auth: authServer.Authenticator.Chain(
			auditFilter),
		Handler: responsewriter.Chain{
			responsewriter.ContentTypeOptions,
			websocket.NewWebsocketHandler,
			proxy.RewriteLocalCluster,
			clusterProxy,
			wranglerContext.MultiClusterManager.Middleware,
			authServer.Management,
			additionalAPI,
			requests.NewRequireAuthenticatedFilter("/v1/"),
		}.Handler(steve),
		Wrangler:   wranglerContext,
		Steve:      steve,
		auditLog:   auditLogWriter,
		authServer: authServer,
		opts:       opts,
	}, nil
}

func (r *Rancher) Start(ctx context.Context) error {
	if err := dashboarddata.EarlyData(ctx, r.Wrangler.K8s); err != nil {
		return err
	}

	if err := dashboardapi.Register(ctx, r.Wrangler); err != nil {
		return err
	}

	if err := steveapi.Setup(ctx, r.Steve, r.Wrangler); err != nil {
		return err
	}

	r.Wrangler.OnLeader(func(ctx context.Context) error {
		return r.Wrangler.StartWithTransaction(ctx, func(ctx context.Context) error {
			if err := dashboarddata.Add(ctx, r.Wrangler, localClusterEnabled(r.opts), r.opts.AddLocal == "false", r.opts.Embedded); err != nil {
				return err
			}
			return dashboard.Register(ctx, r.Wrangler)
		})
	})

	if err := r.authServer.Start(ctx, false); err != nil {
		return err
	}

	r.Wrangler.OnLeader(r.authServer.OnLeader)
	r.auditLog.Start(ctx)

	return r.Wrangler.Start(ctx)
}

func (r *Rancher) ListenAndServe(ctx context.Context) error {
	if err := r.Start(ctx); err != nil {
		return err
	}

	r.Wrangler.MultiClusterManager.Wait(ctx)

	if err := tls.ListenAndServe(ctx, r.Wrangler.RESTConfig,
		r.Auth(r.Handler),
		r.opts.BindHost,
		r.opts.HTTPSListenPort,
		r.opts.HTTPListenPort,
		r.opts.ACMEDomains,
		r.opts.NoCACerts); err != nil {
		return err
	}

	<-ctx.Done()
	return ctx.Err()
}

func newMCM(wrangler *wrangler.Context, opts *Options) wrangler.MultiClusterManager {
	return multiclustermanager.NewDeferredServer(wrangler, &multiclustermanager.Options{
		RemoveLocalCluster:  opts.AddLocal == "false",
		LocalClusterEnabled: localClusterEnabled(opts),
		Embedded:            opts.Embedded,
		HTTPSListenPort:     opts.HTTPSListenPort,
		Debug:               opts.Debug,
		Trace:               opts.Trace,
	})
}

func setupAndValidationRESTConfig(ctx context.Context, restConfig *rest.Config) (*rest.Config, error) {
	restConfig = steveserver.RestConfigDefaults(restConfig)
	return restConfig, k8scheck.Wait(ctx, *restConfig)
}

func localClusterEnabled(opts *Options) bool {
	if opts.AddLocal == "true" || opts.AddLocal == "auto" {
		return true
	}
	return false
}
<commit_msg>Add steve aggregation client to Rancher<commit_after>package rancher

import (
	"context"
	"net/http"

	responsewriter "github.com/rancher/apiserver/pkg/middleware"
	"github.com/rancher/rancher/pkg/api/norman/customization/kontainerdriver"
	"github.com/rancher/rancher/pkg/api/norman/customization/podsecuritypolicytemplate"
	steveapi "github.com/rancher/rancher/pkg/api/steve"
	"github.com/rancher/rancher/pkg/api/steve/proxy"
	"github.com/rancher/rancher/pkg/auth"
	"github.com/rancher/rancher/pkg/auth/audit"
	"github.com/rancher/rancher/pkg/auth/requests"
	"github.com/rancher/rancher/pkg/controllers/dashboard"
	"github.com/rancher/rancher/pkg/controllers/dashboardapi"
	managementauth "github.com/rancher/rancher/pkg/controllers/management/auth"
	crds "github.com/rancher/rancher/pkg/crds/dashboard"
	dashboarddata "github.com/rancher/rancher/pkg/data/dashboard"
	"github.com/rancher/rancher/pkg/features"
	"github.com/rancher/rancher/pkg/multiclustermanager"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/rancher/pkg/tls"
	"github.com/rancher/rancher/pkg/ui"
	"github.com/rancher/rancher/pkg/websocket"
	"github.com/rancher/rancher/pkg/wrangler"
	steveauth "github.com/rancher/steve/pkg/auth"
	steveserver "github.com/rancher/steve/pkg/server"
	"github.com/rancher/wrangler/pkg/k8scheck"
	"github.com/urfave/cli"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

type Options struct {
	ACMEDomains       cli.StringSlice
	AddLocal          string
	Embedded          bool
	BindHost          string
	HTTPListenPort    int
	HTTPSListenPort   int
	K8sMode           string
	Debug             bool
	Trace             bool
	NoCACerts         bool
	AuditLogPath      string
	AuditLogMaxage    int
	AuditLogMaxsize   int
	AuditLogMaxbackup int
	AuditLevel        int
	Agent             bool
	Features          string
}

type Rancher struct {
	Auth     steveauth.Middleware
	Handler  http.Handler
	Wrangler *wrangler.Context
	Steve    *steveserver.Server

	auditLog   *audit.LogWriter
	authServer *auth.Server
	opts       *Options
}

func New(ctx context.Context, clientConfg clientcmd.ClientConfig, opts *Options) (*Rancher, error) {
	var (
		authServer *auth.Server
	)

	if opts == nil {
		opts = &Options{}
	}

	restConfig, err := clientConfg.ClientConfig()
	if err != nil {
		return nil, err
	}

	restConfig, err = setupAndValidationRESTConfig(ctx, restConfig)
	if err != nil {
		return nil, err
	}

	lockID := "cattle-controllers"
	if opts.Agent {
		lockID = "cattle-agent-controllers"
	}

	wranglerContext, err := wrangler.NewContext(ctx, lockID, clientConfg, restConfig)
	if err != nil {
		return nil, err
	}
	wranglerContext.MultiClusterManager = newMCM(wranglerContext, opts)
	wranglerContext.Agent = opts.Agent

	podsecuritypolicytemplate.RegisterIndexers(wranglerContext)
	kontainerdriver.RegisterIndexers(wranglerContext)
	managementauth.RegisterWranglerIndexers(wranglerContext)

	if err := crds.Create(ctx, restConfig); err != nil {
		return nil, err
	}

	// Initialize Features as early as possible
	features.InitializeFeatures(wranglerContext.Mgmt.Feature(), opts.Features)

	if opts.Agent {
		authServer, err = auth.NewHeaderAuth()
		if err != nil {
			return nil, err
		}
		features.MCM.Disable()
		features.Fleet.Disable()
	} else if features.Auth.Enabled() {
		authServer, err = auth.NewServer(ctx, restConfig)
		if err != nil {
			return nil, err
		}
	} else {
		authServer, err = auth.NewAlwaysAdmin()
		if err != nil {
			return nil, err
		}
	}

	steve, err := steveserver.New(ctx, restConfig, &steveserver.Options{
		Controllers:                wranglerContext.Controllers,
		AccessSetLookup:            wranglerContext.ASL,
		AuthMiddleware:             steveauth.ExistingContext,
		AggregationSecretNamespace: namespace.System,
		AggregationSecretName:      "steve-aggregation",
		Next:                       ui.New(wranglerContext.Mgmt.Preference().Cache()),
	})
	if err != nil {
		return nil, err
	}

	clusterProxy, err := proxy.NewProxyMiddleware(wranglerContext.K8s.AuthorizationV1().SubjectAccessReviews(),
		wranglerContext.MultiClusterManager,
		wranglerContext.Mgmt.Cluster().Cache(),
		localClusterEnabled(opts),
		steve,
	)
	if err != nil {
		return nil, err
	}

	additionalAPI, err := steveapi.AdditionalAPIs(ctx, wranglerContext, steve)
	if err != nil {
		return nil, err
	}

	auditLogWriter := audit.NewLogWriter(opts.AuditLogPath, opts.AuditLevel, opts.AuditLogMaxage, opts.AuditLogMaxbackup, opts.AuditLogMaxsize)
	auditFilter := audit.NewAuditLogMiddleware(auditLogWriter)

	return &Rancher{
		Auth: authServer.Authenticator.Chain(
			auditFilter),
		Handler: responsewriter.Chain{
			responsewriter.ContentTypeOptions,
			websocket.NewWebsocketHandler,
			proxy.RewriteLocalCluster,
			clusterProxy,
			wranglerContext.MultiClusterManager.Middleware,
			authServer.Management,
			additionalAPI,
			requests.NewRequireAuthenticatedFilter("/v1/"),
		}.Handler(steve),
		Wrangler:   wranglerContext,
		Steve:      steve,
		auditLog:   auditLogWriter,
		authServer: authServer,
		opts:       opts,
	}, nil
}

func (r *Rancher) Start(ctx context.Context) error {
	if err := dashboarddata.EarlyData(ctx, r.Wrangler.K8s); err != nil {
		return err
	}

	if err := dashboardapi.Register(ctx, r.Wrangler); err != nil {
		return err
	}

	if err := steveapi.Setup(ctx, r.Steve, r.Wrangler); err != nil {
		return err
	}

	r.Wrangler.OnLeader(func(ctx context.Context) error {
		return r.Wrangler.StartWithTransaction(ctx, func(ctx context.Context) error {
			if err := dashboarddata.Add(ctx, r.Wrangler, localClusterEnabled(r.opts), r.opts.AddLocal == "false", r.opts.Embedded); err != nil {
				return err
			}
			return dashboard.Register(ctx, r.Wrangler)
		})
	})

	if err := r.authServer.Start(ctx, false); err != nil {
		return err
	}

	r.Wrangler.OnLeader(r.authServer.OnLeader)
	r.auditLog.Start(ctx)

	return r.Wrangler.Start(ctx)
}

func (r *Rancher) ListenAndServe(ctx context.Context) error {
	if err := r.Start(ctx); err != nil {
		return err
	}

	r.Wrangler.MultiClusterManager.Wait(ctx)

	if err := tls.ListenAndServe(ctx, r.Wrangler.RESTConfig,
		r.Auth(r.Handler),
		r.opts.BindHost,
		r.opts.HTTPSListenPort,
		r.opts.HTTPListenPort,
		r.opts.ACMEDomains,
		r.opts.NoCACerts); err != nil {
		return err
	}

	<-ctx.Done()
	return ctx.Err()
}

func newMCM(wrangler *wrangler.Context, opts *Options) wrangler.MultiClusterManager {
	return multiclustermanager.NewDeferredServer(wrangler, &multiclustermanager.Options{
		RemoveLocalCluster:  opts.AddLocal == "false",
		LocalClusterEnabled: localClusterEnabled(opts),
		Embedded:            opts.Embedded,
		HTTPSListenPort:     opts.HTTPSListenPort,
		Debug:               opts.Debug,
		Trace:               opts.Trace,
	})
}

func setupAndValidationRESTConfig(ctx context.Context, restConfig *rest.Config) (*rest.Config, error) {
	restConfig = steveserver.RestConfigDefaults(restConfig)
	return restConfig, k8scheck.Wait(ctx, *restConfig)
}

func localClusterEnabled(opts *Options) bool {
	if opts.AddLocal == "true" || opts.AddLocal == "auto" {
		return true
	}
	return false
}
<|endoftext|>