<commit_before>package main

import (
	"testing"
)

func Test_is_url(t *testing.T) {
	url1 := "https://github.com/ekalinin/envirius/blob/master/README.md"
	if !IsUrl(url1) {
		t.Error("This is url: ", url1)
	}

	url2 := "./README.md"
	if IsUrl(url2) {
		t.Error("This is not url: ", url2)
	}
}

func Test_grab_toc_onerow(t *testing.T) {
	toc_expected := []string{
		"  * [README in another language](#readme-in-another-language)",
	}
	toc := GrabToc(`

README in another language

	`)
	if toc[0] != toc_expected[0] {
		t.Error("Res :", toc, "\nExpected     :", toc_expected)
	}
}

func Test_grab_toc_onerow_with_newlines(t *testing.T) {
	toc_expected := []string{
		"  * [README in another language](#readme-in-another-language)",
	}
	toc := GrabToc(`

		README in another language

	`)
	if toc[0] != toc_expected[0] {
		t.Error("Res :", toc, "\nExpected     :", toc_expected)
	}
}

func Test_grab_toc_multiline_origin_github(t *testing.T) {

	toc_expected := []string{
		"  * [How to add a plugin?](#how-to-add-a-plugin)",
		"    * [Mandatory elements](#mandatory-elements)",
		"      * [plug_list_versions](#plug_list_versions)",
	}
	toc := GrabToc(`

How to add a plugin?

All plugins are in the directory
nv-plugins.
If you need to add support for a new language you should add it as plugin
inside this directory.

Mandatory elements

If you create a plugin which builds all stuff from source then In a simplest
case you need to implement 2 functions in the plugin's body:

plug_list_versions

This function should return list of available versions of the plugin.
For example:

	`)
	for i := 0; i <= len(toc_expected)-1; i++ {
		if toc[i] != toc_expected[i] {
			t.Error("Res :", toc[i], "\nExpected     :", toc_expected[i])
		}
	}
}

func Test_GrabToc_backquoted(t *testing.T) {
	toc_expected := []string{
		"  * [The command foo1](#the-command-foo1)",
		"    * [The command foo2 is better](#the-command-foo2-is-better)",
		"  * [The command bar1](#the-command-bar1)",
		"    * [The command bar2 is better](#the-command-bar2-is-better)",
	}

	toc := GrabToc(`

The command foo1

Blabla...

The command foo2 is better

Blabla...

The command bar1

Blabla...

The command bar2 is better

Blabla...

	`)

	for i := 0; i <= len(toc_expected)-1; i++ {
		if toc[i] != toc_expected[i] {
			t.Error("Res :", toc[i], "\nExpected      :", toc_expected[i])
		}
	}
}

func Test_grab_toc_with_abspath(t *testing.T) {
	link := "https://github.com/ekalinin/envirius/blob/master/README.md"
	toc_expected := []string{
		"  * [README in another language](" + link + "#readme-in-another-language)",
	}
	toc := GrabTocX(`

README in another language

	`, link)
	if toc[0] != toc_expected[0] {
		t.Error("Res :", toc, "\nExpected     :", toc_expected)
	}
}
<commit_msg>fixed tests<commit_after>package main

import (
	"testing"
)

func Test_is_url(t *testing.T) {
	url1 := "https://github.com/ekalinin/envirius/blob/master/README.md"
	if !IsUrl(url1) {
		t.Error("This is url: ", url1)
	}

	url2 := "./README.md"
	if IsUrl(url2) {
		t.Error("This is not url: ", url2)
	}
}

func Test_grab_toc_onerow(t *testing.T) {
	toc_expected := []string{
		"  * [README in another language](#readme-in-another-language)",
	}
	toc := *GrabToc(`

README in another language

	`)
	if toc[0] != toc_expected[0] {
		t.Error("Res :", toc, "\nExpected     :", toc_expected)
	}
}

func Test_grab_toc_onerow_with_newlines(t *testing.T) {
	toc_expected := []string{
		"  * [README in another language](#readme-in-another-language)",
	}
	toc := *GrabToc(`

		README in another language

	`)
	if toc[0] != toc_expected[0] {
		t.Error("Res :", toc, "\nExpected     :", toc_expected)
	}
}

func Test_grab_toc_multiline_origin_github(t *testing.T) {

	toc_expected := []string{
		"  * [How to add a plugin?](#how-to-add-a-plugin)",
		"    * [Mandatory elements](#mandatory-elements)",
		"      * [plug_list_versions](#plug_list_versions)",
	}
	toc := *GrabToc(`

How to add a plugin?

All plugins are in the directory
nv-plugins.
If you need to add support for a new language you should add it as plugin
inside this directory.

Mandatory elements

If you create a plugin which builds all stuff from source then In a simplest
case you need to implement 2 functions in the plugin's body:

plug_list_versions

This function should return list of available versions of the plugin.
For example:

	`)
	for i := 0; i <= len(toc_expected)-1; i++ {
		if toc[i] != toc_expected[i] {
			t.Error("Res :", toc[i], "\nExpected     :", toc_expected[i])
		}
	}
}

func Test_GrabToc_backquoted(t *testing.T) {
	toc_expected := []string{
		"  * [The command foo1](#the-command-foo1)",
		"    * [The command foo2 is better](#the-command-foo2-is-better)",
		"  * [The command bar1](#the-command-bar1)",
		"    * [The command bar2 is better](#the-command-bar2-is-better)",
	}

	toc := *GrabToc(`

The command foo1

Blabla...

The command foo2 is better

Blabla...

The command bar1

Blabla...

The command bar2 is better

Blabla...

	`)

	for i := 0; i <= len(toc_expected)-1; i++ {
		if toc[i] != toc_expected[i] {
			t.Error("Res :", toc[i], "\nExpected      :", toc_expected[i])
		}
	}
}

func Test_grab_toc_with_abspath(t *testing.T) {
	link := "https://github.com/ekalinin/envirius/blob/master/README.md"
	toc_expected := []string{
		"  * [README in another language](" + link + "#readme-in-another-language)",
	}
	toc := *GrabTocX(`

README in another language

	`, link)
	if toc[0] != toc_expected[0] {
		t.Error("Res :", toc, "\nExpected     :", toc_expected)
	}
}
<|endoftext|>


<commit_before>package main
<commit_msg>Add test for handling config files.<commit_after>package main

import "testing"

func TestHandleConfigFile(t *testing.T) {

	if _, err := HandleConfigFile(""); err == nil {
		t.FailNow()
	}

	// Depends on default config being avaiable and correct (which is nice!)
	if _, err := HandleConfigFile("config.yaml"); err != nil {
		t.FailNow()
	}

}
<|endoftext|>


<commit_before>// +build integration

package main

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"testing"

	"regexp"

	"github.com/elliotchance/c2go/util"
)

type programOut struct {
	stdout bytes.Buffer
	stderr bytes.Buffer
	isZero bool
}

// TestIntegrationScripts tests all programs in the tests directory.
//
// Integration tests are not run by default (only unit tests). These are
// indicated by the build flags at the top of the file. To include integration
// tests use:
//
//     go test -tags=integration
//
// You can also run a single file with:
//
//     go test -tags=integration -run=TestIntegrationScripts/tests/ctype/isalnum.c
//
func TestIntegrationScripts(t *testing.T) {
	testFiles, err := filepath.Glob("tests/*.c")
	if err != nil {
		t.Fatal(err)
	}

	exampleFiles, err := filepath.Glob("examples/*.c")
	if err != nil {
		t.Fatal(err)
	}

	files := append(testFiles, exampleFiles...)

	isVerbose := flag.CommandLine.Lookup("test.v").Value.String() == "true"

	totalTapTests := 0

	buildFolder = "build"
	os.RemoveAll(buildFolder)
	// Create build folder
	os.Mkdir(buildFolder, os.ModePerm)

	t.Parallel()
	var (
		cPath  = buildFolder + os.PathSeparator + "a.out"
		goPath = buildFolder + os.PathSeparator + "go.out"
		stdin  = "7"
		args   = []string{"some", "args"}
	)

	for _, file := range files {
		t.Run(file, func(t *testing.T) {
			cProgram := programOut{}
			goProgram := programOut{}

			create sub dir for test

			// Compile C.
			out, err := exec.Command("clang", "-lm", "-o", cPath, file).CombinedOutput()
			if err != nil {
				t.Fatalf("error: %s\n%s", err, out)
			}

			// Run C program
			cmd := exec.Command(cPath, args...)
			cmd.Stdin = strings.NewReader(stdin)
			cmd.Stdout = &cProgram.stdout
			cmd.Stderr = &cProgram.stderr
			err = cmd.Run()
			cProgram.isZero = err == nil

			programArgs := ProgramArgs{
				inputFile:   file,
				outputFile:  buildFolder + os.PathSeparator + "main.go",
				packageName: "main",
			}

			// Compile Go
			err = Start(programArgs)
			if err != nil {
				t.Fatalf("error: %s\n%s", err, out)
			}

			buildErr, err := exec.Command("go", "build", "-o", goPath, "build/main.go").CombinedOutput()
			if err != nil {
				t.Fatal(string(buildErr), err)
			}

			// Run Go program
			cmd = exec.Command(goPath, args...)
			cmd.Stdin = strings.NewReader(stdin)
			cmd.Stdout = &goProgram.stdout
			cmd.Stderr = &goProgram.stderr
			err = cmd.Run()
			goProgram.isZero = err == nil

			// Check for special exit codes that signal that tests have failed.
			if exitError, ok := err.(*exec.ExitError); ok {
				exitStatus := exitError.Sys().(syscall.WaitStatus).ExitStatus()
				if exitStatus == 101 || exitStatus == 102 {
					t.Fatal(goProgram.stdout.String())
				}
			}

			// Check if both exit codes are zero (or non-zero)
			if cProgram.isZero != goProgram.isZero {
				t.Fatalf("Exit statuses did not match.\n" +
					util.ShowDiff(cProgram.stdout.String(),
						goProgram.stdout.String()),
				)
			}

			// Check stderr
			if cProgram.stderr.String() != goProgram.stderr.String() {
				t.Fatalf("Expected %q, Got: %q",
					cProgram.stderr.String(),
					goProgram.stderr.String())
			}

			// Check stdout
			if cProgram.stdout.String() != goProgram.stdout.String() {
				t.Fatalf(util.ShowDiff(cProgram.stdout.String(),
					goProgram.stdout.String()))
			}

			// If this is not an example we will extact the number of tests run.
			if strings.Index(file, "examples/") == -1 && isVerbose {
				firstLine := strings.Split(goProgram.stdout.String(), "\n")[0]

				matches := regexp.MustCompile(`1\.\.(\d+)`).
					FindStringSubmatch(firstLine)
				if len(matches) == 0 {
					t.Fatalf("Test did not output tap: %s", file)
				}

				fmt.Printf("TAP: # %s: %s tests\n", file, matches[1])
				totalTapTests += util.Atoi(matches[1])
			}
		})
	}

	if isVerbose {
		fmt.Printf("TAP: # Total tests: %d\n", totalTapTests)
	}
}

func TestStartPreprocess(t *testing.T) {
	// temp dir
	tempDir := os.TempDir()

	// create temp file with garantee
	// wrong file body
	tempFile, err := newTempFile(tempDir, "c2go", "preprocess.c")
	if err != nil {
		t.Errorf("Cannot create temp file for execute test")
	}
	defer os.Remove(tempFile.Name())

	fmt.Fprintf(tempFile, "#include <AbsoluteWrongInclude.h>\nint main(void){\nwrong();\n}")

	err = tempFile.Close()
	if err != nil {
		t.Errorf("Cannot close the temp file")
	}

	var args ProgramArgs
	args.inputFile = tempFile.Name()

	err = Start(args)
	if err == nil {
		t.Errorf("Cannot test preprocess of application")
	}
}

func TestGoPath(t *testing.T) {
	gopath := "GOPATH"

	existEnv := os.Getenv(gopath)
	if existEnv == "" {
		t.Errorf("$GOPATH is not set")
	}

	// return env.var.
	defer func() {
		err := os.Setenv(gopath, existEnv)
		if err != nil {
			t.Errorf("Cannot restore the value of $GOPATH")
		}
	}()

	// reset value of env.var.
	err := os.Setenv(gopath, "")
	if err != nil {
		t.Errorf("Cannot set value of $GOPATH")
	}

	// testing
	err = Start(ProgramArgs{})
	if err == nil {
		t.Errorf(err.Error())
	}
}
<commit_msg>change constants<commit_after>// +build integration

package main

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"testing"

	"regexp"

	"github.com/elliotchance/c2go/util"
)

type programOut struct {
	stdout bytes.Buffer
	stderr bytes.Buffer
	isZero bool
}

// TestIntegrationScripts tests all programs in the tests directory.
//
// Integration tests are not run by default (only unit tests). These are
// indicated by the build flags at the top of the file. To include integration
// tests use:
//
//     go test -tags=integration
//
// You can also run a single file with:
//
//     go test -tags=integration -run=TestIntegrationScripts/tests/ctype/isalnum.c
//
func TestIntegrationScripts(t *testing.T) {
	testFiles, err := filepath.Glob("tests/*.c")
	if err != nil {
		t.Fatal(err)
	}

	exampleFiles, err := filepath.Glob("examples/*.c")
	if err != nil {
		t.Fatal(err)
	}

	files := append(testFiles, exampleFiles...)

	isVerbose := flag.CommandLine.Lookup("test.v").Value.String() == "true"

	totalTapTests := 0

	buildFolder = "build"
	os.RemoveAll(buildFolder)
	// Create build folder
	os.Mkdir(buildFolder, os.ModePerm)

	t.Parallel()
	var (
		cPath  = buildFolder + os.PathSeparator + "a.out"
		goPath = buildFolder + os.PathSeparator + "go.out"
		stdin  = "7"
		args   = []string{"some", "args"}
	)

	for _, file := range files {
		t.Run(file, func(t *testing.T) {
			cProgram := programOut{}
			goProgram := programOut{}

			// TODO: create sub dir for test

			// Compile C.
			out, err := exec.Command("clang", "-lm", "-o", cPath, file).CombinedOutput()
			if err != nil {
				t.Fatalf("error: %s\n%s", err, out)
			}

			// Run C program
			cmd := exec.Command(cPath, args...)
			cmd.Stdin = strings.NewReader(stdin)
			cmd.Stdout = &cProgram.stdout
			cmd.Stderr = &cProgram.stderr
			err = cmd.Run()
			cProgram.isZero = err == nil

			programArgs := ProgramArgs{
				inputFile:   file,
				outputFile:  buildFolder + os.PathSeparator + "main.go",
				packageName: "main",
			}

			// Compile Go
			err = Start(programArgs)
			if err != nil {
				t.Fatalf("error: %s\n%s", err, out)
			}

			buildErr, err := exec.Command("go", "build", "-o", goPath, buildFolder+os.PathSeparator+"main.go").CombinedOutput()
			if err != nil {
				t.Fatal(string(buildErr), err)
			}

			// Run Go program
			cmd = exec.Command(goPath, args...)
			cmd.Stdin = strings.NewReader(stdin)
			cmd.Stdout = &goProgram.stdout
			cmd.Stderr = &goProgram.stderr
			err = cmd.Run()
			goProgram.isZero = err == nil

			// Check for special exit codes that signal that tests have failed.
			if exitError, ok := err.(*exec.ExitError); ok {
				exitStatus := exitError.Sys().(syscall.WaitStatus).ExitStatus()
				if exitStatus == 101 || exitStatus == 102 {
					t.Fatal(goProgram.stdout.String())
				}
			}

			// Check if both exit codes are zero (or non-zero)
			if cProgram.isZero != goProgram.isZero {
				t.Fatalf("Exit statuses did not match.\n" +
					util.ShowDiff(cProgram.stdout.String(),
						goProgram.stdout.String()),
				)
			}

			// Check stderr
			if cProgram.stderr.String() != goProgram.stderr.String() {
				t.Fatalf("Expected %q, Got: %q",
					cProgram.stderr.String(),
					goProgram.stderr.String())
			}

			// Check stdout
			if cProgram.stdout.String() != goProgram.stdout.String() {
				t.Fatalf(util.ShowDiff(cProgram.stdout.String(),
					goProgram.stdout.String()))
			}

			// If this is not an example we will extact the number of tests run.
			if strings.Index(file, "examples/") == -1 && isVerbose {
				firstLine := strings.Split(goProgram.stdout.String(), "\n")[0]

				matches := regexp.MustCompile(`1\.\.(\d+)`).
					FindStringSubmatch(firstLine)
				if len(matches) == 0 {
					t.Fatalf("Test did not output tap: %s", file)
				}

				fmt.Printf("TAP: # %s: %s tests\n", file, matches[1])
				totalTapTests += util.Atoi(matches[1])
			}
		})
	}

	if isVerbose {
		fmt.Printf("TAP: # Total tests: %d\n", totalTapTests)
	}
}

func TestStartPreprocess(t *testing.T) {
	// temp dir
	tempDir := os.TempDir()

	// create temp file with garantee
	// wrong file body
	tempFile, err := newTempFile(tempDir, "c2go", "preprocess.c")
	if err != nil {
		t.Errorf("Cannot create temp file for execute test")
	}
	defer os.Remove(tempFile.Name())

	fmt.Fprintf(tempFile, "#include <AbsoluteWrongInclude.h>\nint main(void){\nwrong();\n}")

	err = tempFile.Close()
	if err != nil {
		t.Errorf("Cannot close the temp file")
	}

	var args ProgramArgs
	args.inputFile = tempFile.Name()

	err = Start(args)
	if err == nil {
		t.Errorf("Cannot test preprocess of application")
	}
}

func TestGoPath(t *testing.T) {
	gopath := "GOPATH"

	existEnv := os.Getenv(gopath)
	if existEnv == "" {
		t.Errorf("$GOPATH is not set")
	}

	// return env.var.
	defer func() {
		err := os.Setenv(gopath, existEnv)
		if err != nil {
			t.Errorf("Cannot restore the value of $GOPATH")
		}
	}()

	// reset value of env.var.
	err := os.Setenv(gopath, "")
	if err != nil {
		t.Errorf("Cannot set value of $GOPATH")
	}

	// testing
	err = Start(ProgramArgs{})
	if err == nil {
		t.Errorf(err.Error())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"testing"
)

func BenchmarkHello(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ds := Datasource{Name: fmt.Sprintf("datasource %v", i)}
		addItemToState(ds)
	}
}

// All state is not reset

func TestGettingAsset(t *testing.T) {
	file := fmt.Sprintf("%vindex.html", staticAssetsURL)[1:]
	data, _ := Asset(file)
	if len(data) == 0 {
		t.Fatal(fmt.Sprintf("Could not load static asset: %v", file))
	}
}

func TestSingleDsIntoState(t *testing.T) {
	tmp := len(State.Vals)
	ds1 := Datasource{Name: "some name"}
	addItemToState(ds1)
	if (len(State.Vals) - tmp) != 1 {
		t.Fatal("Not able to add datasource to internal state")
	}
}

func TestDuplicateEntriesIntoState(t *testing.T) {
	tmp := len(State.Vals)
	ds1 := Datasource{Name: "TestDuplicateEntriesIntoState same name"}
	ds2 := Datasource{Name: "TestDuplicateEntriesIntoState same name"}
	addItemToState(ds1)
	addItemToState(ds2)
	if (len(State.Vals) - tmp) > 1 {
		t.Fatal("Able to add more than one data source with the same name")
	}
}

func TestDontStoreMoreThan1k(t *testing.T) {
	// test that we dont keep more than maxState items in the state
	// and that when we go above it, we keep the last values (and not
	// the oldest ones)

	// reset internal state so that we know exactly what the last value
	// should be in the internal state
	State.Vals = nil
	const testString string = "TestDontStoreMoreThanMaxState, item: "

	ds := Datasource{Name: "tmp"}
	for i := 1; i < maxState+11; i++ {
		ds.Name = fmt.Sprintf("%v %v", testString, i)
		addItemToState(ds)
	}
	if len(State.Vals) > maxState {
		t.Fatal("Able to add more than maxState items into State.Vals")
	}
	if len(State.Vals) < maxState-10 {
		t.Fatal("Too few items in State (e.g. got reset somewhere in the middle?)")
	}

	// At this point the last item should be maxState+10
	knownName := fmt.Sprintf("%v %v", testString, maxState+10)
	lastItem := State.Vals[len(State.Vals)-1:]
	if lastItem[0].Name != knownName {
		t.Fatal(fmt.Sprintf("The expected last item (after adding more then maxState items) was [%v] but actually found [%v]!", knownName, lastItem[0].Name))
	}
}

func TestParsing(t *testing.T) {

	type testpair struct {
		incr int
		line string
	}

	// integer indicates with how much the count of internal state should go up
	// (could be 0, eg. not valid test input, or ds already captured)
	// Also, each line should have a non-nil Create_date
	var testCases = []testpair{
		{1, "launchctl-carbon.stdout:24/08/2014 17:59:53 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Recovery_HD/df_complex-free.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{0, "astt"},
		{0, "launchctl-carbon.stdout:24/08/2014 17:59:53 :: [creates] creating database file / (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "13/09/2014 23:10:56 :: [creates] creating database file /opt/graphite/storage/whisper/local/random/diceroll.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 17:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Recovery_HD/df_complex-reserved.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 17:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Recovery_HD/df_complex-used.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 20:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Media/df_complex-free.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 20:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Media/df_complex-reserved.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 20:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Media/df_complex-used.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 23:10:40 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/curl_xml-default/gauge-tvseries_watched-Babylon_5.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 23:10:40 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/curl_xml-default/gauge-tvseries_total-Babylon_5.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{0, "launchctl-carbon.stdout:24/08/2014 23:10:40 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/curl_xml-default/.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
	}
	State.Vals = nil // start fresh
	prev_count := len(State.Vals)

	for _, test := range testCases {
		parseLine(test.line)

		if len(State.Vals) != prev_count+test.incr {
			t.Fatal(fmt.Sprintf("Parsed line, should have seen %v new entries, saw %v. Line: %v", test.incr, len(State.Vals)-prev_count, test))
		}
		prev_count = len(State.Vals)

		// Check date parseing
		last_ds := State.Vals[len(State.Vals)-1:][0]
		// Do any checking on the actual values for the data source (not complete yet)
		if last_ds.Create_date.IsZero() {
			t.Fatal(fmt.Sprintf("Data source has invalid Create_date: %+v", last_ds))
		}
		if len(last_ds.Name) < 1 {
			t.Fatal(fmt.Sprintf("Data source doesnt have proper Name: %+v", last_ds))
		}
	}
}
<commit_msg>rewrote tests a bit better/less error prone<commit_after>package main

import (
	"fmt"
	"testing"
)

func BenchmarkHello(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ds := Datasource{Name: fmt.Sprintf("datasource %v", i)}
		addItemToState(ds)
	}
}

// All state is not reset

func TestGettingAsset(t *testing.T) {
	file := fmt.Sprintf("%vindex.html", staticAssetsURL)[1:]
	data, _ := Asset(file)
	if len(data) == 0 {
		t.Fatal(fmt.Sprintf("Could not load static asset: %v", file))
	}
}

func TestSingleDsIntoState(t *testing.T) {
	tmp := len(State.Vals)
	ds1 := Datasource{Name: "some name"}
	addItemToState(ds1)
	if (len(State.Vals) - tmp) != 1 {
		t.Fatal("Not able to add datasource to internal state")
	}
}

func TestDuplicateEntriesIntoState(t *testing.T) {
	tmp := len(State.Vals)
	ds1 := Datasource{Name: "TestDuplicateEntriesIntoState same name"}
	ds2 := Datasource{Name: "TestDuplicateEntriesIntoState same name"}
	addItemToState(ds1)
	addItemToState(ds2)
	if (len(State.Vals) - tmp) > 1 {
		t.Fatal("Able to add more than one data source with the same name")
	}
}

func TestDontStoreMoreThan1k(t *testing.T) {
	// test that we dont keep more than maxState items in the state
	// and that when we go above it, we keep the last values (and not
	// the oldest ones)

	// reset internal state so that we know exactly what the last value
	// should be in the internal state
	State.Vals = nil
	const testString string = "TestDontStoreMoreThanMaxState, item: "

	ds := Datasource{Name: "tmp"}
	for i := 1; i < maxState+11; i++ {
		ds.Name = fmt.Sprintf("%v %v", testString, i)
		addItemToState(ds)
	}
	if len(State.Vals) > maxState {
		t.Fatal("Able to add more than maxState items into State.Vals")
	}
	if len(State.Vals) < maxState-10 {
		t.Fatal("Too few items in State (e.g. got reset somewhere in the middle?)")
	}

	lastItem := State.Vals[len(State.Vals)-1:][0]
	if lastItem.Name != ds.Name {
		t.Fatal(fmt.Sprintf("The expected last item (after adding more then maxState items) was [%v] but actually found [%v]!", ds.Name, lastItem.Name))
	}
}

func TestParsing(t *testing.T) {

	type testpair struct {
		incr int
		line string
	}

	// integer indicates with how much the count of internal state should go up
	// (could be 0, eg. not valid test input, or ds already captured)
	// Also, each line should have a non-nil Create_date
	var testCases = []testpair{
		{1, "launchctl-carbon.stdout:24/08/2014 17:59:53 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Recovery_HD/df_complex-free.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{0, "astt"},
		{0, "launchctl-carbon.stdout:24/08/2014 17:59:53 :: [creates] creating database file / (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "13/09/2014 23:10:56 :: [creates] creating database file /opt/graphite/storage/whisper/local/random/diceroll.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 17:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Recovery_HD/df_complex-reserved.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 17:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Recovery_HD/df_complex-used.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 20:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Media/df_complex-free.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 20:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Media/df_complex-reserved.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 20:59:54 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/df-Volumes-Media/df_complex-used.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 23:10:40 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/curl_xml-default/gauge-tvseries_watched-Babylon_5.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{1, "launchctl-carbon.stdout:24/08/2014 23:10:40 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/curl_xml-default/gauge-tvseries_total-Babylon_5.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
		{0, "launchctl-carbon.stdout:24/08/2014 23:10:40 :: [creates] creating database file /opt/graphite/storage/whisper/mac-mini_local/collectd/curl_xml-default/.wsp (archive=[(60, 525600), (600, 518400)] xff=None agg=None)"},
	}
	State.Vals = nil // start fresh
	prev_count := len(State.Vals)

	for _, test := range testCases {
		parseLine(test.line)

		if len(State.Vals) != prev_count+test.incr {
			t.Fatal(fmt.Sprintf("Parsed line, should have seen %v new entries, saw %v. Line: %v", test.incr, len(State.Vals)-prev_count, test))
		}
		prev_count = len(State.Vals)

		// Check date parseing
		last_ds := State.Vals[len(State.Vals)-1:][0]
		// Do any checking on the actual values for the data source (not complete yet)
		if last_ds.Create_date.IsZero() {
			t.Fatal(fmt.Sprintf("Data source has invalid Create_date: %+v", last_ds))
		}
		if len(last_ds.Name) < 1 {
			t.Fatal(fmt.Sprintf("Data source doesnt have proper Name: %+v", last_ds))
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kube // import "k8s.io/helm/pkg/kube"

import (
	"time"

	appsv1 "k8s.io/api/apps/v1"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	appsv1beta2 "k8s.io/api/apps/v1beta2"
	"k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
	"k8s.io/kubernetes/pkg/apis/core/v1/helper"
	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
)

// deployment holds associated replicaSets for a deployment
type deployment struct {
	replicaSets *extensions.ReplicaSet
	deployment  *extensions.Deployment
}

// waitForResources polls to get the current status of all pods, PVCs, and Services
// until all are ready or a timeout is reached
func (c *Client) waitForResources(timeout time.Duration, created Result) error {
	c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout)

	kcs, err := c.KubernetesClientSet()
	if err != nil {
		return err
	}
	return wait.Poll(2*time.Second, timeout, func() (bool, error) {
		pods := []v1.Pod{}
		services := []v1.Service{}
		pvc := []v1.PersistentVolumeClaim{}
		deployments := []deployment{}
		for _, v := range created {
			obj, err := c.AsVersionedObject(v.Object)
			if err != nil && !runtime.IsNotRegisteredError(err) {
				return false, err
			}
			switch value := obj.(type) {
			case *v1.ReplicationController:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *v1.Pod:
				pod, err := kcs.CoreV1().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				pods = append(pods, *pod)
			case *appsv1.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *appsv1beta1.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *appsv1beta2.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *extensions.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *extensions.DaemonSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1.StatefulSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1beta1.StatefulSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1beta2.StatefulSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *extensions.ReplicaSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *v1.PersistentVolumeClaim:
				claim, err := kcs.CoreV1().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				pvc = append(pvc, *claim)
			case *v1.Service:
				svc, err := kcs.CoreV1().Services(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				services = append(services, *svc)
			}
		}
		isReady := c.podsReady(pods) && c.servicesReady(services) && c.volumesReady(pvc) && c.deploymentsReady(deployments)
		return isReady, nil
	})
}

func (c *Client) podsReady(pods []v1.Pod) bool {
	for _, pod := range pods {
		if !podutil.IsPodReady(&pod) {
			c.Log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName())
			return false
		}
	}
	return true
}

func (c *Client) servicesReady(svc []v1.Service) bool {
	for _, s := range svc {
		// ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set)
		if s.Spec.Type == v1.ServiceTypeExternalName {
			continue
		}

		// Make sure the service is not explicitly set to "None" before checking the IP
		if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) {
			c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName())
			return false
		}
		// This checks if the service has a LoadBalancer and that balancer has an Ingress defined
		if s.Spec.Type == v1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil {
			c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName())
			return false
		}
	}
	return true
}

func (c *Client) volumesReady(vols []v1.PersistentVolumeClaim) bool {
	for _, v := range vols {
		if v.Status.Phase != v1.ClaimBound {
			c.Log("PersistentVolumeClaim is not ready: %s/%s", v.GetNamespace(), v.GetName())
			return false
		}
	}
	return true
}

func (c *Client) deploymentsReady(deployments []deployment) bool {
	for _, v := range deployments {
		if !(v.replicaSets.Status.ReadyReplicas >= *v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) {
			c.Log("Deployment is not ready: %s/%s", v.deployment.GetNamespace(), v.deployment.GetName())
			return false
		}
	}
	return true
}

func getPods(client kubernetes.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) {
	list, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{
		FieldSelector: fields.Everything().String(),
		LabelSelector: labels.Set(selector).AsSelector().String(),
	})
	return list.Items, err
}
<commit_msg>adding other missing apiVersions<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kube // import "k8s.io/helm/pkg/kube"

import (
	"time"

	appsv1 "k8s.io/api/apps/v1"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	appsv1beta2 "k8s.io/api/apps/v1beta2"
	"k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
	"k8s.io/kubernetes/pkg/apis/core/v1/helper"
	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
)

// deployment holds associated replicaSets for a deployment
type deployment struct {
	replicaSets *extensions.ReplicaSet
	deployment  *extensions.Deployment
}

// waitForResources polls to get the current status of all pods, PVCs, and Services
// until all are ready or a timeout is reached
func (c *Client) waitForResources(timeout time.Duration, created Result) error {
	c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout)

	kcs, err := c.KubernetesClientSet()
	if err != nil {
		return err
	}
	return wait.Poll(2*time.Second, timeout, func() (bool, error) {
		pods := []v1.Pod{}
		services := []v1.Service{}
		pvc := []v1.PersistentVolumeClaim{}
		deployments := []deployment{}
		for _, v := range created {
			obj, err := c.AsVersionedObject(v.Object)
			if err != nil && !runtime.IsNotRegisteredError(err) {
				return false, err
			}
			switch value := obj.(type) {
			case *v1.ReplicationController:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *v1.Pod:
				pod, err := kcs.CoreV1().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				pods = append(pods, *pod)
			case *appsv1.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *appsv1beta1.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *appsv1beta2.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *extensions.Deployment:
				currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				// Find RS associated with deployment
				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1())
				if err != nil || newReplicaSet == nil {
					return false, err
				}
				newDeployment := deployment{
					newReplicaSet,
					currentDeployment,
				}
				deployments = append(deployments, newDeployment)
			case *extensions.DaemonSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1.DaemonSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1beta2.DaemonSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1.StatefulSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1beta1.StatefulSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1beta2.StatefulSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *extensions.ReplicaSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1beta2.ReplicaSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *appsv1.ReplicaSet:
				list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels)
				if err != nil {
					return false, err
				}
				pods = append(pods, list...)
			case *v1.PersistentVolumeClaim:
				claim, err := kcs.CoreV1().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				pvc = append(pvc, *claim)
			case *v1.Service:
				svc, err := kcs.CoreV1().Services(value.Namespace).Get(value.Name, metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				services = append(services, *svc)
			}
		}
		isReady := c.podsReady(pods) && c.servicesReady(services) && c.volumesReady(pvc) && c.deploymentsReady(deployments)
		return isReady, nil
	})
}

func (c *Client) podsReady(pods []v1.Pod) bool {
	for _, pod := range pods {
		if !podutil.IsPodReady(&pod) {
			c.Log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName())
			return false
		}
	}
	return true
}

func (c *Client) servicesReady(svc []v1.Service) bool {
	for _, s := range svc {
		// ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set)
		if s.Spec.Type == v1.ServiceTypeExternalName {
			continue
		}

		// Make sure the service is not explicitly set to "None" before checking the IP
		if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) {
			c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName())
			return false
		}
		// This checks if the service has a LoadBalancer and that balancer has an Ingress defined
		if s.Spec.Type == v1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil {
			c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName())
			return false
		}
	}
	return true
}

func (c *Client) volumesReady(vols []v1.PersistentVolumeClaim) bool {
	for _, v := range vols {
		if v.Status.Phase != v1.ClaimBound {
			c.Log("PersistentVolumeClaim is not ready: %s/%s", v.GetNamespace(), v.GetName())
			return false
		}
	}
	return true
}

func (c *Client) deploymentsReady(deployments []deployment) bool {
	for _, v := range deployments {
		if !(v.replicaSets.Status.ReadyReplicas >= *v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) {
			c.Log("Deployment is not ready: %s/%s", v.deployment.GetNamespace(), v.deployment.GetName())
			return false
		}
	}
	return true
}

func getPods(client kubernetes.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) {
	list, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{
		FieldSelector: fields.Everything().String(),
		LabelSelector: labels.Set(selector).AsSelector().String(),
	})
	return list.Items, err
}
<|endoftext|>


<commit_before>package auth

import (
	"context"
	"net/http"
	"net/url"
	"path"
	"strings"

	"golang.org/x/oauth2"

	"fmt"

	"crypto/rand"

	"encoding/base64"

	oidc "github.com/coreos/go-oidc"
	jwt "github.com/dgrijalva/jwt-go"
	uuid "github.com/nu7hatch/gouuid"
	log "github.com/sirupsen/logrus"
)

const (
	oidcStateCookie = "oidc_state"
	jwtCookieName   = "jwt"
)

type oidcHandler struct {
	name         string
	description  string
	nonce        string
	provider     *oidc.Provider
	verifier     *oidc.IDTokenVerifier
	oauth2Config oauth2.Config
	httpCtx      context.Context
	groupsClaim  string
	idClaim      string
	iconURL      string
}

// NewOIDCHandler creates a new oidc handler with the provided configuration items
func NewOIDCHandler(name, description, publicURL, oidcProvider, clientID, clientSecret string, additionalScopes []string, idClaim string, groupsClaim string) (Authenticator, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("'name' is required")
	}

	iconURL, err := url.Parse(oidcProvider)
	if err != nil {
		return nil, err
	}
	iconURL.Path = path.Join(iconURL.Path, "favicon.ico")

	o := &oidcHandler{
		name:        name,
		groupsClaim: groupsClaim,
		idClaim:     idClaim,
		iconURL:     iconURL.String(),
	}

	if len(description) > 0 {
		o.description = description
	} else {
		o.description = name
	}

	appNonce, err := uuid.NewV4()
	o.nonce = appNonce.String()
	o.httpCtx = context.Background()

	provider, err := oidc.NewProvider(o.httpCtx, oidcProvider)
	if err != nil {
		return nil, err
	}
	o.provider = provider

	oidcConfig := &oidc.Config{
		// We'll check expiry ourselves, so we can try refresh
		SkipExpiryCheck: true,
		ClientID:        clientID,
	}
	// Use the nonce source to create a custom ID Token verifier.
	o.verifier = provider.Verifier(oidcConfig)

	scopes := []string{oidc.ScopeOpenID}
	scopes = append(scopes, additionalScopes...)

	if strings.HasSuffix(publicURL, "/") {
		publicURL = publicURL[:len(publicURL)-1]
	}

	o.oauth2Config = oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		Endpoint:     provider.Endpoint(),
		RedirectURL:  publicURL + o.LoginURL(),
		Scopes:       scopes,
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Configured oidcHandler: %#v", struct {
			IDClaim      string
			GroupsClaim  string
			OAuth2Config oauth2.Config
		}{o.idClaim, o.groupsClaim, o.oauth2Config})
	}

	return o, nil
}

// Name returns the name of this authenticator
func (o *oidcHandler) Name() string {
	return o.name
}

// Description returns the user-friendly description of this authenticator
func (o *oidcHandler) Description() string {
	return o.description
}

// Type returns the type of this authenticator
func (o *oidcHandler) Type() string {
	return "oidc"
}

// IconURL returns an icon URL to signify this login method; empty string implies a default can be used
func (o *oidcHandler) IconURL() string {
	return o.iconURL
}

// LoginURL returns the initial login URL for this handler
func (o *oidcHandler) LoginURL() string {
	return path.Join("/", "auth", o.Type(), o.Name())
}

// PostWithCredentials returns true if this authenticator expects username/password credentials be POST'd
func (o *oidcHandler) PostWithCredentials() bool {
	return false
}

func randomBytes(len int) []byte {
	b := make([]byte, len)
	rand.Read(b)
	return b
}

// authCallback handles OIDC authentication callback for the app
func (o *oidcHandler) Authenticate(w http.ResponseWriter, r *http.Request) (*SessionToken, error) {

	oidcCode := r.URL.Query().Get("code")

	if len(oidcCode) == 0 {
		stateBytes := randomBytes(24)
		target := r.URL.Query().Get("target")
		if len(target) > 0 {
			stateBytes = append(stateBytes, []byte("::"+target)...)
		}
		state := base64.URLEncoding.EncodeToString(stateBytes)

		http.SetCookie(w, &http.Cookie{
			Name:     oidcStateCookie,
			Value:    state,
			HttpOnly: true,
		})

		http.Redirect(w, r,
			o.oauth2Config.AuthCodeURL(state, oidc.Nonce(o.nonce)), http.StatusFound)
		return nil, nil

	}

	stateCookie, err := r.Cookie(oidcStateCookie)
	if err != nil || stateCookie == nil {
		msg := "State did not match: missing/invalid state cookie"
		http.Error(w, msg, http.StatusBadRequest)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}
	state := stateCookie.Value

	if r.URL.Query().Get("state") != state {
		msg := "State did not match: missing/invalid state cookie"
		http.Error(w, msg, http.StatusBadRequest)
		return nil, fmt.Errorf(msg)
	}

	stateBytes, err := base64.URLEncoding.DecodeString(state)
	if err != nil {
		msg := "Failed to decode state"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	stateParts := strings.Split(string(stateBytes), "::")
	if len(stateParts) > 1 {
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Parsed redirect target: %s", stateParts[1])
		}

		query := r.URL.Query()
		query.Set("target", stateParts[1])
		r.URL.RawQuery = query.Encode()
	}

	oauth2Token, err := o.oauth2Config.Exchange(o.httpCtx, r.URL.Query().Get("code"))
	if err != nil {
		msg := "Failed to exchange token"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	rawIDToken, ok := oauth2Token.Extra("id_token").(string)
	if !ok {
		msg := "No id_token field in oauth2 token"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	} else if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Received OIDC idToken: %s", rawIDToken)
	}
	// Verify the ID Token signature and nonce.
	idToken, err := o.verifier.Verify(o.httpCtx, rawIDToken)
	if err != nil {
		msg := "Failed to verify ID Token"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	if idToken.Nonce != o.nonce {
		msg := "Invalid ID Token nonce"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf(msg)
	}

	user, groups, err := o.resolveUserAndGroups(oauth2Token, idToken)
	if err != nil {
		msg := "Failed to resolve user/group info"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	appToken := NewSessionToken(user, groups, jwt.MapClaims{
		"o2a": oauth2Token.AccessToken,
		"oid": rawIDToken,
		"orf": oauth2Token.RefreshToken,
	})
	return appToken, nil

}

func (o *oidcHandler) getUserInfoClaims(claims *map[string]interface{}, oauth2Token *oauth2.Token) error {
	if claims == nil {
		*claims = make(map[string]interface{})
	}
	if len(*claims) == 0 {
		userInfo, err := o.provider.UserInfo(o.httpCtx, oauth2.StaticTokenSource(oauth2Token))
		if err != nil {
			return fmt.Errorf("Failed to get userinfo: " + err.Error())
		}

		err = userInfo.Claims(claims)
		if err != nil {
			return err
		}
		if _, ok := (*claims)["email"]; !ok {
			(*claims)["email"] = userInfo.Email
		}

		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resulting UserInfo claims: %v", *claims)
		}

	}
	return nil
}

func (o *oidcHandler) resolveUserAndGroups(oauth2Token *oauth2.Token, idToken *oidc.IDToken) (string, []string, error) {

	var idClaims map[string]interface{}
	var infoClaims map[string]interface{}

	var user string
	var userGroups []string

	if o.idClaim == "sub" {
		user = idToken.Subject
	} else {
		idClaims := make(map[string]interface{})
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resolving ID claims...")
		}
		err := idToken.Claims(&idClaims)
		if err != nil {
			return "", nil, err
		}
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resolved IDToken %v, with claims: %v", idToken, idClaims)
		}
		if u, ok := idClaims[o.idClaim]; ok {
			user = u.(string)
		} else {
			err = o.getUserInfoClaims(&infoClaims, oauth2Token)
			if err != nil {
				return "", nil, err
			}
			if u, ok := infoClaims[o.idClaim]; ok {
				user = u.(string)
			}
		}
	}

	if len(user) == 0 {
		user = idToken.Subject
	}

	if idClaims == nil {
		idClaims := make(map[string]interface{})
		err := idToken.Claims(&idClaims)
		if err != nil {
			return "", nil, err
		}
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Resulting IDToken claims: %v", idClaims)
	}

	if g, ok := idClaims[o.groupsClaim]; ok {
		userGroups = g.([]string)
	} else {
		err := o.getUserInfoClaims(&infoClaims, oauth2Token)
		if err != nil {
			return "", nil, err
		}
		if g, ok := infoClaims[o.groupsClaim]; ok {
			userGroups = g.([]string)
		}
	}

	// var userGroups []string
	// if groups, ok := userClaims[groupsClaim]; ok {
	// 	userGroups = groups.([]string)
	// }
	// default testing:
	userGroups = []string{"system:masters"}

	return user, userGroups, nil

}
<commit_msg>support custom nonce (for mutliple instance support)<commit_after>package auth

import (
	"context"
	"net/http"
	"net/url"
	"path"
	"strings"

	"golang.org/x/oauth2"

	"fmt"

	"crypto/rand"

	"encoding/base64"

	oidc "github.com/coreos/go-oidc"
	jwt "github.com/dgrijalva/jwt-go"
	uuid "github.com/nu7hatch/gouuid"
	log "github.com/sirupsen/logrus"
)

const (
	oidcStateCookie = "oidc_state"
	jwtCookieName   = "jwt"
)

type oidcHandler struct {
	name         string
	description  string
	nonce        string
	provider     *oidc.Provider
	verifier     *oidc.IDTokenVerifier
	oauth2Config oauth2.Config
	httpCtx      context.Context
	groupsClaim  string
	idClaim      string
	iconURL      string
}

// NewOIDCHandler creates a new oidc handler with the provided configuration items
func NewOIDCHandler(name, description, publicURL, oidcProvider, clientID, clientSecret string, additionalScopes []string, idClaim string, groupsClaim string, nonce string) (Authenticator, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("'name' is required")
	}

	iconURL, err := url.Parse(oidcProvider)
	if err != nil {
		return nil, err
	}
	iconURL.Path = path.Join(iconURL.Path, "favicon.ico")

	o := &oidcHandler{
		name:        name,
		groupsClaim: groupsClaim,
		idClaim:     idClaim,
		iconURL:     iconURL.String(),
	}

	if len(description) > 0 {
		o.description = description
	} else {
		o.description = name
	}

	if len(nonce) > 0 {
		o.nonce = nonce
	} else {
		appNonce, _ := uuid.NewV4()
		o.nonce = appNonce.String()
	}
	o.httpCtx = context.Background()

	provider, err := oidc.NewProvider(o.httpCtx, oidcProvider)
	if err != nil {
		return nil, err
	}
	o.provider = provider

	oidcConfig := &oidc.Config{
		// We'll check expiry ourselves, so we can try refresh
		SkipExpiryCheck: true,
		ClientID:        clientID,
	}
	// Use the nonce source to create a custom ID Token verifier.
	o.verifier = provider.Verifier(oidcConfig)

	scopes := []string{oidc.ScopeOpenID}
	scopes = append(scopes, additionalScopes...)

	if strings.HasSuffix(publicURL, "/") {
		publicURL = publicURL[:len(publicURL)-1]
	}

	o.oauth2Config = oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		Endpoint:     provider.Endpoint(),
		RedirectURL:  publicURL + o.LoginURL(),
		Scopes:       scopes,
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Configured oidcHandler: %#v", struct {
			IDClaim      string
			GroupsClaim  string
			OAuth2Config oauth2.Config
		}{o.idClaim, o.groupsClaim, o.oauth2Config})
	}

	return o, nil
}

// Name returns the name of this authenticator
func (o *oidcHandler) Name() string {
	return o.name
}

// Description returns the user-friendly description of this authenticator
func (o *oidcHandler) Description() string {
	return o.description
}

// Type returns the type of this authenticator
func (o *oidcHandler) Type() string {
	return "oidc"
}

// IconURL returns an icon URL to signify this login method; empty string implies a default can be used
func (o *oidcHandler) IconURL() string {
	return o.iconURL
}

// LoginURL returns the initial login URL for this handler
func (o *oidcHandler) LoginURL() string {
	return path.Join("/", "auth", o.Type(), o.Name())
}

// PostWithCredentials returns true if this authenticator expects username/password credentials be POST'd
func (o *oidcHandler) PostWithCredentials() bool {
	return false
}

func randomBytes(len int) []byte {
	b := make([]byte, len)
	rand.Read(b)
	return b
}

// authCallback handles OIDC authentication callback for the app
func (o *oidcHandler) Authenticate(w http.ResponseWriter, r *http.Request) (*SessionToken, error) {

	oidcCode := r.URL.Query().Get("code")

	if len(oidcCode) == 0 {
		stateBytes := randomBytes(24)
		target := r.URL.Query().Get("target")
		if len(target) > 0 {
			stateBytes = append(stateBytes, []byte("::"+target)...)
		}
		state := base64.URLEncoding.EncodeToString(stateBytes)

		http.SetCookie(w, &http.Cookie{
			Name:     oidcStateCookie,
			Value:    state,
			HttpOnly: true,
		})

		http.Redirect(w, r,
			o.oauth2Config.AuthCodeURL(state, oidc.Nonce(o.nonce)), http.StatusFound)
		return nil, nil

	}

	stateCookie, err := r.Cookie(oidcStateCookie)
	if err != nil || stateCookie == nil {
		msg := "State did not match: missing/invalid state cookie"
		http.Error(w, msg, http.StatusBadRequest)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}
	state := stateCookie.Value

	if r.URL.Query().Get("state") != state {
		msg := "State did not match: missing/invalid state cookie"
		http.Error(w, msg, http.StatusBadRequest)
		return nil, fmt.Errorf(msg)
	}

	stateBytes, err := base64.URLEncoding.DecodeString(state)
	if err != nil {
		msg := "Failed to decode state"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	stateParts := strings.Split(string(stateBytes), "::")
	if len(stateParts) > 1 {
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Parsed redirect target: %s", stateParts[1])
		}

		query := r.URL.Query()
		query.Set("target", stateParts[1])
		r.URL.RawQuery = query.Encode()
	}

	oauth2Token, err := o.oauth2Config.Exchange(o.httpCtx, r.URL.Query().Get("code"))
	if err != nil {
		msg := "Failed to exchange token"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	rawIDToken, ok := oauth2Token.Extra("id_token").(string)
	if !ok {
		msg := "No id_token field in oauth2 token"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	} else if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Received OIDC idToken: %s", rawIDToken)
	}
	// Verify the ID Token signature and nonce.
	idToken, err := o.verifier.Verify(o.httpCtx, rawIDToken)
	if err != nil {
		msg := "Failed to verify ID Token"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	if idToken.Nonce != o.nonce {
		msg := "Invalid ID Token nonce"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf(msg)
	}

	user, groups, err := o.resolveUserAndGroups(oauth2Token, idToken)
	if err != nil {
		msg := "Failed to resolve user/group info"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, fmt.Errorf("%s: %v", msg, err)
	}

	appToken := NewSessionToken(user, groups, jwt.MapClaims{
		"o2a": oauth2Token.AccessToken,
		"oid": rawIDToken,
		"orf": oauth2Token.RefreshToken,
	})
	return appToken, nil

}

func (o *oidcHandler) getUserInfoClaims(claims *map[string]interface{}, oauth2Token *oauth2.Token) error {
	if claims == nil {
		*claims = make(map[string]interface{})
	}
	if len(*claims) == 0 {
		userInfo, err := o.provider.UserInfo(o.httpCtx, oauth2.StaticTokenSource(oauth2Token))
		if err != nil {
			return fmt.Errorf("Failed to get userinfo: " + err.Error())
		}

		err = userInfo.Claims(claims)
		if err != nil {
			return err
		}
		if _, ok := (*claims)["email"]; !ok {
			(*claims)["email"] = userInfo.Email
		}

		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resulting UserInfo claims: %v", *claims)
		}

	}
	return nil
}

func (o *oidcHandler) resolveUserAndGroups(oauth2Token *oauth2.Token, idToken *oidc.IDToken) (string, []string, error) {

	var idClaims map[string]interface{}
	var infoClaims map[string]interface{}

	var user string
	var userGroups []string

	if o.idClaim == "sub" {
		user = idToken.Subject
	} else {
		idClaims := make(map[string]interface{})
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resolving ID claims...")
		}
		err := idToken.Claims(&idClaims)
		if err != nil {
			return "", nil, err
		}
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resolved IDToken %v, with claims: %v", idToken, idClaims)
		}
		if u, ok := idClaims[o.idClaim]; ok {
			user = u.(string)
		} else {
			err = o.getUserInfoClaims(&infoClaims, oauth2Token)
			if err != nil {
				return "", nil, err
			}
			if u, ok := infoClaims[o.idClaim]; ok {
				user = u.(string)
			}
		}
	}

	if len(user) == 0 {
		user = idToken.Subject
	}

	if idClaims == nil {
		idClaims := make(map[string]interface{})
		err := idToken.Claims(&idClaims)
		if err != nil {
			return "", nil, err
		}
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Resulting IDToken claims: %v", idClaims)
	}

	if g, ok := idClaims[o.groupsClaim]; ok {
		userGroups = g.([]string)
	} else {
		err := o.getUserInfoClaims(&infoClaims, oauth2Token)
		if err != nil {
			return "", nil, err
		}
		if g, ok := infoClaims[o.groupsClaim]; ok {
			userGroups = g.([]string)
		}
	}

	// var userGroups []string
	// if groups, ok := userClaims[groupsClaim]; ok {
	// 	userGroups = groups.([]string)
	// }
	// default testing:
	userGroups = []string{"system:masters"}

	return user, userGroups, nil

}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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 site define HTTP handlers.
package site

import (
	"fmt"
	"html/template"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/hubbub"
	"github.com/google/triage-party/pkg/triage"
	"github.com/google/triage-party/pkg/updater"

	"github.com/dustin/go-humanize"
	"github.com/google/go-github/v31/github"
	"gopkg.in/yaml.v2"

	"k8s.io/klog/v2"
)

// VERSION is what version of Triage Party we advertise as.
const VERSION = "v1.2.0-beta.3"

var (
	nonWordRe = regexp.MustCompile(`\W`)

	// MaxPlayers is how many players to enable in the web interface.
	MaxPlayers = 20

	// Cut-off points for human duration (reversed order)
	defaultMagnitudes = []humanize.RelTimeMagnitude{
		{time.Second, "now", time.Second},
		{2 * time.Second, "1 second %s", 1},
		{time.Minute, "%d seconds %s", time.Second},
		{2 * time.Minute, "1 minute %s", 1},
		{time.Hour, "%d minutes %s", time.Minute},
		{2 * time.Hour, "1 hour %s", 1},
		{humanize.Day, "%d hours %s", time.Hour},
		{2 * humanize.Day, "1 day %s", 1},
		{20 * humanize.Day, "%d days %s", humanize.Day},
		{8 * humanize.Week, "%d weeks %s", humanize.Week},
		{humanize.Year, "%d months %s", humanize.Month},
		{18 * humanize.Month, "1 year %s", 1},
		{2 * humanize.Year, "2 years %s", 1},
		{humanize.LongTime, "%d years %s", humanize.Year},
		{math.MaxInt64, "a long while %s", 1},
	}
)

// Config is how external users interact with this package.
type Config struct {
	BaseDirectory string
	Name          string
	WarnAge       time.Duration
	Updater       *updater.Updater
	Party         *triage.Party
}

func New(c *Config) *Handlers {
	return &Handlers{
		baseDir:   c.BaseDirectory,
		updater:   c.Updater,
		party:     c.Party,
		siteName:  c.Name,
		warnAge:   c.WarnAge,
		startTime: time.Now(),
	}
}

// Handlers is a mix of config and client interfaces to connect with.
type Handlers struct {
	baseDir   string
	updater   *updater.Updater
	party     *triage.Party
	siteName  string
	warnAge   time.Duration
	startTime time.Time
}

// Root redirects to leaderboard.
func (h *Handlers) Root() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		sts, err := h.party.ListCollections()
		if err != nil {
			klog.Errorf("collections: %v", err)
			return
		}
		http.Redirect(w, r, fmt.Sprintf("/s/%s", sts[0].ID), http.StatusSeeOther)
	}
}

// Page are values that are passed into the renderer
type Page struct {
	Version     string
	SiteName    string
	ID          string
	Title       string
	Description string
	Warning     template.HTML
	Total       int
	TotalShown  int
	Types       string
	UniqueItems []*hubbub.Conversation
	ResultAge   time.Duration

	Player        int
	Players       int
	PlayerChoices []string
	PlayerNums    []int
	Index         int

	AverageResponseLatency time.Duration
	TotalPullRequests      int
	TotalIssues            int

	ClosedPerDay float64

	Collection  triage.Collection
	Collections []triage.Collection

	Swimlanes            []*Swimlane
	CollectionResult     *triage.CollectionResult
	SelectorVar          string
	SelectorOptions      []Choice
	Milestone            *github.Milestone
	MilestoneETA         time.Time
	MilestoneCountOffset int
	MilestoneVeryLate    bool

	OpenStats     *triage.CollectionResult
	VelocityStats *triage.CollectionResult
	GetVars       string
}

// Choice is a selector choice
type Choice struct {
	Value    int
	Text     string
	Selected bool
}

// is this request an HTTP refresh?
func isRefresh(r *http.Request) bool {
	cc := r.Header["Cache-Control"]
	if len(cc) == 0 {
		return false
	}
	//	klog.Infof("cc=%s headers=%+v", cc, r.Header)
	return cc[0] == "max-age-0" || cc[0] == "no-cache"
}

// helper to get integers from a URL
func getInt(url *url.URL, key string, fallback int) int {
	vals := url.Query()[key]
	if len(vals) == 1 {
		i, err := strconv.ParseInt(vals[0], 10, 32)
		if err != nil {
			klog.Warningf("bad %s int value: %v", key, vals)
			return fallback
		}
		return int(i)
	}
	return fallback
}

func toYAML(v interface{}) string {
	s, err := yaml.Marshal(v)
	if err != nil {
		return fmt.Sprintf("yaml err: %v", err)
	}
	return string(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJS(s string) template.JS {
	return template.JS(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJSfunc(s string) template.JS {
	return template.JS(nonWordRe.ReplaceAllString(s, "_"))
}

// Make a class name
func className(s string) template.HTMLAttr {
	s = strings.ToLower(nonWordRe.ReplaceAllString(s, "-"))
	s = strings.Replace(s, "_", "-", -1)
	return template.HTMLAttr(s)
}

func unixNano(t time.Time) int64 {
	return t.UnixNano()
}

func humanDuration(d time.Duration) string {
	return roughTime(time.Now().Add(-d))
}

func toDays(d time.Duration) string {
	return fmt.Sprintf("%0.1fd", d.Hours()/24)
}

func roughTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}

	ds := humanize.CustomRelTime(t, time.Now(), "ago", "from now", defaultMagnitudes)
	ds = strings.Replace(ds, " ago", "", 1)

	ds = strings.Replace(ds, " minutes", "min", 1)
	ds = strings.Replace(ds, " minute", "min", 1)

	ds = strings.Replace(ds, " hours", "h", 1)
	ds = strings.Replace(ds, " hour", "h", 1)

	ds = strings.Replace(ds, " days", "d", 1)
	ds = strings.Replace(ds, " day", "d", 1)

	ds = strings.Replace(ds, " months", "mo", 1)
	ds = strings.Replace(ds, " month", "mo", 1)

	ds = strings.Replace(ds, " years", "y", 1)
	ds = strings.Replace(ds, " year", "y", 1)

	ds = strings.Replace(ds, " weeks", "wk", 1)
	ds = strings.Replace(ds, " week", "wk", 1)

	return ds
}

func avatar(u *github.User) template.HTML {
	return template.HTML(fmt.Sprintf(`<a href="%s" title="%s"><img src="%s" width="20" height="20"></a>`, u.GetHTMLURL(), u.GetLogin(), u.GetAvatarURL()))
}

// playerFilter filters out results for a particular player
func playerFilter(result *triage.CollectionResult, player int, players int) *triage.CollectionResult {
	klog.Infof("Filtering for player %d of %d ...", player, players)

	os := []*triage.RuleResult{}
	seen := map[string]*triage.Rule{}

	for _, o := range result.RuleResults {
		cs := []*hubbub.Conversation{}

		for _, i := range o.Items {
			if (i.ID % players) == (player - 1) {
				klog.V(3).Infof("%d belongs to player %d", i.ID, player)
				cs = append(cs, i)
			}
		}

		os = append(os, triage.SummarizeRuleResult(o.Rule, cs, seen))
	}

	return triage.SummarizeCollectionResult(result.Collection, os)
}
<commit_msg>Version bump to v1.2.0-beta.4<commit_after>// Copyright 2020 Google LLC
//
// 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 site define HTTP handlers.
package site

import (
	"fmt"
	"html/template"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/hubbub"
	"github.com/google/triage-party/pkg/triage"
	"github.com/google/triage-party/pkg/updater"

	"github.com/dustin/go-humanize"
	"github.com/google/go-github/v31/github"
	"gopkg.in/yaml.v2"

	"k8s.io/klog/v2"
)

// VERSION is what version of Triage Party we advertise as.
const VERSION = "v1.2.0-beta.4"

var (
	nonWordRe = regexp.MustCompile(`\W`)

	// MaxPlayers is how many players to enable in the web interface.
	MaxPlayers = 20

	// Cut-off points for human duration (reversed order)
	defaultMagnitudes = []humanize.RelTimeMagnitude{
		{time.Second, "now", time.Second},
		{2 * time.Second, "1 second %s", 1},
		{time.Minute, "%d seconds %s", time.Second},
		{2 * time.Minute, "1 minute %s", 1},
		{time.Hour, "%d minutes %s", time.Minute},
		{2 * time.Hour, "1 hour %s", 1},
		{humanize.Day, "%d hours %s", time.Hour},
		{2 * humanize.Day, "1 day %s", 1},
		{20 * humanize.Day, "%d days %s", humanize.Day},
		{8 * humanize.Week, "%d weeks %s", humanize.Week},
		{humanize.Year, "%d months %s", humanize.Month},
		{18 * humanize.Month, "1 year %s", 1},
		{2 * humanize.Year, "2 years %s", 1},
		{humanize.LongTime, "%d years %s", humanize.Year},
		{math.MaxInt64, "a long while %s", 1},
	}
)

// Config is how external users interact with this package.
type Config struct {
	BaseDirectory string
	Name          string
	WarnAge       time.Duration
	Updater       *updater.Updater
	Party         *triage.Party
}

func New(c *Config) *Handlers {
	return &Handlers{
		baseDir:   c.BaseDirectory,
		updater:   c.Updater,
		party:     c.Party,
		siteName:  c.Name,
		warnAge:   c.WarnAge,
		startTime: time.Now(),
	}
}

// Handlers is a mix of config and client interfaces to connect with.
type Handlers struct {
	baseDir   string
	updater   *updater.Updater
	party     *triage.Party
	siteName  string
	warnAge   time.Duration
	startTime time.Time
}

// Root redirects to leaderboard.
func (h *Handlers) Root() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		sts, err := h.party.ListCollections()
		if err != nil {
			klog.Errorf("collections: %v", err)
			return
		}
		http.Redirect(w, r, fmt.Sprintf("/s/%s", sts[0].ID), http.StatusSeeOther)
	}
}

// Page are values that are passed into the renderer
type Page struct {
	Version     string
	SiteName    string
	ID          string
	Title       string
	Description string
	Warning     template.HTML
	Total       int
	TotalShown  int
	Types       string
	UniqueItems []*hubbub.Conversation
	ResultAge   time.Duration

	Player        int
	Players       int
	PlayerChoices []string
	PlayerNums    []int
	Index         int

	AverageResponseLatency time.Duration
	TotalPullRequests      int
	TotalIssues            int

	ClosedPerDay float64

	Collection  triage.Collection
	Collections []triage.Collection

	Swimlanes            []*Swimlane
	CollectionResult     *triage.CollectionResult
	SelectorVar          string
	SelectorOptions      []Choice
	Milestone            *github.Milestone
	MilestoneETA         time.Time
	MilestoneCountOffset int
	MilestoneVeryLate    bool

	OpenStats     *triage.CollectionResult
	VelocityStats *triage.CollectionResult
	GetVars       string
}

// Choice is a selector choice
type Choice struct {
	Value    int
	Text     string
	Selected bool
}

// is this request an HTTP refresh?
func isRefresh(r *http.Request) bool {
	cc := r.Header["Cache-Control"]
	if len(cc) == 0 {
		return false
	}
	//	klog.Infof("cc=%s headers=%+v", cc, r.Header)
	return cc[0] == "max-age-0" || cc[0] == "no-cache"
}

// helper to get integers from a URL
func getInt(url *url.URL, key string, fallback int) int {
	vals := url.Query()[key]
	if len(vals) == 1 {
		i, err := strconv.ParseInt(vals[0], 10, 32)
		if err != nil {
			klog.Warningf("bad %s int value: %v", key, vals)
			return fallback
		}
		return int(i)
	}
	return fallback
}

func toYAML(v interface{}) string {
	s, err := yaml.Marshal(v)
	if err != nil {
		return fmt.Sprintf("yaml err: %v", err)
	}
	return string(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJS(s string) template.JS {
	return template.JS(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJSfunc(s string) template.JS {
	return template.JS(nonWordRe.ReplaceAllString(s, "_"))
}

// Make a class name
func className(s string) template.HTMLAttr {
	s = strings.ToLower(nonWordRe.ReplaceAllString(s, "-"))
	s = strings.Replace(s, "_", "-", -1)
	return template.HTMLAttr(s)
}

func unixNano(t time.Time) int64 {
	return t.UnixNano()
}

func humanDuration(d time.Duration) string {
	return roughTime(time.Now().Add(-d))
}

func toDays(d time.Duration) string {
	return fmt.Sprintf("%0.1fd", d.Hours()/24)
}

func roughTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}

	ds := humanize.CustomRelTime(t, time.Now(), "ago", "from now", defaultMagnitudes)
	ds = strings.Replace(ds, " ago", "", 1)

	ds = strings.Replace(ds, " minutes", "min", 1)
	ds = strings.Replace(ds, " minute", "min", 1)

	ds = strings.Replace(ds, " hours", "h", 1)
	ds = strings.Replace(ds, " hour", "h", 1)

	ds = strings.Replace(ds, " days", "d", 1)
	ds = strings.Replace(ds, " day", "d", 1)

	ds = strings.Replace(ds, " months", "mo", 1)
	ds = strings.Replace(ds, " month", "mo", 1)

	ds = strings.Replace(ds, " years", "y", 1)
	ds = strings.Replace(ds, " year", "y", 1)

	ds = strings.Replace(ds, " weeks", "wk", 1)
	ds = strings.Replace(ds, " week", "wk", 1)

	return ds
}

func avatar(u *github.User) template.HTML {
	return template.HTML(fmt.Sprintf(`<a href="%s" title="%s"><img src="%s" width="20" height="20"></a>`, u.GetHTMLURL(), u.GetLogin(), u.GetAvatarURL()))
}

// playerFilter filters out results for a particular player
func playerFilter(result *triage.CollectionResult, player int, players int) *triage.CollectionResult {
	klog.Infof("Filtering for player %d of %d ...", player, players)

	os := []*triage.RuleResult{}
	seen := map[string]*triage.Rule{}

	for _, o := range result.RuleResults {
		cs := []*hubbub.Conversation{}

		for _, i := range o.Items {
			if (i.ID % players) == (player - 1) {
				klog.V(3).Infof("%d belongs to player %d", i.ID, player)
				cs = append(cs, i)
			}
		}

		os = append(os, triage.SummarizeRuleResult(o.Rule, cs, seen))
	}

	return triage.SummarizeCollectionResult(result.Collection, os)
}
<|endoftext|>


<commit_before>package beam

import (
	"fmt"
	"net"
	"os"
	"syscall"
	"bufio"
)

func debugCheckpoint(msg string, args ...interface{}) {
	return
	os.Stdout.Sync()
	tty,_ := os.OpenFile("/dev/tty", os.O_RDWR, 0700)
	fmt.Fprintf(tty, msg, args...)
	bufio.NewScanner(tty).Scan()
	tty.Close()
}

// Send sends a new message on conn with data and f as payload and
// attachment, respectively.
func Send(conn *net.UnixConn, data []byte, f *os.File) error {
	{
		var fd int = -1
		if f != nil {
			fd = int(f.Fd())
		}
		debugCheckpoint("===DEBUG=== about to send '%s'[%d]. Hit enter to confirm: ", data, fd)
	}
	var fds []int
	if f != nil {
		fds = append(fds, int(f.Fd()))
	}
	return sendUnix(conn, data, fds...)
}

// Receive waits for a new message on conn, and receives its payload
// and attachment, or an error if any.
//
// If more than 1 file descriptor is sent in the message, they are all
// closed except for the first, which is the attachment.
// It is legal for a message to have no attachment or an empty payload.
func Receive(conn *net.UnixConn) (rdata []byte, rf *os.File, rerr error) {
	defer func() {
		var fd int = -1
		if rf != nil {
			fd = int(rf.Fd())
		}
		debugCheckpoint("===DEBUG=== Receive() -> '%s'[%d]. Hit enter to continue.\n", rdata, fd)
	}()
	for {
		data, fds, err := receiveUnix(conn)
		if err != nil {
			return nil, nil, err
		}
		var f *os.File
		if len(fds) > 1 {
			for _, fd := range fds[1:] {
				syscall.Close(fd)
			}
		}
		if len(fds) >= 1 {
			f = os.NewFile(uintptr(fds[0]), "")
		}
		return data, f, nil
	}
	panic("impossibru")
	return nil, nil, nil
}

// SendPipe creates a new unix socket pair, sends one end as the attachment
// to a beam message with the payload `data`, and returns the other end.
//
// This is a common pattern to open a new service endpoint.
// For example, a service wishing to advertise its presence to clients might
// open an endpoint with:
//
//  endpoint, _ := SendPipe(conn, []byte("sql"))
//  defer endpoint.Close()
//  for {
//  	conn, _ := endpoint.Receive()
//	go func() {
//		Handle(conn)
//		conn.Close()
//	}()
//  }
//
// Note that beam does not distinguish between clients and servers in the logical
// sense: any program wishing to establishing a communication with another program
// may use SendPipe() to create an endpoint.
// For example, here is how an application might use it to connect to a database client.
//
//  endpoint, _ := SendPipe(conn, []byte("userdb"))
//  defer endpoint.Close()
//  conn, _ := endpoint.Receive()
//  defer conn.Close()
//  db := NewDBClient(conn)
//
// In this example note that we only need the first connection out of the endpoint,
// but we could open new ones to retry after a broken connection.
// Note that, because the underlying service transport is abstracted away, this
// allows for arbitrarily complex service discovery and retry logic to take place,
// without complicating application code.
//
func SendPipe(conn *net.UnixConn, data []byte) (endpoint *net.UnixConn, err error) {
	local, remote, err := SocketPair()
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			local.Close()
			remote.Close()
		}
	}()
	endpoint, err = FdConn(int(local.Fd()))
	if err != nil {
		return nil, err
	}
	if err := Send(conn, data, remote); err != nil {
		return nil, err
	}
	return endpoint, nil
}

func receiveUnix(conn *net.UnixConn) ([]byte, []int, error) {
	buf := make([]byte, 4096)
	oob := make([]byte, 4096)
	bufn, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
	if err != nil {
		return nil, nil, err
	}
	return buf[:bufn], extractFds(oob[:oobn]), nil
}

func sendUnix(conn *net.UnixConn, data []byte, fds ...int) error {
	_, _, err := conn.WriteMsgUnix(data, syscall.UnixRights(fds...), nil)
	if err == nil {
		for _, fd := range fds {
			fmt.Printf("Closing sent fd %v\n", fd)
			syscall.Close(fd)
		}
	}
	return err
}

func extractFds(oob []byte) (fds []int) {
	scms, err := syscall.ParseSocketControlMessage(oob)
	if err != nil {
		return
	}
	for _, scm := range scms {
		gotFds, err := syscall.ParseUnixRights(&scm)
		if err != nil {
			continue
		}
		fds = append(fds, gotFds...)
	}
	return
}

func socketpair() ([2]int, error) {
	return syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.FD_CLOEXEC, 0)
}

// SocketPair is a convenience wrapper around the socketpair(2) syscall.
// It returns a unix socket of type SOCK_STREAM in the form of 2 file descriptors
// not bound to the underlying filesystem.
// Messages sent on one end are received on the other, and vice-versa.
// It is the caller's responsibility to close both ends.
func SocketPair() (*os.File, *os.File, error) {
	pair, err := socketpair()
	if err != nil {
		return nil, nil, err
	}
	return os.NewFile(uintptr(pair[0]), ""), os.NewFile(uintptr(pair[1]), ""), nil
}

func USocketPair() (*net.UnixConn, *net.UnixConn, error) {
	a, b, err := SocketPair()
	if err != nil {
		return nil, nil, err
	}
	fmt.Printf("SocketPair() = %v, %v\n", a.Fd(), b.Fd())
	uA, err := FdConn(int(a.Fd()))
	if err != nil {
		a.Close()
		b.Close()
		return nil, nil, err
	}
	uB, err := FdConn(int(b.Fd()))
	if err != nil {
		a.Close()
		b.Close()
		return nil, nil, err
	}
	return uA, uB, nil
}

// FdConn wraps a file descriptor in a standard *net.UnixConn object, or
// returns an error if the file descriptor does not point to a unix socket.
// This creates a duplicate file descriptor. It's the caller's responsibility
// to close both.
func FdConn(fd int) (*net.UnixConn, error) {
	f := os.NewFile(uintptr(fd), fmt.Sprintf("%d", fd))
	conn, err := net.FileConn(f)
	if err != nil {
		return nil, err
	}
	uconn, ok := conn.(*net.UnixConn)
	if !ok {
		conn.Close()
		return nil, fmt.Errorf("%d: not a unix connection", fd)
	}
	return uconn, nil
}
<commit_msg>Beam: remove leftover debugging messages<commit_after>package beam

import (
	"fmt"
	"net"
	"os"
	"syscall"
	"bufio"
)

func debugCheckpoint(msg string, args ...interface{}) {
	return
	os.Stdout.Sync()
	tty,_ := os.OpenFile("/dev/tty", os.O_RDWR, 0700)
	fmt.Fprintf(tty, msg, args...)
	bufio.NewScanner(tty).Scan()
	tty.Close()
}

// Send sends a new message on conn with data and f as payload and
// attachment, respectively.
func Send(conn *net.UnixConn, data []byte, f *os.File) error {
	{
		var fd int = -1
		if f != nil {
			fd = int(f.Fd())
		}
		debugCheckpoint("===DEBUG=== about to send '%s'[%d]. Hit enter to confirm: ", data, fd)
	}
	var fds []int
	if f != nil {
		fds = append(fds, int(f.Fd()))
	}
	return sendUnix(conn, data, fds...)
}

// Receive waits for a new message on conn, and receives its payload
// and attachment, or an error if any.
//
// If more than 1 file descriptor is sent in the message, they are all
// closed except for the first, which is the attachment.
// It is legal for a message to have no attachment or an empty payload.
func Receive(conn *net.UnixConn) (rdata []byte, rf *os.File, rerr error) {
	defer func() {
		var fd int = -1
		if rf != nil {
			fd = int(rf.Fd())
		}
		debugCheckpoint("===DEBUG=== Receive() -> '%s'[%d]. Hit enter to continue.\n", rdata, fd)
	}()
	for {
		data, fds, err := receiveUnix(conn)
		if err != nil {
			return nil, nil, err
		}
		var f *os.File
		if len(fds) > 1 {
			for _, fd := range fds[1:] {
				syscall.Close(fd)
			}
		}
		if len(fds) >= 1 {
			f = os.NewFile(uintptr(fds[0]), "")
		}
		return data, f, nil
	}
	panic("impossibru")
	return nil, nil, nil
}

// SendPipe creates a new unix socket pair, sends one end as the attachment
// to a beam message with the payload `data`, and returns the other end.
//
// This is a common pattern to open a new service endpoint.
// For example, a service wishing to advertise its presence to clients might
// open an endpoint with:
//
//  endpoint, _ := SendPipe(conn, []byte("sql"))
//  defer endpoint.Close()
//  for {
//  	conn, _ := endpoint.Receive()
//	go func() {
//		Handle(conn)
//		conn.Close()
//	}()
//  }
//
// Note that beam does not distinguish between clients and servers in the logical
// sense: any program wishing to establishing a communication with another program
// may use SendPipe() to create an endpoint.
// For example, here is how an application might use it to connect to a database client.
//
//  endpoint, _ := SendPipe(conn, []byte("userdb"))
//  defer endpoint.Close()
//  conn, _ := endpoint.Receive()
//  defer conn.Close()
//  db := NewDBClient(conn)
//
// In this example note that we only need the first connection out of the endpoint,
// but we could open new ones to retry after a broken connection.
// Note that, because the underlying service transport is abstracted away, this
// allows for arbitrarily complex service discovery and retry logic to take place,
// without complicating application code.
//
func SendPipe(conn *net.UnixConn, data []byte) (endpoint *net.UnixConn, err error) {
	local, remote, err := SocketPair()
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			local.Close()
			remote.Close()
		}
	}()
	endpoint, err = FdConn(int(local.Fd()))
	if err != nil {
		return nil, err
	}
	if err := Send(conn, data, remote); err != nil {
		return nil, err
	}
	return endpoint, nil
}

func receiveUnix(conn *net.UnixConn) ([]byte, []int, error) {
	buf := make([]byte, 4096)
	oob := make([]byte, 4096)
	bufn, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
	if err != nil {
		return nil, nil, err
	}
	return buf[:bufn], extractFds(oob[:oobn]), nil
}

func sendUnix(conn *net.UnixConn, data []byte, fds ...int) error {
	_, _, err := conn.WriteMsgUnix(data, syscall.UnixRights(fds...), nil)
	if err == nil {
		for _, fd := range fds {
			syscall.Close(fd)
		}
	}
	return err
}

func extractFds(oob []byte) (fds []int) {
	scms, err := syscall.ParseSocketControlMessage(oob)
	if err != nil {
		return
	}
	for _, scm := range scms {
		gotFds, err := syscall.ParseUnixRights(&scm)
		if err != nil {
			continue
		}
		fds = append(fds, gotFds...)
	}
	return
}

func socketpair() ([2]int, error) {
	return syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.FD_CLOEXEC, 0)
}

// SocketPair is a convenience wrapper around the socketpair(2) syscall.
// It returns a unix socket of type SOCK_STREAM in the form of 2 file descriptors
// not bound to the underlying filesystem.
// Messages sent on one end are received on the other, and vice-versa.
// It is the caller's responsibility to close both ends.
func SocketPair() (*os.File, *os.File, error) {
	pair, err := socketpair()
	if err != nil {
		return nil, nil, err
	}
	return os.NewFile(uintptr(pair[0]), ""), os.NewFile(uintptr(pair[1]), ""), nil
}

func USocketPair() (*net.UnixConn, *net.UnixConn, error) {
	a, b, err := SocketPair()
	if err != nil {
		return nil, nil, err
	}
	uA, err := FdConn(int(a.Fd()))
	if err != nil {
		a.Close()
		b.Close()
		return nil, nil, err
	}
	uB, err := FdConn(int(b.Fd()))
	if err != nil {
		a.Close()
		b.Close()
		return nil, nil, err
	}
	return uA, uB, nil
}

// FdConn wraps a file descriptor in a standard *net.UnixConn object, or
// returns an error if the file descriptor does not point to a unix socket.
// This creates a duplicate file descriptor. It's the caller's responsibility
// to close both.
func FdConn(fd int) (*net.UnixConn, error) {
	f := os.NewFile(uintptr(fd), fmt.Sprintf("%d", fd))
	conn, err := net.FileConn(f)
	if err != nil {
		return nil, err
	}
	uconn, ok := conn.(*net.UnixConn)
	if !ok {
		conn.Close()
		return nil, fmt.Errorf("%d: not a unix connection", fd)
	}
	return uconn, nil
}
<|endoftext|>


<commit_before>package slug

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/flant/werf/pkg/util"
)

const slugSeparator = "-"

var (
	slugMaxSize = 42

	dockerTagRegexp  = regexp.MustCompile(`^[\w][\w.-]*$`)
	dockerTagMaxSize = 128

	projectNameRegex   = regexp.MustCompile(`^(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$`)
	projectNameMaxSize = 50

	dnsLabelRegex   = regexp.MustCompile(`^(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])$`)
	dnsLabelMaxSize = 63

	helmReleaseRegexp  = regexp.MustCompile(`^(?:(?:[A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$`)
	helmReleaseMaxSize = 53
)

func Slug(data string) string {
	if len(data) == 0 || slugify(data) == data && len(data) < slugMaxSize {
		return data
	}

	return slug(data, slugMaxSize)
}

func Project(name string) string {
	if shouldNotBeSlugged(name, projectNameRegex, projectNameMaxSize) {
		return name
	}

	res := slugify(name)

	if len(res) > projectNameMaxSize {
		res = res[:projectNameMaxSize]
	}

	return res
}

func ValidateProject(name string) error {
	if shouldNotBeSlugged(name, projectNameRegex, projectNameMaxSize) {
		return nil
	}
	return fmt.Errorf("Project name should comply with regex '%s' and be maximum %d chars", projectNameRegex, projectNameMaxSize)
}

func DockerTag(tag string) string {
	if shouldNotBeSlugged(tag, dockerTagRegexp, dockerTagMaxSize) {
		return tag
	}

	return slug(tag, dockerTagMaxSize)
}

func ValidateDockerTag(tag string) error {
	if shouldNotBeSlugged(tag, dockerTagRegexp, dockerTagMaxSize) {
		return nil
	}
	return fmt.Errorf("Docker tag should comply with regex '%s' and be maximum %d chars", dockerTagRegexp, dockerTagMaxSize)
}

func KubernetesNamespace(namespace string) string {
	if shouldNotBeSlugged(namespace, dnsLabelRegex, dnsLabelMaxSize) {
		return namespace
	}

	return slug(namespace, dnsLabelMaxSize)
}

func ValidateKubernetesNamespace(namespace string) error {
	if shouldNotBeSlugged(namespace, dnsLabelRegex, dnsLabelMaxSize) {
		return nil
	}
	return fmt.Errorf("Kubernetes namespace should comply with DNS Label requirements: %s and %d bytes max", dnsLabelRegex, dnsLabelMaxSize)
}

func HelmRelease(name string) string {
	if shouldNotBeSlugged(name, helmReleaseRegexp, helmReleaseMaxSize) {
		return name
	}

	return slug(name, helmReleaseMaxSize)
}

func ValidateHelmRelease(name string) error {
	if shouldNotBeSlugged(name, helmReleaseRegexp, helmReleaseMaxSize) {
		return nil
	}
	return fmt.Errorf("Helm release name should comply with regex '%s' and be maximum %d chars", helmReleaseRegexp, helmReleaseMaxSize)
}

func shouldNotBeSlugged(data string, regexp *regexp.Regexp, maxSize int) bool {
	return len(data) == 0 || regexp.Match([]byte(data)) && len(data) < maxSize
}

func slug(data string, maxSize int) string {
	sluggedData := slugify(data)
	murmurHash := util.MurmurHash(data)

	var slugParts []string
	if sluggedData != "" {
		croppedSluggedData := cropSluggedData(sluggedData, murmurHash, maxSize)
		if strings.HasPrefix(croppedSluggedData, "-") {
			slugParts = append(slugParts, croppedSluggedData[:len(croppedSluggedData)-1])
		} else {
			slugParts = append(slugParts, croppedSluggedData)
		}
	}
	slugParts = append(slugParts, murmurHash)

	consistentUniqSlug := strings.Join(slugParts, slugSeparator)

	return consistentUniqSlug
}

func cropSluggedData(data string, hash string, maxSize int) string {
	var index int
	maxLength := maxSize - len(hash) - len(slugSeparator)
	if len(data) > maxLength {
		index = maxLength
	} else {
		index = len(data)
	}

	return data[:index]
}

func slugify(data string) string {
	var result []rune

	var isCursorDash bool
	var isPreviousDash bool
	var isStartedDash, isDoubledDash bool

	isResultEmpty := true
	for _, r := range data {
		cursor := algorithm(string(r))
		if cursor == "" {
			continue
		}

		isCursorDash = cursor == "-"
		isStartedDash = isCursorDash && isResultEmpty
		isDoubledDash = isCursorDash && !isResultEmpty && isPreviousDash

		if isStartedDash || isDoubledDash {
			continue
		}

		result = append(result, []rune(cursor)...)
		isPreviousDash = isCursorDash
		isResultEmpty = false
	}

	isEndedDash := !isResultEmpty && isCursorDash
	if isEndedDash {
		return string(result[:len(result)-1])
	}
	return string(result)
}

func algorithm(data string) string {
	var result string
	for ind := range data {
		char, ok := mapping[string([]rune(data)[ind])]
		if ok {
			result += char
		}
	}

	return result
}
<commit_msg>Fix slug max size check<commit_after>package slug

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/flant/werf/pkg/util"
)

const slugSeparator = "-"

var (
	slugMaxSize = 42

	dockerTagRegexp  = regexp.MustCompile(`^[\w][\w.-]*$`)
	dockerTagMaxSize = 128

	projectNameRegex   = regexp.MustCompile(`^(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$`)
	projectNameMaxSize = 50

	dnsLabelRegex   = regexp.MustCompile(`^(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])$`)
	dnsLabelMaxSize = 63

	helmReleaseRegexp  = regexp.MustCompile(`^(?:(?:[A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$`)
	helmReleaseMaxSize = 53
)

func Slug(data string) string {
	if len(data) == 0 || slugify(data) == data && len(data) < slugMaxSize {
		return data
	}

	return slug(data, slugMaxSize)
}

func Project(name string) string {
	if shouldNotBeSlugged(name, projectNameRegex, projectNameMaxSize) {
		return name
	}

	res := slugify(name)

	if len(res) > projectNameMaxSize {
		res = res[:projectNameMaxSize]
	}

	return res
}

func ValidateProject(name string) error {
	if shouldNotBeSlugged(name, projectNameRegex, projectNameMaxSize) {
		return nil
	}
	return fmt.Errorf("Project name should comply with regex '%s' and be maximum %d chars", projectNameRegex, projectNameMaxSize)
}

func DockerTag(tag string) string {
	if shouldNotBeSlugged(tag, dockerTagRegexp, dockerTagMaxSize) {
		return tag
	}

	return slug(tag, dockerTagMaxSize)
}

func ValidateDockerTag(tag string) error {
	if shouldNotBeSlugged(tag, dockerTagRegexp, dockerTagMaxSize) {
		return nil
	}
	return fmt.Errorf("Docker tag should comply with regex '%s' and be maximum %d chars", dockerTagRegexp, dockerTagMaxSize)
}

func KubernetesNamespace(namespace string) string {
	if shouldNotBeSlugged(namespace, dnsLabelRegex, dnsLabelMaxSize) {
		return namespace
	}

	return slug(namespace, dnsLabelMaxSize)
}

func ValidateKubernetesNamespace(namespace string) error {
	if shouldNotBeSlugged(namespace, dnsLabelRegex, dnsLabelMaxSize) {
		return nil
	}
	return fmt.Errorf("Kubernetes namespace should comply with DNS Label requirements: %s and %d bytes max", dnsLabelRegex, dnsLabelMaxSize)
}

func HelmRelease(name string) string {
	if shouldNotBeSlugged(name, helmReleaseRegexp, helmReleaseMaxSize) {
		return name
	}

	return slug(name, helmReleaseMaxSize)
}

func ValidateHelmRelease(name string) error {
	if shouldNotBeSlugged(name, helmReleaseRegexp, helmReleaseMaxSize) {
		return nil
	}
	return fmt.Errorf("Helm release name should comply with regex '%s' and be maximum %d chars", helmReleaseRegexp, helmReleaseMaxSize)
}

func shouldNotBeSlugged(data string, regexp *regexp.Regexp, maxSize int) bool {
	return len(data) == 0 || regexp.Match([]byte(data)) && len(data) <= maxSize
}

func slug(data string, maxSize int) string {
	sluggedData := slugify(data)
	murmurHash := util.MurmurHash(data)

	var slugParts []string
	if sluggedData != "" {
		croppedSluggedData := cropSluggedData(sluggedData, murmurHash, maxSize)
		if strings.HasPrefix(croppedSluggedData, "-") {
			slugParts = append(slugParts, croppedSluggedData[:len(croppedSluggedData)-1])
		} else {
			slugParts = append(slugParts, croppedSluggedData)
		}
	}
	slugParts = append(slugParts, murmurHash)

	consistentUniqSlug := strings.Join(slugParts, slugSeparator)

	return consistentUniqSlug
}

func cropSluggedData(data string, hash string, maxSize int) string {
	var index int
	maxLength := maxSize - len(hash) - len(slugSeparator)
	if len(data) > maxLength {
		index = maxLength
	} else {
		index = len(data)
	}

	return data[:index]
}

func slugify(data string) string {
	var result []rune

	var isCursorDash bool
	var isPreviousDash bool
	var isStartedDash, isDoubledDash bool

	isResultEmpty := true
	for _, r := range data {
		cursor := algorithm(string(r))
		if cursor == "" {
			continue
		}

		isCursorDash = cursor == "-"
		isStartedDash = isCursorDash && isResultEmpty
		isDoubledDash = isCursorDash && !isResultEmpty && isPreviousDash

		if isStartedDash || isDoubledDash {
			continue
		}

		result = append(result, []rune(cursor)...)
		isPreviousDash = isCursorDash
		isResultEmpty = false
	}

	isEndedDash := !isResultEmpty && isCursorDash
	if isEndedDash {
		return string(result[:len(result)-1])
	}
	return string(result)
}

func algorithm(data string) string {
	var result string
	for ind := range data {
		char, ok := mapping[string([]rune(data)[ind])]
		if ok {
			result += char
		}
	}

	return result
}
<|endoftext|>