<commit_before>package githttp

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"
)

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

	// Path to git binary
	GitBinPath string

	// Access rules
	UploadPack  bool
	ReceivePack bool
}

// 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,
	}
}

type Service struct {
	Method  string
	Handler func(HandlerReq)
	Rpc     string
}

type HandlerReq struct {
	w    http.ResponseWriter
	r    *http.Request
	Rpc  string
	Dir  string
	File string
}

func (g *GitHttp) services() map[string]Service {
	return map[string]Service{
		"(.*?)/git-upload-pack$":                       Service{"POST", g.serviceRpc, "upload-pack"},
		"(.*?)/git-receive-pack$":                      Service{"POST", g.serviceRpc, "receive-pack"},
		"(.*?)/info/refs$":                             Service{"GET", g.getInfoRefs, ""},
		"(.*?)/HEAD$":                                  Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/info/alternates$":               Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/info/http-alternates$":          Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/info/packs$":                    Service{"GET", g.getInfoPacks, ""},
		"(.*?)/objects/info/[^/]*$":                    Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$":      Service{"GET", g.getLooseObject, ""},
		"(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$": Service{"GET", g.getPackFile, ""},
		"(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$":  Service{"GET", g.getIdxFile, ""},
	}
}

// Request handling function

func (g *GitHttp) requestHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s %s %s", r.RemoteAddr, r.Method, r.URL.Path, r.Proto)
	for match, service := range g.services() {
		// Ensure that regex mathces
		re, err := regexp.Compile(match)
		if err != nil {
			log.Print(err)
		}

		if m := re.FindStringSubmatch(r.URL.Path); m != nil {
			if service.Method != r.Method {
				renderMethodNotAllowed(w, r)
				return
			}

			rpc := service.Rpc
			// Get specific file
			file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
			// Resolve directory
			dir, err := g.getGitDir(m[1])

			if err != nil {
				log.Print(err)
				renderNotFound(w)
				return
			}

			hr := HandlerReq{w, r, rpc, dir, file}
			service.Handler(hr)
			return
		}
	}
	renderNotFound(w)
	return
}

// Actual command handling functions

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

	if access == false {
		renderNoAccess(w)
		return
	}

	input, _ := ioutil.ReadAll(r.Body)

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

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

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Print(err)
	}

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

	in.Write(input)
	io.Copy(w, stdout)
	cmd.Wait()
}

func (g *GitHttp) getInfoRefs(hr HandlerReq) {
	w, r, dir := hr.w, hr.r, hr.Dir
	service_name := getServiceType(r)
	access := g.hasAccess(r, dir, service_name, false)

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

		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)
	} else {
		g.updateServerInfo(dir)
		hdrNocache(w)
		sendFile("text/plain; charset=utf-8", hr)
	}
}

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

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

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

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

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

// Logic helping functions

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

	f, err := os.Stat(req_file)
	if os.IsNotExist(err) {
		renderNotFound(w)
		return
	}

	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)
}

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

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

		if err != nil {
			log.Print(err)
			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 {
	if check_content_type {
		if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
			return false
		}
	}

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

	return g.getConfigSetting(rpc, dir)
}

func (g *GitHttp) getConfigSetting(service_name string, dir string) bool {
	service_name = strings.Replace(service_name, "-", "", -1)
	setting := g.getGitConfig("http."+service_name, dir)

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

	return setting == "true"
}

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

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

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

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

	return out
}

// HTTP parsing utility functions

func getServiceType(r *http.Request) string {
	service_type := r.FormValue("service")

	if s := strings.HasPrefix(service_type, "git-"); !s {
		return ""
	}

	return strings.Replace(service_type, "git-", "", 1)
}

// HTTP error response handling functions

func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
	if r.Proto == "HTTP/1.1" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
	} else {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
	}
}

func renderNotFound(w http.ResponseWriter) {
	w.WriteHeader(http.StatusNotFound)
	w.Write([]byte("Not Found"))
}

func renderNoAccess(w http.ResponseWriter) {
	w.WriteHeader(http.StatusForbidden)
	w.Write([]byte("Forbidden"))
}

// Packet-line handling function

func packetFlush() []byte {
	return []byte("0000")
}

func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64(len(str)+4), 16)

	if len(s)%4 != 0 {
		s = strings.Repeat("0", 4-len(s)%4) + s
	}

	return []byte(s + str)
}

// Header writing functions

func hdrNocache(w http.ResponseWriter) {
	w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
	w.Header().Set("Pragma", "no-cache")
	w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
}

func hdrCacheForever(w http.ResponseWriter) {
	now := time.Now().Unix()
	expires := now + 31536000
	w.Header().Set("Date", fmt.Sprintf("%d", now))
	w.Header().Set("Expires", fmt.Sprintf("%d", expires))
	w.Header().Set("Cache-Control", "public, max-age=31536000")
}
<commit_msg>Add event system<commit_after>package githttp

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"
)

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)
}

// An event (triggered on push/pull)
type Event struct {
	Type EventType `json:"type"`

	// Set for pushes and pulls
	Commit string `json:"commit"`

	// Set for pushes or tagging
	Tag string `json:"tag,omitempty"`
	Last string `json:"last,omitempty"`
	Branch string `json:"branch,omitempty"`
}

type EventType int

// Possible event types
const (
	TAG = iota + 1
	PUSH
	FETCH
)

func (e EventType) String() string {
	switch e {
		case TAG:
			return "tag"
		case PUSH:
			return "push"
		case FETCH:
			return "fetch"
	}
	return "unknown"
}

func (e EventType) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`"%s"`, e)), nil
}

func (e EventType) UnmarshalJSON(data []byte) error {
	str := string(data[:])
	switch str {
		case "tag":
			e = TAG
		case "push":
			e = PUSH
		case "fetch":
			e = FETCH
		default:
			return fmt.Errorf("'%s' is not a known git event type")
	}
	return nil
}

// 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,
	}
}

type Service struct {
	Method  string
	Handler func(HandlerReq)
	Rpc     string
}

type HandlerReq struct {
	w    http.ResponseWriter
	r    *http.Request
	Rpc  string
	Dir  string
	File string
}

func (g *GitHttp) services() map[string]Service {
	return map[string]Service{
		"(.*?)/git-upload-pack$":                       Service{"POST", g.serviceRpc, "upload-pack"},
		"(.*?)/git-receive-pack$":                      Service{"POST", g.serviceRpc, "receive-pack"},
		"(.*?)/info/refs$":                             Service{"GET", g.getInfoRefs, ""},
		"(.*?)/HEAD$":                                  Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/info/alternates$":               Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/info/http-alternates$":          Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/info/packs$":                    Service{"GET", g.getInfoPacks, ""},
		"(.*?)/objects/info/[^/]*$":                    Service{"GET", g.getTextFile, ""},
		"(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$":      Service{"GET", g.getLooseObject, ""},
		"(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$": Service{"GET", g.getPackFile, ""},
		"(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$":  Service{"GET", g.getIdxFile, ""},
	}
}

// Request handling function

func (g *GitHttp) requestHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s %s %s", r.RemoteAddr, r.Method, r.URL.Path, r.Proto)
	for match, service := range g.services() {
		// Ensure that regex mathces
		re, err := regexp.Compile(match)
		if err != nil {
			log.Print(err)
		}

		if m := re.FindStringSubmatch(r.URL.Path); m != nil {
			if service.Method != r.Method {
				renderMethodNotAllowed(w, r)
				return
			}

			rpc := service.Rpc
			// Get specific file
			file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
			// Resolve directory
			dir, err := g.getGitDir(m[1])

			if err != nil {
				log.Print(err)
				renderNotFound(w)
				return
			}

			hr := HandlerReq{w, r, rpc, dir, file}
			service.Handler(hr)
			return
		}
	}
	renderNotFound(w)
	return
}

// Regexes to detect types of actions (fetch, push, etc ...)
var (
	receivePackRegex = regexp.MustCompile("([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) refs\\/(heads|tags)\\/(.*?)( |00|\u0000)|^(0000)$")
	uploadPackRegex  = regexp.MustCompile("^\\S+ ([0-9a-fA-F]{40})")
)

// 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) {
	w, r, rpc, dir := hr.w, hr.r, hr.Rpc, hr.Dir
	access := g.hasAccess(r, dir, rpc, true)

	if access == false {
		renderNoAccess(w)
		return
	}

	input, _ := ioutil.ReadAll(r.Body)

	if(rpc == "upload-pack") {
		matches := uploadPackRegex.FindAllStringSubmatch(string(input[:]), -1)
		if matches != nil {
			for _, m := range matches {
				g.event(Event{
					Type: FETCH,
					Commit: m[1],
				})
			}
		}
	} else if(rpc == "receive-pack") {
		matches := receivePackRegex.FindAllStringSubmatch(string(input[:]), -1)
		if matches != nil {
			for _, m := range matches {
				e := Event{
					Last: m[1],
					Commit: m[2],
				}

				// Handle pushes to branches and tags differently
				if m[3] == "heads" {
					e.Type = PUSH
					e.Branch = m[4]
				} else {
					e.Type = TAG
					e.Tag = m[4]
				}

				g.event(e)
			}
		}
	}

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

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

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Print(err)
	}

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

	in.Write(input)
	io.Copy(w, stdout)
	cmd.Wait()
}

func (g *GitHttp) getInfoRefs(hr HandlerReq) {
	w, r, dir := hr.w, hr.r, hr.Dir
	service_name := getServiceType(r)
	access := g.hasAccess(r, dir, service_name, false)

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

		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)
	} else {
		g.updateServerInfo(dir)
		hdrNocache(w)
		sendFile("text/plain; charset=utf-8", hr)
	}
}

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

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

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

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

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

// Logic helping functions

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

	f, err := os.Stat(req_file)
	if os.IsNotExist(err) {
		renderNotFound(w)
		return
	}

	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)
}

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

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

		if err != nil {
			log.Print(err)
			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 {
	if check_content_type {
		if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
			return false
		}
	}

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

	return g.getConfigSetting(rpc, dir)
}

func (g *GitHttp) getConfigSetting(service_name string, dir string) bool {
	service_name = strings.Replace(service_name, "-", "", -1)
	setting := g.getGitConfig("http."+service_name, dir)

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

	return setting == "true"
}

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

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

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

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

	return out
}

// HTTP parsing utility functions

func getServiceType(r *http.Request) string {
	service_type := r.FormValue("service")

	if s := strings.HasPrefix(service_type, "git-"); !s {
		return ""
	}

	return strings.Replace(service_type, "git-", "", 1)
}

// HTTP error response handling functions

func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
	if r.Proto == "HTTP/1.1" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
	} else {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
	}
}

func renderNotFound(w http.ResponseWriter) {
	w.WriteHeader(http.StatusNotFound)
	w.Write([]byte("Not Found"))
}

func renderNoAccess(w http.ResponseWriter) {
	w.WriteHeader(http.StatusForbidden)
	w.Write([]byte("Forbidden"))
}

// Packet-line handling function

func packetFlush() []byte {
	return []byte("0000")
}

func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64(len(str)+4), 16)

	if len(s)%4 != 0 {
		s = strings.Repeat("0", 4-len(s)%4) + s
	}

	return []byte(s + str)
}

// Header writing functions

func hdrNocache(w http.ResponseWriter) {
	w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
	w.Header().Set("Pragma", "no-cache")
	w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
}

func hdrCacheForever(w http.ResponseWriter) {
	now := time.Now().Unix()
	expires := now + 31536000
	w.Header().Set("Date", fmt.Sprintf("%d", now))
	w.Header().Set("Expires", fmt.Sprintf("%d", expires))
	w.Header().Set("Cache-Control", "public, max-age=31536000")
}
<|endoftext|>


<commit_before>package main

import (
	"testing"

	. "github.com/onsi/gomega"
)

func TestGitConfigAll(t *testing.T) {
	RegisterTestingT(t)

	Expect(GitConfigAll("ghq.non.existent.key")).To(HaveLen(0))
}

func TestGitConfigURL(t *testing.T) {
	RegisterTestingT(t)

	if GitHasFeatureConfigURLMatch() != nil {
		t.Skip("Git does not have config --get-urlmatch feature")
	}

	reset, err := WithGitconfigFile(`
[ghq "https://ghe.example.com/"]
vcs = github
[ghq "https://ghe.example.com/hg/"]
vcs = hg
`)
	if err != nil {
		t.Fatal(err)
	}
	defer reset()

	var (
		value string
	)

	value, err = GitConfig("--get-urlmatch", "ghq.vcs", "https://ghe.example.com/foo/bar")
	Expect(err).NotTo(HaveOccurred())
	Expect(value).To(Equal("github"))

	value, err = GitConfig("--get-urlmatch", "ghq.vcs", "https://ghe.example.com/hg/repo")
	Expect(err).NotTo(HaveOccurred())
	Expect(value).To(Equal("hg"))

	value, err = GitConfig("--get-urlmatch", "ghq.vcs", "https://example.com")
	Expect(err).NotTo(HaveOccurred())
	Expect(value).To(Equal(""))
}
<commit_msg>rewrite git_test.go<commit_after>package main

import "testing"

func TestGitConfigAll(t *testing.T) {
	dummyKey := "ghq.non.existent.key"
	confs, err := GitConfigAll(dummyKey)
	if err != nil {
		t.Errorf("error should be nil but: %s", err)
	}
	if len(confs) > 0 {
		t.Errorf("GitConfigAll(%q) = %v; want %v", dummyKey, confs, nil)
	}
}

func TestGitConfigURL(t *testing.T) {
	if GitHasFeatureConfigURLMatch() != nil {
		t.Skip("Git does not have config --get-urlmatch feature")
	}

	reset, err := WithGitconfigFile(`
[ghq "https://ghe.example.com/"]
vcs = github
[ghq "https://ghe.example.com/hg/"]
vcs = hg
`)
	if err != nil {
		t.Fatal(err)
	}
	defer reset()

	testCases := []struct {
		name   string
		config []string
		expect string
	}{{
		name:   "github",
		config: []string{"--get-urlmatch", "ghq.vcs", "https://ghe.example.com/foo/bar"},
		expect: "github",
	}, {
		name:   "hg",
		config: []string{"--get-urlmatch", "ghq.vcs", "https://ghe.example.com/hg/repo"},
		expect: "hg",
	}, {
		name:   "empty",
		config: []string{"--get-urlmatch", "ghq.vcs", "https://example.com"},
		expect: "",
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			value, err := GitConfig(tc.config...)
			if err != nil {
				t.Errorf("error should be nil but: %s", err)
			}
			if value != tc.expect {
				t.Errorf("got: %s, expect: %s", value, tc.expect)
			}
		})
	}
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"

	"github.com/labstack/echo"
)

type Prefix struct {
	Background string   `json:"background"`
	Color      string   `json:"color"`
	Words      []string `json:"words"`
	TimedEvent bool     `json:"timedEvent"`
	Default    bool     `json:"default"`
}

var DefaultPrefixes = []Prefix{
	Prefix{
		Background: "4C6C9B",
		Color:      "FFFFFF",
		Words:      []string{"HW", "Read", "Reading"},
		Default:    true,
	},
	Prefix{
		Background: "9ACD32",
		Color:      "FFFFFF",
		Words:      []string{"Project"},
		Default:    true,
	},
	Prefix{
		Background: "FFD700",
		Color:      "FFFFFF",
		Words:      []string{"Report", "Essay", "Paper"},
		Default:    true,
	},
	Prefix{
		Background: "FFA500",
		Color:      "FFFFFF",
		Words:      []string{"Quiz", "PopQuiz"},
		Default:    true,
	},
	Prefix{
		Background: "DC143C",
		Color:      "FFFFFF",
		Words:      []string{"Test", "Final", "Exam", "Midterm"},
		Default:    true,
	},
	Prefix{
		Background: "2AC0F1",
		Color:      "FFFFFF",
		Words:      []string{"ICA"},
		Default:    true,
	},
	Prefix{
		Background: "2AF15E",
		Color:      "FFFFFF",
		Words:      []string{"Lab", "Study", "Memorize"},
		TimedEvent: true,
		Default:    true,
	},
	Prefix{
		Background: "003DAD",
		Color:      "FFFFFF",
		Words:      []string{"DocID"},
		Default:    true,
	},
	Prefix{
		Background: "000000",
		Color:      "00FF00",
		Words:      []string{"Trojun", "Hex"},
		Default:    true,
	},
	Prefix{
		Background: "FCF8E3",
		Color:      "000000",
		Words:      []string{"Trojun", "Hex"},
		Default:    true,
	},
	Prefix{
		Background: "5000BC",
		Color:      "FFFFFF",
		Words:      []string{"OptionalHW", "Challenge"},
		Default:    true,
	},
	Prefix{
		Background: "000099",
		Color:      "FFFFFF",
		Words:      []string{"Presentation", "Prez"},
		Default:    true,
	},
	Prefix{
		Background: "123456",
		Color:      "FFFFFF",
		Words:      []string{"BuildSession", "Build"},
		TimedEvent: true,
		Default:    true,
	},
	Prefix{
		Background: "5A1B87",
		Color:      "FFFFFF",
		Words:      []string{"Meeting", "Meet"},
		TimedEvent: true,
		Default:    true,
	},
}

type PrefixesResponse struct {
	Status   string   `json:"status"`
	Prefixes []Prefix `json:"prefixes"`
}

func InitPrefixesAPI(e *echo.Echo) {
	e.GET("/prefixes/getList", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		return c.JSON(http.StatusOK, PrefixesResponse{"ok", DefaultPrefixes})
	})
}
<commit_msg>add fallback color information too<commit_after>package api

import (
	"net/http"

	"github.com/labstack/echo"
)

type Prefix struct {
	Background string   `json:"background"`
	Color      string   `json:"color"`
	Words      []string `json:"words"`
	TimedEvent bool     `json:"timedEvent"`
	Default    bool     `json:"default"`
}

var DefaultPrefixes = []Prefix{
	Prefix{
		Background: "4C6C9B",
		Color:      "FFFFFF",
		Words:      []string{"HW", "Read", "Reading"},
		Default:    true,
	},
	Prefix{
		Background: "9ACD32",
		Color:      "FFFFFF",
		Words:      []string{"Project"},
		Default:    true,
	},
	Prefix{
		Background: "FFD700",
		Color:      "FFFFFF",
		Words:      []string{"Report", "Essay", "Paper"},
		Default:    true,
	},
	Prefix{
		Background: "FFA500",
		Color:      "FFFFFF",
		Words:      []string{"Quiz", "PopQuiz"},
		Default:    true,
	},
	Prefix{
		Background: "DC143C",
		Color:      "FFFFFF",
		Words:      []string{"Test", "Final", "Exam", "Midterm"},
		Default:    true,
	},
	Prefix{
		Background: "2AC0F1",
		Color:      "FFFFFF",
		Words:      []string{"ICA"},
		Default:    true,
	},
	Prefix{
		Background: "2AF15E",
		Color:      "FFFFFF",
		Words:      []string{"Lab", "Study", "Memorize"},
		TimedEvent: true,
		Default:    true,
	},
	Prefix{
		Background: "003DAD",
		Color:      "FFFFFF",
		Words:      []string{"DocID"},
		Default:    true,
	},
	Prefix{
		Background: "000000",
		Color:      "00FF00",
		Words:      []string{"Trojun", "Hex"},
		Default:    true,
	},
	Prefix{
		Background: "FCF8E3",
		Color:      "000000",
		Words:      []string{"Trojun", "Hex"},
		Default:    true,
	},
	Prefix{
		Background: "5000BC",
		Color:      "FFFFFF",
		Words:      []string{"OptionalHW", "Challenge"},
		Default:    true,
	},
	Prefix{
		Background: "000099",
		Color:      "FFFFFF",
		Words:      []string{"Presentation", "Prez"},
		Default:    true,
	},
	Prefix{
		Background: "123456",
		Color:      "FFFFFF",
		Words:      []string{"BuildSession", "Build"},
		TimedEvent: true,
		Default:    true,
	},
	Prefix{
		Background: "5A1B87",
		Color:      "FFFFFF",
		Words:      []string{"Meeting", "Meet"},
		TimedEvent: true,
		Default:    true,
	},
}

type PrefixesResponse struct {
	Status             string   `json:"status"`
	Prefixes           []Prefix `json:"prefixes"`
	FallbackBackground string   `json:"fallbackBackground"`
	FallbackColor      string   `json:"fallbackColor"`
}

func InitPrefixesAPI(e *echo.Echo) {
	e.GET("/prefixes/getList", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		return c.JSON(http.StatusOK, PrefixesResponse{"ok", DefaultPrefixes, "FFD3BD", "000000"})
	})
}
<|endoftext|>


<commit_before>package editor

import (
	"fmt"
	"io"
)

func Help(w io.Writer) error {
	for _, x := range quickref {
		_, err := w.Write([]byte(fmt.Sprintf("%-12s%s\n", x.k, x.v)))
		if err != nil {
			return err
		}
	}
	return nil
}

// Reference: Vim's quickref.txt.
var quickref = []struct{ k, v string }{
	{"h", "left"},
	{"l", "right"},
	{"0", "to first character in the line"},
	{"^", "to first non-blank character in the line"},
	{"$", "to the last character in the line"},
	{"|", "to column N"},
	{"f", "to the Nth occurrence of {char} to the right"},
	{"F", "to the Nth occurrence of {char} to the left"},
	{"t": "till before the Nth occurrence of {char} to the right"},
	{"T": "till bl before the Nth occurrence of {char} to the left"},

	{"k", "go back history"},
	{"j", "go forward history"},

	{"-", "decrement the number at or after the cursor"},
	{"+", "increment the number at or after the cursor"},

	{"w", "N words forward"},
	{"W", "N blank-separated WORDs forward"},
	{"e", "forward to the end of the Nth word"},
	{"E", "forward to the end of the Nth blank-separated WORD"},
	{"b", "N words backward"},
	{"B", "N blank-separated WORDs backward"},
	{"ge", "backward to the end of the Nth word"},
	{"gE", "backward to the end of the Nth blank-separated WORD"},

	{"[(": "N times back to unclosed '('"},
	{"[{": "N times back to unclosed '{'"},
	{"])": "N times forward to unclosed ')'"},
	{"]}": "N times forward to unclosed '}'"},

	{"/", "search forward"},
	{"?", "search backward"},

	{"n", "repeat last search"},
	{"N", "repeat last search, in opposite direction"},

	{"a", "append text after the cursor"},
	{"A", "append text at the end of the line"},
	{"i", "insert text before the cursor"},
	{"I", "insert text before the first non-blank in the line"},
	{"gI", "insert text in column 1"},

	// insert mode...

	{"i_<Esc>", "end Insert mode, back to Normal mode"},
	{"i_CTRL-C", "like <Esc>"},

	{"i_CTRL-R", "insert the contents of a register"},
	{"i_CTRL-X", "complete the word before the cursor in various ways"},
	{"i_<BS>", "delete the character before the cursor"},
	{"i_CTRL-W", "delete word before the cursor"},
	{"i_CTRL-U", "delete all entered characters in the current line"},

	{"x", "delete N characters under and after the cursor"},
	{"<Del>", "delete N characters under and after the cursor"},
	{"X", "delete N characters before the cursor"},
	{"d", "delete the text that is moved over with {motion}"},
	{"v_d", "delete the highlighted text"},
	{"dd", "delete N lines"},
	{"D", "delete to the end of the line"},

	{"\"": "use register {char} for the next delete, yank, or put"},
	{"y": "yank the text moved over with {motion} into a register"},
	{"v_y": "yank the highlighted text into a register"},
	{"yy": "yank N lines into a register"},
	{"Y": "yank to the end of line into a register"},
	{"p": "put a register after the cursor position (N times)"},
	{"P": "put a register before the cursor position (N times)"},

	{"r": "replace N characters with {char}"},
	{"R": "enter Replace mode"},
	{"c": "change the text that is moved over with {motion}"},
	{"v_c": "change the highlighted text"},
	{"cc": "change N lines"},
	{"C": "change to the end of the line"},
	{"~": "switch case for N characters and advance cursor"},
	{"v_~": "switch case for highlighted text"},
	{"v_u": "make highlighted text lowercase"},
	{"v_U": "make highlighted text uppercase"},
	{"g~": "switch case for the text that is moved over with {motion}"},
	{"gu": "make the text that is moved over with {motion} lowercase"},
	{"gU": "make the text that is moved over with {motion} uppercase"},

	{"v": "start highlighting characters  }  move cursor and use"},
	{"V": "start highlighting linewise    }  operator to affect"},
	{"o": "exchange cursor position with start of highlighting"},
	{"v_v": "highlight characters or stop highlighting"},

	{"aw": `Select "a word"`},
	{"iw": `Select "inner word"`},
	{"aW": `Select "a |WORD|"`},
	{"iW": `Select "inner |WORD|"`},
	{"as": `Select "a sentence"`},
	{"is": `Select "inner sentence"`},
	{"ap": `Select "a paragraph"`},
	{"ip": `Select "inner paragraph"`},
	{"ab": `Select "a block" (from "[(" to "])")`},
	{"ib": `Select "inner block" (from "[(" to "])")`},
	{"aB": `Select "a Block" (from "[{" to "]}")`},
	{"iB": `Select "inner Block" (from "[{" to "]}")`},
	{"a>": `Select "a <> block"`},
	{"i>": `Select "inner <> block"`},
	{"at": `Select "a tag block" (from <aaa> to </aaa>)`},
	{"it": `Select "inner tag block" (from <aaa> to </aaa>)`},
	{"a'": `Select "a single quoted string"`},
	{"i'": `Select "inner single quoted string"`},
	{"a\"": `Select "a double quoted string"`},
	{"i\"": `Select "inner double quoted string"`},
	{"a`": `Select "a backward quoted string"`},
	{"i`": `Select "inner backward quoted string"`},
}
<commit_msg>Fix syntax error<commit_after>package editor

import (
	"fmt"
	"io"
)

func Help(w io.Writer) error {
	for _, x := range quickref {
		_, err := w.Write([]byte(fmt.Sprintf("%-12s%s\n", x.k, x.v)))
		if err != nil {
			return err
		}
	}
	return nil
}

// Reference: Vim's quickref.txt.
var quickref = []struct{ k, v string }{
	{"h", "left"},
	{"l", "right"},
	{"0", "to first character in the line"},
	{"^", "to first non-blank character in the line"},
	{"$", "to the last character in the line"},
	{"|", "to column N"},
	{"f", "to the Nth occurrence of {char} to the right"},
	{"F", "to the Nth occurrence of {char} to the left"},
	{"t", "till before the Nth occurrence of {char} to the right"},
	{"T", "till bl before the Nth occurrence of {char} to the left"},

	{"k", "go back history"},
	{"j", "go forward history"},

	{"-", "decrement the number at or after the cursor"},
	{"+", "increment the number at or after the cursor"},

	{"w", "N words forward"},
	{"W", "N blank-separated WORDs forward"},
	{"e", "forward to the end of the Nth word"},
	{"E", "forward to the end of the Nth blank-separated WORD"},
	{"b", "N words backward"},
	{"B", "N blank-separated WORDs backward"},
	{"ge", "backward to the end of the Nth word"},
	{"gE", "backward to the end of the Nth blank-separated WORD"},

	{"[(", "N times back to unclosed '('"},
	{"[{", "N times back to unclosed '{'"},
	{"])", "N times forward to unclosed ')'"},
	{"]}", "N times forward to unclosed '}'"},

	{"/", "search forward"},
	{"?", "search backward"},

	{"n", "repeat last search"},
	{"N", "repeat last search, in opposite direction"},

	{"a", "append text after the cursor"},
	{"A", "append text at the end of the line"},
	{"i", "insert text before the cursor"},
	{"I", "insert text before the first non-blank in the line"},
	{"gI", "insert text in column 1"},

	// insert mode...

	{"i_<Esc>", "end Insert mode, back to Normal mode"},
	{"i_CTRL-C", "like <Esc>"},

	{"i_CTRL-R", "insert the contents of a register"},
	{"i_CTRL-X", "complete the word before the cursor in various ways"},
	{"i_<BS>", "delete the character before the cursor"},
	{"i_CTRL-W", "delete word before the cursor"},
	{"i_CTRL-U", "delete all entered characters in the current line"},

	{"x", "delete N characters under and after the cursor"},
	{"<Del>", "delete N characters under and after the cursor"},
	{"X", "delete N characters before the cursor"},
	{"d", "delete the text that is moved over with {motion}"},
	{"v_d", "delete the highlighted text"},
	{"dd", "delete N lines"},
	{"D", "delete to the end of the line"},

	{"\"", "use register {char} for the next delete, yank, or put"},
	{"y", "yank the text moved over with {motion} into a register"},
	{"v_y", "yank the highlighted text into a register"},
	{"yy", "yank N lines into a register"},
	{"Y", "yank to the end of line into a register"},
	{"p", "put a register after the cursor position (N times)"},
	{"P", "put a register before the cursor position (N times)"},

	{"r", "replace N characters with {char}"},
	{"R", "enter Replace mode"},
	{"c", "change the text that is moved over with {motion}"},
	{"v_c", "change the highlighted text"},
	{"cc", "change N lines"},
	{"C", "change to the end of the line"},
	{"~", "switch case for N characters and advance cursor"},
	{"v_~", "switch case for highlighted text"},
	{"v_u", "make highlighted text lowercase"},
	{"v_U", "make highlighted text uppercase"},
	{"g~", "switch case for the text that is moved over with {motion}"},
	{"gu", "make the text that is moved over with {motion} lowercase"},
	{"gU", "make the text that is moved over with {motion} uppercase"},

	{"v", "start highlighting characters  }  move cursor and use"},
	{"V", "start highlighting linewise    }  operator to affect"},
	{"o", "exchange cursor position with start of highlighting"},
	{"v_v", "highlight characters or stop highlighting"},

	{"aw", `Select "a word"`},
	{"iw", `Select "inner word"`},
	{"aW", `Select "a |WORD|"`},
	{"iW", `Select "inner |WORD|"`},
	{"as", `Select "a sentence"`},
	{"is", `Select "inner sentence"`},
	{"ap", `Select "a paragraph"`},
	{"ip", `Select "inner paragraph"`},
	{"ab", `Select "a block" (from "[(" to "])")`},
	{"ib", `Select "inner block" (from "[(" to "])")`},
	{"aB", `Select "a Block" (from "[{" to "]}")`},
	{"iB", `Select "inner Block" (from "[{" to "]}")`},
	{"a>", `Select "a <> block"`},
	{"i>", `Select "inner <> block"`},
	{"at", `Select "a tag block" (from <aaa> to </aaa>)`},
	{"it", `Select "inner tag block" (from <aaa> to </aaa>)`},
	{"a'", `Select "a single quoted string"`},
	{"i'", `Select "inner single quoted string"`},
	{"a\"", `Select "a double quoted string"`},
	{"i\"", `Select "inner double quoted string"`},
	{"a`", `Select "a backward quoted string"`},
	{"i`", `Select "inner backward quoted string"`},
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/spf13/pflag"
)

var opts = struct {
	Version string

	IgnoreBranchName         bool
	IgnoreUncommittedChanges bool
	IgnoreChangelogVersion   bool
	IgnoreChangelogRelease   bool
	IgnoreChangelogCurrent   bool

	tarFilename string
	buildDir    string
}{}

var versionRegex = regexp.MustCompile(`^\d+\.\d+\.\d+$`)

func init() {
	pflag.BoolVar(&opts.IgnoreBranchName, "ignore-branch-name", false, "allow releasing from other branches as 'master'")
	pflag.BoolVar(&opts.IgnoreUncommittedChanges, "ignore-uncommitted-changes", false, "allow uncommitted changes")
	pflag.BoolVar(&opts.IgnoreChangelogVersion, "ignore-changelog-version", false, "ignore missing entry in CHANGELOG.md")
	pflag.BoolVar(&opts.IgnoreChangelogRelease, "ignore-changelog-releases", false, "ignore missing entry changelog/releases")
	pflag.BoolVar(&opts.IgnoreChangelogCurrent, "ignore-changelog-current", false, "ignore check if CHANGELOG.md is up to date")
	pflag.Parse()
}

func die(f string, args ...interface{}) {
	if !strings.HasSuffix(f, "\n") {
		f += "\n"
	}
	f = "\x1b[31m" + f + "\x1b[0m"
	fmt.Fprintf(os.Stderr, f, args...)
	os.Exit(1)
}

func msg(f string, args ...interface{}) {
	if !strings.HasSuffix(f, "\n") {
		f += "\n"
	}
	f = "\x1b[32m" + f + "\x1b[0m"
	fmt.Printf(f, args...)
}

func run(cmd string, args ...string) {
	c := exec.Command(cmd, args...)
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	err := c.Run()
	if err != nil {
		die("error running %s %s: %v", cmd, args, err)
	}
}

func rm(file string) {
	err := os.Remove(file)
	if err != nil {
		die("error removing %v: %v", file, err)
	}
}

func rmdir(dir string) {
	err := os.RemoveAll(dir)
	if err != nil {
		die("error removing %v: %v", dir, err)
	}
}

func mkdir(dir string) {
	err := os.Mkdir(dir, 0755)
	if err != nil {
		die("mkdir %v: %v", dir, err)
	}
}

func getwd() string {
	pwd, err := os.Getwd()
	if err != nil {
		die("Getwd(): %v", err)
	}
	return pwd
}

func uncommittedChanges(dirs ...string) string {
	args := []string{"status", "--porcelain", "--untracked-files=no"}
	if len(dirs) > 0 {
		args = append(args, dirs...)
	}

	changes, err := exec.Command("git", args...).Output()
	if err != nil {
		die("unable to run command: %v", err)
	}

	return string(changes)
}

func preCheckBranchMaster() {
	if opts.IgnoreBranchName {
		return
	}

	branch, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output()
	if err != nil {
		die("error running 'git': %v", err)
	}

	if strings.TrimSpace(string(branch)) != "master" {
		die("wrong branch: %s", branch)
	}
}

func preCheckUncommittedChanges() {
	if opts.IgnoreUncommittedChanges {
		return
	}

	changes := uncommittedChanges()
	if len(changes) > 0 {
		die("uncommitted changes found:\n%s\n", changes)
	}
}

func preCheckVersionExists() {
	buf, err := exec.Command("git", "tag", "-l").Output()
	if err != nil {
		die("error running 'git tag -l': %v", err)
	}

	sc := bufio.NewScanner(bytes.NewReader(buf))
	for sc.Scan() {
		if sc.Err() != nil {
			die("error scanning version tags: %v", sc.Err())
		}

		if strings.TrimSpace(sc.Text()) == "v"+opts.Version {
			die("tag v%v already exists", opts.Version)
		}
	}
}

func preCheckChangelogCurrent() {
	if opts.IgnoreChangelogCurrent {
		return
	}

	// regenerate changelog
	run("calens", "--output", "CHANGELOG.md")

	// check for uncommitted changes in changelog
	if len(uncommittedChanges("CHANGELOG.md")) > 0 {
		msg("committing file CHANGELOG.md")
		run("git", "commit", "-m", fmt.Sprintf("Generate CHANGELOG.md for %v", opts.Version), "CHANGELOG.md")
	}
}

func preCheckChangelogRelease() {
	if opts.IgnoreChangelogRelease {
		return
	}

	f, err := os.Open(filepath.FromSlash("changelog/releases"))
	if err != nil {
		die("unable to open releases file in changelog/: %v", err)
	}

	sc := bufio.NewScanner(f)
	for sc.Scan() {
		if sc.Err() != nil {
			die("error reading releases file in changelog: %v", err)
		}

		if sc.Text() == fmt.Sprintf("%v %v", opts.Version, time.Now().Format("2006-01-02")) {
			return
		}
	}

	err = f.Close()
	if err != nil {
		die("close releases error: %v", err)
	}

	die("unable to find correct line for version %v (released today) in changelog/releases", opts.Version)
}

func preCheckChangelogVersion() {
	if opts.IgnoreChangelogVersion {
		return
	}

	f, err := os.Open("CHANGELOG.md")
	if err != nil {
		die("unable to open CHANGELOG.md: %v", err)
	}
	defer f.Close()

	sc := bufio.NewScanner(f)
	for sc.Scan() {
		if sc.Err() != nil {
			die("error scanning: %v", sc.Err())
		}

		if strings.Contains(strings.TrimSpace(sc.Text()), fmt.Sprintf("Changelog for restic %v", opts.Version)) {
			return
		}
	}

	die("CHANGELOG.md does not contain version %v", opts.Version)
}

func generateFiles() {
	msg("generate files")
	run("go", "run", "build.go", "-o", "restic-generate.temp")

	mandir := filepath.Join("doc", "man")
	rmdir(mandir)
	mkdir(mandir)
	run("./restic-generate.temp", "generate",
		"--man", "doc/man",
		"--zsh-completion", "doc/zsh-completion.zsh",
		"--bash-completion", "doc/bash-completion.sh")
	rm("restic-generate.temp")

	run("git", "add", "doc")
	changes := uncommittedChanges("doc")
	if len(changes) > 0 {
		msg("committing manpages and auto-completion")
		run("git", "commit", "-m", "Update manpages and auto-completion", "doc")
	}
}

func updateVersion() {
	err := ioutil.WriteFile("VERSION", []byte(opts.Version+"\n"), 0644)
	if err != nil {
		die("unable to write version to file: %v", err)
	}

	if len(uncommittedChanges("VERSION")) > 0 {
		msg("committing file VERSION")
		run("git", "commit", "-m", fmt.Sprintf("Add VERSION for %v", opts.Version), "VERSION")
	}
}

func addTag() {
	tagname := "v" + opts.Version
	msg("add tag %v", tagname)
	run("git", "tag", "-a", "-s", "-m", tagname, tagname)
}

func exportTar() {
	cmd := fmt.Sprintf("git archive --format=tar --prefix=restic-%s/ v%s | gzip -n > %s",
		opts.Version, opts.Version, opts.tarFilename)
	run("sh", "-c", cmd)
	msg("build restic-%s.tar.gz", opts.Version)
}

func runBuild() {
	msg("building binaries...")
	run("docker", "pull", "restic/builder")
	run("docker", "run", "--volume", getwd()+":/home/build", "restic/builder", opts.tarFilename)
}

func findBuildDir() string {
	nameRegex := regexp.MustCompile(`restic-` + opts.Version + `-\d{8}-\d{6}`)

	f, err := os.Open(".")
	if err != nil {
		die("Open(.): %v", err)
	}

	entries, err := f.Readdirnames(-1)
	if err != nil {
		die("Readdirnames(): %v", err)
	}

	err = f.Close()
	if err != nil {
		die("Close(): %v", err)
	}

	sort.Slice(entries, func(i, j int) bool {
		return entries[j] < entries[i]
	})

	for _, entry := range entries {
		if nameRegex.MatchString(entry) {
			msg("found restic build dir: %v", entry)
			return entry
		}
	}

	die("restic build dir not found")
	return ""
}

func signFiles() {
	run("gpg", "--armor", "--detach-sign", filepath.Join(opts.buildDir, "SHA256SUMS"))
	run("gpg", "--armor", "--detach-sign", filepath.Join(opts.buildDir, opts.tarFilename))
}

func updateDocker() {
	cmd := fmt.Sprintf("bzcat %s/restic_%s_linux_amd64.bz2 > restic", opts.buildDir, opts.Version)
	run("sh", "-c", cmd)
	run("chmod", "+x", "restic")
	run("docker", "build", "--rm", "--tag", "restic/restic:latest", "-f", "docker/Dockerfile", ".")
}

func main() {
	if len(pflag.Args()) == 0 {
		die("USAGE: release-version [OPTIONS] VERSION")
	}

	opts.Version = pflag.Args()[0]
	if !versionRegex.MatchString(opts.Version) {
		die("invalid new version")
	}

	opts.tarFilename = fmt.Sprintf("restic-%s.tar.gz", opts.Version)

	preCheckBranchMaster()
	preCheckUncommittedChanges()
	preCheckVersionExists()
	preCheckChangelogCurrent()
	preCheckChangelogRelease()
	preCheckChangelogVersion()

	generateFiles()
	updateVersion()
	addTag()

	exportTar()
	runBuild()
	opts.buildDir = findBuildDir()
	signFiles()

	updateDocker()

	msg("done, build dir is %v", opts.buildDir)

	msg("now run:\n\ngit push --tags origin master\ndocker push restic/restic\n")
}
<commit_msg>script/release: Tag versioned docker image<commit_after>// +build ignore

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/spf13/pflag"
)

var opts = struct {
	Version string

	IgnoreBranchName         bool
	IgnoreUncommittedChanges bool
	IgnoreChangelogVersion   bool
	IgnoreChangelogRelease   bool
	IgnoreChangelogCurrent   bool

	tarFilename string
	buildDir    string
}{}

var versionRegex = regexp.MustCompile(`^\d+\.\d+\.\d+$`)

func init() {
	pflag.BoolVar(&opts.IgnoreBranchName, "ignore-branch-name", false, "allow releasing from other branches as 'master'")
	pflag.BoolVar(&opts.IgnoreUncommittedChanges, "ignore-uncommitted-changes", false, "allow uncommitted changes")
	pflag.BoolVar(&opts.IgnoreChangelogVersion, "ignore-changelog-version", false, "ignore missing entry in CHANGELOG.md")
	pflag.BoolVar(&opts.IgnoreChangelogRelease, "ignore-changelog-releases", false, "ignore missing entry changelog/releases")
	pflag.BoolVar(&opts.IgnoreChangelogCurrent, "ignore-changelog-current", false, "ignore check if CHANGELOG.md is up to date")
	pflag.Parse()
}

func die(f string, args ...interface{}) {
	if !strings.HasSuffix(f, "\n") {
		f += "\n"
	}
	f = "\x1b[31m" + f + "\x1b[0m"
	fmt.Fprintf(os.Stderr, f, args...)
	os.Exit(1)
}

func msg(f string, args ...interface{}) {
	if !strings.HasSuffix(f, "\n") {
		f += "\n"
	}
	f = "\x1b[32m" + f + "\x1b[0m"
	fmt.Printf(f, args...)
}

func run(cmd string, args ...string) {
	c := exec.Command(cmd, args...)
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	err := c.Run()
	if err != nil {
		die("error running %s %s: %v", cmd, args, err)
	}
}

func rm(file string) {
	err := os.Remove(file)
	if err != nil {
		die("error removing %v: %v", file, err)
	}
}

func rmdir(dir string) {
	err := os.RemoveAll(dir)
	if err != nil {
		die("error removing %v: %v", dir, err)
	}
}

func mkdir(dir string) {
	err := os.Mkdir(dir, 0755)
	if err != nil {
		die("mkdir %v: %v", dir, err)
	}
}

func getwd() string {
	pwd, err := os.Getwd()
	if err != nil {
		die("Getwd(): %v", err)
	}
	return pwd
}

func uncommittedChanges(dirs ...string) string {
	args := []string{"status", "--porcelain", "--untracked-files=no"}
	if len(dirs) > 0 {
		args = append(args, dirs...)
	}

	changes, err := exec.Command("git", args...).Output()
	if err != nil {
		die("unable to run command: %v", err)
	}

	return string(changes)
}

func preCheckBranchMaster() {
	if opts.IgnoreBranchName {
		return
	}

	branch, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output()
	if err != nil {
		die("error running 'git': %v", err)
	}

	if strings.TrimSpace(string(branch)) != "master" {
		die("wrong branch: %s", branch)
	}
}

func preCheckUncommittedChanges() {
	if opts.IgnoreUncommittedChanges {
		return
	}

	changes := uncommittedChanges()
	if len(changes) > 0 {
		die("uncommitted changes found:\n%s\n", changes)
	}
}

func preCheckVersionExists() {
	buf, err := exec.Command("git", "tag", "-l").Output()
	if err != nil {
		die("error running 'git tag -l': %v", err)
	}

	sc := bufio.NewScanner(bytes.NewReader(buf))
	for sc.Scan() {
		if sc.Err() != nil {
			die("error scanning version tags: %v", sc.Err())
		}

		if strings.TrimSpace(sc.Text()) == "v"+opts.Version {
			die("tag v%v already exists", opts.Version)
		}
	}
}

func preCheckChangelogCurrent() {
	if opts.IgnoreChangelogCurrent {
		return
	}

	// regenerate changelog
	run("calens", "--output", "CHANGELOG.md")

	// check for uncommitted changes in changelog
	if len(uncommittedChanges("CHANGELOG.md")) > 0 {
		msg("committing file CHANGELOG.md")
		run("git", "commit", "-m", fmt.Sprintf("Generate CHANGELOG.md for %v", opts.Version), "CHANGELOG.md")
	}
}

func preCheckChangelogRelease() {
	if opts.IgnoreChangelogRelease {
		return
	}

	f, err := os.Open(filepath.FromSlash("changelog/releases"))
	if err != nil {
		die("unable to open releases file in changelog/: %v", err)
	}

	sc := bufio.NewScanner(f)
	for sc.Scan() {
		if sc.Err() != nil {
			die("error reading releases file in changelog: %v", err)
		}

		if sc.Text() == fmt.Sprintf("%v %v", opts.Version, time.Now().Format("2006-01-02")) {
			return
		}
	}

	err = f.Close()
	if err != nil {
		die("close releases error: %v", err)
	}

	die("unable to find correct line for version %v (released today) in changelog/releases", opts.Version)
}

func preCheckChangelogVersion() {
	if opts.IgnoreChangelogVersion {
		return
	}

	f, err := os.Open("CHANGELOG.md")
	if err != nil {
		die("unable to open CHANGELOG.md: %v", err)
	}
	defer f.Close()

	sc := bufio.NewScanner(f)
	for sc.Scan() {
		if sc.Err() != nil {
			die("error scanning: %v", sc.Err())
		}

		if strings.Contains(strings.TrimSpace(sc.Text()), fmt.Sprintf("Changelog for restic %v", opts.Version)) {
			return
		}
	}

	die("CHANGELOG.md does not contain version %v", opts.Version)
}

func generateFiles() {
	msg("generate files")
	run("go", "run", "build.go", "-o", "restic-generate.temp")

	mandir := filepath.Join("doc", "man")
	rmdir(mandir)
	mkdir(mandir)
	run("./restic-generate.temp", "generate",
		"--man", "doc/man",
		"--zsh-completion", "doc/zsh-completion.zsh",
		"--bash-completion", "doc/bash-completion.sh")
	rm("restic-generate.temp")

	run("git", "add", "doc")
	changes := uncommittedChanges("doc")
	if len(changes) > 0 {
		msg("committing manpages and auto-completion")
		run("git", "commit", "-m", "Update manpages and auto-completion", "doc")
	}
}

func updateVersion() {
	err := ioutil.WriteFile("VERSION", []byte(opts.Version+"\n"), 0644)
	if err != nil {
		die("unable to write version to file: %v", err)
	}

	if len(uncommittedChanges("VERSION")) > 0 {
		msg("committing file VERSION")
		run("git", "commit", "-m", fmt.Sprintf("Add VERSION for %v", opts.Version), "VERSION")
	}
}

func addTag() {
	tagname := "v" + opts.Version
	msg("add tag %v", tagname)
	run("git", "tag", "-a", "-s", "-m", tagname, tagname)
}

func exportTar() {
	cmd := fmt.Sprintf("git archive --format=tar --prefix=restic-%s/ v%s | gzip -n > %s",
		opts.Version, opts.Version, opts.tarFilename)
	run("sh", "-c", cmd)
	msg("build restic-%s.tar.gz", opts.Version)
}

func runBuild() {
	msg("building binaries...")
	run("docker", "pull", "restic/builder")
	run("docker", "run", "--volume", getwd()+":/home/build", "restic/builder", opts.tarFilename)
}

func findBuildDir() string {
	nameRegex := regexp.MustCompile(`restic-` + opts.Version + `-\d{8}-\d{6}`)

	f, err := os.Open(".")
	if err != nil {
		die("Open(.): %v", err)
	}

	entries, err := f.Readdirnames(-1)
	if err != nil {
		die("Readdirnames(): %v", err)
	}

	err = f.Close()
	if err != nil {
		die("Close(): %v", err)
	}

	sort.Slice(entries, func(i, j int) bool {
		return entries[j] < entries[i]
	})

	for _, entry := range entries {
		if nameRegex.MatchString(entry) {
			msg("found restic build dir: %v", entry)
			return entry
		}
	}

	die("restic build dir not found")
	return ""
}

func signFiles() {
	run("gpg", "--armor", "--detach-sign", filepath.Join(opts.buildDir, "SHA256SUMS"))
	run("gpg", "--armor", "--detach-sign", filepath.Join(opts.buildDir, opts.tarFilename))
}

func updateDocker() {
	cmd := fmt.Sprintf("bzcat %s/restic_%s_linux_amd64.bz2 > restic", opts.buildDir, opts.Version)
	run("sh", "-c", cmd)
	run("chmod", "+x", "restic")
	run("docker", "build", "--rm", "--tag", "restic/restic:latest", "-f", "docker/Dockerfile", ".")
	run("docker", "tag", "restic/restic:latest", "restic/restic:"+opts.Version)
}

func main() {
	if len(pflag.Args()) == 0 {
		die("USAGE: release-version [OPTIONS] VERSION")
	}

	opts.Version = pflag.Args()[0]
	if !versionRegex.MatchString(opts.Version) {
		die("invalid new version")
	}

	opts.tarFilename = fmt.Sprintf("restic-%s.tar.gz", opts.Version)

	preCheckBranchMaster()
	preCheckUncommittedChanges()
	preCheckVersionExists()
	preCheckChangelogCurrent()
	preCheckChangelogRelease()
	preCheckChangelogVersion()

	generateFiles()
	updateVersion()
	addTag()

	exportTar()
	runBuild()
	opts.buildDir = findBuildDir()
	signFiles()

	updateDocker()

	msg("done, build dir is %v", opts.buildDir)

	msg("now run:\n\ngit push --tags origin master\ndocker push restic/restic\n")
}
<|endoftext|>


<commit_before>package gridas

import (
	"labix.org/v2/mgo"

	"gridas/config"
	"gridas/mylog"
)

//Recoverer takes the petitions stored in PetitionStore and enqueues them again into SendTo.
type Recoverer struct {
	SendTo chan<- *Petition
	//Configuration object
	Cfg *config.Config
	//Session seed for mongo
	SessionSeed *mgo.Session
}

//Recover gets all the petitions stored and sends them to a channel for processing by a consumer.
//It returns when all of them are re-enqueued or when an error happens. It should be run before starting
//a listener (with the same PetitionStore) or new petitions could be enqueued twice. Listeners with a different PetitionStore
//should not be a problem. A Consumer can be started before with the same PetitionStore to avoid overflowing the queue.
func (r *Recoverer) Recover() error {
	mylog.Debug("begin recoverer")
	db := r.SessionSeed.DB(r.Cfg.Database)
	petColl := db.C(r.Cfg.Instance + r.Cfg.PetitionsColl)
	p := Petition{}
	iter := petColl.Find(nil).Iter()
	for iter.Next(&p) {
		paux := Petition{}
		paux = p
		paux.Session = r.SessionSeed.New()
		paux.Session.SetMode(mgo.Monotonic, true)
		mylog.Debugf("re-enqueue petition %+v", paux)
		r.SendTo <- &paux
	}
	//iter.Err()
	if err := iter.Close(); err != nil {
		mylog.Alertf("error closing cursor %+v", err)
		return err
	}
	mylog.Debug("end recoverer")
	return nil
}
<commit_msg>Avoid unnecessary empty object creation<commit_after>package gridas

import (
	"labix.org/v2/mgo"

	"gridas/config"
	"gridas/mylog"
)

//Recoverer takes the petitions stored in PetitionStore and enqueues them again into SendTo.
type Recoverer struct {
	SendTo chan<- *Petition
	//Configuration object
	Cfg *config.Config
	//Session seed for mongo
	SessionSeed *mgo.Session
}

//Recover gets all the petitions stored and sends them to a channel for processing by a consumer.
//It returns when all of them are re-enqueued or when an error happens. It should be run before starting
//a listener (with the same PetitionStore) or new petitions could be enqueued twice. Listeners with a different PetitionStore
//should not be a problem. A Consumer can be started before with the same PetitionStore to avoid overflowing the queue.
func (r *Recoverer) Recover() error {
	mylog.Debug("begin recoverer")
	db := r.SessionSeed.DB(r.Cfg.Database)
	petColl := db.C(r.Cfg.Instance + r.Cfg.PetitionsColl)
	p := Petition{}
	iter := petColl.Find(nil).Iter()
	for iter.Next(&p) {
		paux := p
		paux.Session = r.SessionSeed.New()
		paux.Session.SetMode(mgo.Monotonic, true)
		mylog.Debugf("re-enqueue petition %+v", paux)
		r.SendTo <- &paux
	}
	//iter.Err()
	if err := iter.Close(); err != nil {
		mylog.Alertf("error closing cursor %+v", err)
		return err
	}
	mylog.Debug("end recoverer")
	return nil
}
<|endoftext|>


<commit_before>// Copyright Istio Authors
//
// 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 controller

import (
	"errors"
	"time"

	klabels "k8s.io/apimachinery/pkg/labels"
	listerv1 "k8s.io/client-go/listers/core/v1"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/cache"

	kubelib "istio.io/istio/pkg/kube"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pkg/config/mesh"
)

const (
	defaultFakeDomainSuffix = "company.com"
)

// FakeXdsUpdater is used to test the registry.
type FakeXdsUpdater struct {
	// Events tracks notifications received by the updater
	Events chan FakeXdsEvent
}

func (fx *FakeXdsUpdater) ConfigUpdate(*model.PushRequest) {
	select {
	case fx.Events <- FakeXdsEvent{Type: "xds"}:
	default:
	}
}

func (fx *FakeXdsUpdater) ProxyUpdate(_, _ string) {
	select {
	case fx.Events <- FakeXdsEvent{Type: "proxy"}:
	default:
	}
}

// FakeXdsEvent is used to watch XdsEvents
type FakeXdsEvent struct {
	// Type of the event
	Type string

	// The id of the event
	ID string

	// The endpoints associated with an EDS push if any
	Endpoints []*model.IstioEndpoint
}

// NewFakeXDS creates a XdsUpdater reporting events via a channel.
func NewFakeXDS() *FakeXdsUpdater {
	return &FakeXdsUpdater{
		Events: make(chan FakeXdsEvent, 100),
	}
}

func (fx *FakeXdsUpdater) EDSUpdate(_, hostname string, _ string, entry []*model.IstioEndpoint) error {
	if len(entry) > 0 {
		select {
		case fx.Events <- FakeXdsEvent{Type: "eds", ID: hostname, Endpoints: entry}:
		default:
		}

	}
	return nil
}

// SvcUpdate is called when a service port mapping definition is updated.
// This interface is WIP - labels, annotations and other changes to service may be
// updated to force a EDS and CDS recomputation and incremental push, as it doesn't affect
// LDS/RDS.
func (fx *FakeXdsUpdater) SvcUpdate(_, hostname string, _ string, _ model.Event) {
	select {
	case fx.Events <- FakeXdsEvent{Type: "service", ID: hostname}:
	default:
	}
}

func (fx *FakeXdsUpdater) Wait(et string) *FakeXdsEvent {
	for {
		select {
		case e := <-fx.Events:
			if e.Type == et {
				return &e
			}
			continue
		case <-time.After(5 * time.Second):
			return nil
		}
	}
}

// Clear any pending event
func (fx *FakeXdsUpdater) Clear() {
	wait := true
	for wait {
		select {
		case <-fx.Events:
		default:
			wait = false
		}
	}
}

type FakeControllerOptions struct {
	Objects           []runtime.Object
	NetworksWatcher   mesh.NetworksWatcher
	ServiceHandler    func(service *model.Service, event model.Event)
	InstanceHandler   func(instance *model.ServiceInstance, event model.Event)
	Mode              EndpointMode
	ClusterID         string
	WatchedNamespaces string
	DomainSuffix      string
	XDSUpdater        model.XDSUpdater
}

type FakeController struct {
	*Controller
}

func (f *FakeController) ResyncEndpoints() error {
	e, ok := f.endpoints.(*endpointsController)
	if !ok {
		return errors.New("cannot run ResyncEndpoints; EndpointsMode must be EndpointsOnly")
	}
	eps, err := listerv1.NewEndpointsLister(e.informer.GetIndexer()).List(klabels.Everything())
	if err != nil {
		return err
	}
	// endpoint processing may beat services
	for _, ep := range eps {
		err = f.endpoints.onEvent(ep, model.EventAdd)
		if err != nil {
			return err
		}
	}
	return nil
}

func NewFakeControllerWithOptions(opts FakeControllerOptions) (*FakeController, *FakeXdsUpdater) {
	xdsUpdater := opts.XDSUpdater
	if xdsUpdater == nil {
		xdsUpdater = NewFakeXDS()
	}

	domainSuffix := defaultFakeDomainSuffix
	if opts.DomainSuffix != "" {
		domainSuffix = opts.DomainSuffix
	}
	clients := kubelib.NewFakeClient(opts.Objects...)
	options := Options{
		WatchedNamespaces: opts.WatchedNamespaces, // default is all namespaces
		ResyncPeriod:      1 * time.Second,
		DomainSuffix:      domainSuffix,
		XDSUpdater:        xdsUpdater,
		Metrics:           &model.Environment{},
		NetworksWatcher:   opts.NetworksWatcher,
		EndpointMode:      opts.Mode,
		ClusterID:         opts.ClusterID,
	}
	c := NewController(clients, options)
	if opts.InstanceHandler != nil {
		_ = c.AppendInstanceHandler(opts.InstanceHandler)
	}
	if opts.ServiceHandler != nil {
		_ = c.AppendServiceHandler(opts.ServiceHandler)
	}
	c.stop = make(chan struct{})
	// Run in initiation to prevent calling each test
	// TODO: fix it, so we can remove `stop` channel
	go c.Run(c.stop)
	clients.RunAndWait(c.stop)
	// Wait for the caches to sync, otherwise we may hit race conditions where events are dropped
	cache.WaitForCacheSync(c.stop, c.pods.informer.HasSynced, c.serviceInformer.HasSynced, c.endpoints.HasSynced)

	var fx *FakeXdsUpdater
	if x, ok := xdsUpdater.(*FakeXdsUpdater); ok {
		fx = x
	}
	return &FakeController{c}, fx
}
<commit_msg>fix kubecontroller flake in XDS tests (#25874)<commit_after>// Copyright Istio Authors
//
// 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 controller

import (
	"errors"
	"fmt"
	"time"

	klabels "k8s.io/apimachinery/pkg/labels"
	listerv1 "k8s.io/client-go/listers/core/v1"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/cache"

	kubelib "istio.io/istio/pkg/kube"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pkg/config/mesh"
	"istio.io/istio/pkg/test/util/retry"
)

const (
	defaultFakeDomainSuffix = "company.com"
)

// FakeXdsUpdater is used to test the registry.
type FakeXdsUpdater struct {
	// Events tracks notifications received by the updater
	Events chan FakeXdsEvent
}

func (fx *FakeXdsUpdater) ConfigUpdate(*model.PushRequest) {
	select {
	case fx.Events <- FakeXdsEvent{Type: "xds"}:
	default:
	}
}

func (fx *FakeXdsUpdater) ProxyUpdate(_, _ string) {
	select {
	case fx.Events <- FakeXdsEvent{Type: "proxy"}:
	default:
	}
}

// FakeXdsEvent is used to watch XdsEvents
type FakeXdsEvent struct {
	// Type of the event
	Type string

	// The id of the event
	ID string

	// The endpoints associated with an EDS push if any
	Endpoints []*model.IstioEndpoint
}

// NewFakeXDS creates a XdsUpdater reporting events via a channel.
func NewFakeXDS() *FakeXdsUpdater {
	return &FakeXdsUpdater{
		Events: make(chan FakeXdsEvent, 100),
	}
}

func (fx *FakeXdsUpdater) EDSUpdate(_, hostname string, _ string, entry []*model.IstioEndpoint) error {
	if len(entry) > 0 {
		select {
		case fx.Events <- FakeXdsEvent{Type: "eds", ID: hostname, Endpoints: entry}:
		default:
		}

	}
	return nil
}

// SvcUpdate is called when a service port mapping definition is updated.
// This interface is WIP - labels, annotations and other changes to service may be
// updated to force a EDS and CDS recomputation and incremental push, as it doesn't affect
// LDS/RDS.
func (fx *FakeXdsUpdater) SvcUpdate(_, hostname string, _ string, _ model.Event) {
	select {
	case fx.Events <- FakeXdsEvent{Type: "service", ID: hostname}:
	default:
	}
}

func (fx *FakeXdsUpdater) Wait(et string) *FakeXdsEvent {
	for {
		select {
		case e := <-fx.Events:
			if e.Type == et {
				return &e
			}
			continue
		case <-time.After(5 * time.Second):
			return nil
		}
	}
}

// Clear any pending event
func (fx *FakeXdsUpdater) Clear() {
	wait := true
	for wait {
		select {
		case <-fx.Events:
		default:
			wait = false
		}
	}
}

type FakeControllerOptions struct {
	Objects           []runtime.Object
	NetworksWatcher   mesh.NetworksWatcher
	ServiceHandler    func(service *model.Service, event model.Event)
	InstanceHandler   func(instance *model.ServiceInstance, event model.Event)
	Mode              EndpointMode
	ClusterID         string
	WatchedNamespaces string
	DomainSuffix      string
	XDSUpdater        model.XDSUpdater
}

type FakeController struct {
	*Controller
}

func (f *FakeController) ResyncEndpoints() error {
	// TODO this workaround fixes a flake that indicates a real issue.
	// TODO(cont) See https://github.com/istio/istio/issues/24117 and https://github.com/istio/istio/pull/24339

	e, ok := f.endpoints.(*endpointsController)
	if !ok {
		return errors.New("cannot run ResyncEndpoints; EndpointsMode must be EndpointsOnly")
	}
	eps, err := listerv1.NewEndpointsLister(e.informer.GetIndexer()).List(klabels.Everything())
	if err != nil {
		return err
	}
	// endpoint processing may beat services
	for _, ep := range eps {
		// endpoint updates are skipped when the service is not there yet
		if host, svc, ns := e.getServiceInfo(ep); host != "" {
			_ = retry.UntilSuccess(func() error {
				f.RLock()
				defer f.RUnlock()
				if f.servicesMap[host] == nil {
					return fmt.Errorf("waiting for service %s in %s to be populated", svc, ns)
				}
				return nil
			}, retry.Delay(time.Second), retry.Timeout(10*time.Second))
		}

		err = f.endpoints.onEvent(ep, model.EventAdd)
		if err != nil {
			return err
		}
	}
	return nil
}

func NewFakeControllerWithOptions(opts FakeControllerOptions) (*FakeController, *FakeXdsUpdater) {
	xdsUpdater := opts.XDSUpdater
	if xdsUpdater == nil {
		xdsUpdater = NewFakeXDS()
	}

	domainSuffix := defaultFakeDomainSuffix
	if opts.DomainSuffix != "" {
		domainSuffix = opts.DomainSuffix
	}
	clients := kubelib.NewFakeClient(opts.Objects...)
	options := Options{
		WatchedNamespaces: opts.WatchedNamespaces, // default is all namespaces
		ResyncPeriod:      1 * time.Second,
		DomainSuffix:      domainSuffix,
		XDSUpdater:        xdsUpdater,
		Metrics:           &model.Environment{},
		NetworksWatcher:   opts.NetworksWatcher,
		EndpointMode:      opts.Mode,
		ClusterID:         opts.ClusterID,
	}
	c := NewController(clients, options)
	if opts.InstanceHandler != nil {
		_ = c.AppendInstanceHandler(opts.InstanceHandler)
	}
	if opts.ServiceHandler != nil {
		_ = c.AppendServiceHandler(opts.ServiceHandler)
	}
	c.stop = make(chan struct{})
	// Run in initiation to prevent calling each test
	// TODO: fix it, so we can remove `stop` channel
	go c.Run(c.stop)
	clients.RunAndWait(c.stop)
	// Wait for the caches to sync, otherwise we may hit race conditions where events are dropped
	cache.WaitForCacheSync(c.stop, c.pods.informer.HasSynced, c.serviceInformer.HasSynced, c.endpoints.HasSynced)

	var fx *FakeXdsUpdater
	if x, ok := xdsUpdater.(*FakeXdsUpdater); ok {
		fx = x
	}
	return &FakeController{c}, fx
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Jetstack cert-manager contributors.

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 vault

import (
	"context"

	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/record"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/certificaterequests"
	crutil "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/util"
	vaultinternal "github.com/jetstack/cert-manager/pkg/internal/vault"
	"github.com/jetstack/cert-manager/pkg/issuer"
	logf "github.com/jetstack/cert-manager/pkg/logs"
	"github.com/jetstack/cert-manager/pkg/util/api"
)

const (
	CRControllerName = "certificaterequests-issuer-vault"
)

type Vault struct {
	// used to record Events about resources to the API
	recorder      record.EventRecorder
	secretsLister corelisters.SecretLister
	helper        issuer.Helper

	vaultClientBuilder vaultinternal.VaultClientBuilder
}

func init() {
	// create certificate request controller for vault issuer
	controllerpkg.Register(CRControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		vault := NewVault(ctx)

		controller := certificaterequests.New(apiutil.IssuerVault, vault)

		c, err := controllerpkg.New(ctx, CRControllerName, controller)
		if err != nil {
			return nil, err
		}

		return c.Run, nil
	})
}

func NewVault(ctx *controllerpkg.Context) *Vault {
	return &Vault{
		recorder:      ctx.Recorder,
		secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
		helper: issuer.NewHelper(
			ctx.SharedInformerFactory.Certmanager().V1alpha1().Issuers().Lister(),
			ctx.SharedInformerFactory.Certmanager().V1alpha1().ClusterIssuers().Lister(),
		),
		vaultClientBuilder: vaultinternal.New,
	}
}

func (v *Vault) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest, issuerObj v1alpha1.GenericIssuer) (*issuer.IssueResponse, error) {
	log := logf.FromContext(ctx, "sign")
	reporter := crutil.NewReporter(cr, v.recorder)

	client, err := v.vaultClientBuilder(cr.Namespace, v.secretsLister, issuerObj)
	if err != nil {
		log = logf.WithRelatedResource(log, issuerObj)

		if k8sErrors.IsNotFound(err) {
			message := "Required secret resource not found"

			reporter.Pending(err, "MissingSecret", message)
			log.Error(err, message)

			return nil, nil
		}

		message := "Failed to initialise vault client for signing"
		reporter.Pending(err, "ErrorVaultInit", message)
		log.Error(err, message)

		return nil, err
	}

	certDuration := api.DefaultCertDuration(cr.Spec.Duration)
	certPem, caPem, err := client.Sign(cr.Spec.CSRPEM, certDuration)
	if err != nil {
		message := "Vault failed to sign certificate"

		reporter.Failed(err, "ErrorSigning", message)
		log.Error(err, message)

		return nil, nil
	}

	log.Info("certificate issued")

	return &issuer.IssueResponse{
		Certificate: certPem,
		CA:          caPem,
	}, nil
}
<commit_msg>Improve code flow and remove unused issuer helper in vault cr controller<commit_after>/*
Copyright 2019 The Jetstack cert-manager contributors.

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 vault

import (
	"context"

	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/record"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/certificaterequests"
	crutil "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/util"
	vaultinternal "github.com/jetstack/cert-manager/pkg/internal/vault"
	"github.com/jetstack/cert-manager/pkg/issuer"
	logf "github.com/jetstack/cert-manager/pkg/logs"
	"github.com/jetstack/cert-manager/pkg/util/api"
)

const (
	CRControllerName = "certificaterequests-issuer-vault"
)

type Vault struct {
	// used to record Events about resources to the API
	recorder      record.EventRecorder
	secretsLister corelisters.SecretLister

	vaultClientBuilder vaultinternal.VaultClientBuilder
}

func init() {
	// create certificate request controller for vault issuer
	controllerpkg.Register(CRControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		vault := NewVault(ctx)

		controller := certificaterequests.New(apiutil.IssuerVault, vault)

		c, err := controllerpkg.New(ctx, CRControllerName, controller)
		if err != nil {
			return nil, err
		}

		return c.Run, nil
	})
}

func NewVault(ctx *controllerpkg.Context) *Vault {
	return &Vault{
		recorder:           ctx.Recorder,
		secretsLister:      ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
		vaultClientBuilder: vaultinternal.New,
	}
}

func (v *Vault) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest, issuerObj v1alpha1.GenericIssuer) (*issuer.IssueResponse, error) {
	log := logf.WithRelatedResource(
		logf.FromContext(ctx, "sign"), issuerObj)
	reporter := crutil.NewReporter(cr, v.recorder)

	client, err := v.vaultClientBuilder(cr.Namespace, v.secretsLister, issuerObj)
	if k8sErrors.IsNotFound(err) {
		message := "Required secret resource not found"

		reporter.Pending(err, "MissingSecret", message)
		log.Error(err, message)
		return nil, nil
	}

	if err != nil {
		message := "Failed to initialise vault client for signing"
		reporter.Pending(err, "ErrorVaultInit", message)
		log.Error(err, message)
		return nil, err
	}

	certDuration := api.DefaultCertDuration(cr.Spec.Duration)
	certPem, caPem, err := client.Sign(cr.Spec.CSRPEM, certDuration)
	if err != nil {
		message := "Vault failed to sign certificate"

		reporter.Failed(err, "ErrorSigning", message)
		log.Error(err, message)

		return nil, nil
	}

	log.Info("certificate issued")

	return &issuer.IssueResponse{
		Certificate: certPem,
		CA:          caPem,
	}, nil
}
<|endoftext|>


<commit_before>package ualert

import (
	"encoding/json"
	"fmt"
	"os"
	"time"

	pb "github.com/prometheus/alertmanager/silence/silencepb"
	"xorm.io/xorm"

	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)

const GENERAL_FOLDER = "General Alerting"
const DASHBOARD_FOLDER = "Migrated %s"

// FOLDER_CREATED_BY us used to track folders created by this migration
// during alert migration cleanup.
const FOLDER_CREATED_BY = -8

var migTitle = "move dashboard alerts to unified alerting"

var rmMigTitle = "remove unified alerting data"

type MigrationError struct {
	AlertId int64
	Err     error
}

func (e MigrationError) Error() string {
	return fmt.Sprintf("failed to migrate alert %d: %s", e.AlertId, e.Err.Error())
}

func (e *MigrationError) Unwrap() error { return e.Err }

func AddDashAlertMigration(mg *migrator.Migrator) {
	logs, err := mg.GetMigrationLog()
	if err != nil {
		mg.Logger.Crit("alert migration failure: could not get migration log", "error", err)
		os.Exit(1)
	}

	_, migrationRun := logs[migTitle]

	ngEnabled := mg.Cfg.IsNgAlertEnabled()

	switch {
	case ngEnabled && !migrationRun:
		// Remove the migration entry that removes all unified alerting data. This is so when the feature
		// flag is removed in future the "remove unified alerting data" migration will be run again.
		err = mg.ClearMigrationEntry(rmMigTitle)
		if err != nil {
			mg.Logger.Error("alert migration error: could not clear alert migration for removing data", "error", err)
		}
		mg.AddMigration(migTitle, &migration{
			seenChannelUIDs:  make(map[string]struct{}),
			migratedChannels: make(map[*notificationChannel]struct{}),
		})
	case !ngEnabled && migrationRun:
		// Remove the migration entry that creates unified alerting data. This is so when the feature
		// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
		err = mg.ClearMigrationEntry(migTitle)
		if err != nil {
			mg.Logger.Error("alert migration error: could not clear dashboard alert migration", "error", err)
		}
		mg.AddMigration(rmMigTitle, &rmMigration{})
	}
}

type migration struct {
	migrator.MigrationBase
	// session and mg are attached for convenience.
	sess *xorm.Session
	mg   *migrator.Migrator

	seenChannelUIDs  map[string]struct{}
	migratedChannels map[*notificationChannel]struct{}
	silences         []*pb.MeshSilence
}

func (m *migration) SQL(dialect migrator.Dialect) string {
	return "code migration"
}

func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
	m.sess = sess
	m.mg = mg

	dashAlerts, err := m.slurpDashAlerts()
	if err != nil {
		return err
	}

	// [orgID, dataSourceId] -> UID
	dsIDMap, err := m.slurpDSIDs()
	if err != nil {
		return err
	}

	// [orgID, dashboardId] -> dashUID
	dashIDMap, err := m.slurpDashUIDs()
	if err != nil {
		return err
	}

	// allChannels: channelUID -> channelConfig
	allChannels, defaultChannels, err := m.getNotificationChannelMap()
	if err != nil {
		return err
	}

	amConfig := PostableUserConfig{}
	amConfig.AlertmanagerConfig.Route = &Route{}

	for _, da := range dashAlerts {
		newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
		if err != nil {
			return err
		}

		da.DashboardUID = dashIDMap[[2]int64{da.OrgId, da.DashboardId}]

		// get dashboard
		dash := dashboard{}
		exists, err := m.sess.Where("org_id=? AND uid=?", da.OrgId, da.DashboardUID).Get(&dash)
		if err != nil {
			return MigrationError{
				Err:     fmt.Errorf("failed to get dashboard %s under organisation %d: %w", da.DashboardUID, da.OrgId, err),
				AlertId: da.Id,
			}
		}
		if !exists {
			return MigrationError{
				Err:     fmt.Errorf("dashboard with UID %v under organisation %d not found: %w", da.DashboardUID, da.OrgId, err),
				AlertId: da.Id,
			}
		}

		// get folder if exists
		folder := dashboard{}
		if dash.FolderId > 0 {
			exists, err := m.sess.Where("id=?", dash.FolderId).Get(&folder)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to get folder %d: %w", dash.FolderId, err),
					AlertId: da.Id,
				}
			}
			if !exists {
				return MigrationError{
					Err:     fmt.Errorf("folder with id %v not found", dash.FolderId),
					AlertId: da.Id,
				}
			}
			if !folder.IsFolder {
				return MigrationError{
					Err:     fmt.Errorf("id %v is a dashboard not a folder", dash.FolderId),
					AlertId: da.Id,
				}
			}
		}

		switch {
		case dash.HasAcl:
			// create folder and assign the permissions of the dashboard (included default and inherited)
			ptr, err := m.createFolder(dash.OrgId, fmt.Sprintf(DASHBOARD_FOLDER, getMigrationString(da)))
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to create folder: %w", err),
					AlertId: da.Id,
				}
			}
			folder = *ptr
			permissions, err := m.getACL(dash.OrgId, dash.Id)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to get dashboard %d under organisation %d permissions: %w", dash.Id, dash.OrgId, err),
					AlertId: da.Id,
				}
			}
			err = m.setACL(folder.OrgId, folder.Id, permissions)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to set folder %d under organisation %d permissions: %w", folder.Id, folder.OrgId, err),
					AlertId: da.Id,
				}
			}
		case dash.FolderId > 0:
			// link the new rule to the existing folder
		default:
			// get or create general folder
			ptr, err := m.getOrCreateGeneralFolder(dash.OrgId)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
					AlertId: da.Id,
				}
			}
			// No need to assign default permissions to general folder
			// because they are included to the query result if it's a folder with no permissions
			// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
			folder = *ptr
		}

		if folder.Uid == "" {
			return MigrationError{
				Err:     fmt.Errorf("empty folder identifier"),
				AlertId: da.Id,
			}
		}
		rule, err := m.makeAlertRule(*newCond, da, folder.Uid)
		if err != nil {
			return err
		}

		if err := m.updateReceiverAndRoute(allChannels, defaultChannels, da, rule, &amConfig); err != nil {
			return err
		}

		_, err = m.sess.Insert(rule)
		if err != nil {
			// TODO better error handling, if constraint
			rule.Title += fmt.Sprintf(" %v", rule.Uid)
			rule.RuleGroup += fmt.Sprintf(" %v", rule.Uid)

			_, err = m.sess.Insert(rule)
			if err != nil {
				return err
			}
		}

		// create entry in alert_rule_version
		_, err = m.sess.Insert(rule.makeVersion())
		if err != nil {
			return err
		}
	}

	// Create a separate receiver for all the unmigrated channels.
	err = m.updateDefaultAndUnmigratedChannels(&amConfig, allChannels, defaultChannels)
	if err != nil {
		return err
	}

	if err := amConfig.EncryptSecureSettings(); err != nil {
		return err
	}
	rawAmConfig, err := json.Marshal(&amConfig)
	if err != nil {
		return err
	}

	// TODO: should we apply the config here? Because Alertmanager can take upto 1 min to pick it up.
	_, err = m.sess.Insert(AlertConfiguration{
		AlertmanagerConfiguration: string(rawAmConfig),
		// Since we are migration for a snapshot of the code, it is always going to migrate to
		// the v1 config.
		ConfigurationVersion: "v1",
	})
	if err != nil {
		return err
	}

	if err := m.writeSilencesFile(); err != nil {
		m.mg.Logger.Error("alert migration error: failed to write silence file", "err", err)
	}

	return nil
}

type AlertConfiguration struct {
	ID int64 `xorm:"pk autoincr 'id'"`

	AlertmanagerConfiguration string
	ConfigurationVersion      string
	CreatedAt                 time.Time `xorm:"created"`
}

type rmMigration struct {
	migrator.MigrationBase
}

func (m *rmMigration) SQL(dialect migrator.Dialect) string {
	return "code migration"
}

func (m *rmMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
	_, err := sess.Exec("delete from alert_rule")
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from alert_rule_version")
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from dashboard_acl where dashboard_id IN (select id from dashboard where created_by = ?)", FOLDER_CREATED_BY)
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from dashboard where created_by = ?", FOLDER_CREATED_BY)
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from alert_configuration")
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from alert_instance")
	if err != nil {
		return err
	}

	if err := os.RemoveAll(silencesFileName(mg)); err != nil {
		mg.Logger.Error("alert migration error: failed to remove silence file", "err", err)
	}

	return nil
}
<commit_msg>Alerting: Don't save Alertmanager config on migration when 0 channels (#35119)<commit_after>package ualert

import (
	"encoding/json"
	"fmt"
	"os"
	"time"

	pb "github.com/prometheus/alertmanager/silence/silencepb"
	"xorm.io/xorm"

	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)

const GENERAL_FOLDER = "General Alerting"
const DASHBOARD_FOLDER = "Migrated %s"

// FOLDER_CREATED_BY us used to track folders created by this migration
// during alert migration cleanup.
const FOLDER_CREATED_BY = -8

var migTitle = "move dashboard alerts to unified alerting"

var rmMigTitle = "remove unified alerting data"

type MigrationError struct {
	AlertId int64
	Err     error
}

func (e MigrationError) Error() string {
	return fmt.Sprintf("failed to migrate alert %d: %s", e.AlertId, e.Err.Error())
}

func (e *MigrationError) Unwrap() error { return e.Err }

func AddDashAlertMigration(mg *migrator.Migrator) {
	logs, err := mg.GetMigrationLog()
	if err != nil {
		mg.Logger.Crit("alert migration failure: could not get migration log", "error", err)
		os.Exit(1)
	}

	_, migrationRun := logs[migTitle]

	ngEnabled := mg.Cfg.IsNgAlertEnabled()

	switch {
	case ngEnabled && !migrationRun:
		// Remove the migration entry that removes all unified alerting data. This is so when the feature
		// flag is removed in future the "remove unified alerting data" migration will be run again.
		err = mg.ClearMigrationEntry(rmMigTitle)
		if err != nil {
			mg.Logger.Error("alert migration error: could not clear alert migration for removing data", "error", err)
		}
		mg.AddMigration(migTitle, &migration{
			seenChannelUIDs:  make(map[string]struct{}),
			migratedChannels: make(map[*notificationChannel]struct{}),
		})
	case !ngEnabled && migrationRun:
		// Remove the migration entry that creates unified alerting data. This is so when the feature
		// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
		err = mg.ClearMigrationEntry(migTitle)
		if err != nil {
			mg.Logger.Error("alert migration error: could not clear dashboard alert migration", "error", err)
		}
		mg.AddMigration(rmMigTitle, &rmMigration{})
	}
}

type migration struct {
	migrator.MigrationBase
	// session and mg are attached for convenience.
	sess *xorm.Session
	mg   *migrator.Migrator

	seenChannelUIDs  map[string]struct{}
	migratedChannels map[*notificationChannel]struct{}
	silences         []*pb.MeshSilence
}

func (m *migration) SQL(dialect migrator.Dialect) string {
	return "code migration"
}

func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
	m.sess = sess
	m.mg = mg

	dashAlerts, err := m.slurpDashAlerts()
	if err != nil {
		return err
	}

	// [orgID, dataSourceId] -> UID
	dsIDMap, err := m.slurpDSIDs()
	if err != nil {
		return err
	}

	// [orgID, dashboardId] -> dashUID
	dashIDMap, err := m.slurpDashUIDs()
	if err != nil {
		return err
	}

	// allChannels: channelUID -> channelConfig
	allChannels, defaultChannels, err := m.getNotificationChannelMap()
	if err != nil {
		return err
	}

	amConfig := PostableUserConfig{}
	amConfig.AlertmanagerConfig.Route = &Route{}

	for _, da := range dashAlerts {
		newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
		if err != nil {
			return err
		}

		da.DashboardUID = dashIDMap[[2]int64{da.OrgId, da.DashboardId}]

		// get dashboard
		dash := dashboard{}
		exists, err := m.sess.Where("org_id=? AND uid=?", da.OrgId, da.DashboardUID).Get(&dash)
		if err != nil {
			return MigrationError{
				Err:     fmt.Errorf("failed to get dashboard %s under organisation %d: %w", da.DashboardUID, da.OrgId, err),
				AlertId: da.Id,
			}
		}
		if !exists {
			return MigrationError{
				Err:     fmt.Errorf("dashboard with UID %v under organisation %d not found: %w", da.DashboardUID, da.OrgId, err),
				AlertId: da.Id,
			}
		}

		// get folder if exists
		folder := dashboard{}
		if dash.FolderId > 0 {
			exists, err := m.sess.Where("id=?", dash.FolderId).Get(&folder)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to get folder %d: %w", dash.FolderId, err),
					AlertId: da.Id,
				}
			}
			if !exists {
				return MigrationError{
					Err:     fmt.Errorf("folder with id %v not found", dash.FolderId),
					AlertId: da.Id,
				}
			}
			if !folder.IsFolder {
				return MigrationError{
					Err:     fmt.Errorf("id %v is a dashboard not a folder", dash.FolderId),
					AlertId: da.Id,
				}
			}
		}

		switch {
		case dash.HasAcl:
			// create folder and assign the permissions of the dashboard (included default and inherited)
			ptr, err := m.createFolder(dash.OrgId, fmt.Sprintf(DASHBOARD_FOLDER, getMigrationString(da)))
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to create folder: %w", err),
					AlertId: da.Id,
				}
			}
			folder = *ptr
			permissions, err := m.getACL(dash.OrgId, dash.Id)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to get dashboard %d under organisation %d permissions: %w", dash.Id, dash.OrgId, err),
					AlertId: da.Id,
				}
			}
			err = m.setACL(folder.OrgId, folder.Id, permissions)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to set folder %d under organisation %d permissions: %w", folder.Id, folder.OrgId, err),
					AlertId: da.Id,
				}
			}
		case dash.FolderId > 0:
			// link the new rule to the existing folder
		default:
			// get or create general folder
			ptr, err := m.getOrCreateGeneralFolder(dash.OrgId)
			if err != nil {
				return MigrationError{
					Err:     fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
					AlertId: da.Id,
				}
			}
			// No need to assign default permissions to general folder
			// because they are included to the query result if it's a folder with no permissions
			// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
			folder = *ptr
		}

		if folder.Uid == "" {
			return MigrationError{
				Err:     fmt.Errorf("empty folder identifier"),
				AlertId: da.Id,
			}
		}
		rule, err := m.makeAlertRule(*newCond, da, folder.Uid)
		if err != nil {
			return err
		}

		if err := m.updateReceiverAndRoute(allChannels, defaultChannels, da, rule, &amConfig); err != nil {
			return err
		}

		_, err = m.sess.Insert(rule)
		if err != nil {
			// TODO better error handling, if constraint
			rule.Title += fmt.Sprintf(" %v", rule.Uid)
			rule.RuleGroup += fmt.Sprintf(" %v", rule.Uid)

			_, err = m.sess.Insert(rule)
			if err != nil {
				return err
			}
		}

		// create entry in alert_rule_version
		_, err = m.sess.Insert(rule.makeVersion())
		if err != nil {
			return err
		}
	}

	// Create a separate receiver for all the unmigrated channels.
	err = m.updateDefaultAndUnmigratedChannels(&amConfig, allChannels, defaultChannels)
	if err != nil {
		return err
	}

	if err := m.writeAlertmanagerConfig(&amConfig, allChannels); err != nil {
		return err
	}

	if err := m.writeSilencesFile(); err != nil {
		m.mg.Logger.Error("alert migration error: failed to write silence file", "err", err)
	}

	return nil
}

func (m *migration) writeAlertmanagerConfig(amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel) error {
	if len(allChannels) == 0 {
		// No channels, hence don't require Alertmanager config.
		m.mg.Logger.Info("alert migration: no notification channel found, skipping Alertmanager config")
		return nil
	}

	if err := amConfig.EncryptSecureSettings(); err != nil {
		return err
	}
	rawAmConfig, err := json.Marshal(amConfig)
	if err != nil {
		return err
	}

	// TODO: should we apply the config here? Because Alertmanager can take upto 1 min to pick it up.
	_, err = m.sess.Insert(AlertConfiguration{
		AlertmanagerConfiguration: string(rawAmConfig),
		// Since we are migration for a snapshot of the code, it is always going to migrate to
		// the v1 config.
		ConfigurationVersion: "v1",
	})
	if err != nil {
		return err
	}

	return nil
}

type AlertConfiguration struct {
	ID int64 `xorm:"pk autoincr 'id'"`

	AlertmanagerConfiguration string
	ConfigurationVersion      string
	CreatedAt                 time.Time `xorm:"created"`
}

type rmMigration struct {
	migrator.MigrationBase
}

func (m *rmMigration) SQL(dialect migrator.Dialect) string {
	return "code migration"
}

func (m *rmMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
	_, err := sess.Exec("delete from alert_rule")
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from alert_rule_version")
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from dashboard_acl where dashboard_id IN (select id from dashboard where created_by = ?)", FOLDER_CREATED_BY)
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from dashboard where created_by = ?", FOLDER_CREATED_BY)
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from alert_configuration")
	if err != nil {
		return err
	}

	_, err = sess.Exec("delete from alert_instance")
	if err != nil {
		return err
	}

	if err := os.RemoveAll(silencesFileName(mg)); err != nil {
		mg.Logger.Error("alert migration error: failed to remove silence file", "err", err)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go9p 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 clnt

import (
	"code.google.com/p/go9p/p"
	"net"
	"syscall"
)

// Creates an authentication fid for the specified user. Returns the fid, if
// successful, or an Error.
func (clnt *Clnt) Auth(user p.User, aname string) (*Fid, error) {
	fid := clnt.FidAlloc()
	tc := clnt.NewFcall()
	err := p.PackTauth(tc, fid.Fid, user.Name(), aname, uint32(user.Id()), clnt.Dotu)
	if err != nil {
		fid.Clunk()
		return nil, err
	}

	_, err = clnt.Rpc(tc)
	if err != nil {
		fid.Clunk()
		return nil, err
	}

	fid.User = user
	fid.walked = true
	return fid, nil
}

// Creates a fid for the specified user that points to the root
// of the file server's file tree. Returns a Fid pointing to the root,
// if successful, or an Error.
func (clnt *Clnt) Attach(afid *Fid, user p.User, aname string) (*Fid, error) {
	var afno uint32

	if afid != nil {
		afno = afid.Fid
	} else {
		afno = p.NOFID
	}

	fid := clnt.FidAlloc()
	tc := clnt.NewFcall()
	err := p.PackTattach(tc, fid.Fid, afno, user.Name(), aname, uint32(user.Id()), clnt.Dotu)
	if err != nil {
		fid.Clunk()
		return nil, err
	}

	rc, err := clnt.Rpc(tc)
	if err != nil {
		fid.Clunk()
		return nil, err
	}
	if rc.Type == p.Rerror {
		fid.Clunk()
		return nil, &p.Error{rc.Error, syscall.Errno(rc.Errornum)}
	}

	fid.Qid = rc.Qid
	fid.User = user
	fid.walked = true
	return fid, nil
}

// Connects to a file server and attaches to it as the specified user.
func Mount(ntype, addr, aname string, user p.User) (*Clnt, error) {
	c, e := net.Dial(ntype, addr)
	if e != nil {
		return nil, &p.Error{e.Error(), p.EIO}
	}

	return MountConn(c, aname, user)
}

func MountConn(c net.Conn, aname string, user p.User) (*Clnt, error) {
	clnt, err := Connect(c, 8192+p.IOHDRSZ, true)
	if err != nil {
		return nil, err
	}

	fid, err := clnt.Attach(nil, user, aname)
	if err != nil {
		clnt.Unmount()
		return nil, err
	}

	clnt.Root = fid
	return clnt, nil
}

// Closes the connection to the file sever.
func (clnt *Clnt) Unmount() {
	clnt.Lock()
	clnt.err = &p.Error{"connection closed", p.ECONNRESET}
	clnt.conn.Close()
	clnt.Unlock()
}
<commit_msg>Added aqid copy from RAuth message.<commit_after>// Copyright 2009 The Go9p 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 clnt

import (
	"code.google.com/p/go9p/p"
	"net"
	"syscall"
)

// Creates an authentication fid for the specified user. Returns the fid, if
// successful, or an Error.
func (clnt *Clnt) Auth(user p.User, aname string) (*Fid, error) {
	fid := clnt.FidAlloc()
	tc := clnt.NewFcall()
	err := p.PackTauth(tc, fid.Fid, user.Name(), aname, uint32(user.Id()), clnt.Dotu)
	if err != nil {
		fid.Clunk()
		return nil, err
	}

	rc, err := clnt.Rpc(tc)
	if err != nil {
		fid.Clunk()
		return nil, err
	}

	fid.Qid = rc.Qid
	fid.User = user
	fid.walked = true
	return fid, nil
}

// Creates a fid for the specified user that points to the root
// of the file server's file tree. Returns a Fid pointing to the root,
// if successful, or an Error.
func (clnt *Clnt) Attach(afid *Fid, user p.User, aname string) (*Fid, error) {
	var afno uint32

	if afid != nil {
		afno = afid.Fid
	} else {
		afno = p.NOFID
	}

	fid := clnt.FidAlloc()
	tc := clnt.NewFcall()
	err := p.PackTattach(tc, fid.Fid, afno, user.Name(), aname, uint32(user.Id()), clnt.Dotu)
	if err != nil {
		fid.Clunk()
		return nil, err
	}

	rc, err := clnt.Rpc(tc)
	if err != nil {
		fid.Clunk()
		return nil, err
	}
	if rc.Type == p.Rerror {
		fid.Clunk()
		return nil, &p.Error{rc.Error, syscall.Errno(rc.Errornum)}
	}

	fid.Qid = rc.Qid
	fid.User = user
	fid.walked = true
	return fid, nil
}

// Connects to a file server and attaches to it as the specified user.
func Mount(ntype, addr, aname string, user p.User) (*Clnt, error) {
	c, e := net.Dial(ntype, addr)
	if e != nil {
		return nil, &p.Error{e.Error(), p.EIO}
	}

	return MountConn(c, aname, user)
}

func MountConn(c net.Conn, aname string, user p.User) (*Clnt, error) {
	clnt, err := Connect(c, 8192+p.IOHDRSZ, true)
	if err != nil {
		return nil, err
	}

	fid, err := clnt.Attach(nil, user, aname)
	if err != nil {
		clnt.Unmount()
		return nil, err
	}

	clnt.Root = fid
	return clnt, nil
}

// Closes the connection to the file sever.
func (clnt *Clnt) Unmount() {
	clnt.Lock()
	clnt.err = &p.Error{"connection closed", p.ECONNRESET}
	clnt.conn.Close()
	clnt.Unlock()
}
<|endoftext|>