<commit_before>package bind

import (
	"bytes"
	"flag"
	"go/ast"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

func init() {
	log.SetFlags(log.Lshortfile)
}

var updateFlag = flag.Bool("update", false, "Update the golden files.")

var tests = []string{
	"testdata/basictypes.go",
	"testdata/structs.go",
	"testdata/interfaces.go",
	"testdata/issue10788.go",
	"testdata/issue12328.go",
	"testdata/issue12403.go",
	"testdata/try.go",
	"testdata/vars.go",
}

var fset = token.NewFileSet()

func typeCheck(t *testing.T, filename string) *types.Package {
	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
	if err != nil {
		t.Fatalf("%s: %v", filename, err)
	}

	pkgName := filepath.Base(filename)
	pkgName = strings.TrimSuffix(pkgName, ".go")

	// typecheck and collect typechecker errors
	var conf types.Config
	conf.Error = func(err error) {
		t.Error(err)
	}
	pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil)
	if err != nil {
		t.Fatal(err)
	}
	return pkg
}

// diff runs the command "diff a b" and returns its output
func diff(a, b string) string {
	var buf bytes.Buffer
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "plan9":
		cmd = exec.Command("/bin/diff", "-c", a, b)
	default:
		cmd = exec.Command("/usr/bin/diff", "-u", a, b)
	}
	cmd.Stdout = &buf
	cmd.Stderr = &buf
	cmd.Run()
	return buf.String()
}

func writeTempFile(t *testing.T, name string, contents []byte) string {
	f, err := ioutil.TempFile("", name)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := f.Write(contents); err != nil {
		t.Fatal(err)
	}
	if err := f.Close(); err != nil {
		t.Fatal(err)
	}
	return f.Name()
}

func TestGenObjc(t *testing.T) {
	var suffixes = map[fileType]string{
		ObjcH:   ".objc.h.golden",
		ObjcM:   ".objc.m.golden",
		ObjcGoH: ".objc.go.h.golden",
	}

	for _, filename := range tests {
		pkg := typeCheck(t, filename)

		for typ, suffix := range suffixes {
			var buf bytes.Buffer
			if err := GenObjc(&buf, fset, pkg, []*types.Package{pkg}, "", typ); err != nil {
				t.Errorf("%s: %v", filename, err)
				continue
			}
			out := writeTempFile(t, "generated"+suffix, buf.Bytes())
			defer os.Remove(out)
			golden := filename[:len(filename)-len(".go")] + suffix
			if diffstr := diff(golden, out); diffstr != "" {
				t.Errorf("%s: does not match Objective-C golden:\n%s", filename, diffstr)
				if *updateFlag {
					t.Logf("Updating %s...", golden)
					err := exec.Command("/bin/cp", out, golden).Run()
					if err != nil {
						t.Errorf("Update failed: %s", err)
					}
				}
			}
		}
	}
}

func TestGenJava(t *testing.T) {
	var suffixes = map[fileType]string{
		Java:  ".java.golden",
		JavaC: ".java.c.golden",
		JavaH: ".java.h.golden",
	}

	for _, filename := range tests {
		pkg := typeCheck(t, filename)
		for typ, suffix := range suffixes {
			var buf bytes.Buffer
			if err := GenJava(&buf, fset, pkg, []*types.Package{pkg}, "", typ); err != nil {
				t.Errorf("%s: %v", filename, err)
				continue
			}
			out := writeTempFile(t, "generated"+suffix, buf.Bytes())
			defer os.Remove(out)
			golden := filename[:len(filename)-len(".go")] + suffix
			if diffstr := diff(golden, out); diffstr != "" {
				t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)

				if *updateFlag {
					t.Logf("Updating %s...", golden)
					if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
						t.Errorf("Update failed: %s", err)
					}
				}

			}
		}
	}
}

func TestGenGo(t *testing.T) {
	for _, filename := range tests {
		var buf bytes.Buffer
		pkg := typeCheck(t, filename)
		if err := GenGo(&buf, fset, pkg, []*types.Package{pkg}); err != nil {
			t.Errorf("%s: %v", filename, err)
			continue
		}
		out := writeTempFile(t, "go", buf.Bytes())
		defer os.Remove(out)
		golden := filename + ".golden"
		if diffstr := diff(golden, out); diffstr != "" {
			t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)

			if *updateFlag {
				t.Logf("Updating %s...", golden)
				if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
					t.Errorf("Update failed: %s", err)
				}
			}
		}
	}
}

func TestCustomPrefix(t *testing.T) {
	const datafile = "testdata/customprefix.go"
	const isHeader = true
	pkg := typeCheck(t, datafile)

	testCases := []struct {
		golden string
		gen    func(w io.Writer) error
	}{
		{
			"testdata/customprefix.java.golden",
			func(w io.Writer) error { return GenJava(w, fset, pkg, []*types.Package{pkg}, "com.example", Java) },
		},
		{
			"testdata/customprefix.java.h.golden",
			func(w io.Writer) error { return GenJava(w, fset, pkg, []*types.Package{pkg}, "com.example", JavaH) },
		},
		{
			"testdata/customprefix.java.c.golden",
			func(w io.Writer) error { return GenJava(w, fset, pkg, []*types.Package{pkg}, "com.example", JavaC) },
		},
		{
			"testdata/customprefix.objc.go.h.golden",
			func(w io.Writer) error { return GenObjc(w, fset, pkg, []*types.Package{pkg}, "EX", ObjcGoH) },
		},
		{
			"testdata/customprefix.objc.h.golden",
			func(w io.Writer) error { return GenObjc(w, fset, pkg, []*types.Package{pkg}, "EX", ObjcH) },
		},
		{
			"testdata/customprefix.objc.m.golden",
			func(w io.Writer) error { return GenObjc(w, fset, pkg, []*types.Package{pkg}, "EX", ObjcM) },
		},
	}

	for _, tc := range testCases {
		var buf bytes.Buffer
		if err := tc.gen(&buf); err != nil {
			t.Errorf("generating %s: %v", tc.golden, err)
			continue
		}
		out := writeTempFile(t, "generated", buf.Bytes())
		defer os.Remove(out)
		if diffstr := diff(tc.golden, out); diffstr != "" {
			t.Errorf("%s: generated file does not match:\b%s", tc.golden, diffstr)
			if *updateFlag {
				t.Logf("Updating %s...", tc.golden)
				err := exec.Command("/bin/cp", out, tc.golden).Run()
				if err != nil {
					t.Errorf("Update failed: %s", err)
				}
			}
		}
	}
}

func TestLowerFirst(t *testing.T) {
	testCases := []struct {
		in, want string
	}{
		{"", ""},
		{"Hello", "hello"},
		{"HelloGopher", "helloGopher"},
		{"hello", "hello"},
		{"ID", "id"},
		{"IDOrName", "idOrName"},
		{"ΓειαΣας", "γειαΣας"},
	}

	for _, tc := range testCases {
		if got := lowerFirst(tc.in); got != tc.want {
			t.Errorf("lowerFirst(%q) = %q; want %q", tc.in, got, tc.want)
		}
	}
}
<commit_msg>mobile/bind: fix tests<commit_after>package bind

import (
	"bytes"
	"flag"
	"go/ast"
	"go/parser"
	"go/token"
	"go/types"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

func init() {
	log.SetFlags(log.Lshortfile)
}

var updateFlag = flag.Bool("update", false, "Update the golden files.")

var tests = []string{
	"testdata/basictypes.go",
	"testdata/structs.go",
	"testdata/interfaces.go",
	"testdata/issue10788.go",
	"testdata/issue12328.go",
	"testdata/issue12403.go",
	"testdata/try.go",
	"testdata/vars.go",
}

var fset = token.NewFileSet()

func typeCheck(t *testing.T, filename string) *types.Package {
	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
	if err != nil {
		t.Fatalf("%s: %v", filename, err)
	}

	pkgName := filepath.Base(filename)
	pkgName = strings.TrimSuffix(pkgName, ".go")

	// typecheck and collect typechecker errors
	var conf types.Config
	conf.Error = func(err error) {
		t.Error(err)
	}
	pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil)
	if err != nil {
		t.Fatal(err)
	}
	return pkg
}

// diff runs the command "diff a b" and returns its output
func diff(a, b string) string {
	var buf bytes.Buffer
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "plan9":
		cmd = exec.Command("/bin/diff", "-c", a, b)
	default:
		cmd = exec.Command("/usr/bin/diff", "-u", a, b)
	}
	cmd.Stdout = &buf
	cmd.Stderr = &buf
	cmd.Run()
	return buf.String()
}

func writeTempFile(t *testing.T, name string, contents []byte) string {
	f, err := ioutil.TempFile("", name)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := f.Write(contents); err != nil {
		t.Fatal(err)
	}
	if err := f.Close(); err != nil {
		t.Fatal(err)
	}
	return f.Name()
}

func TestGenObjc(t *testing.T) {
	var suffixes = map[fileType]string{
		ObjcH:   ".objc.h.golden",
		ObjcM:   ".objc.m.golden",
		ObjcGoH: ".objc.go.h.golden",
	}

	for _, filename := range tests {
		pkg := typeCheck(t, filename)

		for typ, suffix := range suffixes {
			var buf bytes.Buffer
			conf := &GeneratorConfig{
				Writer: &buf,
				Fset:   fset,
				Pkg:    pkg,
				AllPkg: []*types.Package{pkg},
			}
			if err := GenObjc(conf, "", typ); err != nil {
				t.Errorf("%s: %v", filename, err)
				continue
			}
			out := writeTempFile(t, "generated"+suffix, buf.Bytes())
			defer os.Remove(out)
			golden := filename[:len(filename)-len(".go")] + suffix
			if diffstr := diff(golden, out); diffstr != "" {
				t.Errorf("%s: does not match Objective-C golden:\n%s", filename, diffstr)
				if *updateFlag {
					t.Logf("Updating %s...", golden)
					err := exec.Command("/bin/cp", out, golden).Run()
					if err != nil {
						t.Errorf("Update failed: %s", err)
					}
				}
			}
		}
	}
}

func TestGenJava(t *testing.T) {
	var suffixes = map[fileType]string{
		Java:  ".java.golden",
		JavaC: ".java.c.golden",
		JavaH: ".java.h.golden",
	}

	for _, filename := range tests {
		pkg := typeCheck(t, filename)
		for typ, suffix := range suffixes {
			var buf bytes.Buffer
			conf := &GeneratorConfig{
				Writer: &buf,
				Fset:   fset,
				Pkg:    pkg,
				AllPkg: []*types.Package{pkg},
			}
			if err := GenJava(conf, "", typ); err != nil {
				t.Errorf("%s: %v", filename, err)
				continue
			}
			out := writeTempFile(t, "generated"+suffix, buf.Bytes())
			defer os.Remove(out)
			golden := filename[:len(filename)-len(".go")] + suffix
			if diffstr := diff(golden, out); diffstr != "" {
				t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)

				if *updateFlag {
					t.Logf("Updating %s...", golden)
					if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
						t.Errorf("Update failed: %s", err)
					}
				}

			}
		}
	}
}

func TestGenGo(t *testing.T) {
	for _, filename := range tests {
		var buf bytes.Buffer
		pkg := typeCheck(t, filename)
		conf := &GeneratorConfig{
			Writer: &buf,
			Fset:   fset,
			Pkg:    pkg,
			AllPkg: []*types.Package{pkg},
		}
		if err := GenGo(conf); err != nil {
			t.Errorf("%s: %v", filename, err)
			continue
		}
		out := writeTempFile(t, "go", buf.Bytes())
		defer os.Remove(out)
		golden := filename + ".golden"
		if diffstr := diff(golden, out); diffstr != "" {
			t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)

			if *updateFlag {
				t.Logf("Updating %s...", golden)
				if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
					t.Errorf("Update failed: %s", err)
				}
			}
		}
	}
}

func TestCustomPrefix(t *testing.T) {
	const datafile = "testdata/customprefix.go"
	const isHeader = true
	pkg := typeCheck(t, datafile)

	conf := &GeneratorConfig{
		Fset:   fset,
		Pkg:    pkg,
		AllPkg: []*types.Package{pkg},
	}
	testCases := []struct {
		golden string
		gen    func(w io.Writer) error
	}{
		{
			"testdata/customprefix.java.golden",
			func(w io.Writer) error { conf.Writer = w; return GenJava(conf, "com.example", Java) },
		},
		{
			"testdata/customprefix.java.h.golden",
			func(w io.Writer) error { conf.Writer = w; return GenJava(conf, "com.example", JavaH) },
		},
		{
			"testdata/customprefix.java.c.golden",
			func(w io.Writer) error { conf.Writer = w; return GenJava(conf, "com.example", JavaC) },
		},
		{
			"testdata/customprefix.objc.go.h.golden",
			func(w io.Writer) error { conf.Writer = w; return GenObjc(conf, "EX", ObjcGoH) },
		},
		{
			"testdata/customprefix.objc.h.golden",
			func(w io.Writer) error { conf.Writer = w; return GenObjc(conf, "EX", ObjcH) },
		},
		{
			"testdata/customprefix.objc.m.golden",
			func(w io.Writer) error { conf.Writer = w; return GenObjc(conf, "EX", ObjcM) },
		},
	}

	for _, tc := range testCases {
		var buf bytes.Buffer
		if err := tc.gen(&buf); err != nil {
			t.Errorf("generating %s: %v", tc.golden, err)
			continue
		}
		out := writeTempFile(t, "generated", buf.Bytes())
		defer os.Remove(out)
		if diffstr := diff(tc.golden, out); diffstr != "" {
			t.Errorf("%s: generated file does not match:\b%s", tc.golden, diffstr)
			if *updateFlag {
				t.Logf("Updating %s...", tc.golden)
				err := exec.Command("/bin/cp", out, tc.golden).Run()
				if err != nil {
					t.Errorf("Update failed: %s", err)
				}
			}
		}
	}
}

func TestLowerFirst(t *testing.T) {
	testCases := []struct {
		in, want string
	}{
		{"", ""},
		{"Hello", "hello"},
		{"HelloGopher", "helloGopher"},
		{"hello", "hello"},
		{"ID", "id"},
		{"IDOrName", "idOrName"},
		{"ΓειαΣας", "γειαΣας"},
	}

	for _, tc := range testCases {
		if got := lowerFirst(tc.in); got != tc.want {
			t.Errorf("lowerFirst(%q) = %q; want %q", tc.in, got, tc.want)
		}
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"

/*
动态规划
1、状态定义：dp[i]：以s[i]结尾的字符串是否在wordMap能找到
2、状态转移方程：dp[i] = j{0-i}(s[i-j]是否在WordMap中)
*/
func wordBreak(s string, wordDict []string) bool {
	wordMap := make(map[string]bool)
	for _, word := range wordDict {
		wordMap[word] = true
	}

	dp := make([]bool, len(s)+1)
	dp[0] = true
	for i := 1; i <= len(s); i++ {
		for j := 0; j < i; j++ {
			if dp[j] && wordMap[s[j:i]] {
				dp[i] = true
				break
			}
		}
	}
	return dp[len(s)]
}

func main() {
	fmt.Println("vim-go")
	s := "leetcode"
	wordDict := []string{"leet", "code"}
	result := wordBreak(s, wordDict)
	fmt.Println(result)
}
<commit_msg>Update - modify 139<commit_after>package main

import "fmt"

/*
动态规划
1、状态定义：dp[i]：以s[i]结尾的字符串是否在wordMap能找到
2、状态转移方程：dp[i] = j{0-i}(s[i-j]是否在WordMap中)

注意：
初始状态值的设置，然后在遍历中一定不要覆盖掉初始状态值
*/
func wordBreak(s string, wordDict []string) bool {
	wordMap := make(map[string]bool)
	for _, word := range wordDict {
		wordMap[word] = true
	}

	dp := make([]bool, len(s)+1)
	dp[0] = true
	for i := 1; i <= len(s); i++ {
		for j := 0; j < i; j++ {
			if dp[j] && wordMap[s[j:i]] {
				dp[i] = true
				break
			}
		}
	}
	return dp[len(s)]
}

func main() {
	fmt.Println("vim-go")
	s := "leetcode"
	wordDict := []string{"leet", "code"}
	result := wordBreak(s, wordDict)
	fmt.Println(result)
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/axw/gocov"
	"go/ast"
	"go/build"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

const gocovPackagePath = "github.com/axw/gocov"

func usage() {
	fmt.Fprintf(os.Stderr, "Usage:\n\n\tgocov command [arguments]\n\n")
	fmt.Fprintf(os.Stderr, "The commands are:\n\n")
	fmt.Fprintf(os.Stderr, "\tannotate\n")
	fmt.Fprintf(os.Stderr, "\ttest\n")
	fmt.Fprintf(os.Stderr, "\treport\n")
	fmt.Fprintf(os.Stderr, "\n")
	flag.PrintDefaults()
	os.Exit(2)
}

var (
	testFlags = flag.NewFlagSet("test", flag.ExitOnError)
	testExcludeFlag = testFlags.String(
		"exclude", "",
		"packages to exclude, separated by comma")
)

type instrumenter struct {
	gopath       string // temporary gopath
	excluded     []string
	instrumented map[string]*gocov.Package
}

func putenv(env []string, key, value string) []string {
	for i, s := range env {
		if strings.HasPrefix(s, key+"=") {
			env[i] = key + "=" + value
			return env
		}
	}
	return append(env, key+"="+value)
}

func parsePackage(path string, fset *token.FileSet) (*build.Package, *ast.Package, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return nil, nil, err
	}
	p, err := build.Import(path, cwd, 0)
	if err != nil {
		return nil, nil, err
	}
	sort.Strings(p.GoFiles)
	filter := func(f os.FileInfo) bool {
		name := f.Name()
		i := sort.SearchStrings(p.GoFiles, name)
		return i < len(p.GoFiles) && p.GoFiles[i] == name
	}
	pkgs, err := parser.ParseDir(fset, p.Dir, filter, parser.DeclarationErrors)
	if err != nil {
		return nil, nil, err
	}
	return p, pkgs[p.Name], err
}

func symlinkHierarchy(src, dst string) error {
	fn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		rel, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}

		target := filepath.Join(dst, rel)
		if info.IsDir() {
			return os.MkdirAll(target, 0700)
		} else {
			err = os.Symlink(path, target)
			if err != nil {
				srcfile, err := os.Open(path)
				if err != nil {
					return err
				}
				defer srcfile.Close()
				dstfile, err := os.OpenFile(
					target, os.O_RDWR|os.O_CREATE, 0600)
				if err != nil {
					return err
				}
				defer dstfile.Close()
				_, err = io.Copy(dstfile, srcfile)
				return err
			}
		}
		return nil
	}
	return filepath.Walk(src, fn)
}

func (in *instrumenter) instrumentPackage(pkgpath string) error {
	// Certain, special packages should always be skipped.
	switch pkgpath {
	case "C":
		return nil
	}

	// Ignore explicitly excluded packages.
	if i := sort.SearchStrings(in.excluded, pkgpath); i < len(in.excluded) {
		if in.excluded[i] == pkgpath {
			return nil
		}
	}

	fset := token.NewFileSet()
	buildpkg, pkg, err := parsePackage(pkgpath, fset)
	if err != nil {
		return err
	}
	in.instrumented[pkgpath] = nil // created in first instrumented file
	if buildpkg.Goroot {
		// ignore packages in GOROOT
		return nil
	}

	// Clone the directory structure, symlinking files (if possible),
	// otherwise copying the files. Instrumented files will replace
	// the symlinks with new files.
	cloneDir := filepath.Join(in.gopath, "src", pkgpath)
	err = symlinkHierarchy(buildpkg.Dir, cloneDir)

	for filename, f := range pkg.Files {
		err := in.instrumentFile(f, fset)
		if err != nil {
			return err
		}

		if err == nil {
			filepath := filepath.Join(cloneDir, filepath.Base(filename))
			err = os.Remove(filepath)
			if err != nil {
				return err
			}
			file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0600)
			if err != nil {
				return err
			}
			printer.Fprint(file, fset, f) // TODO check err?
			err = file.Close()
			if err != nil {
				return err
			}
		}
		if err != nil {
			return err
		}
	}

	// TODO include/exclude package names with a pattern.
	for _, subpkgpath := range buildpkg.Imports {
		if _, done := in.instrumented[subpkgpath]; !done {
			err = in.instrumentPackage(subpkgpath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func marshalJson(packages []*gocov.Package) ([]byte, error) {
	return json.Marshal(struct{ Packages []*gocov.Package }{packages})
}

func unmarshalJson(data []byte) (packages []*gocov.Package, err error) {
	result := &struct{ Packages []*gocov.Package }{}
	err = json.Unmarshal(data, result)
	if err == nil {
		packages = result.Packages
	}
	return
}

func instrumentAndTest() (rc int) {
	testFlags.Parse(os.Args[2:])
	packageName := "."
	if testFlags.NArg() > 0 {
		packageName = testFlags.Arg(0)
	}

	tempDir, err := ioutil.TempDir("", "gocov")
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to create temporary GOPATH: %s", err)
		return 1
	}
	defer func() {
		err := os.RemoveAll(tempDir)
		if err != nil {
			fmt.Fprintf(os.Stderr,
				"warning: failed to delete temporary GOPATH (%s)", tempDir)
		}
	}()

	err = os.Mkdir(filepath.Join(tempDir, "src"), 0700)
	if err != nil {
		fmt.Fprintf(os.Stderr,
			"failed to create temporary src directory: %s", err)
		return 1
	}

	var excluded []string
	if len(*testExcludeFlag) > 0 {
		excluded = strings.Split(*testExcludeFlag, ",")
		sort.Strings(excluded)
	}

	in := &instrumenter{
		gopath:       tempDir,
		instrumented: make(map[string]*gocov.Package),
		excluded:     excluded}
	err = in.instrumentPackage(packageName)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to instrument package(%s): %s\n",
			packageName, err)
		return 1
	}

	// Run "go test".
	// TODO pass through test flags.
	outfilePath := filepath.Join(tempDir, "gocov.out")
	env := os.Environ()
	env = putenv(env, "GOCOVOUT", outfilePath)
	if gopath := os.Getenv("GOPATH"); gopath != "" {
		gopath = fmt.Sprintf("%s%c%s", tempDir, os.PathListSeparator, gopath)
		env = putenv(env, "GOPATH", gopath)
	} else {
		env = putenv(env, "GOPATH", tempDir)
	}
	cmd := exec.Command("go", "test", "-v", packageName)
	cmd.Env = env
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	err = cmd.Run()

	if err != nil {
		fmt.Fprintf(os.Stderr, "go test failed: %s\n", err)
		rc = 1
	}

	packages, err := gocov.ParseTrace(outfilePath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to parse gocov output: %s\n", err)
	} else {
		data, err := marshalJson(packages)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to format as JSON: %s\n", err)
		} else {
			fmt.Println(string(data))
		}
	}
	return
}

func main() {
	flag.Usage = usage
	flag.Parse()
	command := ""
	if flag.NArg() > 0 {
		command = flag.Arg(0)
		switch command {
		case "annotate":
			os.Exit(annotateSource())
		case "report":
			os.Exit(reportCoverage())
		case "test":
			os.Exit(instrumentAndTest())
		//case "run"
		default:
			fmt.Fprintf(os.Stderr, "Unknown command: %#q\n\n", command)
			usage()
		}
	} else {
		usage()
	}
}
<commit_msg>Introduce gocov test -v<commit_after>// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/axw/gocov"
	"go/ast"
	"go/build"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

const gocovPackagePath = "github.com/axw/gocov"

func usage() {
	fmt.Fprintf(os.Stderr, "Usage:\n\n\tgocov command [arguments]\n\n")
	fmt.Fprintf(os.Stderr, "The commands are:\n\n")
	fmt.Fprintf(os.Stderr, "\tannotate\n")
	fmt.Fprintf(os.Stderr, "\ttest\n")
	fmt.Fprintf(os.Stderr, "\treport\n")
	fmt.Fprintf(os.Stderr, "\n")
	flag.PrintDefaults()
	os.Exit(2)
}

var (
	testFlags       = flag.NewFlagSet("test", flag.ExitOnError)
	testExcludeFlag = testFlags.String(
		"exclude", "",
		"packages to exclude, separated by comma")
	verbose bool
)

func init() {
	testFlags.BoolVar(&verbose, "v", false, "verbose")
}

type instrumenter struct {
	gopath       string // temporary gopath
	excluded     []string
	instrumented map[string]*gocov.Package
}

func putenv(env []string, key, value string) []string {
	for i, s := range env {
		if strings.HasPrefix(s, key+"=") {
			env[i] = key + "=" + value
			return env
		}
	}
	return append(env, key+"="+value)
}

func parsePackage(path string, fset *token.FileSet) (*build.Package, *ast.Package, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return nil, nil, err
	}
	p, err := build.Import(path, cwd, 0)
	if err != nil {
		return nil, nil, err
	}
	sort.Strings(p.GoFiles)
	filter := func(f os.FileInfo) bool {
		name := f.Name()
		i := sort.SearchStrings(p.GoFiles, name)
		return i < len(p.GoFiles) && p.GoFiles[i] == name
	}
	pkgs, err := parser.ParseDir(fset, p.Dir, filter, parser.DeclarationErrors)
	if err != nil {
		return nil, nil, err
	}
	return p, pkgs[p.Name], err
}

func symlinkHierarchy(src, dst string) error {
	fn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		rel, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}

		target := filepath.Join(dst, rel)
		if info.IsDir() {
			return os.MkdirAll(target, 0700)
		} else {
			err = os.Symlink(path, target)
			if err != nil {
				srcfile, err := os.Open(path)
				if err != nil {
					return err
				}
				defer srcfile.Close()
				dstfile, err := os.OpenFile(
					target, os.O_RDWR|os.O_CREATE, 0600)
				if err != nil {
					return err
				}
				defer dstfile.Close()
				_, err = io.Copy(dstfile, srcfile)
				return err
			}
		}
		return nil
	}
	return filepath.Walk(src, fn)
}

func (in *instrumenter) instrumentPackage(pkgpath string) error {
	// Certain, special packages should always be skipped.
	switch pkgpath {
	case "C":
		return nil
	}

	if verbose {
		fmt.Fprintf(os.Stderr, "instrumenting package %q\n", pkgpath)
	}

	// Ignore explicitly excluded packages.
	if i := sort.SearchStrings(in.excluded, pkgpath); i < len(in.excluded) {
		if in.excluded[i] == pkgpath {
			return nil
		}
	}

	fset := token.NewFileSet()
	buildpkg, pkg, err := parsePackage(pkgpath, fset)
	if err != nil {
		return err
	}
	in.instrumented[pkgpath] = nil // created in first instrumented file
	if buildpkg.Goroot {
		// ignore packages in GOROOT
		return nil
	}

	// Clone the directory structure, symlinking files (if possible),
	// otherwise copying the files. Instrumented files will replace
	// the symlinks with new files.
	cloneDir := filepath.Join(in.gopath, "src", pkgpath)
	err = symlinkHierarchy(buildpkg.Dir, cloneDir)

	for filename, f := range pkg.Files {
		err := in.instrumentFile(f, fset)
		if err != nil {
			return err
		}

		if err == nil {
			filepath := filepath.Join(cloneDir, filepath.Base(filename))
			err = os.Remove(filepath)
			if err != nil {
				return err
			}
			file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0600)
			if err != nil {
				return err
			}
			printer.Fprint(file, fset, f) // TODO check err?
			err = file.Close()
			if err != nil {
				return err
			}
		}
		if err != nil {
			return err
		}
	}

	// TODO include/exclude package names with a pattern.
	for _, subpkgpath := range buildpkg.Imports {
		if _, done := in.instrumented[subpkgpath]; !done {
			err = in.instrumentPackage(subpkgpath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func marshalJson(packages []*gocov.Package) ([]byte, error) {
	return json.Marshal(struct{ Packages []*gocov.Package }{packages})
}

func unmarshalJson(data []byte) (packages []*gocov.Package, err error) {
	result := &struct{ Packages []*gocov.Package }{}
	err = json.Unmarshal(data, result)
	if err == nil {
		packages = result.Packages
	}
	return
}

func instrumentAndTest() (rc int) {
	testFlags.Parse(os.Args[2:])
	packageName := "."
	if testFlags.NArg() > 0 {
		packageName = testFlags.Arg(0)
	}

	tempDir, err := ioutil.TempDir("", "gocov")
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to create temporary GOPATH: %s", err)
		return 1
	}
	defer func() {
		err := os.RemoveAll(tempDir)
		if err != nil {
			fmt.Fprintf(os.Stderr,
				"warning: failed to delete temporary GOPATH (%s)", tempDir)
		}
	}()

	err = os.Mkdir(filepath.Join(tempDir, "src"), 0700)
	if err != nil {
		fmt.Fprintf(os.Stderr,
			"failed to create temporary src directory: %s", err)
		return 1
	}

	var excluded []string
	if len(*testExcludeFlag) > 0 {
		excluded = strings.Split(*testExcludeFlag, ",")
		sort.Strings(excluded)
	}

	in := &instrumenter{
		gopath:       tempDir,
		instrumented: make(map[string]*gocov.Package),
		excluded:     excluded}
	err = in.instrumentPackage(packageName)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to instrument package(%s): %s\n",
			packageName, err)
		return 1
	}

	// Run "go test".
	// TODO pass through test flags.
	outfilePath := filepath.Join(tempDir, "gocov.out")
	env := os.Environ()
	env = putenv(env, "GOCOVOUT", outfilePath)
	if gopath := os.Getenv("GOPATH"); gopath != "" {
		gopath = fmt.Sprintf("%s%c%s", tempDir, os.PathListSeparator, gopath)
		env = putenv(env, "GOPATH", gopath)
	} else {
		env = putenv(env, "GOPATH", tempDir)
	}
	cmd := exec.Command("go", "test", "-v", packageName)
	cmd.Env = env
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	err = cmd.Run()

	if err != nil {
		fmt.Fprintf(os.Stderr, "go test failed: %s\n", err)
		rc = 1
	}

	packages, err := gocov.ParseTrace(outfilePath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to parse gocov output: %s\n", err)
	} else {
		data, err := marshalJson(packages)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to format as JSON: %s\n", err)
		} else {
			fmt.Println(string(data))
		}
	}
	return
}

func main() {
	flag.Usage = usage
	flag.Parse()
	command := ""
	if flag.NArg() > 0 {
		command = flag.Arg(0)
		switch command {
		case "annotate":
			os.Exit(annotateSource())
		case "report":
			os.Exit(reportCoverage())
		case "test":
			os.Exit(instrumentAndTest())
		//case "run"
		default:
			fmt.Fprintf(os.Stderr, "Unknown command: %#q\n\n", command)
			usage()
		}
	} else {
		usage()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/mailru/easyjson/bootstrap"
	// Reference the gen package to be friendly to vendoring tools,
	// as it is an indirect dependency.
	// (The temporary bootstrapping code uses it.)
	_ "github.com/mailru/easyjson/gen"
	"github.com/mailru/easyjson/parser"
)

var buildTags = flag.String("build_tags", "", "build tags to add to generated file")
var snakeCase = flag.Bool("snake_case", false, "use snake_case names instead of CamelCase by default")
var noStdMarshalers = flag.Bool("no_std_marshalers", false, "don't generate MarshalJSON/UnmarshalJSON methods")
var omitEmpty = flag.Bool("omit_empty", false, "omit empty fields by default")
var allStructs = flag.Bool("all", false, "generate un-/marshallers for all structs in a file")
var leaveTemps = flag.Bool("leave_temps", false, "do not delete temporary files")
var stubs = flag.Bool("stubs", false, "only generate stubs for marshallers/unmarshallers methods")
var noformat = flag.Bool("noformat", false, "do not run 'gofmt -w' on output file")
var specifiedName = flag.String("output_filename", "", "specify the filename of the output")
var processPkg = flag.Bool("pkg", false, "process the whole package instead of just the given file")

func generate(fname string) (err error) {
	fInfo, err := os.Stat(fname)
	if err != nil {
		return err
	}

	p := parser.Parser{AllStructs: *allStructs}
	if err := p.Parse(fname, fInfo.IsDir()); err != nil {
		return fmt.Errorf("Error parsing %v: %v", fname, err)
	}

	var outName string
	if fInfo.IsDir() {
		outName = filepath.Join(fname, filepath.Base(filepath.Clean(fname))+"_easyjson.go")
	} else {
		if s := strings.TrimSuffix(fname, ".go"); s == fname {
			return errors.New("Filename must end in '.go'")
		} else {
			outName = s + "_easyjson.go"
		}
	}

	if *specifiedName != "" {
		outName = *specifiedName
	}

	g := bootstrap.Generator{
		BuildTags:       *buildTags,
		PkgPath:         p.PkgPath,
		PkgName:         p.PkgName,
		Types:           p.StructNames,
		SnakeCase:       *snakeCase,
		NoStdMarshalers: *noStdMarshalers,
		OmitEmpty:       *omitEmpty,
		LeaveTemps:      *leaveTemps,
		OutName:         outName,
		StubsOnly:       *stubs,
		NoFormat:        *noformat,
	}

	if err := g.Run(); err != nil {
		return fmt.Errorf("Bootstrap failed: %v", err)
	}
	return nil
}

func main() {
	flag.Parse()

	files := flag.Args()

	gofile := os.Getenv("GOFILE")
	if *processPkg {
		gofile = filepath.Dir(gofile)
	}

	if len(files) == 0 && gofile != "" {
		files = []string{gofile}
	} else if len(files) == 0 {
		flag.Usage()
		os.Exit(1)
	}

	for _, fname := range files {
		if err := generate(fname); err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
}
<commit_msg>Fix for path = "."<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/mailru/easyjson/bootstrap"
	// Reference the gen package to be friendly to vendoring tools,
	// as it is an indirect dependency.
	// (The temporary bootstrapping code uses it.)
	_ "github.com/mailru/easyjson/gen"
	"github.com/mailru/easyjson/parser"
)

var buildTags = flag.String("build_tags", "", "build tags to add to generated file")
var snakeCase = flag.Bool("snake_case", false, "use snake_case names instead of CamelCase by default")
var noStdMarshalers = flag.Bool("no_std_marshalers", false, "don't generate MarshalJSON/UnmarshalJSON methods")
var omitEmpty = flag.Bool("omit_empty", false, "omit empty fields by default")
var allStructs = flag.Bool("all", false, "generate un-/marshallers for all structs in a file")
var leaveTemps = flag.Bool("leave_temps", false, "do not delete temporary files")
var stubs = flag.Bool("stubs", false, "only generate stubs for marshallers/unmarshallers methods")
var noformat = flag.Bool("noformat", false, "do not run 'gofmt -w' on output file")
var specifiedName = flag.String("output_filename", "", "specify the filename of the output")
var processPkg = flag.Bool("pkg", false, "process the whole package instead of just the given file")

func generate(fname string) (err error) {
	fInfo, err := os.Stat(fname)
	if err != nil {
		return err
	}

	p := parser.Parser{AllStructs: *allStructs}
	if err := p.Parse(fname, fInfo.IsDir()); err != nil {
		return fmt.Errorf("Error parsing %v: %v", fname, err)
	}

	var outName string
	if fInfo.IsDir() {
		outName = filepath.Join(fname, p.PkgName+"_easyjson.go")
	} else {
		if s := strings.TrimSuffix(fname, ".go"); s == fname {
			return errors.New("Filename must end in '.go'")
		} else {
			outName = s + "_easyjson.go"
		}
	}

	if *specifiedName != "" {
		outName = *specifiedName
	}

	g := bootstrap.Generator{
		BuildTags:       *buildTags,
		PkgPath:         p.PkgPath,
		PkgName:         p.PkgName,
		Types:           p.StructNames,
		SnakeCase:       *snakeCase,
		NoStdMarshalers: *noStdMarshalers,
		OmitEmpty:       *omitEmpty,
		LeaveTemps:      *leaveTemps,
		OutName:         outName,
		StubsOnly:       *stubs,
		NoFormat:        *noformat,
	}

	if err := g.Run(); err != nil {
		return fmt.Errorf("Bootstrap failed: %v", err)
	}
	return nil
}

func main() {
	flag.Parse()

	files := flag.Args()

	gofile := os.Getenv("GOFILE")
	if *processPkg {
		gofile = filepath.Dir(gofile)
	}

	if len(files) == 0 && gofile != "" {
		files = []string{gofile}
	} else if len(files) == 0 {
		flag.Usage()
		os.Exit(1)
	}

	for _, fname := range files {
		if err := generate(fname); err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 storage

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"strings"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	"k8s.io/apiserver/pkg/storage/value"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
)

// Backend describes the storage servers, the information here should be enough
// for health validations.
type Backend struct {
	// the url of storage backend like: https://etcd.domain:2379
	Server string
	// the required tls config
	TLSConfig *tls.Config
}

// StorageFactory is the interface to locate the storage for a given GroupResource
type StorageFactory interface {
	// New finds the storage destination for the given group and resource. It will
	// return an error if the group has no storage destination configured.
	NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error)

	// ResourcePrefix returns the overridden resource prefix for the GroupResource
	// This allows for cohabitation of resources with different native types and provides
	// centralized control over the shape of etcd directories
	ResourcePrefix(groupResource schema.GroupResource) string

	// Backends gets all backends for all registered storage destinations.
	// Used for getting all instances for health validations.
	Backends() []Backend
}

// DefaultStorageFactory takes a GroupResource and returns back its storage interface.  This result includes:
// 1. Merged etcd config, including: auth, server locations, prefixes
// 2. Resource encodings for storage: group,version,kind to store as
// 3. Cohabitating default: some resources like hpa are exposed through multiple APIs.  They must agree on 1 and 2
type DefaultStorageFactory struct {
	// StorageConfig describes how to create a storage backend in general.
	// Its authentication information will be used for every storage.Interface returned.
	StorageConfig storagebackend.Config

	Overrides map[schema.GroupResource]groupResourceOverrides

	DefaultResourcePrefixes map[schema.GroupResource]string

	// DefaultMediaType is the media type used to store resources. If it is not set, "application/json" is used.
	DefaultMediaType string

	// DefaultSerializer is used to create encoders and decoders for the storage.Interface.
	DefaultSerializer runtime.StorageSerializer

	// ResourceEncodingConfig describes how to encode a particular GroupVersionResource
	ResourceEncodingConfig ResourceEncodingConfig

	// APIResourceConfigSource indicates whether the *storage* is enabled, NOT the API
	// This is discrete from resource enablement because those are separate concerns.  How this source is configured
	// is left to the caller.
	APIResourceConfigSource APIResourceConfigSource

	// newStorageCodecFn exists to be overwritten for unit testing.
	newStorageCodecFn func(opts StorageCodecConfig) (codec runtime.Codec, err error)
}

type groupResourceOverrides struct {
	// etcdLocation contains the list of "special" locations that are used for particular GroupResources
	// These are merged on top of the StorageConfig when requesting the storage.Interface for a given GroupResource
	etcdLocation []string
	// etcdPrefix is the base location for a GroupResource.
	etcdPrefix string
	// etcdResourcePrefix is the location to use to store a particular type under the `etcdPrefix` location
	// If empty, the default mapping is used.  If the default mapping doesn't contain an entry, it will use
	// the ToLowered name of the resource, not including the group.
	etcdResourcePrefix string
	// mediaType is the desired serializer to choose. If empty, the default is chosen.
	mediaType string
	// serializer contains the list of "special" serializers for a GroupResource.  Resource=* means for the entire group
	serializer runtime.StorageSerializer
	// cohabitatingResources keeps track of which resources must be stored together.  This happens when we have multiple ways
	// of exposing one set of concepts.  autoscaling.HPA and extensions.HPA as a for instance
	// The order of the slice matters!  It is the priority order of lookup for finding a storage location
	cohabitatingResources []schema.GroupResource
	// encoderDecoratorFn is optional and may wrap the provided encoder prior to being serialized.
	encoderDecoratorFn func(runtime.Encoder) runtime.Encoder
	// decoderDecoratorFn is optional and may wrap the provided decoders (can add new decoders). The order of
	// returned decoders will be priority for attempt to decode.
	decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder
	// transformer is optional and shall encrypt that resource at rest.
	transformer value.Transformer
	// disablePaging will prevent paging on the provided resource.
	disablePaging bool
}

// Apply overrides the provided config and options if the override has a value in that position
func (o groupResourceOverrides) Apply(config *storagebackend.Config, options *StorageCodecConfig) {
	if len(o.etcdLocation) > 0 {
		config.ServerList = o.etcdLocation
	}
	if len(o.etcdPrefix) > 0 {
		config.Prefix = o.etcdPrefix
	}

	if len(o.mediaType) > 0 {
		options.StorageMediaType = o.mediaType
	}
	if o.serializer != nil {
		options.StorageSerializer = o.serializer
	}
	if o.encoderDecoratorFn != nil {
		options.EncoderDecoratorFn = o.encoderDecoratorFn
	}
	if o.decoderDecoratorFn != nil {
		options.DecoderDecoratorFn = o.decoderDecoratorFn
	}
	if o.transformer != nil {
		config.Transformer = o.transformer
	}
	if o.disablePaging {
		config.Paging = false
	}
}

var _ StorageFactory = &DefaultStorageFactory{}

const AllResources = "*"

// specialDefaultResourcePrefixes are prefixes compiled into Kubernetes.
// TODO: move out of this package, it is not generic
var specialDefaultResourcePrefixes = map[schema.GroupResource]string{
	{Group: "", Resource: "replicationControllers"}:        "controllers",
	{Group: "", Resource: "replicationcontrollers"}:        "controllers",
	{Group: "", Resource: "endpoints"}:                     "services/endpoints",
	{Group: "", Resource: "nodes"}:                         "minions",
	{Group: "", Resource: "services"}:                      "services/specs",
	{Group: "extensions", Resource: "ingresses"}:           "ingress",
	{Group: "extensions", Resource: "podsecuritypolicies"}: "podsecuritypolicy",
}

func NewDefaultStorageFactory(config storagebackend.Config, defaultMediaType string, defaultSerializer runtime.StorageSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource) *DefaultStorageFactory {
	config.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
	if len(defaultMediaType) == 0 {
		defaultMediaType = runtime.ContentTypeJSON
	}
	return &DefaultStorageFactory{
		StorageConfig:           config,
		Overrides:               map[schema.GroupResource]groupResourceOverrides{},
		DefaultMediaType:        defaultMediaType,
		DefaultSerializer:       defaultSerializer,
		ResourceEncodingConfig:  resourceEncodingConfig,
		APIResourceConfigSource: resourceConfig,
		DefaultResourcePrefixes: specialDefaultResourcePrefixes,

		newStorageCodecFn: NewStorageCodec,
	}
}

func (s *DefaultStorageFactory) SetEtcdLocation(groupResource schema.GroupResource, location []string) {
	overrides := s.Overrides[groupResource]
	overrides.etcdLocation = location
	s.Overrides[groupResource] = overrides
}

func (s *DefaultStorageFactory) SetEtcdPrefix(groupResource schema.GroupResource, prefix string) {
	overrides := s.Overrides[groupResource]
	overrides.etcdPrefix = prefix
	s.Overrides[groupResource] = overrides
}

// SetDisableAPIListChunking allows a specific resource to disable paging at the storage layer, to prevent
// exposure of key names in continuations. This may be overriden by feature gates.
func (s *DefaultStorageFactory) SetDisableAPIListChunking(groupResource schema.GroupResource) {
	overrides := s.Overrides[groupResource]
	overrides.disablePaging = true
	s.Overrides[groupResource] = overrides
}

// SetResourceEtcdPrefix sets the prefix for a resource, but not the base-dir.  You'll end up in `etcdPrefix/resourceEtcdPrefix`.
func (s *DefaultStorageFactory) SetResourceEtcdPrefix(groupResource schema.GroupResource, prefix string) {
	overrides := s.Overrides[groupResource]
	overrides.etcdResourcePrefix = prefix
	s.Overrides[groupResource] = overrides
}

func (s *DefaultStorageFactory) SetSerializer(groupResource schema.GroupResource, mediaType string, serializer runtime.StorageSerializer) {
	overrides := s.Overrides[groupResource]
	overrides.mediaType = mediaType
	overrides.serializer = serializer
	s.Overrides[groupResource] = overrides
}

func (s *DefaultStorageFactory) SetTransformer(groupResource schema.GroupResource, transformer value.Transformer) {
	overrides := s.Overrides[groupResource]
	overrides.transformer = transformer
	s.Overrides[groupResource] = overrides
}

// AddCohabitatingResources links resources together the order of the slice matters!  its the priority order of lookup for finding a storage location
func (s *DefaultStorageFactory) AddCohabitatingResources(groupResources ...schema.GroupResource) {
	for _, groupResource := range groupResources {
		overrides := s.Overrides[groupResource]
		overrides.cohabitatingResources = groupResources
		s.Overrides[groupResource] = overrides
	}
}

func (s *DefaultStorageFactory) AddSerializationChains(encoderDecoratorFn func(runtime.Encoder) runtime.Encoder, decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder, groupResources ...schema.GroupResource) {
	for _, groupResource := range groupResources {
		overrides := s.Overrides[groupResource]
		overrides.encoderDecoratorFn = encoderDecoratorFn
		overrides.decoderDecoratorFn = decoderDecoratorFn
		s.Overrides[groupResource] = overrides
	}
}

func getAllResourcesAlias(resource schema.GroupResource) schema.GroupResource {
	return schema.GroupResource{Group: resource.Group, Resource: AllResources}
}

func (s *DefaultStorageFactory) getStorageGroupResource(groupResource schema.GroupResource) schema.GroupResource {
	for _, potentialStorageResource := range s.Overrides[groupResource].cohabitatingResources {
		if s.APIResourceConfigSource.AnyVersionOfResourceEnabled(potentialStorageResource) {
			return potentialStorageResource
		}
	}

	return groupResource
}

// New finds the storage destination for the given group and resource. It will
// return an error if the group has no storage destination configured.
func (s *DefaultStorageFactory) NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error) {
	chosenStorageResource := s.getStorageGroupResource(groupResource)

	// operate on copy
	storageConfig := s.StorageConfig
	codecConfig := StorageCodecConfig{
		StorageMediaType:  s.DefaultMediaType,
		StorageSerializer: s.DefaultSerializer,
	}

	if override, ok := s.Overrides[getAllResourcesAlias(chosenStorageResource)]; ok {
		override.Apply(&storageConfig, &codecConfig)
	}
	if override, ok := s.Overrides[chosenStorageResource]; ok {
		override.Apply(&storageConfig, &codecConfig)
	}

	var err error
	codecConfig.StorageVersion, err = s.ResourceEncodingConfig.StorageEncodingFor(chosenStorageResource)
	if err != nil {
		return nil, err
	}
	codecConfig.MemoryVersion, err = s.ResourceEncodingConfig.InMemoryEncodingFor(groupResource)
	if err != nil {
		return nil, err
	}
	codecConfig.Config = storageConfig

	storageConfig.Codec, err = s.newStorageCodecFn(codecConfig)
	if err != nil {
		return nil, err
	}
	glog.V(3).Infof("storing %v in %v, reading as %v from %v", groupResource, codecConfig.StorageVersion, codecConfig.MemoryVersion, codecConfig.Config)

	return &storageConfig, nil
}

// Backends returns all backends for all registered storage destinations.
// Used for getting all instances for health validations.
func (s *DefaultStorageFactory) Backends() []Backend {
	servers := sets.NewString(s.StorageConfig.ServerList...)

	for _, overrides := range s.Overrides {
		servers.Insert(overrides.etcdLocation...)
	}

	tlsConfig := &tls.Config{
		InsecureSkipVerify: true,
	}
	if len(s.StorageConfig.CertFile) > 0 && len(s.StorageConfig.KeyFile) > 0 {
		cert, err := tls.LoadX509KeyPair(s.StorageConfig.CertFile, s.StorageConfig.KeyFile)
		if err != nil {
			glog.Errorf("failed to load key pair while getting backends: %s", err)
		} else {
			tlsConfig.Certificates = []tls.Certificate{cert}
		}
	}
	if len(s.StorageConfig.CAFile) > 0 {
		if caCert, err := ioutil.ReadFile(s.StorageConfig.CAFile); err != nil {
			glog.Errorf("failed to read ca file while getting backends: %s", err)
		} else {
			caPool := x509.NewCertPool()
			caPool.AppendCertsFromPEM(caCert)
			tlsConfig.RootCAs = caPool
			tlsConfig.InsecureSkipVerify = false
		}
	}

	backends := []Backend{}
	for server := range servers {
		backends = append(backends, Backend{
			Server:    server,
			TLSConfig: tlsConfig,
		})
	}
	return backends
}

func (s *DefaultStorageFactory) ResourcePrefix(groupResource schema.GroupResource) string {
	chosenStorageResource := s.getStorageGroupResource(groupResource)
	groupOverride := s.Overrides[getAllResourcesAlias(chosenStorageResource)]
	exactResourceOverride := s.Overrides[chosenStorageResource]

	etcdResourcePrefix := s.DefaultResourcePrefixes[chosenStorageResource]
	if len(groupOverride.etcdResourcePrefix) > 0 {
		etcdResourcePrefix = groupOverride.etcdResourcePrefix
	}
	if len(exactResourceOverride.etcdResourcePrefix) > 0 {
		etcdResourcePrefix = exactResourceOverride.etcdResourcePrefix
	}
	if len(etcdResourcePrefix) == 0 {
		etcdResourcePrefix = strings.ToLower(chosenStorageResource.Resource)
	}

	return etcdResourcePrefix
}
<commit_msg>Provide field info in storage configuration<commit_after>/*
Copyright 2016 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 storage

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"strings"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/storage/storagebackend"
	"k8s.io/apiserver/pkg/storage/value"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
)

// Backend describes the storage servers, the information here should be enough
// for health validations.
type Backend struct {
	// the url of storage backend like: https://etcd.domain:2379
	Server string
	// the required tls config
	TLSConfig *tls.Config
}

// StorageFactory is the interface to locate the storage for a given GroupResource
type StorageFactory interface {
	// New finds the storage destination for the given group and resource. It will
	// return an error if the group has no storage destination configured.
	NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error)

	// ResourcePrefix returns the overridden resource prefix for the GroupResource
	// This allows for cohabitation of resources with different native types and provides
	// centralized control over the shape of etcd directories
	ResourcePrefix(groupResource schema.GroupResource) string

	// Backends gets all backends for all registered storage destinations.
	// Used for getting all instances for health validations.
	Backends() []Backend
}

// DefaultStorageFactory takes a GroupResource and returns back its storage interface.  This result includes:
// 1. Merged etcd config, including: auth, server locations, prefixes
// 2. Resource encodings for storage: group,version,kind to store as
// 3. Cohabitating default: some resources like hpa are exposed through multiple APIs.  They must agree on 1 and 2
type DefaultStorageFactory struct {
	// StorageConfig describes how to create a storage backend in general.
	// Its authentication information will be used for every storage.Interface returned.
	StorageConfig storagebackend.Config

	Overrides map[schema.GroupResource]groupResourceOverrides

	DefaultResourcePrefixes map[schema.GroupResource]string

	// DefaultMediaType is the media type used to store resources. If it is not set, "application/json" is used.
	DefaultMediaType string

	// DefaultSerializer is used to create encoders and decoders for the storage.Interface.
	DefaultSerializer runtime.StorageSerializer

	// ResourceEncodingConfig describes how to encode a particular GroupVersionResource
	ResourceEncodingConfig ResourceEncodingConfig

	// APIResourceConfigSource indicates whether the *storage* is enabled, NOT the API
	// This is discrete from resource enablement because those are separate concerns.  How this source is configured
	// is left to the caller.
	APIResourceConfigSource APIResourceConfigSource

	// newStorageCodecFn exists to be overwritten for unit testing.
	newStorageCodecFn func(opts StorageCodecConfig) (codec runtime.Codec, err error)
}

type groupResourceOverrides struct {
	// etcdLocation contains the list of "special" locations that are used for particular GroupResources
	// These are merged on top of the StorageConfig when requesting the storage.Interface for a given GroupResource
	etcdLocation []string
	// etcdPrefix is the base location for a GroupResource.
	etcdPrefix string
	// etcdResourcePrefix is the location to use to store a particular type under the `etcdPrefix` location
	// If empty, the default mapping is used.  If the default mapping doesn't contain an entry, it will use
	// the ToLowered name of the resource, not including the group.
	etcdResourcePrefix string
	// mediaType is the desired serializer to choose. If empty, the default is chosen.
	mediaType string
	// serializer contains the list of "special" serializers for a GroupResource.  Resource=* means for the entire group
	serializer runtime.StorageSerializer
	// cohabitatingResources keeps track of which resources must be stored together.  This happens when we have multiple ways
	// of exposing one set of concepts.  autoscaling.HPA and extensions.HPA as a for instance
	// The order of the slice matters!  It is the priority order of lookup for finding a storage location
	cohabitatingResources []schema.GroupResource
	// encoderDecoratorFn is optional and may wrap the provided encoder prior to being serialized.
	encoderDecoratorFn func(runtime.Encoder) runtime.Encoder
	// decoderDecoratorFn is optional and may wrap the provided decoders (can add new decoders). The order of
	// returned decoders will be priority for attempt to decode.
	decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder
	// transformer is optional and shall encrypt that resource at rest.
	transformer value.Transformer
	// disablePaging will prevent paging on the provided resource.
	disablePaging bool
}

// Apply overrides the provided config and options if the override has a value in that position
func (o groupResourceOverrides) Apply(config *storagebackend.Config, options *StorageCodecConfig) {
	if len(o.etcdLocation) > 0 {
		config.ServerList = o.etcdLocation
	}
	if len(o.etcdPrefix) > 0 {
		config.Prefix = o.etcdPrefix
	}

	if len(o.mediaType) > 0 {
		options.StorageMediaType = o.mediaType
	}
	if o.serializer != nil {
		options.StorageSerializer = o.serializer
	}
	if o.encoderDecoratorFn != nil {
		options.EncoderDecoratorFn = o.encoderDecoratorFn
	}
	if o.decoderDecoratorFn != nil {
		options.DecoderDecoratorFn = o.decoderDecoratorFn
	}
	if o.transformer != nil {
		config.Transformer = o.transformer
	}
	if o.disablePaging {
		config.Paging = false
	}
}

var _ StorageFactory = &DefaultStorageFactory{}

const AllResources = "*"

// specialDefaultResourcePrefixes are prefixes compiled into Kubernetes.
// TODO: move out of this package, it is not generic
var specialDefaultResourcePrefixes = map[schema.GroupResource]string{
	{Group: "", Resource: "replicationControllers"}:        "controllers",
	{Group: "", Resource: "replicationcontrollers"}:        "controllers",
	{Group: "", Resource: "endpoints"}:                     "services/endpoints",
	{Group: "", Resource: "nodes"}:                         "minions",
	{Group: "", Resource: "services"}:                      "services/specs",
	{Group: "extensions", Resource: "ingresses"}:           "ingress",
	{Group: "extensions", Resource: "podsecuritypolicies"}: "podsecuritypolicy",
}

func NewDefaultStorageFactory(config storagebackend.Config, defaultMediaType string, defaultSerializer runtime.StorageSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource) *DefaultStorageFactory {
	config.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
	if len(defaultMediaType) == 0 {
		defaultMediaType = runtime.ContentTypeJSON
	}
	return &DefaultStorageFactory{
		StorageConfig:           config,
		Overrides:               map[schema.GroupResource]groupResourceOverrides{},
		DefaultMediaType:        defaultMediaType,
		DefaultSerializer:       defaultSerializer,
		ResourceEncodingConfig:  resourceEncodingConfig,
		APIResourceConfigSource: resourceConfig,
		DefaultResourcePrefixes: specialDefaultResourcePrefixes,

		newStorageCodecFn: NewStorageCodec,
	}
}

func (s *DefaultStorageFactory) SetEtcdLocation(groupResource schema.GroupResource, location []string) {
	overrides := s.Overrides[groupResource]
	overrides.etcdLocation = location
	s.Overrides[groupResource] = overrides
}

func (s *DefaultStorageFactory) SetEtcdPrefix(groupResource schema.GroupResource, prefix string) {
	overrides := s.Overrides[groupResource]
	overrides.etcdPrefix = prefix
	s.Overrides[groupResource] = overrides
}

// SetDisableAPIListChunking allows a specific resource to disable paging at the storage layer, to prevent
// exposure of key names in continuations. This may be overriden by feature gates.
func (s *DefaultStorageFactory) SetDisableAPIListChunking(groupResource schema.GroupResource) {
	overrides := s.Overrides[groupResource]
	overrides.disablePaging = true
	s.Overrides[groupResource] = overrides
}

// SetResourceEtcdPrefix sets the prefix for a resource, but not the base-dir.  You'll end up in `etcdPrefix/resourceEtcdPrefix`.
func (s *DefaultStorageFactory) SetResourceEtcdPrefix(groupResource schema.GroupResource, prefix string) {
	overrides := s.Overrides[groupResource]
	overrides.etcdResourcePrefix = prefix
	s.Overrides[groupResource] = overrides
}

func (s *DefaultStorageFactory) SetSerializer(groupResource schema.GroupResource, mediaType string, serializer runtime.StorageSerializer) {
	overrides := s.Overrides[groupResource]
	overrides.mediaType = mediaType
	overrides.serializer = serializer
	s.Overrides[groupResource] = overrides
}

func (s *DefaultStorageFactory) SetTransformer(groupResource schema.GroupResource, transformer value.Transformer) {
	overrides := s.Overrides[groupResource]
	overrides.transformer = transformer
	s.Overrides[groupResource] = overrides
}

// AddCohabitatingResources links resources together the order of the slice matters!  its the priority order of lookup for finding a storage location
func (s *DefaultStorageFactory) AddCohabitatingResources(groupResources ...schema.GroupResource) {
	for _, groupResource := range groupResources {
		overrides := s.Overrides[groupResource]
		overrides.cohabitatingResources = groupResources
		s.Overrides[groupResource] = overrides
	}
}

func (s *DefaultStorageFactory) AddSerializationChains(encoderDecoratorFn func(runtime.Encoder) runtime.Encoder, decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder, groupResources ...schema.GroupResource) {
	for _, groupResource := range groupResources {
		overrides := s.Overrides[groupResource]
		overrides.encoderDecoratorFn = encoderDecoratorFn
		overrides.decoderDecoratorFn = decoderDecoratorFn
		s.Overrides[groupResource] = overrides
	}
}

func getAllResourcesAlias(resource schema.GroupResource) schema.GroupResource {
	return schema.GroupResource{Group: resource.Group, Resource: AllResources}
}

func (s *DefaultStorageFactory) getStorageGroupResource(groupResource schema.GroupResource) schema.GroupResource {
	for _, potentialStorageResource := range s.Overrides[groupResource].cohabitatingResources {
		if s.APIResourceConfigSource.AnyVersionOfResourceEnabled(potentialStorageResource) {
			return potentialStorageResource
		}
	}

	return groupResource
}

// New finds the storage destination for the given group and resource. It will
// return an error if the group has no storage destination configured.
func (s *DefaultStorageFactory) NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error) {
	chosenStorageResource := s.getStorageGroupResource(groupResource)

	// operate on copy
	storageConfig := s.StorageConfig
	codecConfig := StorageCodecConfig{
		StorageMediaType:  s.DefaultMediaType,
		StorageSerializer: s.DefaultSerializer,
	}

	if override, ok := s.Overrides[getAllResourcesAlias(chosenStorageResource)]; ok {
		override.Apply(&storageConfig, &codecConfig)
	}
	if override, ok := s.Overrides[chosenStorageResource]; ok {
		override.Apply(&storageConfig, &codecConfig)
	}

	var err error
	codecConfig.StorageVersion, err = s.ResourceEncodingConfig.StorageEncodingFor(chosenStorageResource)
	if err != nil {
		return nil, err
	}
	codecConfig.MemoryVersion, err = s.ResourceEncodingConfig.InMemoryEncodingFor(groupResource)
	if err != nil {
		return nil, err
	}
	codecConfig.Config = storageConfig

	storageConfig.Codec, err = s.newStorageCodecFn(codecConfig)
	if err != nil {
		return nil, err
	}
	glog.V(3).Infof("storing %v in %v, reading as %v from %#v", groupResource, codecConfig.StorageVersion, codecConfig.MemoryVersion, codecConfig.Config)

	return &storageConfig, nil
}

// Backends returns all backends for all registered storage destinations.
// Used for getting all instances for health validations.
func (s *DefaultStorageFactory) Backends() []Backend {
	servers := sets.NewString(s.StorageConfig.ServerList...)

	for _, overrides := range s.Overrides {
		servers.Insert(overrides.etcdLocation...)
	}

	tlsConfig := &tls.Config{
		InsecureSkipVerify: true,
	}
	if len(s.StorageConfig.CertFile) > 0 && len(s.StorageConfig.KeyFile) > 0 {
		cert, err := tls.LoadX509KeyPair(s.StorageConfig.CertFile, s.StorageConfig.KeyFile)
		if err != nil {
			glog.Errorf("failed to load key pair while getting backends: %s", err)
		} else {
			tlsConfig.Certificates = []tls.Certificate{cert}
		}
	}
	if len(s.StorageConfig.CAFile) > 0 {
		if caCert, err := ioutil.ReadFile(s.StorageConfig.CAFile); err != nil {
			glog.Errorf("failed to read ca file while getting backends: %s", err)
		} else {
			caPool := x509.NewCertPool()
			caPool.AppendCertsFromPEM(caCert)
			tlsConfig.RootCAs = caPool
			tlsConfig.InsecureSkipVerify = false
		}
	}

	backends := []Backend{}
	for server := range servers {
		backends = append(backends, Backend{
			Server:    server,
			TLSConfig: tlsConfig,
		})
	}
	return backends
}

func (s *DefaultStorageFactory) ResourcePrefix(groupResource schema.GroupResource) string {
	chosenStorageResource := s.getStorageGroupResource(groupResource)
	groupOverride := s.Overrides[getAllResourcesAlias(chosenStorageResource)]
	exactResourceOverride := s.Overrides[chosenStorageResource]

	etcdResourcePrefix := s.DefaultResourcePrefixes[chosenStorageResource]
	if len(groupOverride.etcdResourcePrefix) > 0 {
		etcdResourcePrefix = groupOverride.etcdResourcePrefix
	}
	if len(exactResourceOverride.etcdResourcePrefix) > 0 {
		etcdResourcePrefix = exactResourceOverride.etcdResourcePrefix
	}
	if len(etcdResourcePrefix) == 0 {
		etcdResourcePrefix = strings.ToLower(chosenStorageResource.Resource)
	}

	return etcdResourcePrefix
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2016 Eli Janssen
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Package gostrftime formats time.Time using strftime(3) conventions.
package gostrftime

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

func strftime(b *bytes.Buffer, c byte, t time.Time) error {
	switch c {
	case 'A':
		b.WriteString(t.Weekday().String())
	case 'a':
		b.WriteString(t.Weekday().String()[:3])
	case 'B':
		b.WriteString(t.Month().String())
	case 'b':
		b.WriteString(t.Month().String()[:3])
	case 'C':
		fmt.Fprintf(b, "%02d", t.Year())
	case 'D':
		y, m, d := t.Date()
		fmt.Fprintf(b, "%02d/%02d/%02d", m, d, y%100)
	case 'd':
		fmt.Fprintf(b, "%02d", t.Day())
	case 'e':
		fmt.Fprintf(b, "%2d", t.Day())
	case 'F':
		y, m, d := t.Date()
		fmt.Fprintf(b, "%04d-%02d-%02d", y, m, d)
	case 'f':
		usec := t.Nanosecond() / 1e3
		fmt.Fprintf(b, "%06d", int(usec))
	case 'H':
		fmt.Fprintf(b, "%02d", t.Hour())
	case 'h':
		b.WriteString(t.Month().String()[:3])
	case 'I':
		hr := t.Hour() % 12
		if hr == 0 {
			hr = 12
		}
		fmt.Fprintf(b, "%02d", hr)
	case 'j':
		fmt.Fprintf(b, "%03d", t.YearDay())
	case 'k':
		fmt.Fprintf(b, "%2d", t.Hour())
	case 'L':
		msec := t.Nanosecond() / 1e6
		fmt.Fprintf(b, "%03d", int(msec))
	case 'l':
		hr := t.Hour() % 12
		if hr == 0 {
			hr = 12
		}
		fmt.Fprintf(b, "%2d", hr)
	case 'M':
		fmt.Fprintf(b, "%02d", t.Minute())
	case 'm':
		fmt.Fprintf(b, "%02d", t.Month())
	case 'N':
		fmt.Fprintf(b, "%09d", t.Nanosecond())
	case 'n':
		b.WriteByte('\n')
	case 'P':
		if t.Hour() < 12 {
			b.WriteString("am")
		} else {
			b.WriteString("pm")
		}
	case 'p':
		if t.Hour() < 12 {
			b.WriteString("AM")
		} else {
			b.WriteString("PM")
		}
	case 'R':
		h, m, _ := t.Clock()
		fmt.Fprintf(b, "%02d:%02d", h, m)
	case 'r':
		h, m, s := t.Clock()

		var tm string
		if h < 12 {
			tm = "AM"
		} else {
			tm = "PM"
		}

		hr := h % 12
		if hr == 0 {
			hr = 12
		}

		fmt.Fprintf(b, "%02d:%02d:%02d %s", hr, m, s, tm)
	case 'S':
		fmt.Fprintf(b, "%02d", t.Second())
	case 's':
		b.WriteString(strconv.FormatInt(t.Unix(), 10))
	case 'T':
		h, m, s := t.Clock()
		fmt.Fprintf(b, "%02d:%02d:%02d", h, m, s)
	case 't':
		b.WriteByte('\t')
	case 'v':
		fmt.Fprintf(b, "%2d-%s-%04d", t.Day(), t.Month().String()[:3], t.Year())
	case 'w':
		fmt.Fprintf(b, "%d", t.Weekday())
	case 'Y':
		fmt.Fprintf(b, "%04d", t.Year())
	case 'y':
		fmt.Fprintf(b, "%02d", t.Year()%100)
	case 'Z':
		zone, _ := t.Zone()
		b.WriteString(zone)
	case 'z':
		_, offset := t.Zone()
		allMinutes := int(offset / 60)
		fmt.Fprintf(b, "%+03d%02d", int(allMinutes/60), int(allMinutes%60))
	default:
		return fmt.Errorf("No valid replacement")
	}

	return nil
}

// Format returns a textual representation of the time value
// formatting according to format. format supports most of the formatting
// methods defined in strftime(3), minus the GNU libc extensions and POSIX
// locale extensions.
//
// List of accepted format expansion values:
//  %A  full weekday name (Sunday)
//  %a  abbreviated weekday name (Sun)
//  %B  full month name (September)
//  %b  abbreviated month name (Sep)
//  %C  (year / 100) as number. Single digits are preceded by zero (20)
//  %D  equivalent to %m/%d/%y (09/21/14)
//  %d  day of month as number. Single digits are preceded by zero (21)
//  %e  day of month as number. Single digits are preceded by a blank (21)
//  %f  microsecond as a six digit decimal number, zero-padded on the left (001234)
//  %F  equivalent to %Y-%m-%d (2014-09-21)
//  %H  the hour (24 hour clock) as a number. Single digits are preceded by zero (15)
//  %h  same as %b
//  %I  the hour (12 hour clock) as a number. Single digits are preceded by zero (03)
//  %j  the day of the year as a decimal number. Single digits are preced by zeros (264)
//  %k  the hour (24 hour clock) as a number. Single digits are preceded by a blank (15)
//  %L  millisecond as a three digit decimal number, zero-padded on the left (001)
//  %l  the hour (12 hour clock) as a number. Single digits are preceded by blank ( 3)
//  %M  the minute as a decimal number. Single digits are preceded by a zero (32)
//  %m  the month as a decimal number. Single digits are preceded by a zero (09)
//  %N  nanosecond as a 9 digit decimal number, zero-padded on the left (001234567)
//  %n  a newline (\n)
//  %P  am or pm as appropriate
//  %p  AM or PM as appropriate
//  %R  equivalent to %H:%M
//  %r  equivalent to %I:%M:%S %p
//  %S  the second as a number. Single digits are preceded by a zero (05)
//  %s  the number of seconds since the Epoch, UTC
//  %T  equivalent to %H:%M:%S
//  %t  a tab (\t)
//  %v  equivalent to %e-%b-%Y
//  %w  the weekday (Sunday as first day of the week) as a number. (0)
//  %Y  the year with century as a number (2014)
//  %y  year without century as a number. Single digits are preceded by zero (14)
//  %Z  time zone name (UTC)
//  %z  the time zone offset from UTC (-0700)
func Format(format string, t time.Time) string {
	if strings.IndexByte(format, '%') < 0 {
		return format
	}

	outBuf := &bytes.Buffer{}
	rr := strings.NewReader(format)
	for {
		// avoid a tiny bit of overhead by using ReadByte instead of ReadRune.
		// ReadRune does a comparison check against utf8.RuneSelf on every
		// "read", and we only care about that check /right after/ seeing a
		// '%', as we won't call format in that case.
		r, err := rr.ReadByte()
		if err != nil {
			break
		}

		if r != '%' {
			outBuf.WriteByte(r)
			continue
		}

		nr, err := rr.ReadByte()
		if err != nil {
			// got a percent, but then end of string
			// just append % and finish
			outBuf.WriteByte('%')
			break
		}

		if nr == '%' {
			outBuf.WriteByte('%')
			continue
		} else if nr >= utf8.RuneSelf {
			// if part of a unicode surrogate pair, write out and continue
			outBuf.WriteByte('%')
			outBuf.WriteByte(nr)
			continue
		}

		err = strftime(outBuf, nr, t)
		if err != nil {
			outBuf.WriteByte('%')
			outBuf.WriteByte(nr)
			continue
		}
	}
	return outBuf.String()
}

// Strftime is an alias for Format
func Strftime(format string, t time.Time) string {
	return Format(format, t)
}
<commit_msg>fix comment wording to be clearer<commit_after>// Copyright (c) 2014-2016 Eli Janssen
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Package gostrftime formats time.Time using strftime(3) conventions.
package gostrftime

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

func strftime(b *bytes.Buffer, c byte, t time.Time) error {
	switch c {
	case 'A':
		b.WriteString(t.Weekday().String())
	case 'a':
		b.WriteString(t.Weekday().String()[:3])
	case 'B':
		b.WriteString(t.Month().String())
	case 'b':
		b.WriteString(t.Month().String()[:3])
	case 'C':
		fmt.Fprintf(b, "%02d", t.Year())
	case 'D':
		y, m, d := t.Date()
		fmt.Fprintf(b, "%02d/%02d/%02d", m, d, y%100)
	case 'd':
		fmt.Fprintf(b, "%02d", t.Day())
	case 'e':
		fmt.Fprintf(b, "%2d", t.Day())
	case 'F':
		y, m, d := t.Date()
		fmt.Fprintf(b, "%04d-%02d-%02d", y, m, d)
	case 'f':
		usec := t.Nanosecond() / 1e3
		fmt.Fprintf(b, "%06d", int(usec))
	case 'H':
		fmt.Fprintf(b, "%02d", t.Hour())
	case 'h':
		b.WriteString(t.Month().String()[:3])
	case 'I':
		hr := t.Hour() % 12
		if hr == 0 {
			hr = 12
		}
		fmt.Fprintf(b, "%02d", hr)
	case 'j':
		fmt.Fprintf(b, "%03d", t.YearDay())
	case 'k':
		fmt.Fprintf(b, "%2d", t.Hour())
	case 'L':
		msec := t.Nanosecond() / 1e6
		fmt.Fprintf(b, "%03d", int(msec))
	case 'l':
		hr := t.Hour() % 12
		if hr == 0 {
			hr = 12
		}
		fmt.Fprintf(b, "%2d", hr)
	case 'M':
		fmt.Fprintf(b, "%02d", t.Minute())
	case 'm':
		fmt.Fprintf(b, "%02d", t.Month())
	case 'N':
		fmt.Fprintf(b, "%09d", t.Nanosecond())
	case 'n':
		b.WriteByte('\n')
	case 'P':
		if t.Hour() < 12 {
			b.WriteString("am")
		} else {
			b.WriteString("pm")
		}
	case 'p':
		if t.Hour() < 12 {
			b.WriteString("AM")
		} else {
			b.WriteString("PM")
		}
	case 'R':
		h, m, _ := t.Clock()
		fmt.Fprintf(b, "%02d:%02d", h, m)
	case 'r':
		h, m, s := t.Clock()

		var tm string
		if h < 12 {
			tm = "AM"
		} else {
			tm = "PM"
		}

		hr := h % 12
		if hr == 0 {
			hr = 12
		}

		fmt.Fprintf(b, "%02d:%02d:%02d %s", hr, m, s, tm)
	case 'S':
		fmt.Fprintf(b, "%02d", t.Second())
	case 's':
		b.WriteString(strconv.FormatInt(t.Unix(), 10))
	case 'T':
		h, m, s := t.Clock()
		fmt.Fprintf(b, "%02d:%02d:%02d", h, m, s)
	case 't':
		b.WriteByte('\t')
	case 'v':
		fmt.Fprintf(b, "%2d-%s-%04d", t.Day(), t.Month().String()[:3], t.Year())
	case 'w':
		fmt.Fprintf(b, "%d", t.Weekday())
	case 'Y':
		fmt.Fprintf(b, "%04d", t.Year())
	case 'y':
		fmt.Fprintf(b, "%02d", t.Year()%100)
	case 'Z':
		zone, _ := t.Zone()
		b.WriteString(zone)
	case 'z':
		_, offset := t.Zone()
		allMinutes := int(offset / 60)
		fmt.Fprintf(b, "%+03d%02d", int(allMinutes/60), int(allMinutes%60))
	default:
		return fmt.Errorf("No valid replacement")
	}

	return nil
}

// Format returns a textual representation of the time value
// formatting according to format. format supports most of the formatting
// methods defined in strftime(3), minus the GNU libc extensions and POSIX
// locale extensions.
//
// List of accepted format expansion values:
//  %A  full weekday name (Sunday)
//  %a  abbreviated weekday name (Sun)
//  %B  full month name (September)
//  %b  abbreviated month name (Sep)
//  %C  (year / 100) as number. Single digits are preceded by zero (20)
//  %D  equivalent to %m/%d/%y (09/21/14)
//  %d  day of month as number. Single digits are preceded by zero (21)
//  %e  day of month as number. Single digits are preceded by a blank (21)
//  %f  microsecond as a six digit decimal number, zero-padded on the left (001234)
//  %F  equivalent to %Y-%m-%d (2014-09-21)
//  %H  the hour (24 hour clock) as a number. Single digits are preceded by zero (15)
//  %h  same as %b
//  %I  the hour (12 hour clock) as a number. Single digits are preceded by zero (03)
//  %j  the day of the year as a decimal number. Single digits are preced by zeros (264)
//  %k  the hour (24 hour clock) as a number. Single digits are preceded by a blank (15)
//  %L  millisecond as a three digit decimal number, zero-padded on the left (001)
//  %l  the hour (12 hour clock) as a number. Single digits are preceded by blank ( 3)
//  %M  the minute as a decimal number. Single digits are preceded by a zero (32)
//  %m  the month as a decimal number. Single digits are preceded by a zero (09)
//  %N  nanosecond as a 9 digit decimal number, zero-padded on the left (001234567)
//  %n  a newline (\n)
//  %P  am or pm as appropriate
//  %p  AM or PM as appropriate
//  %R  equivalent to %H:%M
//  %r  equivalent to %I:%M:%S %p
//  %S  the second as a number. Single digits are preceded by a zero (05)
//  %s  the number of seconds since the Epoch, UTC
//  %T  equivalent to %H:%M:%S
//  %t  a tab (\t)
//  %v  equivalent to %e-%b-%Y
//  %w  the weekday (Sunday as first day of the week) as a number. (0)
//  %Y  the year with century as a number (2014)
//  %y  year without century as a number. Single digits are preceded by zero (14)
//  %Z  time zone name (UTC)
//  %z  the time zone offset from UTC (-0700)
func Format(format string, t time.Time) string {
	if strings.IndexByte(format, '%') < 0 {
		return format
	}

	outBuf := &bytes.Buffer{}
	rr := strings.NewReader(format)
	for {
		// avoid a tiny bit of overhead by using ReadByte instead of ReadRune.
		// ReadRune does a comparison check against utf8.RuneSelf on every
		// "read", and we only care about that check /right after/ seeing a
		// '%', as we won't call format in that case.
		r, err := rr.ReadByte()
		if err != nil {
			break
		}

		if r != '%' {
			outBuf.WriteByte(r)
			continue
		}

		nr, err := rr.ReadByte()
		if err != nil {
			// got a percent, but then end of string
			// just append % and finish
			outBuf.WriteByte('%')
			break
		}

		if nr == '%' {
			outBuf.WriteByte('%')
			continue
		} else if nr >= utf8.RuneSelf {
			// if part of a utf8 continuation (>=0x80), then write out and continue
			outBuf.WriteByte('%')
			outBuf.WriteByte(nr)
			continue
		}

		err = strftime(outBuf, nr, t)
		if err != nil {
			outBuf.WriteByte('%')
			outBuf.WriteByte(nr)
			continue
		}
	}
	return outBuf.String()
}

// Strftime is an alias for Format
func Strftime(format string, t time.Time) string {
	return Format(format, t)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package networkingcommon

import (
	"github.com/juju/errors"
	"github.com/juju/names"

	"github.com/juju/juju/apiserver/common"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/environs"
)

// SupportsSpaces checks if the environment implements NetworkingEnviron
// and also if it supports spaces.
func SupportsSpaces(backing NetworkBacking) error {
	config, err := backing.EnvironConfig()
	if err != nil {
		return errors.Annotate(err, "getting environment config")
	}
	env, err := environs.New(config)
	if err != nil {
		return errors.Annotate(err, "validating environment config")
	}
	netEnv, ok := environs.SupportsNetworking(env)
	if !ok {
		return errors.NotSupportedf("networking")
	}
	ok, err = netEnv.SupportsSpaces()
	if !ok {
		return errors.NotSupportedf("spaces")
	}
	return err
}

// CreateSpaces creates a new Juju network space, associating the
// specified subnets with it (optional; can be empty).
func CreateSpaces(backing NetworkBacking, args params.CreateSpacesParams) (results params.ErrorResults, err error) {
	err = SupportsSpaces(backing)
	if err != nil {
		return results, common.ServerError(errors.Trace(err))
	}

	results.Results = make([]params.ErrorResult, len(args.Spaces))

	for i, space := range args.Spaces {
		err := createOneSpace(backing, space)
		if err == nil {
			continue
		}
		results.Results[i].Error = common.ServerError(errors.Trace(err))
	}

	return results, nil
}

func createOneSpace(backing NetworkBacking, args params.CreateSpaceParams) error {
	// Validate the args, assemble information for api.backing.AddSpaces
	var subnets []string

	spaceTag, err := names.ParseSpaceTag(args.SpaceTag)
	if err != nil {
		return errors.Trace(err)
	}

	for _, tag := range args.SubnetTags {
		subnetTag, err := names.ParseSubnetTag(tag)
		if err != nil {
			return errors.Trace(err)
		}
		subnets = append(subnets, subnetTag.Id())
	}

	// Add the validated space.
	err = backing.AddSpace(spaceTag.Id(), args.ProviderId, subnets, args.Public)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}
<commit_msg>Type correction<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package networkingcommon

import (
	"github.com/juju/errors"
	"github.com/juju/names"

	"github.com/juju/juju/apiserver/common"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/network"
)

// SupportsSpaces checks if the environment implements NetworkingEnviron
// and also if it supports spaces.
func SupportsSpaces(backing NetworkBacking) error {
	config, err := backing.EnvironConfig()
	if err != nil {
		return errors.Annotate(err, "getting environment config")
	}
	env, err := environs.New(config)
	if err != nil {
		return errors.Annotate(err, "validating environment config")
	}
	netEnv, ok := environs.SupportsNetworking(env)
	if !ok {
		return errors.NotSupportedf("networking")
	}
	ok, err = netEnv.SupportsSpaces()
	if !ok {
		return errors.NotSupportedf("spaces")
	}
	return err
}

// CreateSpaces creates a new Juju network space, associating the
// specified subnets with it (optional; can be empty).
func CreateSpaces(backing NetworkBacking, args params.CreateSpacesParams) (results params.ErrorResults, err error) {
	err = SupportsSpaces(backing)
	if err != nil {
		return results, common.ServerError(errors.Trace(err))
	}

	results.Results = make([]params.ErrorResult, len(args.Spaces))

	for i, space := range args.Spaces {
		err := createOneSpace(backing, space)
		if err == nil {
			continue
		}
		results.Results[i].Error = common.ServerError(errors.Trace(err))
	}

	return results, nil
}

func createOneSpace(backing NetworkBacking, args params.CreateSpaceParams) error {
	// Validate the args, assemble information for api.backing.AddSpaces
	var subnets []string

	spaceTag, err := names.ParseSpaceTag(args.SpaceTag)
	if err != nil {
		return errors.Trace(err)
	}

	for _, tag := range args.SubnetTags {
		subnetTag, err := names.ParseSubnetTag(tag)
		if err != nil {
			return errors.Trace(err)
		}
		subnets = append(subnets, subnetTag.Id())
	}

	// Add the validated space.
	err = backing.AddSpace(spaceTag.Id(), network.Id(args.ProviderId), subnets, args.Public)
	if err != nil {
		return errors.Trace(err)
	}
	return nil
}
<|endoftext|>


<commit_before>package image

import (
	"bytes"
	gocontext "context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"runtime"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/travis-ci/worker/context"
	"golang.org/x/sync/errgroup"
)

type LXCImage struct {
	Aliases []struct {
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"aliases"`
	Size         int       `json:"size"`
	ExpiresAt    time.Time `json:"expires_at"`
	AutoUpdate   bool      `json:"auto_update"`
	LastUsedAt   time.Time `json:"last_used_at"`
	Cached       bool      `json:"cached"`
	UploadedAt   time.Time `json:"uploaded_at"`
	Fingerprint  string    `json:"fingerprint"`
	Architecture string    `json:"architecture"`
	Public       bool      `json:"public"`
	Properties   struct {
		Description string `json:"description"`
	} `json:"properties"`
	Filename  string    `json:"filename"`
	Profiles  []string  `json:"profiles"`
	CreatedAt time.Time `json:"created_at"`
	Type      string    `json:"type"`
}

var (
	arch  = runtime.GOARCH
	infra = fmt.Sprintf("lxd-%s", arch)

	tags = []string{"os:linux", "group:stable", "group:edge", "group:dev"}
)

func NewManager(ctx gocontext.Context, selector *APISelector, imagesBaseURL *url.URL) *Manager {
	logger := context.LoggerFromContext(ctx).WithField("self", "image_manager")

	return &Manager{
		selector:      selector,
		logger:        logger,
		imagesBaseURL: imagesBaseURL,
	}
}

type Manager struct {
	selector      *APISelector
	logger        *logrus.Entry
	imagesBaseURL *url.URL
}

func (m *Manager) Load(imageName string) error {
	ok, err := m.Exists(imageName)
	if err != nil {
		return err
	}

	if ok {
		m.logger.WithFields(logrus.Fields{
			"image_name": imageName,
		}).Info("image already present")

		return nil
	}

	url := m.imageUrl(imageName)

	m.logger.WithFields(logrus.Fields{
		"image_name": imageName,
		"url":        url,
	}).Info("downloading image")

	imgPath, err := m.download(url)
	if err != nil {
		return err
	}
	defer os.Remove(imgPath)

	m.logger.WithFields(logrus.Fields{
		"image_name": imageName,
		"image_path": imgPath,
	}).Info("initializing image")

	return m.initialize(imageName, imgPath)
}

func (m *Manager) Exists(imageName string) (bool, error) {
	result, err := m.exec("lxc", "image", "list", "--format=json")
	if err != nil {
		return false, err
	}

	images := []LXCImage{}
	err = json.Unmarshal(result, &images)
	if err != nil {
		return false, err
	}

	for _, img := range images {
		for _, alias := range img.Aliases {
			if alias.Name == imageName {
				return true, nil
			}
		}
	}

	return false, nil
}

func (m *Manager) Update(ctx gocontext.Context) error {
	m.logger.WithFields(logrus.Fields{
		"arch":  arch,
		"infra": infra,
	}).Info("updating lxc images")

	images, err := m.selector.SelectAll(ctx, infra, tags)
	if err != nil {
		return err
	}

	g, _ := errgroup.WithContext(ctx)
	for _, img := range images {
		img := img

		g.Go(func() error {
			m.logger.WithFields(logrus.Fields{
				"image_name": img.Name,
				"infra":      infra,
				"group":      img.Group(),
			}).Info("updating image")

			return m.Load(img.Name)
		})

	}

	return g.Wait()
}

func (m *Manager) Cleanup() error {
	// TODO

	return nil
}

func (m *Manager) download(imageURL string) (string, error) {
	tmpfile, err := ioutil.TempFile("", "")
	if err != nil {
		return "", err
	}

	// TODO: Should we limit max size of the image?
	resp, err := http.Get(imageURL)
	if err != nil {
		return "", fmt.Errorf("failed to download image: %w", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("unexpected status code: %s", resp.Status)
	}

	_, err = io.Copy(tmpfile, resp.Body)
	if err != nil {
		return "", err
	}

	err = tmpfile.Close()
	if err != nil {
		return "", err
	}

	return tmpfile.Name(), nil
}

func (m *Manager) initialize(imageName, path string) error {
	_, err := m.exec("lxc", "image", "import", path, fmt.Sprintf("--alias=%s", imageName))
	if err != nil {
		return err
	}

	containerName := fmt.Sprintf("%s-warmup", imageName)
	_, err = m.exec("lxc", "init", imageName, containerName)
	defer m.exec("lxc", "delete", "-f", containerName)

	return err
}

func (m *Manager) exec(command string, args ...string) ([]byte, error) {
	var out bytes.Buffer

	cmd := exec.Command(command, args...)
	cmd.Stdout = &out
	cmd.Stderr = &out

	defer out.Reset()

	m.logger.WithFields(logrus.Fields{
		"command": command,
		"args":    args,
	}).Debug("running lxc command")

	err := cmd.Run()
	if err != nil {
		m.logger.WithFields(logrus.Fields{
			"command": command,
			"args":    args,
			"error":   err,
			"output":  out.String(),
		}).Error("lxc command failed")
	} else {
		m.logger.WithFields(logrus.Fields{
			"output": out.String(),
		}).Debug("lxc command succeeded")
	}

	return out.Bytes(), err
}

// TODO: image URL should be returned by job-board
func (m *Manager) imageUrl(name string) string {
	u := *m.imagesBaseURL
	u.Path = fmt.Sprintf("/%s/%s.tar.gz", arch, name)
	return u.String()
}
<commit_msg>Make golangci happy<commit_after>package image

import (
	"bytes"
	gocontext "context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"runtime"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/travis-ci/worker/context"
	"golang.org/x/sync/errgroup"
)

type LXCImage struct {
	Aliases []struct {
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"aliases"`
	Size         int       `json:"size"`
	ExpiresAt    time.Time `json:"expires_at"`
	AutoUpdate   bool      `json:"auto_update"`
	LastUsedAt   time.Time `json:"last_used_at"`
	Cached       bool      `json:"cached"`
	UploadedAt   time.Time `json:"uploaded_at"`
	Fingerprint  string    `json:"fingerprint"`
	Architecture string    `json:"architecture"`
	Public       bool      `json:"public"`
	Properties   struct {
		Description string `json:"description"`
	} `json:"properties"`
	Filename  string    `json:"filename"`
	Profiles  []string  `json:"profiles"`
	CreatedAt time.Time `json:"created_at"`
	Type      string    `json:"type"`
}

var (
	arch  = runtime.GOARCH
	infra = fmt.Sprintf("lxd-%s", arch)

	tags = []string{"os:linux", "group:stable", "group:edge", "group:dev"}
)

func NewManager(ctx gocontext.Context, selector *APISelector, imagesBaseURL *url.URL) *Manager {
	logger := context.LoggerFromContext(ctx).WithField("self", "image_manager")

	return &Manager{
		selector:      selector,
		logger:        logger,
		imagesBaseURL: imagesBaseURL,
	}
}

type Manager struct {
	selector      *APISelector
	logger        *logrus.Entry
	imagesBaseURL *url.URL
}

func (m *Manager) Load(imageName string) error {
	ok, err := m.Exists(imageName)
	if err != nil {
		return err
	}

	if ok {
		m.logger.WithFields(logrus.Fields{
			"image_name": imageName,
		}).Info("image already present")

		return nil
	}

	url := m.imageUrl(imageName)

	m.logger.WithFields(logrus.Fields{
		"image_name": imageName,
		"url":        url,
	}).Info("downloading image")

	imgPath, err := m.download(url)
	if err != nil {
		return err
	}
	defer os.Remove(imgPath)

	m.logger.WithFields(logrus.Fields{
		"image_name": imageName,
		"image_path": imgPath,
	}).Info("initializing image")

	return m.initialize(imageName, imgPath)
}

func (m *Manager) Exists(imageName string) (bool, error) {
	result, err := m.exec("lxc", "image", "list", "--format=json")
	if err != nil {
		return false, err
	}

	images := []LXCImage{}
	err = json.Unmarshal(result, &images)
	if err != nil {
		return false, err
	}

	for _, img := range images {
		for _, alias := range img.Aliases {
			if alias.Name == imageName {
				return true, nil
			}
		}
	}

	return false, nil
}

func (m *Manager) Update(ctx gocontext.Context) error {
	m.logger.WithFields(logrus.Fields{
		"arch":  arch,
		"infra": infra,
	}).Info("updating lxc images")

	images, err := m.selector.SelectAll(ctx, infra, tags)
	if err != nil {
		return err
	}

	g, _ := errgroup.WithContext(ctx)
	for _, img := range images {
		img := img

		g.Go(func() error {
			m.logger.WithFields(logrus.Fields{
				"image_name": img.Name,
				"infra":      infra,
				"group":      img.Group(),
			}).Info("updating image")

			return m.Load(img.Name)
		})

	}

	return g.Wait()
}

func (m *Manager) Cleanup() error {
	// TODO

	return nil
}

func (m *Manager) download(imageURL string) (string, error) {
	tmpfile, err := ioutil.TempFile("", "")
	if err != nil {
		return "", err
	}

	// TODO: Should we limit max size of the image?
	resp, err := http.Get(imageURL)
	if err != nil {
		return "", fmt.Errorf("failed to download image: %w", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("unexpected status code: %s", resp.Status)
	}

	_, err = io.Copy(tmpfile, resp.Body)
	if err != nil {
		return "", err
	}

	err = tmpfile.Close()
	if err != nil {
		return "", err
	}

	return tmpfile.Name(), nil
}

func (m *Manager) initialize(imageName, path string) error {
	_, err := m.exec("lxc", "image", "import", path, fmt.Sprintf("--alias=%s", imageName))
	if err != nil {
		return err
	}

	containerName := fmt.Sprintf("%s-warmup", imageName)
	_, err = m.exec("lxc", "init", imageName, containerName)
	defer func() {
		_, err := m.exec("lxc", "delete", "-f", containerName)
		if err != nil {
			m.logger.WithFields(logrus.Fields{
				"error":          err,
				"container_name": containerName,
			}).Error("failed to delete container")
		}
	}()

	return err
}

func (m *Manager) exec(command string, args ...string) ([]byte, error) {
	var out bytes.Buffer

	cmd := exec.Command(command, args...)
	cmd.Stdout = &out
	cmd.Stderr = &out

	defer out.Reset()

	m.logger.WithFields(logrus.Fields{
		"command": command,
		"args":    args,
	}).Debug("running lxc command")

	err := cmd.Run()
	if err != nil {
		m.logger.WithFields(logrus.Fields{
			"command": command,
			"args":    args,
			"error":   err,
			"output":  out.String(),
		}).Error("lxc command failed")
	} else {
		m.logger.WithFields(logrus.Fields{
			"output": out.String(),
		}).Debug("lxc command succeeded")
	}

	return out.Bytes(), err
}

// TODO: image URL should be returned by job-board
func (m *Manager) imageUrl(name string) string {
	u := *m.imagesBaseURL
	u.Path = fmt.Sprintf("/%s/%s.tar.gz", arch, name)
	return u.String()
}
<|endoftext|>