<commit_before>package config

import (
	"os"
	"path/filepath"
	"strings"

	"github.com/go-git/go-billy/v5/osfs"
	"github.com/go-git/go-billy/v5/util"
	"github.com/go-git/go-git/v5/plumbing"
	. "gopkg.in/check.v1"
)

type ConfigSuite struct{}

var _ = Suite(&ConfigSuite{})

func (s *ConfigSuite) TestUnmarshal(c *C) {
	input := []byte(`[core]
		bare = true
		worktree = foo
		commentchar = bar
[user]
		name = John Doe
		email = john@example.com
[author]
		name = Jane Roe
		email = jane@example.com
[committer]
		name = Richard Roe
		email = richard@example.com
[pack]
		window = 20
[remote "origin"]
		url = git@github.com:mcuadros/go-git.git
		fetch = +refs/heads/*:refs/remotes/origin/*
[remote "alt"]
		url = git@github.com:mcuadros/go-git.git
		url = git@github.com:src-d/go-git.git
		fetch = +refs/heads/*:refs/remotes/origin/*
		fetch = +refs/pull/*:refs/remotes/origin/pull/*
[remote "insteadOf"]
		url = https://github.com/kostyay/go-git.git
[remote "win-local"]
		url = X:\\Git\\
[submodule "qux"]
		path = qux
		url = https://github.com/foo/qux.git
		branch = bar
[branch "master"]
		remote = origin
		merge = refs/heads/master
[init]
		defaultBranch = main
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
`)

	cfg := NewConfig()
	err := cfg.Unmarshal(input)
	c.Assert(err, IsNil)

	c.Assert(cfg.Core.IsBare, Equals, true)
	c.Assert(cfg.Core.Worktree, Equals, "foo")
	c.Assert(cfg.Core.CommentChar, Equals, "bar")
	c.Assert(cfg.User.Name, Equals, "John Doe")
	c.Assert(cfg.User.Email, Equals, "john@example.com")
	c.Assert(cfg.Author.Name, Equals, "Jane Roe")
	c.Assert(cfg.Author.Email, Equals, "jane@example.com")
	c.Assert(cfg.Committer.Name, Equals, "Richard Roe")
	c.Assert(cfg.Committer.Email, Equals, "richard@example.com")
	c.Assert(cfg.Pack.Window, Equals, uint(20))
	c.Assert(cfg.Remotes, HasLen, 4)
	c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
	c.Assert(cfg.Remotes["origin"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git"})
	c.Assert(cfg.Remotes["origin"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*"})
	c.Assert(cfg.Remotes["alt"].Name, Equals, "alt")
	c.Assert(cfg.Remotes["alt"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"})
	c.Assert(cfg.Remotes["alt"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"})
	c.Assert(cfg.Remotes["win-local"].Name, Equals, "win-local")
	c.Assert(cfg.Remotes["win-local"].URLs, DeepEquals, []string{"X:\\Git\\"})
	c.Assert(cfg.Remotes["insteadOf"].URLs, DeepEquals, []string{"ssh://git@github.com/kostyay/go-git.git"})
	c.Assert(cfg.Submodules, HasLen, 1)
	c.Assert(cfg.Submodules["qux"].Name, Equals, "qux")
	c.Assert(cfg.Submodules["qux"].URL, Equals, "https://github.com/foo/qux.git")
	c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar")
	c.Assert(cfg.Branches["master"].Remote, Equals, "origin")
	c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
	c.Assert(cfg.Init.DefaultBranch, Equals, "main")
}

func (s *ConfigSuite) TestMarshal(c *C) {
	output := []byte(`[core]
	bare = true
	worktree = bar
[pack]
	window = 20
[remote "alt"]
	url = git@github.com:mcuadros/go-git.git
	url = git@github.com:src-d/go-git.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	fetch = +refs/pull/*:refs/remotes/origin/pull/*
[remote "insteadOf"]
	url = https://github.com/kostyay/go-git.git
[remote "origin"]
	url = git@github.com:mcuadros/go-git.git
[remote "win-local"]
	url = "X:\\Git\\"
[submodule "qux"]
	url = https://github.com/foo/qux.git
[branch "master"]
	remote = origin
	merge = refs/heads/master
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
[init]
	defaultBranch = main
`)

	cfg := NewConfig()
	cfg.Core.IsBare = true
	cfg.Core.Worktree = "bar"
	cfg.Pack.Window = 20
	cfg.Init.DefaultBranch = "main"
	cfg.Remotes["origin"] = &RemoteConfig{
		Name: "origin",
		URLs: []string{"git@github.com:mcuadros/go-git.git"},
	}

	cfg.Remotes["alt"] = &RemoteConfig{
		Name:  "alt",
		URLs:  []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"},
		Fetch: []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"},
	}

	cfg.Remotes["win-local"] = &RemoteConfig{
		Name: "win-local",
		URLs: []string{"X:\\Git\\"},
	}

	cfg.Remotes["insteadOf"] = &RemoteConfig{
		Name: "insteadOf",
		URLs: []string{"https://github.com/kostyay/go-git.git"},
	}

	cfg.Submodules["qux"] = &Submodule{
		Name: "qux",
		URL:  "https://github.com/foo/qux.git",
	}

	cfg.Branches["master"] = &Branch{
		Name:   "master",
		Remote: "origin",
		Merge:  "refs/heads/master",
	}

	cfg.URLs["ssh://git@github.com/"] = &URL{
		Name:      "ssh://git@github.com/",
		InsteadOf: "https://github.com/",
	}

	b, err := cfg.Marshal()
	c.Assert(err, IsNil)

	c.Assert(string(b), Equals, string(output))
}

func (s *ConfigSuite) TestUnmarshalMarshal(c *C) {
	input := []byte(`[core]
	bare = true
	worktree = foo
	custom = ignored
[user]
	name = John Doe
	email = john@example.com
[author]
	name = Jane Roe
	email = jane@example.com
[committer]
	name = Richard Roe
	email = richard@example.co
[pack]
	window = 20
[remote "insteadOf"]
	url = https://github.com/kostyay/go-git.git
[remote "origin"]
	url = git@github.com:mcuadros/go-git.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	mirror = true
[remote "win-local"]
	url = "X:\\Git\\"
[branch "master"]
	remote = origin
	merge = refs/heads/master
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
`)

	cfg := NewConfig()
	err := cfg.Unmarshal(input)
	c.Assert(err, IsNil)

	output, err := cfg.Marshal()
	c.Assert(err, IsNil)
	c.Assert(string(output), DeepEquals, string(input))
}

func (s *ConfigSuite) TestLoadConfigXDG(c *C) {
	cfg := NewConfig()
	cfg.User.Name = "foo"
	cfg.User.Email = "foo@foo.com"

	tmp, err := util.TempDir(osfs.Default, "", "test-commit-options")
	c.Assert(err, IsNil)
	defer util.RemoveAll(osfs.Default, tmp)

	err = osfs.Default.MkdirAll(filepath.Join(tmp, "git"), 0777)
	c.Assert(err, IsNil)

	os.Setenv("XDG_CONFIG_HOME", tmp)
	defer func() {
		os.Setenv("XDG_CONFIG_HOME", "")
	}()

	content, err := cfg.Marshal()
	c.Assert(err, IsNil)

	cfgFile := filepath.Join(tmp, "git/config")
	err = util.WriteFile(osfs.Default, cfgFile, content, 0777)
	c.Assert(err, IsNil)

	cfg, err = LoadConfig(GlobalScope)
	c.Assert(err, IsNil)

	c.Assert(cfg.User.Email, Equals, "foo@foo.com")
}

func (s *ConfigSuite) TestValidateConfig(c *C) {
	config := &Config{
		Remotes: map[string]*RemoteConfig{
			"bar": {
				Name: "bar",
				URLs: []string{"http://foo/bar"},
			},
		},
		Branches: map[string]*Branch{
			"bar": {
				Name: "bar",
			},
			"foo": {
				Name:   "foo",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("refs/heads/foo"),
			},
		},
	}

	c.Assert(config.Validate(), IsNil)
}

func (s *ConfigSuite) TestValidateInvalidRemote(c *C) {
	config := &Config{
		Remotes: map[string]*RemoteConfig{
			"foo": {Name: "foo"},
		},
	}

	c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyURL)
}

func (s *ConfigSuite) TestValidateInvalidRemoteKey(c *C) {
	config := &Config{
		Remotes: map[string]*RemoteConfig{
			"bar": {Name: "foo"},
		},
	}

	c.Assert(config.Validate(), Equals, ErrInvalid)
}

func (s *ConfigSuite) TestRemoteConfigValidateMissingURL(c *C) {
	config := &RemoteConfig{Name: "foo"}
	c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyURL)
}

func (s *ConfigSuite) TestRemoteConfigValidateMissingName(c *C) {
	config := &RemoteConfig{}
	c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyName)
}

func (s *ConfigSuite) TestRemoteConfigValidateDefault(c *C) {
	config := &RemoteConfig{Name: "foo", URLs: []string{"http://foo/bar"}}
	c.Assert(config.Validate(), IsNil)

	fetch := config.Fetch
	c.Assert(fetch, HasLen, 1)
	c.Assert(fetch[0].String(), Equals, "+refs/heads/*:refs/remotes/foo/*")
}

func (s *ConfigSuite) TestValidateInvalidBranchKey(c *C) {
	config := &Config{
		Branches: map[string]*Branch{
			"foo": {
				Name:   "bar",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("refs/heads/bar"),
			},
		},
	}

	c.Assert(config.Validate(), Equals, ErrInvalid)
}

func (s *ConfigSuite) TestValidateInvalidBranch(c *C) {
	config := &Config{
		Branches: map[string]*Branch{
			"bar": {
				Name:   "bar",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("refs/heads/bar"),
			},
			"foo": {
				Name:   "foo",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("baz"),
			},
		},
	}

	c.Assert(config.Validate(), Equals, errBranchInvalidMerge)
}

func (s *ConfigSuite) TestRemoteConfigDefaultValues(c *C) {
	config := NewConfig()

	c.Assert(config.Remotes, HasLen, 0)
	c.Assert(config.Branches, HasLen, 0)
	c.Assert(config.Submodules, HasLen, 0)
	c.Assert(config.Raw, NotNil)
	c.Assert(config.Pack.Window, Equals, DefaultPackWindow)
}

func (s *ConfigSuite) TestLoadConfigLocalScope(c *C) {
	cfg, err := LoadConfig(LocalScope)
	c.Assert(err, NotNil)
	c.Assert(cfg, IsNil)
}

func (s *ConfigSuite) TestRemoveUrlOptions(c *C) {
	buf := []byte(`
[remote "alt"]
	url = git@github.com:mcuadros/go-git.git
	url = git@github.com:src-d/go-git.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	fetch = +refs/pull/*:refs/remotes/origin/pull/*`)

	cfg := NewConfig()
	err := cfg.Unmarshal(buf)
	c.Assert(err, IsNil)
	c.Assert(len(cfg.Remotes), Equals, 1)
	cfg.Remotes["alt"].URLs = []string{}

	buf, err = cfg.Marshal()
	if strings.Contains(string(buf), "url") {
		c.Fatal("conifg should not contain any url sections")
	}
}
<commit_msg>config: add tests for branch desc marshaling and unmarshaling<commit_after>package config

import (
	"os"
	"path/filepath"
	"strings"

	"github.com/go-git/go-billy/v5/osfs"
	"github.com/go-git/go-billy/v5/util"
	"github.com/go-git/go-git/v5/plumbing"
	. "gopkg.in/check.v1"
)

type ConfigSuite struct{}

var _ = Suite(&ConfigSuite{})

func (s *ConfigSuite) TestUnmarshal(c *C) {
	input := []byte(`[core]
		bare = true
		worktree = foo
		commentchar = bar
[user]
		name = John Doe
		email = john@example.com
[author]
		name = Jane Roe
		email = jane@example.com
[committer]
		name = Richard Roe
		email = richard@example.com
[pack]
		window = 20
[remote "origin"]
		url = git@github.com:mcuadros/go-git.git
		fetch = +refs/heads/*:refs/remotes/origin/*
[remote "alt"]
		url = git@github.com:mcuadros/go-git.git
		url = git@github.com:src-d/go-git.git
		fetch = +refs/heads/*:refs/remotes/origin/*
		fetch = +refs/pull/*:refs/remotes/origin/pull/*
[remote "insteadOf"]
		url = https://github.com/kostyay/go-git.git
[remote "win-local"]
		url = X:\\Git\\
[submodule "qux"]
		path = qux
		url = https://github.com/foo/qux.git
		branch = bar
[branch "master"]
		remote = origin
		merge = refs/heads/master
		description = "Add support for branch description.\\n\\nEdit branch description: git branch --edit-description\\n"
[init]
		defaultBranch = main
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
`)

	cfg := NewConfig()
	err := cfg.Unmarshal(input)
	c.Assert(err, IsNil)

	c.Assert(cfg.Core.IsBare, Equals, true)
	c.Assert(cfg.Core.Worktree, Equals, "foo")
	c.Assert(cfg.Core.CommentChar, Equals, "bar")
	c.Assert(cfg.User.Name, Equals, "John Doe")
	c.Assert(cfg.User.Email, Equals, "john@example.com")
	c.Assert(cfg.Author.Name, Equals, "Jane Roe")
	c.Assert(cfg.Author.Email, Equals, "jane@example.com")
	c.Assert(cfg.Committer.Name, Equals, "Richard Roe")
	c.Assert(cfg.Committer.Email, Equals, "richard@example.com")
	c.Assert(cfg.Pack.Window, Equals, uint(20))
	c.Assert(cfg.Remotes, HasLen, 4)
	c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
	c.Assert(cfg.Remotes["origin"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git"})
	c.Assert(cfg.Remotes["origin"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*"})
	c.Assert(cfg.Remotes["alt"].Name, Equals, "alt")
	c.Assert(cfg.Remotes["alt"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"})
	c.Assert(cfg.Remotes["alt"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"})
	c.Assert(cfg.Remotes["win-local"].Name, Equals, "win-local")
	c.Assert(cfg.Remotes["win-local"].URLs, DeepEquals, []string{"X:\\Git\\"})
	c.Assert(cfg.Remotes["insteadOf"].URLs, DeepEquals, []string{"ssh://git@github.com/kostyay/go-git.git"})
	c.Assert(cfg.Submodules, HasLen, 1)
	c.Assert(cfg.Submodules["qux"].Name, Equals, "qux")
	c.Assert(cfg.Submodules["qux"].URL, Equals, "https://github.com/foo/qux.git")
	c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar")
	c.Assert(cfg.Branches["master"].Remote, Equals, "origin")
	c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
	c.Assert(cfg.Branches["master"].Description, Equals, "Add support for branch description.\n\nEdit branch description: git branch --edit-description\n")
	c.Assert(cfg.Init.DefaultBranch, Equals, "main")
}

func (s *ConfigSuite) TestMarshal(c *C) {
	output := []byte(`[core]
	bare = true
	worktree = bar
[pack]
	window = 20
[remote "alt"]
	url = git@github.com:mcuadros/go-git.git
	url = git@github.com:src-d/go-git.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	fetch = +refs/pull/*:refs/remotes/origin/pull/*
[remote "insteadOf"]
	url = https://github.com/kostyay/go-git.git
[remote "origin"]
	url = git@github.com:mcuadros/go-git.git
[remote "win-local"]
	url = "X:\\Git\\"
[submodule "qux"]
	url = https://github.com/foo/qux.git
[branch "master"]
	remote = origin
	merge = refs/heads/master
	description = "Add support for branch description.\\n\\nEdit branch description: git branch --edit-description\\n"
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
[init]
	defaultBranch = main
`)

	cfg := NewConfig()
	cfg.Core.IsBare = true
	cfg.Core.Worktree = "bar"
	cfg.Pack.Window = 20
	cfg.Init.DefaultBranch = "main"
	cfg.Remotes["origin"] = &RemoteConfig{
		Name: "origin",
		URLs: []string{"git@github.com:mcuadros/go-git.git"},
	}

	cfg.Remotes["alt"] = &RemoteConfig{
		Name:  "alt",
		URLs:  []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"},
		Fetch: []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"},
	}

	cfg.Remotes["win-local"] = &RemoteConfig{
		Name: "win-local",
		URLs: []string{"X:\\Git\\"},
	}

	cfg.Remotes["insteadOf"] = &RemoteConfig{
		Name: "insteadOf",
		URLs: []string{"https://github.com/kostyay/go-git.git"},
	}

	cfg.Submodules["qux"] = &Submodule{
		Name: "qux",
		URL:  "https://github.com/foo/qux.git",
	}

	cfg.Branches["master"] = &Branch{
		Name:        "master",
		Remote:      "origin",
		Merge:       "refs/heads/master",
		Description: "Add support for branch description.\n\nEdit branch description: git branch --edit-description\n",
	}

	cfg.URLs["ssh://git@github.com/"] = &URL{
		Name:      "ssh://git@github.com/",
		InsteadOf: "https://github.com/",
	}

	b, err := cfg.Marshal()
	c.Assert(err, IsNil)

	c.Assert(string(b), Equals, string(output))
}

func (s *ConfigSuite) TestUnmarshalMarshal(c *C) {
	input := []byte(`[core]
	bare = true
	worktree = foo
	custom = ignored
[user]
	name = John Doe
	email = john@example.com
[author]
	name = Jane Roe
	email = jane@example.com
[committer]
	name = Richard Roe
	email = richard@example.co
[pack]
	window = 20
[remote "insteadOf"]
	url = https://github.com/kostyay/go-git.git
[remote "origin"]
	url = git@github.com:mcuadros/go-git.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	mirror = true
[remote "win-local"]
	url = "X:\\Git\\"
[branch "master"]
	remote = origin
	merge = refs/heads/master
[url "ssh://git@github.com/"]
	insteadOf = https://github.com/
`)

	cfg := NewConfig()
	err := cfg.Unmarshal(input)
	c.Assert(err, IsNil)

	output, err := cfg.Marshal()
	c.Assert(err, IsNil)
	c.Assert(string(output), DeepEquals, string(input))
}

func (s *ConfigSuite) TestLoadConfigXDG(c *C) {
	cfg := NewConfig()
	cfg.User.Name = "foo"
	cfg.User.Email = "foo@foo.com"

	tmp, err := util.TempDir(osfs.Default, "", "test-commit-options")
	c.Assert(err, IsNil)
	defer util.RemoveAll(osfs.Default, tmp)

	err = osfs.Default.MkdirAll(filepath.Join(tmp, "git"), 0777)
	c.Assert(err, IsNil)

	os.Setenv("XDG_CONFIG_HOME", tmp)
	defer func() {
		os.Setenv("XDG_CONFIG_HOME", "")
	}()

	content, err := cfg.Marshal()
	c.Assert(err, IsNil)

	cfgFile := filepath.Join(tmp, "git/config")
	err = util.WriteFile(osfs.Default, cfgFile, content, 0777)
	c.Assert(err, IsNil)

	cfg, err = LoadConfig(GlobalScope)
	c.Assert(err, IsNil)

	c.Assert(cfg.User.Email, Equals, "foo@foo.com")
}

func (s *ConfigSuite) TestValidateConfig(c *C) {
	config := &Config{
		Remotes: map[string]*RemoteConfig{
			"bar": {
				Name: "bar",
				URLs: []string{"http://foo/bar"},
			},
		},
		Branches: map[string]*Branch{
			"bar": {
				Name: "bar",
			},
			"foo": {
				Name:   "foo",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("refs/heads/foo"),
			},
		},
	}

	c.Assert(config.Validate(), IsNil)
}

func (s *ConfigSuite) TestValidateInvalidRemote(c *C) {
	config := &Config{
		Remotes: map[string]*RemoteConfig{
			"foo": {Name: "foo"},
		},
	}

	c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyURL)
}

func (s *ConfigSuite) TestValidateInvalidRemoteKey(c *C) {
	config := &Config{
		Remotes: map[string]*RemoteConfig{
			"bar": {Name: "foo"},
		},
	}

	c.Assert(config.Validate(), Equals, ErrInvalid)
}

func (s *ConfigSuite) TestRemoteConfigValidateMissingURL(c *C) {
	config := &RemoteConfig{Name: "foo"}
	c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyURL)
}

func (s *ConfigSuite) TestRemoteConfigValidateMissingName(c *C) {
	config := &RemoteConfig{}
	c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyName)
}

func (s *ConfigSuite) TestRemoteConfigValidateDefault(c *C) {
	config := &RemoteConfig{Name: "foo", URLs: []string{"http://foo/bar"}}
	c.Assert(config.Validate(), IsNil)

	fetch := config.Fetch
	c.Assert(fetch, HasLen, 1)
	c.Assert(fetch[0].String(), Equals, "+refs/heads/*:refs/remotes/foo/*")
}

func (s *ConfigSuite) TestValidateInvalidBranchKey(c *C) {
	config := &Config{
		Branches: map[string]*Branch{
			"foo": {
				Name:   "bar",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("refs/heads/bar"),
			},
		},
	}

	c.Assert(config.Validate(), Equals, ErrInvalid)
}

func (s *ConfigSuite) TestValidateInvalidBranch(c *C) {
	config := &Config{
		Branches: map[string]*Branch{
			"bar": {
				Name:   "bar",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("refs/heads/bar"),
			},
			"foo": {
				Name:   "foo",
				Remote: "origin",
				Merge:  plumbing.ReferenceName("baz"),
			},
		},
	}

	c.Assert(config.Validate(), Equals, errBranchInvalidMerge)
}

func (s *ConfigSuite) TestRemoteConfigDefaultValues(c *C) {
	config := NewConfig()

	c.Assert(config.Remotes, HasLen, 0)
	c.Assert(config.Branches, HasLen, 0)
	c.Assert(config.Submodules, HasLen, 0)
	c.Assert(config.Raw, NotNil)
	c.Assert(config.Pack.Window, Equals, DefaultPackWindow)
}

func (s *ConfigSuite) TestLoadConfigLocalScope(c *C) {
	cfg, err := LoadConfig(LocalScope)
	c.Assert(err, NotNil)
	c.Assert(cfg, IsNil)
}

func (s *ConfigSuite) TestRemoveUrlOptions(c *C) {
	buf := []byte(`
[remote "alt"]
	url = git@github.com:mcuadros/go-git.git
	url = git@github.com:src-d/go-git.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	fetch = +refs/pull/*:refs/remotes/origin/pull/*`)

	cfg := NewConfig()
	err := cfg.Unmarshal(buf)
	c.Assert(err, IsNil)
	c.Assert(len(cfg.Remotes), Equals, 1)
	cfg.Remotes["alt"].URLs = []string{}

	buf, err = cfg.Marshal()
	if strings.Contains(string(buf), "url") {
		c.Fatal("conifg should not contain any url sections")
	}
	c.Assert(err, IsNil)
}
<|endoftext|>


<commit_before>// +build go1.4

// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package config

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"testing"
)

var emptyContext map[string]string

// func Lookup(context map[string]string, key string) interface{}
var testsLookup = []struct {
	key           string
	expectedValue string
}{
	{
		key:           "TestLookup",
		expectedValue: "this can be anything you can imagine, but now it's just a string",
	},
	{
		key:           "NotExisting",
		expectedValue: "<nil>",
	},
}

func TestLookup(t *testing.T) {
	for i, e := range testsLookup {
		v := Lookup(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupString(context map[string]string, key string) string
var testsLookupString = []struct {
	key           string
	expectedValue string
}{
	{
		key:           "TestLookupString",
		expectedValue: "this should be a string",
	},
	{
		key:           "NotExisting",
		expectedValue: "",
	},
}

func TestLookupString(t *testing.T) {
	for i, e := range testsLookupString {
		v := LookupString(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupInt(context map[string]string, key string) int
var testsLookupInt = []struct {
	key           string
	expectedValue int
}{
	{
		key:           "ThisWillBeAnInt",
		expectedValue: 42,
	},
	{
		key:           "NotExisting",
		expectedValue: -1,
	},
}

func TestLookupInt(t *testing.T) {
	for i, e := range testsLookupInt {
		v := LookupInt(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupStringSlice(context map[string]string, key string) []string
var testsLookupStringSlice = []struct {
	key           string
	expectedValue []string
}{
	{
		// TestLookupStringSlice return slice is ordered
		key:           "TestLookupStringSlice",
		expectedValue: []string{"a", "am", "be", "going", "i", "slice", "to"},
	},
	{
		key:           "NotExisting",
		expectedValue: []string{""},
	},
}

func TestLookupStringSlice(t *testing.T) {
	for i, e := range testsLookupStringSlice {
		v := LookupStringSlice(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupStringMap(context map[string]string, key string) map[string]bool
var testsLookupStringMap = []struct {
	key           string
	expectedValue map[string]bool
}{
	{
		key: "TestLookupStringMap",
		expectedValue: map[string]bool{
			"to":              true,
			"be":              true,
			"a":               true,
			"map[string]bool": true,
			"I":               true,
			"want":            true,
		},
	},
	{
		key:           "NotExisting",
		expectedValue: map[string]bool{},
	},
}

func TestLookupStringMap(t *testing.T) {
	for i, e := range testsLookupStringMap {
		v := LookupStringMap(emptyContext, e.key)
		if !reflect.DeepEqual(v, e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

func configLookupHelper(map[string]string) []string {
	return []string{"this/thing/does/not/exist.json", ".testconfig.json"}
}

func TestMain(m *testing.M) {
	SetFileListBuilder(configLookupHelper)
	log.SetOutput(ioutil.Discard)
	os.Exit(m.Run())
}
<commit_msg>Cover more scenarios with tests.<commit_after>// +build go1.4

// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package config

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"testing"
	"time"
)

var emptyContext map[string]string

// func Lookup(context map[string]string, key string) interface{}
var testsLookup = []struct {
	key           string
	expectedValue string
}{
	{
		key:           "TestLookup",
		expectedValue: "this can be anything you can imagine, but now it's just a string",
	},
	{
		key:           "NotExisting",
		expectedValue: "<nil>",
	},
}

func TestLookup(t *testing.T) {
	for i, e := range testsLookup {
		v := c.Lookup(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupString(context map[string]string, key string) string
var testsLookupString = []struct {
	key           string
	expectedValue string
}{
	{
		key:           "TestLookupString",
		expectedValue: "this should be a string",
	},
	{
		key:           "NotExisting",
		expectedValue: "",
	},
}

func TestLookupString(t *testing.T) {
	for i, e := range testsLookupString {
		v := c.LookupString(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupInt(context map[string]string, key string) int
var testsLookupInt = []struct {
	key           string
	expectedValue int
}{
	{
		key:           "ThisWillBeAnInt",
		expectedValue: 42,
	},
	{
		key:           "NotExisting",
		expectedValue: -1,
	},
}

func TestLookupInt(t *testing.T) {
	for i, e := range testsLookupInt {
		v := c.LookupInt(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupStringSlice(context map[string]string, key string) []string
var testsLookupStringSlice = []struct {
	key           string
	expectedValue []string
}{
	{
		// TestLookupStringSlice return slice is ordered
		key:           "TestLookupStringSlice",
		expectedValue: []string{"a", "am", "be", "going", "i", "slice", "to"},
	},
	{
		key:           "NotExisting",
		expectedValue: []string{""},
	},
}

func TestLookupStringSlice(t *testing.T) {
	for i, e := range testsLookupStringSlice {
		v := c.LookupStringSlice(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupStringMap(context map[string]string, key string) map[string]bool
var testsLookupStringMap = []struct {
	key           string
	expectedValue map[string]bool
}{
	{
		key: "TestLookupStringMap",
		expectedValue: map[string]bool{
			"to":              true,
			"be":              true,
			"a":               true,
			"map[string]bool": true,
			"I":               true,
			"want":            true,
		},
	},
	{
		key:           "NotExisting",
		expectedValue: map[string]bool{},
	},
}

func TestLookupStringMap(t *testing.T) {
	for i, e := range testsLookupStringMap {
		v := c.LookupStringMap(emptyContext, e.key)
		if !reflect.DeepEqual(v, e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

func configLookupHelper(map[string]string) []string {
	return []string{"this/thing/does/not/exist.json", ".testconfig.json"}
}

func complicatedLookupHelper(map[string]string) []string {
	return []string{
		".to-be-removed.json",
		".to-be-replaced-with-gibberish.json",
		".to-be-chmoded-000.json",
		".to-be-replaced.json",
	}
}

var testEvents = []struct {
	desc   string
	action func()
	ev     int
}{
	{
		desc:   "remove file",
		action: func() { _ = os.Remove(".to-be-removed.json") },
		ev:     1,
	},
	{
		desc: "replace with gibberish",
		action: func() {
			_ = ioutil.WriteFile(".to-be-replaced-with-gibberish.json", []byte("!@#%^&!%@$&*#@@%*@^#$^&*@&(!"), 0644)
		},
		ev: 2,
	},
	{
		desc: "chmod 000",
		action: func() {
			_ = os.Chmod(".to-be-chmoded-000.json", 0000)
			_ = os.Chtimes(".to-be-chmoded-000.json", time.Now().Local(), time.Now().Local())
		},
		ev: 3,
	},
	{
		desc:   "replace",
		action: func() { _ = ioutil.WriteFile(".to-be-replaced.json", []byte("{\"testkey\":99}"), 0644) },
		ev:     99,
	},
}

func TestLookupvarConditions(t *testing.T) {
	// create files and put their parsed contents in cache
	for i := len(complicatedLookupHelper(emptyContext)) - 1; i >= 0; i-- {
		path := complicatedLookupHelper(emptyContext)[i]
		err := ioutil.WriteFile(path, []byte(fmt.Sprintf("{\"testkey\":%d}", i)), 0644)
		if err != nil {
			t.Log("file write failed")
		}

		if i != c2.LookupInt(emptyContext, "testkey") {
			t.Log("LookupInt did not return correct value", i)
			t.Fail()
		}
	}

	time.Sleep(time.Second) // make sure we get to update cache

	for _, e := range testEvents {
		e.action()
		v := c2.LookupInt(emptyContext, "testkey")
		if e.ev != v {
			t.Log("Test failed:", e.desc, "expected:", e.ev, "got:", v)
			t.Fail()
		}
	}

	for _, p := range complicatedLookupHelper(emptyContext) {
		_ = os.Remove(p)
	}
}

var c *Config = New()
var c2 *Config = New()

func TestMain(m *testing.M) {
	c.SetFileListBuilder(configLookupHelper)
	c2.SetFileListBuilder(complicatedLookupHelper)
	log.SetOutput(ioutil.Discard)
	os.Exit(m.Run())
}
<|endoftext|>


<commit_before>package config

import (
	"os"
	"strconv"
	"testing"

	"github.com/NYTimes/gizmo/config"
)

const (
	goodConfig1 = "testdata/goodconf1.json"
	goodConfig2 = "testdata/goodconf2.json"
)

func TestCheckConfigFromFile(t *testing.T) {
	tests := []struct {
		givenConfigFile     string
		wantedConfigFile    string
		wantedLogLevel      string
		wantedHTTPPort      int
		wantedHTTPAccessLog string
	}{
		{
			givenConfigFile:     goodConfig1,
			wantedConfigFile:    goodConfig1,
			wantedHTTPPort:      12345,
			wantedLogLevel:      "debug",
			wantedHTTPAccessLog: "/tmp/access.log",
		},
		{
			givenConfigFile:     goodConfig2,
			wantedConfigFile:    goodConfig2,
			wantedHTTPPort:      98765,
			wantedLogLevel:      "warning",
			wantedHTTPAccessLog: "/var/log/khronos/access.log",
		},
	}

	for _, test := range tests {
		cfg := &AppConfig{
			ConfigFilePath: test.givenConfigFile,
			Server:         &config.Server{},
		}
		cfg.loadConfigFromFile()

		if cfg.ConfigFilePath != test.wantedConfigFile {
			t.Errorf("Config file should be %s; obtained %s", test.wantedConfigFile, cfg.ConfigFilePath)
		}

		if cfg.LogLevel != test.wantedLogLevel {
			t.Errorf("Config log level should be %s; obtained %s", test.wantedLogLevel, cfg.LogLevel)
		}

		if cfg.HTTPPort != test.wantedHTTPPort {
			t.Errorf("Config http port should be %d; obtained %d", test.wantedHTTPPort, cfg.HTTPPort)
		}

		if cfg.HTTPAccessLog != test.wantedHTTPAccessLog {
			t.Errorf("Config http access log should be %s; obtained %s", test.wantedHTTPAccessLog, cfg.HTTPAccessLog)
		}
	}
}

func TestCheckConfigFromEnv(t *testing.T) {
	tests := []struct {
		givenLogLevel  string
		givenHTTPPort  int
		wantedLogLevel string
		wantedHTTPPort int
	}{
		{
			givenLogLevel:  "debug",
			givenHTTPPort:  12345,
			wantedLogLevel: "debug",
			wantedHTTPPort: 12345,
		},
		{
			givenLogLevel:  "warning",
			givenHTTPPort:  98765,
			wantedLogLevel: "warning",
			wantedHTTPPort: 98765,
		},
	}

	for _, test := range tests {
		os.Setenv("APP_LOG_LEVEL", test.givenLogLevel)
		os.Setenv("HTTP_PORT", strconv.Itoa(test.givenHTTPPort))

		cfg := &AppConfig{Server: &config.Server{}}
		cfg.loadConfigFromEnv()

		if cfg.LogLevel != test.wantedLogLevel {
			t.Errorf("Config log level should be %s; obtained %s", test.wantedLogLevel, cfg.LogLevel)
		}

		if cfg.HTTPPort != test.wantedHTTPPort {
			t.Errorf("Config http port should be %d; obtained %d", test.wantedHTTPPort, cfg.HTTPPort)
		}
	}
}

func TestConfigureApp(t *testing.T) {
	tests := []struct {
		givenConfigFile     string
		givenHTTPPort       int
		givenLogLevel       string
		wantedConfigFile    string
		wantedLogLevel      string
		wantedHTTPPort      int
		wantedHTTPAccessLog string
	}{
		{
			givenConfigFile: goodConfig1,
			givenHTTPPort:   4433,
			givenLogLevel:   "error",

			wantedConfigFile:    goodConfig1,
			wantedHTTPPort:      4433,
			wantedLogLevel:      "error",
			wantedHTTPAccessLog: "/tmp/access.log",
		},
		{
			givenConfigFile: goodConfig2,
			givenHTTPPort:   99888,
			givenLogLevel:   "info",

			wantedConfigFile:    goodConfig2,
			wantedHTTPPort:      99888,
			wantedLogLevel:      "info",
			wantedHTTPAccessLog: "/var/log/khronos/access.log",
		},
	}

	for _, test := range tests {
		os.Setenv("APP_LOG_LEVEL", test.givenLogLevel)
		os.Setenv("HTTP_PORT", strconv.Itoa(test.givenHTTPPort))

		cfg := &AppConfig{
			ConfigFilePath: test.givenConfigFile,
			Server:         &config.Server{},
		}
		cfg.ConfigureApp()

		// Check if correct merge of settings and priority is made
		if cfg.ConfigFilePath != test.wantedConfigFile {
			t.Errorf("Config file should be %s; obtained %s", test.wantedConfigFile, cfg.ConfigFilePath)
		}

		if cfg.LogLevel != test.wantedLogLevel {
			t.Errorf("Config log level should be %s; obtained %s", test.wantedLogLevel, cfg.LogLevel)
		}

		if cfg.HTTPPort != test.wantedHTTPPort {
			t.Errorf("Config http port should be %d; obtained %d", test.wantedHTTPPort, cfg.HTTPPort)
		}

		if cfg.HTTPAccessLog != test.wantedHTTPAccessLog {
			t.Errorf("Config http access log should be %s; obtained %s", test.wantedHTTPAccessLog, cfg.HTTPAccessLog)
		}
	}

}
<commit_msg>Fix config tests<commit_after>package config

import (
	"os"
	"strconv"
	"testing"

	"github.com/NYTimes/gizmo/config"
)

const (
	goodConfig1 = "testdata/goodconf1.json"
	goodConfig2 = "testdata/goodconf2.json"
)

func TestCheckConfigFromFile(t *testing.T) {
	tests := []struct {
		givenConfigFile     string
		wantedConfigFile    string
		wantedLogLevel      string
		wantedHTTPPort      int
		wantedHTTPAccessLog string
	}{
		{
			givenConfigFile:     goodConfig1,
			wantedConfigFile:    goodConfig1,
			wantedHTTPPort:      12345,
			wantedLogLevel:      "debug",
			wantedHTTPAccessLog: "/tmp/access.log",
		},
		{
			givenConfigFile:     goodConfig2,
			wantedConfigFile:    goodConfig2,
			wantedHTTPPort:      98765,
			wantedLogLevel:      "warning",
			wantedHTTPAccessLog: "/var/log/khronos/access.log",
		},
	}

	for _, test := range tests {
		cfg := &AppConfig{
			ConfigFilePath: test.givenConfigFile,
			Server:         &config.Server{},
			BoltDB:         &BoltDB{},
		}
		cfg.loadConfigFromFile()

		if cfg.ConfigFilePath != test.wantedConfigFile {
			t.Errorf("Config file should be %s; obtained %s", test.wantedConfigFile, cfg.ConfigFilePath)
		}

		if cfg.LogLevel != test.wantedLogLevel {
			t.Errorf("Config log level should be %s; obtained %s", test.wantedLogLevel, cfg.LogLevel)
		}

		if cfg.HTTPPort != test.wantedHTTPPort {
			t.Errorf("Config http port should be %d; obtained %d", test.wantedHTTPPort, cfg.HTTPPort)
		}

		if cfg.HTTPAccessLog != test.wantedHTTPAccessLog {
			t.Errorf("Config http access log should be %s; obtained %s", test.wantedHTTPAccessLog, cfg.HTTPAccessLog)
		}
	}
}

func TestCheckConfigFromEnv(t *testing.T) {
	tests := []struct {
		givenLogLevel  string
		givenHTTPPort  int
		wantedLogLevel string
		wantedHTTPPort int
	}{
		{
			givenLogLevel:  "debug",
			givenHTTPPort:  12345,
			wantedLogLevel: "debug",
			wantedHTTPPort: 12345,
		},
		{
			givenLogLevel:  "warning",
			givenHTTPPort:  98765,
			wantedLogLevel: "warning",
			wantedHTTPPort: 98765,
		},
	}

	for _, test := range tests {
		os.Setenv("APP_LOG_LEVEL", test.givenLogLevel)
		os.Setenv("HTTP_PORT", strconv.Itoa(test.givenHTTPPort))

		cfg := &AppConfig{
			Server: &config.Server{},
			BoltDB: &BoltDB{},
		}
		cfg.loadConfigFromEnv()

		if cfg.LogLevel != test.wantedLogLevel {
			t.Errorf("Config log level should be %s; obtained %s", test.wantedLogLevel, cfg.LogLevel)
		}

		if cfg.HTTPPort != test.wantedHTTPPort {
			t.Errorf("Config http port should be %d; obtained %d", test.wantedHTTPPort, cfg.HTTPPort)
		}
	}
}

func TestConfigureApp(t *testing.T) {
	tests := []struct {
		givenConfigFile     string
		givenHTTPPort       int
		givenLogLevel       string
		wantedConfigFile    string
		wantedLogLevel      string
		wantedHTTPPort      int
		wantedHTTPAccessLog string
	}{
		{
			givenConfigFile: goodConfig1,
			givenHTTPPort:   4433,
			givenLogLevel:   "error",

			wantedConfigFile:    goodConfig1,
			wantedHTTPPort:      4433,
			wantedLogLevel:      "error",
			wantedHTTPAccessLog: "/tmp/access.log",
		},
		{
			givenConfigFile: goodConfig2,
			givenHTTPPort:   99888,
			givenLogLevel:   "info",

			wantedConfigFile:    goodConfig2,
			wantedHTTPPort:      99888,
			wantedLogLevel:      "info",
			wantedHTTPAccessLog: "/var/log/khronos/access.log",
		},
	}

	for _, test := range tests {
		os.Setenv("APP_LOG_LEVEL", test.givenLogLevel)
		os.Setenv("HTTP_PORT", strconv.Itoa(test.givenHTTPPort))

		cfg := &AppConfig{
			ConfigFilePath: test.givenConfigFile,
			Server:         &config.Server{},
			BoltDB:         &BoltDB{},
		}
		cfg.ConfigureApp()

		// Check if correct merge of settings and priority is made
		if cfg.ConfigFilePath != test.wantedConfigFile {
			t.Errorf("Config file should be %s; obtained %s", test.wantedConfigFile, cfg.ConfigFilePath)
		}

		if cfg.LogLevel != test.wantedLogLevel {
			t.Errorf("Config log level should be %s; obtained %s", test.wantedLogLevel, cfg.LogLevel)
		}

		if cfg.HTTPPort != test.wantedHTTPPort {
			t.Errorf("Config http port should be %d; obtained %d", test.wantedHTTPPort, cfg.HTTPPort)
		}

		if cfg.HTTPAccessLog != test.wantedHTTPAccessLog {
			t.Errorf("Config http access log should be %s; obtained %s", test.wantedHTTPAccessLog, cfg.HTTPAccessLog)
		}
	}

}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes 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 reporter implements a reporter interface for gerrit
package reporter

import (
	"fmt"

	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/labels"

	"k8s.io/test-infra/prow/apis/prowjobs/v1"
	pjlister "k8s.io/test-infra/prow/client/listers/prowjobs/v1"
	"k8s.io/test-infra/prow/gerrit/client"
	"k8s.io/test-infra/prow/kube"
)

type gerritClient interface {
	SetReview(instance, id, revision, message string, labels map[string]string) error
}

// Client is a gerrit reporter client
type Client struct {
	gc     gerritClient
	lister pjlister.ProwJobLister
}

// NewReporter returns a reporter client
func NewReporter(cookiefilePath string, projects map[string][]string, lister pjlister.ProwJobLister) (*Client, error) {
	gc, err := client.NewClient(projects)
	if err != nil {
		return nil, err
	}
	gc.Start(cookiefilePath)
	return &Client{
		gc:     gc,
		lister: lister,
	}, nil
}

// GetName returns the name of the reporter
func (c *Client) GetName() string {
	return "gerrit-reporter"
}

// ShouldReport returns if this prowjob should be reported by the gerrit reporter
func (c *Client) ShouldReport(pj *v1.ProwJob) bool {

	if pj.Status.State == v1.TriggeredState || pj.Status.State == v1.PendingState {
		// not done yet
		return false
	}

	// has gerrit metadata (scheduled by gerrit adapter)
	if pj.ObjectMeta.Annotations[client.GerritID] == "" ||
		pj.ObjectMeta.Annotations[client.GerritInstance] == "" ||
		pj.ObjectMeta.Labels[client.GerritRevision] == "" {
		return false
	}

	// Only report when all other jobs on the same revision finished
	selector := labels.Set{client.GerritRevision: pj.ObjectMeta.Labels[client.GerritRevision]}
	pjs, err := c.lister.List(selector.AsSelector())
	if err != nil {
		logrus.WithError(err).Errorf("Cannot list prowjob with selector %v", selector)
		return false
	}

	for _, pj := range pjs {
		if pj.Status.State == v1.TriggeredState || pj.Status.State == v1.PendingState {
			// other jobs are still running on this revision, skip report
			return false
		}
	}

	return true
}

// Report will send the current prowjob status as a gerrit review
func (c *Client) Report(pj *v1.ProwJob) error {
	// If you are hitting here, which means the entire patchset has been finished :-)

	clientGerritRevision := client.GerritRevision
	clientGerritID := client.GerritID
	clientGerritInstance := client.GerritInstance
	pjTypeLabel := kube.ProwJobTypeLabel

	// list all prowjobs in the patchset matching pj's type (pre- or post-submit)
	selector := labels.Set{
		clientGerritRevision: pj.ObjectMeta.Labels[clientGerritRevision],
		pjTypeLabel:          pj.ObjectMeta.Labels[pjTypeLabel],
	}
	pjsOnRevision, err := c.lister.List(selector.AsSelector())
	if err != nil {
		logrus.WithError(err).Errorf("Cannot list prowjob with selector %v", selector)
		return err
	}

	// generate an aggregated report:
	total := len(pjsOnRevision)
	success := 0
	message := ""

	for _, pjOnRevision := range pjsOnRevision {
		if pjOnRevision.Status.PrevReportStates[c.GetName()] == pjOnRevision.Status.State {
			logrus.Infof("Revision %s has been reported already", pj.ObjectMeta.Labels[clientGerritRevision])
			return nil
		}

		if pjOnRevision.Status.State == v1.SuccessState {
			success++
		}

		message = fmt.Sprintf("%s\nJob %s finished with %s -- URL: %s", message, pjOnRevision.Spec.Job, pjOnRevision.Status.State, pjOnRevision.Status.URL)
	}

	message = fmt.Sprintf("%d out of %d jobs passed!\n%s", success, total, message)

	// report back
	gerritID := pj.ObjectMeta.Annotations[clientGerritID]
	gerritInstance := pj.ObjectMeta.Annotations[clientGerritInstance]
	gerritRevision := pj.ObjectMeta.Labels[clientGerritRevision]
	reportLabel := client.CodeReview
	if val, ok := pj.ObjectMeta.Labels[client.GerritReportLabel]; ok {
		reportLabel = val
	}

	vote := client.LBTM
	if success == total {
		vote = client.LGTM
	}
	labels := map[string]string{reportLabel: vote}

	logrus.Infof("Reporting to instance %s on id %s with message %s", gerritInstance, gerritID, message)
	if err := c.gc.SetReview(gerritInstance, gerritID, gerritRevision, message, labels); err != nil {
		logrus.WithError(err).Errorf("fail to set review with %s label on change ID %s", reportLabel, gerritID)

		// possibly don't have label permissions, try without labels
		message = fmt.Sprintf("[NOTICE]: Prow Bot cannot access %s label!\n%s", reportLabel, message)
		if err := c.gc.SetReview(gerritInstance, gerritID, gerritRevision, message, nil); err != nil {
			logrus.WithError(err).Errorf("fail to set plain review on change ID %s", gerritID)
			return err
		}
	}
	logrus.Infof("Review Complete")

	return nil
}
<commit_msg>filter by job type in ShouldReport<commit_after>/*
Copyright 2018 The Kubernetes 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 reporter implements a reporter interface for gerrit
package reporter

import (
	"fmt"

	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/labels"

	"k8s.io/test-infra/prow/apis/prowjobs/v1"
	pjlister "k8s.io/test-infra/prow/client/listers/prowjobs/v1"
	"k8s.io/test-infra/prow/gerrit/client"
	"k8s.io/test-infra/prow/kube"
)

type gerritClient interface {
	SetReview(instance, id, revision, message string, labels map[string]string) error
}

// Client is a gerrit reporter client
type Client struct {
	gc     gerritClient
	lister pjlister.ProwJobLister
}

// NewReporter returns a reporter client
func NewReporter(cookiefilePath string, projects map[string][]string, lister pjlister.ProwJobLister) (*Client, error) {
	gc, err := client.NewClient(projects)
	if err != nil {
		return nil, err
	}
	gc.Start(cookiefilePath)
	return &Client{
		gc:     gc,
		lister: lister,
	}, nil
}

// GetName returns the name of the reporter
func (c *Client) GetName() string {
	return "gerrit-reporter"
}

// ShouldReport returns if this prowjob should be reported by the gerrit reporter
func (c *Client) ShouldReport(pj *v1.ProwJob) bool {

	if pj.Status.State == v1.TriggeredState || pj.Status.State == v1.PendingState {
		// not done yet
		return false
	}

	// has gerrit metadata (scheduled by gerrit adapter)
	if pj.ObjectMeta.Annotations[client.GerritID] == "" ||
		pj.ObjectMeta.Annotations[client.GerritInstance] == "" ||
		pj.ObjectMeta.Labels[client.GerritRevision] == "" {
		return false
	}

	// Only report when all jobs of the same type on the same revision finished
	selector := labels.Set{
		client.GerritRevision: pj.ObjectMeta.Labels[client.GerritRevision],
		kube.ProwJobTypeLabel: pj.ObjectMeta.Labels[kube.ProwJobTypeLabel],
	}
	pjs, err := c.lister.List(selector.AsSelector())
	if err != nil {
		logrus.WithError(err).Errorf("Cannot list prowjob with selector %v", selector)
		return false
	}

	for _, pj := range pjs {
		if pj.Status.State == v1.TriggeredState || pj.Status.State == v1.PendingState {
			// other jobs are still running on this revision, skip report
			return false
		}
	}

	return true
}

// Report will send the current prowjob status as a gerrit review
func (c *Client) Report(pj *v1.ProwJob) error {
	// If you are hitting here, which means the entire patchset has been finished :-)

	clientGerritRevision := client.GerritRevision
	clientGerritID := client.GerritID
	clientGerritInstance := client.GerritInstance
	pjTypeLabel := kube.ProwJobTypeLabel

	// list all prowjobs in the patchset matching pj's type (pre- or post-submit)
	selector := labels.Set{
		clientGerritRevision: pj.ObjectMeta.Labels[clientGerritRevision],
		pjTypeLabel:          pj.ObjectMeta.Labels[pjTypeLabel],
	}
	pjsOnRevision, err := c.lister.List(selector.AsSelector())
	if err != nil {
		logrus.WithError(err).Errorf("Cannot list prowjob with selector %v", selector)
		return err
	}

	// generate an aggregated report:
	total := len(pjsOnRevision)
	success := 0
	message := ""

	for _, pjOnRevision := range pjsOnRevision {
		if pjOnRevision.Status.PrevReportStates[c.GetName()] == pjOnRevision.Status.State {
			logrus.Infof("Revision %s has been reported already", pj.ObjectMeta.Labels[clientGerritRevision])
			return nil
		}

		if pjOnRevision.Status.State == v1.SuccessState {
			success++
		}

		message = fmt.Sprintf("%s\nJob %s finished with %s -- URL: %s", message, pjOnRevision.Spec.Job, pjOnRevision.Status.State, pjOnRevision.Status.URL)
	}

	message = fmt.Sprintf("%d out of %d jobs passed!\n%s", success, total, message)

	// report back
	gerritID := pj.ObjectMeta.Annotations[clientGerritID]
	gerritInstance := pj.ObjectMeta.Annotations[clientGerritInstance]
	gerritRevision := pj.ObjectMeta.Labels[clientGerritRevision]
	reportLabel := client.CodeReview
	if val, ok := pj.ObjectMeta.Labels[client.GerritReportLabel]; ok {
		reportLabel = val
	}

	vote := client.LBTM
	if success == total {
		vote = client.LGTM
	}
	labels := map[string]string{reportLabel: vote}

	logrus.Infof("Reporting to instance %s on id %s with message %s", gerritInstance, gerritID, message)
	if err := c.gc.SetReview(gerritInstance, gerritID, gerritRevision, message, labels); err != nil {
		logrus.WithError(err).Errorf("fail to set review with %s label on change ID %s", reportLabel, gerritID)

		// possibly don't have label permissions, try without labels
		message = fmt.Sprintf("[NOTICE]: Prow Bot cannot access %s label!\n%s", reportLabel, message)
		if err := c.gc.SetReview(gerritInstance, gerritID, gerritRevision, message, nil); err != nil {
			logrus.WithError(err).Errorf("fail to set plain review on change ID %s", gerritID)
			return err
		}
	}
	logrus.Infof("Review Complete")

	return nil
}
<|endoftext|>


<commit_before>package config

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

	"github.com/bpicode/fritzctl/logger"
)

// Config stores client configuration of your FRITZ!Box
type Config struct {
	*Net
	*Login
	*Pki
}

// Net wraps the protocol://host:port data to contact the FRITZ!Box.
type Net struct {
	Protocol string `json:"protocol"` // The protocol to use when communicating with the FRITZ!Box. "http" or "https".
	Host     string `json:"host"`     // Host name or ip address of the FRITZ!Box. In most home setups "fritz.box" can be used. Other possible formats: "192.168.2.200".
	Port     string `json:"port"`     // Port to use for the HTTP interface. Leave empty for default values.
}

// Login wraps the login data to be used by the client.
type Login struct {
	LoginURL string `json:"loginURL"` // The URL for the login negotiation.
	Username string `json:"username"` // Username to log in. In user-agnostic setups this can be left empty.
	Password string `json:"password"` // The password corresponding to the Username.
}

// Pki wraps the client-side certificate handling.
type Pki struct {
	SkipTLSVerify   bool   `json:"skipTlsVerify"`   // Skip TLS verifcation when using https.
	CertificateFile string `json:"certificateFile"` // Points to a certificate file (in PEM format) to verify the integrity of the FRITZ!Box.
}

// New creates a new Config by reading from a file given by the path.
func New(path string) (*Config, error) {
	logger.Info("Reading config file", path)
	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("cannot open configuration file '%s': nested error is: %s", path, err)
	}
	conf := Config{}
	net := Net{}
	pki := Pki{}
	login := Login{}
	err = json.NewDecoder(file).Decode(&struct {
		*Net
		*Login
		*Pki
	}{&net, &login, &pki})
	conf.Pki = &pki
	conf.Login = &login
	conf.Net = &net
	if err != nil {
		return nil, fmt.Errorf("unable to parse configuration file '%s': nested error is: %s", path, err)
	}
	return &conf, nil
}

// GetLoginURL returns the URL that is queried for the login challenge
func (config *Config) GetLoginURL() string {
	return fmt.Sprintf("%s://%s:%s%s", config.Net.Protocol, config.Net.Host, config.Net.Port, config.Login.LoginURL)
}

// GetLoginResponseURL returns the URL that is queried for the login challenge
func (config *Config) GetLoginResponseURL(response string) string {
	return fmt.Sprintf("%s?response=%s&username=%s", config.GetLoginURL(), response, config.Login.Username)
}
<commit_msg>fix typo<commit_after>package config

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

	"github.com/bpicode/fritzctl/logger"
)

// Config stores client configuration of your FRITZ!Box
type Config struct {
	*Net
	*Login
	*Pki
}

// Net wraps the protocol://host:port data to contact the FRITZ!Box.
type Net struct {
	Protocol string `json:"protocol"` // The protocol to use when communicating with the FRITZ!Box. "http" or "https".
	Host     string `json:"host"`     // Host name or ip address of the FRITZ!Box. In most home setups "fritz.box" can be used. Other possible formats: "192.168.2.200".
	Port     string `json:"port"`     // Port to use for the HTTP interface. Leave empty for default values.
}

// Login wraps the login data to be used by the client.
type Login struct {
	LoginURL string `json:"loginURL"` // The URL for the login negotiation.
	Username string `json:"username"` // Username to log in. In user-agnostic setups this can be left empty.
	Password string `json:"password"` // The password corresponding to the Username.
}

// Pki wraps the client-side certificate handling.
type Pki struct {
	SkipTLSVerify   bool   `json:"skipTlsVerify"`   // Skip TLS verification when using https.
	CertificateFile string `json:"certificateFile"` // Points to a certificate file (in PEM format) to verify the integrity of the FRITZ!Box.
}

// New creates a new Config by reading from a file given by the path.
func New(path string) (*Config, error) {
	logger.Info("Reading config file", path)
	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("cannot open configuration file '%s': nested error is: %s", path, err)
	}
	conf := Config{}
	net := Net{}
	pki := Pki{}
	login := Login{}
	err = json.NewDecoder(file).Decode(&struct {
		*Net
		*Login
		*Pki
	}{&net, &login, &pki})
	conf.Pki = &pki
	conf.Login = &login
	conf.Net = &net
	if err != nil {
		return nil, fmt.Errorf("unable to parse configuration file '%s': nested error is: %s", path, err)
	}
	return &conf, nil
}

// GetLoginURL returns the URL that is queried for the login challenge
func (config *Config) GetLoginURL() string {
	return fmt.Sprintf("%s://%s:%s%s", config.Net.Protocol, config.Net.Host, config.Net.Port, config.Login.LoginURL)
}

// GetLoginResponseURL returns the URL that is queried for the login challenge
func (config *Config) GetLoginResponseURL(response string) string {
	return fmt.Sprintf("%s?response=%s&username=%s", config.GetLoginURL(), response, config.Login.Username)
}
<|endoftext|>


<commit_before>package slf4go

import (
    "testing"
    "time"
)

func TestGetLogger(t *testing.T) {
    logger := GetLogger("test")
    logger.Trace("are you prety?", true)
    logger.DebugF("are you prety? %t", true)
    logger.Info("how old are you? ", nil)
    logger.InfoF("i'm %010d", 18)
    logger.Warn("you aren't honest! ")
    logger.WarnF("haha%02d", 1000, nil)
    logger.Trace("set level to warn!!!!!")
    logger.SetLevel(LEVEL_WARN)
    logger.Trace("what?")
    logger.Info("what?")
    logger.Error("what?")
    logger.ErrorF("what?..$%s$", "XD")
    logger.FatalF("import cycle not allowed! %s", "shit...")
    logger.Fatal("never reach here")
    time.Sleep(time.Millisecond * 10)
}

func TestLoggerFormat(t *testing.T) {
    logger := GetLogger("test")
    logger.TraceF("arr: %v, %d, %s", []int{1, 2, 3}, 102, "haha")
    logger.TraceF("arr: %d, %d, %f", 123, 102, 122.33)
}
<commit_msg>benchmark IsEnable*<commit_after>package slf4go

import (
    "testing"
    "time"
)

func TestGetLogger(t *testing.T) {
    logger := GetLogger("test")
    logger.Trace("are you prety?", true)
    logger.DebugF("are you prety? %t", true)
    logger.Info("how old are you? ", nil)
    logger.InfoF("i'm %010d", 18)
    logger.Warn("you aren't honest! ")
    logger.WarnF("haha%02d", 1000, nil)
    logger.Trace("set level to warn!!!!!")
    logger.SetLevel(LEVEL_WARN)
    logger.Trace("what?")
    logger.Info("what?")
    logger.Error("what?")
    logger.ErrorF("what?..$%s$", "XD")
    logger.FatalF("import cycle not allowed! %s", "shit...")
    logger.Fatal("never reach here")
    time.Sleep(time.Millisecond * 10)
}

func TestLoggerFormat(t *testing.T) {
    logger := GetLogger("test")
    logger.TraceF("arr: %v, %d, %s", []int{1, 2, 3}, 102, "haha")
    logger.TraceF("arr: %d, %d, %f", 123, 102, 122.33)
}

/**
    BenchmarkLoggerCheckEnable-8      	500000000	         3.16 ns/op	       0 B/op	       0 allocs/op
    BenchmarkLoggerNotCheckEnable-8   	50000000	        32.9 ns/op	      16 B/op	       1 allocs/op
 */
func BenchmarkLoggerCheckEnable(b *testing.B) {
    logger := GetLogger("test")
    logger.SetLevel(LEVEL_INFO)
    b.ResetTimer()
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        if logger.IsEnableTrace() {
            logger.TraceF("this is a test, b: %v, ", b)
        }
    }
}
func BenchmarkLoggerNotCheckEnable(b *testing.B) {
    logger := GetLogger("test")
    logger.SetLevel(LEVEL_INFO)
    b.ResetTimer()
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        logger.TraceF("this is a test, b: %v, ", b)
    }
}
<|endoftext|>


<commit_before>/* Copyright (c) 2016 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package elasticsearch

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

	eveboxhttp "github.com/jasonish/evebox/http"

	"github.com/jasonish/evebox/log"
	"github.com/jasonish/evebox/resources"
	"github.com/pkg/errors"
	"io/ioutil"
)

type ElasticSearch struct {
	baseUrl  string
	username string
	password string

	EventSearchIndex string
	EventBaseIndex   string

	// These are filled on a ping request. Perhaps when creating an
	// instance of this object we should ping.
	VersionString string
	MajorVersion  int64

	// The keyword to use for "keyword" type queries. Older versions
	// of the Logstash template used "raw", newer ones use "keyword".
	keyword string

	HttpClient *eveboxhttp.HttpClient
}

func New(url string) *ElasticSearch {
	HttpClient := eveboxhttp.NewHttpClient()
	HttpClient.SetBaseUrl(url)

	es := &ElasticSearch{
		baseUrl:    url,
		HttpClient: HttpClient,
	}

	es.InitKeyword()

	return es
}

func (es *ElasticSearch) DisableCertCheck(disableCertCheck bool) {
	es.HttpClient.DisableCertCheck(disableCertCheck)
}

func (es *ElasticSearch) SetUsernamePassword(username ...string) error {
	return es.HttpClient.SetUsernamePassword(username...)
}

func (es *ElasticSearch) SetEventIndex(index string) {
	if strings.HasSuffix(index, "*") {
		baseIndex := strings.TrimSuffix(strings.TrimSuffix(index, "*"), "-")
		es.EventSearchIndex = index
		es.EventBaseIndex = baseIndex
	} else {
		es.EventBaseIndex = index
		es.EventSearchIndex = fmt.Sprintf("%s-*", index)
	}
	log.Info("Event base index: %s", es.EventBaseIndex)
	log.Info("Event search index: %s", es.EventSearchIndex)
}

func (es *ElasticSearch) Decode(response *http.Response, v interface{}) error {
	decoder := json.NewDecoder(response.Body)
	decoder.UseNumber()
	return decoder.Decode(v)
}

func (es *ElasticSearch) Ping() (*PingResponse, error) {

	response, err := es.HttpClient.Get("")
	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return nil, NewElasticSearchError(response)
	}

	var body PingResponse
	if err := es.Decode(response, &body); err != nil {
		return nil, err
	}
	return &body, nil
}

func (es *ElasticSearch) CheckTemplate(name string) (exists bool, err error) {
	response, err := es.HttpClient.Head(fmt.Sprintf("_template/%s", name))
	if err != nil {
		return exists, err
	}
	exists = response.StatusCode == 200
	return exists, nil
}

func (es *ElasticSearch) GetTemplate(name string) (JsonMap, error) {
	response, err := es.HttpClient.Get(fmt.Sprintf("_template/%s", name))
	if err != nil {
		return nil, err
	}

	template := JsonMap{}

	if err := es.Decode(response, &template); err != nil {
		return nil, err
	}

	return template, nil
}

// GetKeywordType is a crude way of determining if the template is using
// Logstash 5 keyword type, or Logstash 2 "raw" type.
func (es *ElasticSearch) GetKeywordType(index string) (string, error) {
	if index == "" {
		index = es.EventBaseIndex
	}
	template, err := es.GetTemplate(index)
	if err != nil {
		log.Warning("Failed to get template from Elastic Search, will use \"raw\" for keyword")
		return "raw", nil
	}

	dynamicTemplates := template.GetMap(index).
		GetMap("mappings").
		GetMap("_default_").
		GetMapList("dynamic_templates")
	for _, entry := range dynamicTemplates {
		if entry["string_fields"] != nil {
			mappingType := entry.GetMap("string_fields").
				GetMap("mapping").
				GetMap("fields").
				GetMap("keyword").
				Get("type")
			if mappingType == "keyword" {
				return "keyword", nil
			}
		}
	}
	return "raw", nil
}

func (es *ElasticSearch) InitKeyword() error {
	keyword, err := es.GetKeywordType(es.EventBaseIndex)
	if err != nil {
		return err
	}
	es.keyword = keyword
	log.Info("Elastic Search keyword initialized to \"%s\"", es.keyword)
	return nil
}

func (es *ElasticSearch) LoadTemplate(index string, majorVersion int64) error {

	var templateFilename string

	if majorVersion == 5 {
		templateFilename = "template-es5x.json"
	} else if majorVersion == 2 {
		templateFilename = "template-es2x.json"
	} else {
		return fmt.Errorf("No template for Elastic Search with major version %d", majorVersion)
	}

	templateFile, err := resources.GetReader(fmt.Sprintf("elasticsearch/%s", templateFilename))
	if err != nil {
		return err
	}

	decoder := json.NewDecoder(templateFile)
	decoder.UseNumber()

	var template map[string]interface{}
	err = decoder.Decode(&template)
	if err != nil {
		return err
	}
	template["template"] = fmt.Sprintf("%s-*", index)

	response, err := es.HttpClient.PutJson(fmt.Sprintf("_template/%s", index), template)
	if err != nil {
		return err
	}
	if response.StatusCode != http.StatusOK {
		return NewElasticSearchError(response)
	}
	es.HttpClient.DiscardResponse(response)

	// Success.
	return nil
}

type DatastoreError struct {
	Message string
	Cause   error
}

func (e *DatastoreError) Error() string {
	if e.Message != "" && e.Cause != nil {
		return fmt.Sprintf("%s: %s", e.Message, e.Cause.Error())
	} else if e.Message != "" {
		return e.Message
	} else if e.Cause != nil {
		return e.Cause.Error()
	}
	return ""
}

func (es *ElasticSearch) Search(query interface{}) (*SearchResponse, error) {
	path := fmt.Sprintf("%s/_search", es.EventSearchIndex)
	response, err := es.HttpClient.PostJson(path, query)
	if err != nil {
		return nil, errors.WithStack(&DatastoreError{
			Message: "Failed to connect to Elastic Search",
			Cause:   err,
		})
	}
	result := SearchResponse{}
	if err := es.Decode(response, &result); err != nil {
		log.Println("Failed to decode response...")
		return nil, err
	}
	return &result, nil
}

func (es *ElasticSearch) SearchScroll(body interface{}, duration string) (*SearchResponse, error) {
	path := fmt.Sprintf("%s/_search?scroll=%s", es.EventSearchIndex, duration)
	response, err := es.HttpClient.PostJson(path, body)
	if err != nil {
		return nil, err
	}
	if response.StatusCode != http.StatusOK {
		return nil, NewElasticSearchError(response)
	}
	searchResponse := SearchResponse{}
	if err := DecodeResponse(response.Body, &searchResponse); err != nil {
		return nil, err
	}
	return &searchResponse, nil
}

func (es *ElasticSearch) Scroll(scrollId string, duration string) (*SearchResponse, error) {
	body := m{
		"scroll_id": scrollId,
		"scroll":    duration,
	}
	response, err := es.HttpClient.PostJson("_search/scroll", body)
	if err != nil {
		return nil, err
	}
	searchResponse := SearchResponse{}
	if err := DecodeResponse(response.Body, &searchResponse); err != nil {
		return nil, err
	}
	return &searchResponse, nil
}

func (es *ElasticSearch) DeleteScroll(scrollId string) (*http.Response, error) {
	return es.HttpClient.Delete("_search/scroll", "application/json",
		strings.NewReader(scrollId))
}

func (es *ElasticSearch) PartialUpdate(index string, doctype string, id string,
	doc interface{}) (*http.Response, error) {
	body := map[string]interface{}{
		"doc": doc,
	}
	return es.HttpClient.PostJson(fmt.Sprintf("%s/%s/%s/_update?refresh=true",
		index, doctype, id), body)
}

// Refresh refreshes all indices logging any error but not returning and
// discarding the response so the caller doesn't have to.
func (es *ElasticSearch) Refresh() {
	response, err := es.HttpClient.PostString("_refresh", "application/json", "{}")
	if err != nil {
		log.Error("Failed to refresh Elastic Search: %v", err)
		return
	}
	io.Copy(ioutil.Discard, response.Body)
}

func DecodeResponse(reader io.Reader, output interface{}) error {
	decoder := json.NewDecoder(reader)
	decoder.UseNumber()
	return decoder.Decode(output)
}
<commit_msg>es: delay keyword resolution until we can connect to es<commit_after>/* Copyright (c) 2016 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package elasticsearch

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

	eveboxhttp "github.com/jasonish/evebox/http"

	"github.com/jasonish/evebox/log"
	"github.com/jasonish/evebox/resources"
	"github.com/pkg/errors"
	"io/ioutil"
)

type ElasticSearch struct {
	baseUrl  string
	username string
	password string

	EventSearchIndex string
	EventBaseIndex   string

	// These are filled on a ping request. Perhaps when creating an
	// instance of this object we should ping.
	VersionString string
	MajorVersion  int64

	// The keyword to use for "keyword" type queries. Older versions
	// of the Logstash template used "raw", newer ones use "keyword".
	keyword string

	HttpClient *eveboxhttp.HttpClient
}

func New(url string) *ElasticSearch {
	HttpClient := eveboxhttp.NewHttpClient()
	HttpClient.SetBaseUrl(url)

	es := &ElasticSearch{
		baseUrl:    url,
		HttpClient: HttpClient,
	}

	es.InitKeyword()

	return es
}

func (es *ElasticSearch) DisableCertCheck(disableCertCheck bool) {
	es.HttpClient.DisableCertCheck(disableCertCheck)
}

func (es *ElasticSearch) SetUsernamePassword(username ...string) error {
	return es.HttpClient.SetUsernamePassword(username...)
}

func (es *ElasticSearch) SetEventIndex(index string) {
	if strings.HasSuffix(index, "*") {
		baseIndex := strings.TrimSuffix(strings.TrimSuffix(index, "*"), "-")
		es.EventSearchIndex = index
		es.EventBaseIndex = baseIndex
	} else {
		es.EventBaseIndex = index
		es.EventSearchIndex = fmt.Sprintf("%s-*", index)
	}
	log.Info("Event base index: %s", es.EventBaseIndex)
	log.Info("Event search index: %s", es.EventSearchIndex)
}

func (es *ElasticSearch) Decode(response *http.Response, v interface{}) error {
	decoder := json.NewDecoder(response.Body)
	decoder.UseNumber()
	return decoder.Decode(v)
}

func (es *ElasticSearch) Ping() (*PingResponse, error) {

	response, err := es.HttpClient.Get("")
	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return nil, NewElasticSearchError(response)
	}

	var body PingResponse
	if err := es.Decode(response, &body); err != nil {
		return nil, err
	}
	return &body, nil
}

func (es *ElasticSearch) CheckTemplate(name string) (exists bool, err error) {
	response, err := es.HttpClient.Head(fmt.Sprintf("_template/%s", name))
	if err != nil {
		return exists, err
	}
	exists = response.StatusCode == 200
	return exists, nil
}

func (es *ElasticSearch) GetTemplate(name string) (JsonMap, error) {
	response, err := es.HttpClient.Get(fmt.Sprintf("_template/%s", name))
	if err != nil {
		return nil, err
	}

	template := JsonMap{}

	if err := es.Decode(response, &template); err != nil {
		return nil, err
	}

	return template, nil
}

// GetKeywordType is a crude way of determining if the template is using
// Logstash 5 keyword type, or Logstash 2 "raw" type.
func (es *ElasticSearch) GetKeywordType(index string) (string, error) {
	if index == "" {
		index = es.EventBaseIndex
	}
	template, err := es.GetTemplate(index)
	if err != nil {
		log.Warning("Failed to get template from Elastic Search, keyword resolution delayed.")
		return "", nil
	}

	dynamicTemplates := template.GetMap(index).
		GetMap("mappings").
		GetMap("_default_").
		GetMapList("dynamic_templates")
	for _, entry := range dynamicTemplates {
		if entry["string_fields"] != nil {
			mappingType := entry.GetMap("string_fields").
				GetMap("mapping").
				GetMap("fields").
				GetMap("keyword").
				Get("type")
			if mappingType == "keyword" {
				return "keyword", nil
			}
		}
	}
	return "raw", nil
}

func (es *ElasticSearch) InitKeyword() error {
	keyword, err := es.GetKeywordType(es.EventBaseIndex)
	if err != nil {
		return err
	}
	es.keyword = keyword
	log.Info("Elastic Search keyword initialized to \"%s\"", es.keyword)
	return nil
}

func (es *ElasticSearch) LoadTemplate(index string, majorVersion int64) error {

	var templateFilename string

	if majorVersion == 5 {
		templateFilename = "template-es5x.json"
	} else if majorVersion == 2 {
		templateFilename = "template-es2x.json"
	} else {
		return fmt.Errorf("No template for Elastic Search with major version %d", majorVersion)
	}

	templateFile, err := resources.GetReader(fmt.Sprintf("elasticsearch/%s", templateFilename))
	if err != nil {
		return err
	}

	decoder := json.NewDecoder(templateFile)
	decoder.UseNumber()

	var template map[string]interface{}
	err = decoder.Decode(&template)
	if err != nil {
		return err
	}
	template["template"] = fmt.Sprintf("%s-*", index)

	response, err := es.HttpClient.PutJson(fmt.Sprintf("_template/%s", index), template)
	if err != nil {
		return err
	}
	if response.StatusCode != http.StatusOK {
		return NewElasticSearchError(response)
	}
	es.HttpClient.DiscardResponse(response)

	// Success.
	return nil
}

type DatastoreError struct {
	Message string
	Cause   error
}

func (e *DatastoreError) Error() string {
	if e.Message != "" && e.Cause != nil {
		return fmt.Sprintf("%s: %s", e.Message, e.Cause.Error())
	} else if e.Message != "" {
		return e.Message
	} else if e.Cause != nil {
		return e.Cause.Error()
	}
	return ""
}

func (es *ElasticSearch) Search(query interface{}) (*SearchResponse, error) {

	if es.keyword == "" {
		log.Warning("Search keyword not known, trying again.")
		es.InitKeyword()
	}

	path := fmt.Sprintf("%s/_search", es.EventSearchIndex)
	response, err := es.HttpClient.PostJson(path, query)
	if err != nil {
		return nil, errors.WithStack(&DatastoreError{
			Message: "Failed to connect to Elastic Search",
			Cause:   err,
		})
	}
	result := SearchResponse{}
	if err := es.Decode(response, &result); err != nil {
		log.Println("Failed to decode response...")
		return nil, err
	}
	return &result, nil
}

func (es *ElasticSearch) SearchScroll(body interface{}, duration string) (*SearchResponse, error) {
	path := fmt.Sprintf("%s/_search?scroll=%s", es.EventSearchIndex, duration)
	response, err := es.HttpClient.PostJson(path, body)
	if err != nil {
		return nil, err
	}
	if response.StatusCode != http.StatusOK {
		return nil, NewElasticSearchError(response)
	}
	searchResponse := SearchResponse{}
	if err := DecodeResponse(response.Body, &searchResponse); err != nil {
		return nil, err
	}
	return &searchResponse, nil
}

func (es *ElasticSearch) Scroll(scrollId string, duration string) (*SearchResponse, error) {
	body := m{
		"scroll_id": scrollId,
		"scroll":    duration,
	}
	response, err := es.HttpClient.PostJson("_search/scroll", body)
	if err != nil {
		return nil, err
	}
	searchResponse := SearchResponse{}
	if err := DecodeResponse(response.Body, &searchResponse); err != nil {
		return nil, err
	}
	return &searchResponse, nil
}

func (es *ElasticSearch) DeleteScroll(scrollId string) (*http.Response, error) {
	return es.HttpClient.Delete("_search/scroll", "application/json",
		strings.NewReader(scrollId))
}

func (es *ElasticSearch) PartialUpdate(index string, doctype string, id string,
	doc interface{}) (*http.Response, error) {
	body := map[string]interface{}{
		"doc": doc,
	}
	return es.HttpClient.PostJson(fmt.Sprintf("%s/%s/%s/_update?refresh=true",
		index, doctype, id), body)
}

// Refresh refreshes all indices logging any error but not returning and
// discarding the response so the caller doesn't have to.
func (es *ElasticSearch) Refresh() {
	response, err := es.HttpClient.PostString("_refresh", "application/json", "{}")
	if err != nil {
		log.Error("Failed to refresh Elastic Search: %v", err)
		return
	}
	io.Copy(ioutil.Discard, response.Body)
}

func DecodeResponse(reader io.Reader, output interface{}) error {
	decoder := json.NewDecoder(reader)
	decoder.UseNumber()
	return decoder.Decode(output)
}
<|endoftext|>


<commit_before>// Package rest implements a simple REST wrapper
//
// All methods are safe for concurrent calling.
package rest

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"sync"

	"github.com/ncw/rclone/fs"
	"github.com/pkg/errors"
)

// Client contains the info to sustain the API
type Client struct {
	mu           sync.RWMutex
	c            *http.Client
	rootURL      string
	errorHandler func(resp *http.Response) error
	headers      map[string]string
}

// NewClient takes an oauth http.Client and makes a new api instance
func NewClient(c *http.Client) *Client {
	api := &Client{
		c:            c,
		errorHandler: defaultErrorHandler,
		headers:      make(map[string]string),
	}
	return api
}

// ReadBody reads resp.Body into result, closing the body
func ReadBody(resp *http.Response) (result []byte, err error) {
	defer fs.CheckClose(resp.Body, &err)
	return ioutil.ReadAll(resp.Body)
}

// defaultErrorHandler doesn't attempt to parse the http body, just
// returns it in the error message
func defaultErrorHandler(resp *http.Response) (err error) {
	body, err := ReadBody(resp)
	if err != nil {
		return errors.Wrap(err, "error reading error out of body")
	}
	return errors.Errorf("HTTP error %v (%v) returned body: %q", resp.StatusCode, resp.Status, body)
}

// SetErrorHandler sets the handler to decode an error response when
// the HTTP status code is not 2xx.  The handler should close resp.Body.
func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) *Client {
	api.mu.Lock()
	defer api.mu.Unlock()
	api.errorHandler = fn
	return api
}

// SetRoot sets the default root URL
func (api *Client) SetRoot(RootURL string) *Client {
	api.mu.Lock()
	defer api.mu.Unlock()
	api.rootURL = RootURL
	return api
}

// SetHeader sets a header for all requests
func (api *Client) SetHeader(key, value string) *Client {
	api.mu.Lock()
	defer api.mu.Unlock()
	api.headers[key] = value
	return api
}

// Opts contains parameters for Call, CallJSON etc
type Opts struct {
	Method        string
	Path          string
	Absolute      bool // Path is absolute
	Body          io.Reader
	NoResponse    bool // set to close Body
	ContentType   string
	ContentLength *int64
	ContentRange  string
	ExtraHeaders  map[string]string
	UserName      string // username for Basic Auth
	Password      string // password for Basic Auth
	Options       []fs.OpenOption
	IgnoreStatus  bool // if set then we don't check error status or parse error body
}

// DecodeJSON decodes resp.Body into result
func DecodeJSON(resp *http.Response, result interface{}) (err error) {
	defer fs.CheckClose(resp.Body, &err)
	decoder := json.NewDecoder(resp.Body)
	return decoder.Decode(result)
}

// ClientWithHeaderReset makes a new http client which resets the
// headers passed in on redirect
//
// FIXME This is now unecessary with go1.8
func ClientWithHeaderReset(c *http.Client, headers map[string]string) *http.Client {
	if len(headers) == 0 {
		return c
	}
	clientCopy := *c
	clientCopy.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		if len(via) >= 10 {
			return errors.New("stopped after 10 redirects")
		}
		// Reset the headers in the new request
		for k, v := range headers {
			if v != "" {
				req.Header.Set(k, v)
			}
		}
		return nil
	}
	return &clientCopy
}

// Call makes the call and returns the http.Response
//
// if err != nil then resp.Body will need to be closed
//
// it will return resp if at all possible, even if err is set
func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
	api.mu.RLock()
	defer api.mu.RUnlock()
	if opts == nil {
		return nil, errors.New("call() called with nil opts")
	}
	var url string
	if opts.Absolute {
		url = opts.Path
	} else {
		if api.rootURL == "" {
			return nil, errors.New("RootURL not set")
		}
		url = api.rootURL + opts.Path
	}
	req, err := http.NewRequest(opts.Method, url, opts.Body)
	if err != nil {
		return
	}
	headers := make(map[string]string)
	// Set default headers
	for k, v := range api.headers {
		headers[k] = v
	}
	if opts.ContentType != "" {
		headers["Content-Type"] = opts.ContentType
	}
	if opts.ContentLength != nil {
		req.ContentLength = *opts.ContentLength
	}
	if opts.ContentRange != "" {
		headers["Content-Range"] = opts.ContentRange
	}
	// Set any extra headers
	if opts.ExtraHeaders != nil {
		for k, v := range opts.ExtraHeaders {
			headers[k] = v
		}
	}
	// add any options to the headers
	fs.OpenOptionAddHeaders(opts.Options, headers)
	// Now set the headers
	for k, v := range headers {
		if v != "" {
			req.Header.Add(k, v)
		}
	}
	if opts.UserName != "" || opts.Password != "" {
		req.SetBasicAuth(opts.UserName, opts.Password)
	}
	c := ClientWithHeaderReset(api.c, headers)
	api.mu.RUnlock()
	resp, err = c.Do(req)
	api.mu.RLock()
	if err != nil {
		return nil, err
	}
	if !opts.IgnoreStatus {
		if resp.StatusCode < 200 || resp.StatusCode > 299 {
			return resp, api.errorHandler(resp)
		}
	}
	if opts.NoResponse {
		return resp, resp.Body.Close()
	}
	return resp, nil
}

// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
//
// If request is not nil then it will be JSON encoded as the body of the request
//
// It will return resp if at all possible, even if err is set
func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
	// Set the body up as a JSON object if required
	if opts.Body == nil && request != nil {
		body, err := json.Marshal(request)
		if err != nil {
			return nil, err
		}
		var newOpts = *opts
		newOpts.Body = bytes.NewBuffer(body)
		newOpts.ContentType = "application/json"
		opts = &newOpts
	}
	resp, err = api.Call(opts)
	if err != nil {
		return resp, err
	}
	if response == nil || opts.NoResponse {
		return resp, nil
	}
	err = DecodeJSON(resp, response)
	return resp, err
}
<commit_msg>rest: implement multipart uploads<commit_after>// Package rest implements a simple REST wrapper
//
// All methods are safe for concurrent calling.
package rest

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"sync"

	"github.com/ncw/rclone/fs"
	"github.com/pkg/errors"
)

// Client contains the info to sustain the API
type Client struct {
	mu           sync.RWMutex
	c            *http.Client
	rootURL      string
	errorHandler func(resp *http.Response) error
	headers      map[string]string
}

// NewClient takes an oauth http.Client and makes a new api instance
func NewClient(c *http.Client) *Client {
	api := &Client{
		c:            c,
		errorHandler: defaultErrorHandler,
		headers:      make(map[string]string),
	}
	return api
}

// ReadBody reads resp.Body into result, closing the body
func ReadBody(resp *http.Response) (result []byte, err error) {
	defer fs.CheckClose(resp.Body, &err)
	return ioutil.ReadAll(resp.Body)
}

// defaultErrorHandler doesn't attempt to parse the http body, just
// returns it in the error message
func defaultErrorHandler(resp *http.Response) (err error) {
	body, err := ReadBody(resp)
	if err != nil {
		return errors.Wrap(err, "error reading error out of body")
	}
	return errors.Errorf("HTTP error %v (%v) returned body: %q", resp.StatusCode, resp.Status, body)
}

// SetErrorHandler sets the handler to decode an error response when
// the HTTP status code is not 2xx.  The handler should close resp.Body.
func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) *Client {
	api.mu.Lock()
	defer api.mu.Unlock()
	api.errorHandler = fn
	return api
}

// SetRoot sets the default root URL
func (api *Client) SetRoot(RootURL string) *Client {
	api.mu.Lock()
	defer api.mu.Unlock()
	api.rootURL = RootURL
	return api
}

// SetHeader sets a header for all requests
func (api *Client) SetHeader(key, value string) *Client {
	api.mu.Lock()
	defer api.mu.Unlock()
	api.headers[key] = value
	return api
}

// Opts contains parameters for Call, CallJSON etc
type Opts struct {
	Method                string
	Path                  string
	Absolute              bool // Path is absolute
	Body                  io.Reader
	NoResponse            bool // set to close Body
	ContentType           string
	ContentLength         *int64
	ContentRange          string
	ExtraHeaders          map[string]string
	UserName              string // username for Basic Auth
	Password              string // password for Basic Auth
	Options               []fs.OpenOption
	IgnoreStatus          bool   // if set then we don't check error status or parse error body
	MultipartMetadataName string // set the following 3 vars
	MultipartContentName  string // and Body and pass in request
	MultipartFileName     string // for multipart upload
}

// Copy creates a copy of the options
func (o *Opts) Copy() *Opts {
	newOpts := *o
	return &newOpts
}

// DecodeJSON decodes resp.Body into result
func DecodeJSON(resp *http.Response, result interface{}) (err error) {
	defer fs.CheckClose(resp.Body, &err)
	decoder := json.NewDecoder(resp.Body)
	return decoder.Decode(result)
}

// ClientWithHeaderReset makes a new http client which resets the
// headers passed in on redirect
//
// FIXME This is now unecessary with go1.8
func ClientWithHeaderReset(c *http.Client, headers map[string]string) *http.Client {
	if len(headers) == 0 {
		return c
	}
	clientCopy := *c
	clientCopy.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		if len(via) >= 10 {
			return errors.New("stopped after 10 redirects")
		}
		// Reset the headers in the new request
		for k, v := range headers {
			if v != "" {
				req.Header.Set(k, v)
			}
		}
		return nil
	}
	return &clientCopy
}

// Call makes the call and returns the http.Response
//
// if err != nil then resp.Body will need to be closed
//
// it will return resp if at all possible, even if err is set
func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
	api.mu.RLock()
	defer api.mu.RUnlock()
	if opts == nil {
		return nil, errors.New("call() called with nil opts")
	}
	var url string
	if opts.Absolute {
		url = opts.Path
	} else {
		if api.rootURL == "" {
			return nil, errors.New("RootURL not set")
		}
		url = api.rootURL + opts.Path
	}
	req, err := http.NewRequest(opts.Method, url, opts.Body)
	if err != nil {
		return
	}
	headers := make(map[string]string)
	// Set default headers
	for k, v := range api.headers {
		headers[k] = v
	}
	if opts.ContentType != "" {
		headers["Content-Type"] = opts.ContentType
	}
	if opts.ContentLength != nil {
		req.ContentLength = *opts.ContentLength
	}
	if opts.ContentRange != "" {
		headers["Content-Range"] = opts.ContentRange
	}
	// Set any extra headers
	if opts.ExtraHeaders != nil {
		for k, v := range opts.ExtraHeaders {
			headers[k] = v
		}
	}
	// add any options to the headers
	fs.OpenOptionAddHeaders(opts.Options, headers)
	// Now set the headers
	for k, v := range headers {
		if v != "" {
			req.Header.Add(k, v)
		}
	}
	if opts.UserName != "" || opts.Password != "" {
		req.SetBasicAuth(opts.UserName, opts.Password)
	}
	c := ClientWithHeaderReset(api.c, headers)
	api.mu.RUnlock()
	resp, err = c.Do(req)
	api.mu.RLock()
	if err != nil {
		return nil, err
	}
	if !opts.IgnoreStatus {
		if resp.StatusCode < 200 || resp.StatusCode > 299 {
			return resp, api.errorHandler(resp)
		}
	}
	if opts.NoResponse {
		return resp, resp.Body.Close()
	}
	return resp, nil
}

// CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
//
// If request is not nil then it will be JSON encoded as the body of the request
//
// It will return resp if at all possible, even if err is set
func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
	var requestBody []byte
	// Marshal the request if given
	if request != nil {
		opts = opts.Copy()
		requestBody, err = json.Marshal(request)
		opts.ContentType = "application/json"
		if err != nil {
			return nil, err
		}
		// Set the body up as a JSON object if required
		if opts.Body == nil {
			opts.Body = bytes.NewBuffer(requestBody)
		}
	}
	errChan := make(chan error, 1)
	isMultipart := opts.MultipartMetadataName != "" && opts.Body != nil && request != nil
	if isMultipart {
		bodyReader, bodyWriter := io.Pipe()
		writer := multipart.NewWriter(bodyWriter)
		opts.ContentType = writer.FormDataContentType()
		in := opts.Body
		opts.Body = bodyReader
		go func() {
			defer func() { _ = bodyWriter.Close() }()
			var err error

			// Create the first part
			err = writer.WriteField(opts.MultipartMetadataName, string(requestBody))
			if err != nil {
				errChan <- err
				return
			}

			// Add the file part
			part, err := writer.CreateFormFile(opts.MultipartContentName, opts.MultipartFileName)
			if err != nil {
				errChan <- err
				return
			}

			// Copy it in
			if _, err := io.Copy(part, in); err != nil {
				errChan <- err
				return
			}
			errChan <- writer.Close()
		}()
	}
	resp, err = api.Call(opts)
	if err != nil {
		return resp, err
	}
	if response == nil || opts.NoResponse {
		return resp, nil
	}
	if isMultipart {
		err = <-errChan
		if err != nil {
			return resp, err
		}
	}
	err = DecodeJSON(resp, response)
	return resp, err
}
<|endoftext|>