<commit_before>package maillog

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"strings"
	"time"
)

// Logger logs emails and errors. If nil, nothing gets logged.
type Logger struct {
	// MailDir writes mails into this directory
	MailDir string
	// ErrDir writes error log file into this directory
	ErrDir string
	errlog *log.Logger
	// ErrFile full file path to the error log file
	ErrFile string
	hosts   []string
}

// New creates a new logger by a given directory. If the directory does not exists
// it will be created recursively. Empty directory means a valid nil logger.
func New(mailDir, errDir string) Logger {
	if mailDir == "" && errDir == "" {
		return Logger{}
	}
	return Logger{
		MailDir: mailDir,
		ErrDir:  errDir,
	}
}

// IsNil returns true if the Logger is empty which means no path are set.
func (l Logger) IsNil() bool {
	return l.MailDir == "" && l.ErrDir == ""
}

// Init creates directories and the error log file
func (l Logger) Init(hosts ...string) (Logger, error) {
	if l.IsNil() {
		return Logger{}, nil
	}
	l.hosts = hosts
	for _, dir := range [...]string{l.MailDir, l.ErrDir} {
		if dir == "" {
			continue
		}
		if false == isDir(dir) {
			if err := os.MkdirAll(dir, 0700); err != nil {
				return Logger{}, fmt.Errorf("Cannot create directory %q because of: %s", dir, err)
			}
		}
	}

	if l.ErrDir == "" {
		return l, nil
	}

	l.ErrFile = path.Join(l.ErrDir, fmt.Sprintf("errors_%s_%d.log", strings.Join(hosts, "_"), time.Now().Unix()))
	f, err := os.OpenFile(l.ErrFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	l.errlog = log.New(f, "", log.LstdFlags)
	return l, err
}

// NewWriter creates a new file with a file name consisting of a time stamp.
// If it fails to create a file it returns a nilWriteCloser and does not log
// anymore any data.
func (l Logger) NewWriter() io.WriteCloser {
	if l.MailDir == "" {
		return nilWriteCloser{}
	}
	fName := fmt.Sprintf("%s%smail_%s_%d.txt", l.MailDir, string(os.PathSeparator), strings.Join(l.hosts, "_"), time.Now().UnixNano())
	f, err := os.OpenFile(fName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		l.Errorf("failed to create %q with error: %s", fName, err)
		return nilWriteCloser{}
	}
	return f
}

// Errorf writes into the error log file. If the logger is nil
// no write will happen.
func (l Logger) Errorf(format string, v ...interface{}) {
	if l.errlog == nil || l.ErrDir == "" {
		return
	}
	l.errlog.Printf(format, v...)
}

func isDir(path string) bool {
	fileInfo, err := os.Stat(path)
	return fileInfo != nil && fileInfo.IsDir() && err == nil
}

type nilWriteCloser struct {
	io.WriteCloser
}

func (wc nilWriteCloser) Write(p []byte) (n int, err error) {
	return
}

func (wc nilWriteCloser) Close() error {
	return nil
}
<commit_msg>Fix bug in nilWriteCloser because Write() must return lengths written<commit_after>package maillog

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"strings"
	"time"
)

// Logger logs emails and errors. If nil, nothing gets logged.
type Logger struct {
	// MailDir writes mails into this directory
	MailDir string
	// ErrDir writes error log file into this directory
	ErrDir string
	errlog *log.Logger
	// ErrFile full file path to the error log file
	ErrFile string
	hosts   []string
}

// New creates a new logger by a given directory. If the directory does not exists
// it will be created recursively. Empty directory means a valid nil logger.
func New(mailDir, errDir string) Logger {
	if mailDir == "" && errDir == "" {
		return Logger{}
	}
	return Logger{
		MailDir: mailDir,
		ErrDir:  errDir,
	}
}

// IsNil returns true if the Logger is empty which means no path are set.
func (l Logger) IsNil() bool {
	return l.MailDir == "" && l.ErrDir == ""
}

// Init creates directories and the error log file
func (l Logger) Init(hosts ...string) (Logger, error) {
	if l.IsNil() {
		return Logger{}, nil
	}
	l.hosts = hosts
	for _, dir := range [...]string{l.MailDir, l.ErrDir} {
		if dir == "" {
			continue
		}
		if false == isDir(dir) {
			if err := os.MkdirAll(dir, 0700); err != nil {
				return Logger{}, fmt.Errorf("Cannot create directory %q because of: %s", dir, err)
			}
		}
	}

	if l.ErrDir == "" {
		return l, nil
	}

	l.ErrFile = path.Join(l.ErrDir, fmt.Sprintf("errors_%s_%d.log", strings.Join(hosts, "_"), time.Now().Unix()))
	f, err := os.OpenFile(l.ErrFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	l.errlog = log.New(f, "", log.LstdFlags)
	return l, err
}

// NewWriter creates a new file with a file name consisting of a time stamp.
// If it fails to create a file it returns a nilWriteCloser and does not log
// anymore any data.
func (l Logger) NewWriter() io.WriteCloser {
	if l.MailDir == "" {
		return nilWriteCloser{}
	}
	fName := fmt.Sprintf("%s%smail_%s_%d.txt", l.MailDir, string(os.PathSeparator), strings.Join(l.hosts, "_"), time.Now().UnixNano())
	f, err := os.OpenFile(fName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		l.Errorf("failed to create %q with error: %s", fName, err)
		return nilWriteCloser{}
	}
	return f
}

// Errorf writes into the error log file. If the logger is nil
// no write will happen.
func (l Logger) Errorf(format string, v ...interface{}) {
	if l.errlog == nil || l.ErrDir == "" {
		return
	}
	l.errlog.Printf(format, v...)
}

func isDir(path string) bool {
	fileInfo, err := os.Stat(path)
	return fileInfo != nil && fileInfo.IsDir() && err == nil
}

type nilWriteCloser struct {
	io.WriteCloser
}

func (wc nilWriteCloser) Write(p []byte) (int, error) {
	return len(p), nil
}

func (wc nilWriteCloser) Close() error {
	return nil
}
<|endoftext|>


<commit_before>// +build windows

package password

import (
	"os"
	"syscall"
)

var (
	kernel32           = syscall.MustLoadDLL("kernel32.dll")
	setConsoleModeProc = kernel.MustFindProc("SetConsoleMod")
)

// Magic constant from MSDN to control whether charactesr read are
// repeated back on the console.
//
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
const ENABLE_ECHO_INPUT = 0x0004

func read(f *os.File) (string, error) {
	handle := syscall.Handle(f.Fd())

	// Grab the old console mode so we can reset it. We defer the reset
	// right away because it doesn't matter (it is idempotent).
	var oldMode uint32
	if err := syscall.GetConsoleMode(handle, &oldMode); err != nil {
		return "", err
	}
	defer setConsoleMode(handle, oldMode)

	// The new mode is the old mode WITHOUT the echo input flag set.
	var newMode uint32 = oldMode & ^ENABLE_ECHO_INPUT
	if err := setConsoleMode(handle, newMode); err != nil {
		return "", err
	}

	return readline(f)
}

func setConsoleMode(console syscall.Handle, mode uint32) error {
	r, _, err := stConsoleModeProc.Call(uintptr(console), uintptr(mode))
	if r == 0 {
		return err
	}

	return nil
}
<commit_msg>helper/passsword: fix windows compilation<commit_after>// +build windows

package password

import (
	"os"
	"syscall"
)

var (
	kernel32           = syscall.MustLoadDLL("kernel32.dll")
	setConsoleModeProc = kernel32.MustFindProc("SetConsoleMod")
)

// Magic constant from MSDN to control whether charactesr read are
// repeated back on the console.
//
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
const ENABLE_ECHO_INPUT = 0x0004

func read(f *os.File) (string, error) {
	handle := syscall.Handle(f.Fd())

	// Grab the old console mode so we can reset it. We defer the reset
	// right away because it doesn't matter (it is idempotent).
	var oldMode uint32
	if err := syscall.GetConsoleMode(handle, &oldMode); err != nil {
		return "", err
	}
	defer setConsoleMode(handle, oldMode)

	// The new mode is the old mode WITHOUT the echo input flag set.
	var newMode uint32 = uint32(int(oldMode) & ^ENABLE_ECHO_INPUT)
	if err := setConsoleMode(handle, newMode); err != nil {
		return "", err
	}

	return readline(f)
}

func setConsoleMode(console syscall.Handle, mode uint32) error {
	r, _, err := setConsoleModeProc.Call(uintptr(console), uintptr(mode))
	if r == 0 {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

// +build ignore

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

	"github.com/ikkerens/disgo"
	"github.com/slf4go/logger"
)

const (
	expireTime = 60

	slotKeyCap = '\U000020e3'
	slotCircle = '\U0001f534'
	slotCross  = '\U0000274e'
)

var winConditions = [...][]int{
	// Horizontal
	{0, 1, 2},
	{3, 4, 5},
	{6, 7, 8},

	// Vertical
	{0, 3, 6},
	{1, 4, 7},
	{2, 5, 8},

	// Diagonal
	{0, 4, 8},
	{2, 4, 6},
}

type ticTacToe struct {
	discord                 *disgo.Session
	player1, player2        *disgo.User
	gameOver, draw, expired bool

	board *disgo.Message
	slots [9]rune
	turn  bool

	expire *time.Timer
}

func (game *ticTacToe) start(channel disgo.Snowflake) disgo.Snowflake {
	for i := range game.slots {
		game.slots[i] = 0
	}

	board, err := game.discord.SendEmbed(channel, *game.buildBoard())
	if err != nil {
		logger.ErrorE(err)
		return 0
	}
	game.board = board

	for i := 1; i <= 9; i++ {
		board.AddReaction(strconv.Itoa(i) + string(slotKeyCap))
	}

	game.expire = time.NewTimer(expireTime * time.Second)
	go func() {
		<-game.expire.C
		game.expired = true
		game.end()
	}()

	return board.ID()
}

func (game *ticTacToe) addReaction(user disgo.Snowflake, emoji string) {
	current, _, piece := game.getCurrentPlayer()
	if current.ID() != user {
		return
	}

	runes := []rune(emoji)
	if utf8.RuneCountInString(emoji) == 2 && runes[1] == slotKeyCap {
		slot, err := strconv.ParseInt(string(runes[0]), 10, 8)
		if err != nil {
			return
		}

		if slot >= 1 && slot <= 9 && game.slots[slot-1] == 0 {
			game.expire.Reset(expireTime * time.Second)
			game.slots[slot-1] = piece
			game.turn = !game.turn

			if !game.checkForEndOfGame() {
				game.board.EditEmbed(*game.buildBoard())
				game.board.DeleteOwnReaction(emoji)
			}
		}
	}
}

func (game *ticTacToe) checkForEndOfGame() bool {
	// Is there a winner?
	for _, win := range winConditions {
		if game.slots[win[0]] == game.slots[win[1]] && game.slots[win[0]] == game.slots[win[2]] && game.slots[win[0]] != 0 {
			if game.slots[win[0]] == slotCircle {
				game.turn = false
			} else {
				game.turn = true
			}
			game.end()
			return true
		}
	}

	// Are there free slots?
	for _, slot := range game.slots {
		if slot == 0 {
			return false
		}
	}

	// No winner, but also no free slots, must be a draw
	game.draw = true
	game.end()
	return true
}

func (game *ticTacToe) end() {
	if !game.gameOver {
		delete(games, game.board.ID())
		game.expire.Stop()
		game.gameOver = true
		game.board.DeleteAllReactions()
		game.board.EditEmbed(*game.buildBoard())
	}
}

func (game *ticTacToe) buildBoard() *disgo.Embed {
	var field bytes.Buffer

	for y := 0; y < 3; y++ {
		for x := 0; x < 3; x++ {
			slot := game.slots[y*3+x]
			if slot == 0 {
				field.WriteString(strconv.Itoa(y*3 + x + 1))
				field.WriteRune(slotKeyCap)
			} else {
				field.WriteRune(slot)
			}
		}

		if y != 2 {
			field.WriteRune('\n')
		}
	}

	var footer string
	current, color, _ := game.getCurrentPlayer()

	if game.gameOver {
		color = 0x4F545C
		if game.draw {
			footer = "The game ended in a draw."
		} else if game.expired {
			footer = "This game has been cancelled due to player inactivity."
		} else {
			footer = fmt.Sprintf("%s won the game.", current.Username())
		}
	} else {
		footer = fmt.Sprintf("It's currently %s's turn.", current.Username())
	}

	return &disgo.Embed{
		Title:       fmt.Sprintf("Tic Tac Toe: %c %s vs %c %s", slotCircle, game.player1.Username(), slotCross, game.player2.Username()),
		Description: field.String(),
		Color:       color,
		Footer: disgo.EmbedFooter{
			Text:    footer,
			IconURL: current.AvatarURL(),
		},
	}
}

func (game *ticTacToe) getCurrentPlayer() (*disgo.User, int, rune) {
	if game.turn {
		return game.player2, 0x77B255, slotCross
	} else {
		return game.player1, 0xDD2E44, slotCircle
	}
}
<commit_msg>The TTT example game is now operable while setting reactions<commit_after>package main

// +build ignore

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

	"github.com/ikkerens/disgo"
	"github.com/slf4go/logger"
)

const (
	expireTime = 60

	slotKeyCap = '\U000020e3'
	slotCircle = '\U0001f534'
	slotCross  = '\U0000274e'
)

var winConditions = [...][]int{
	// Horizontal
	{0, 1, 2},
	{3, 4, 5},
	{6, 7, 8},

	// Vertical
	{0, 3, 6},
	{1, 4, 7},
	{2, 5, 8},

	// Diagonal
	{0, 4, 8},
	{2, 4, 6},
}

type ticTacToe struct {
	discord                 *disgo.Session
	player1, player2        *disgo.User
	gameOver, draw, expired bool

	board *disgo.Message
	slots [9]rune
	turn  bool

	expire *time.Timer
}

func (game *ticTacToe) start(channel disgo.Snowflake) disgo.Snowflake {
	for i := range game.slots {
		game.slots[i] = 0
	}

	board, err := game.discord.SendEmbed(channel, *game.buildBoard())
	if err != nil {
		logger.ErrorE(err)
		return 0
	}
	game.board = board
	game.expire = time.NewTimer(expireTime * time.Second)

	go func() {
		for i := 1; i <= 9; i++ {
			board.AddReaction(strconv.Itoa(i) + string(slotKeyCap))
		}
	}()

	go func() {
		<-game.expire.C
		game.expired = true
		game.end()
	}()

	return board.ID()
}

func (game *ticTacToe) addReaction(user disgo.Snowflake, emoji string) {
	current, _, piece := game.getCurrentPlayer()
	if current.ID() != user {
		return
	}

	runes := []rune(emoji)
	if utf8.RuneCountInString(emoji) == 2 && runes[1] == slotKeyCap {
		slot, err := strconv.ParseInt(string(runes[0]), 10, 8)
		if err != nil {
			return
		}

		if slot >= 1 && slot <= 9 && game.slots[slot-1] == 0 {
			game.expire.Reset(expireTime * time.Second)
			game.slots[slot-1] = piece
			game.turn = !game.turn

			if !game.checkForEndOfGame() {
				game.board.EditEmbed(*game.buildBoard())
				game.board.DeleteOwnReaction(emoji)
			}
		}
	}
}

func (game *ticTacToe) checkForEndOfGame() bool {
	// Is there a winner?
	for _, win := range winConditions {
		if game.slots[win[0]] == game.slots[win[1]] && game.slots[win[0]] == game.slots[win[2]] && game.slots[win[0]] != 0 {
			if game.slots[win[0]] == slotCircle {
				game.turn = false
			} else {
				game.turn = true
			}
			game.end()
			return true
		}
	}

	// Are there free slots?
	for _, slot := range game.slots {
		if slot == 0 {
			return false
		}
	}

	// No winner, but also no free slots, must be a draw
	game.draw = true
	game.end()
	return true
}

func (game *ticTacToe) end() {
	if !game.gameOver {
		delete(games, game.board.ID())
		game.expire.Stop()
		game.gameOver = true
		game.board.DeleteAllReactions()
		game.board.EditEmbed(*game.buildBoard())
	}
}

func (game *ticTacToe) buildBoard() *disgo.Embed {
	var field bytes.Buffer

	for y := 0; y < 3; y++ {
		for x := 0; x < 3; x++ {
			slot := game.slots[y*3+x]
			if slot == 0 {
				field.WriteString(strconv.Itoa(y*3 + x + 1))
				field.WriteRune(slotKeyCap)
			} else {
				field.WriteRune(slot)
			}
		}

		if y != 2 {
			field.WriteRune('\n')
		}
	}

	var footer string
	current, color, _ := game.getCurrentPlayer()

	if game.gameOver {
		color = 0x4F545C
		if game.draw {
			footer = "The game ended in a draw."
		} else if game.expired {
			footer = "This game has been cancelled due to player inactivity."
		} else {
			footer = fmt.Sprintf("%s won the game.", current.Username())
		}
	} else {
		footer = fmt.Sprintf("It's currently %s's turn.", current.Username())
	}

	return &disgo.Embed{
		Title:       fmt.Sprintf("Tic Tac Toe: %c %s vs %c %s", slotCircle, game.player1.Username(), slotCross, game.player2.Username()),
		Description: field.String(),
		Color:       color,
		Footer: disgo.EmbedFooter{
			Text:    footer,
			IconURL: current.AvatarURL(),
		},
	}
}

func (game *ticTacToe) getCurrentPlayer() (*disgo.User, int, rune) {
	if game.turn {
		return game.player2, 0x77B255, slotCross
	} else {
		return game.player1, 0xDD2E44, slotCircle
	}
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"fmt"
	"time"

	"k8s.io/api/extensions/v1beta1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/keel-hq/keel/types"

	log "github.com/sirupsen/logrus"
)

func (p *Provider) forceUpdate(deployment *v1beta1.Deployment) (err error) {

	gracePeriod := types.ParsePodTerminationGracePeriod(deployment.Annotations)
	selector := meta_v1.FormatLabelSelector(deployment.Spec.Selector)
	podDeleteDelay := types.ParsePodDeleteDelay(deployment.Annotations)

	// image tag didn't change, need to terminate pods
	podList, err := p.implementer.Pods(deployment.Namespace, selector)
	if err != nil {
		log.WithFields(log.Fields{
			"error":      err,
			"selector":   selector,
			"namespace":  deployment.Namespace,
			"deployment": deployment.Name,
		}).Error("provider.kubernetes: got error while looking for deployment pods")
		return err
	}

	for index, pod := range podList.Items {

		log.WithFields(log.Fields{
			"selector":     selector,
			"pod":          pod.Name,
			"namespace":    deployment.Namespace,
			"deployment":   deployment.Name,
			"grace_period": fmt.Sprint(gracePeriod),
		}).Info("provider.kubernetes: deleting pod to force pull...")

		err = p.implementer.DeletePod(deployment.Namespace, pod.Name, &meta_v1.DeleteOptions{
			GracePeriodSeconds: &gracePeriod,
		})
		if err != nil {
			log.WithFields(log.Fields{
				"error":      err,
				"selector":   selector,
				"pod":        pod.Name,
				"namespace":  deployment.Namespace,
				"deployment": deployment.Name,
			}).Error("provider.kubernetes: got error while deleting a pod")
		}

		// sleep between pod restarts but not if there aren't more left
		if index < len(podList.Items)-1 {
			time.Sleep(time.Duration(podDeleteDelay))
		}
	}

	return nil
}
<commit_msg>sleeping seconds instead of ms, using default graceful termination if present<commit_after>package kubernetes

import (
	"fmt"
	"time"

	"k8s.io/api/extensions/v1beta1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/keel-hq/keel/types"

	log "github.com/sirupsen/logrus"
)

func (p *Provider) forceUpdate(deployment *v1beta1.Deployment) (err error) {

	gracePeriod := types.ParsePodTerminationGracePeriod(deployment.Annotations)
	selector := meta_v1.FormatLabelSelector(deployment.Spec.Selector)
	podDeleteDelay := types.ParsePodDeleteDelay(deployment.Annotations)

	// image tag didn't change, need to terminate pods
	podList, err := p.implementer.Pods(deployment.Namespace, selector)
	if err != nil {
		log.WithFields(log.Fields{
			"error":      err,
			"selector":   selector,
			"namespace":  deployment.Namespace,
			"deployment": deployment.Name,
		}).Error("provider.kubernetes: got error while looking for deployment pods")
		return err
	}

	for index, pod := range podList.Items {

		var gp int64

		if pod.DeletionGracePeriodSeconds != nil {
			gp = *pod.DeletionGracePeriodSeconds
		}
		if gracePeriod != 0 {
			gp = gracePeriod
		}

		log.WithFields(log.Fields{
			"selector":     selector,
			"pod":          pod.Name,
			"namespace":    deployment.Namespace,
			"deployment":   deployment.Name,
			"grace_period": fmt.Sprint(gp),
		}).Info("provider.kubernetes: deleting pod to force pull...")

		err = p.implementer.DeletePod(deployment.Namespace, pod.Name, &meta_v1.DeleteOptions{
			GracePeriodSeconds: &gp,
		})
		if err != nil {
			log.WithFields(log.Fields{
				"error":      err,
				"selector":   selector,
				"pod":        pod.Name,
				"namespace":  deployment.Namespace,
				"deployment": deployment.Name,
			}).Error("provider.kubernetes: got error while deleting a pod")
		}

		// sleep between pod restarts but not if there aren't more left
		if index < len(podList.Items)-1 {
			time.Sleep(time.Duration(podDeleteDelay) * time.Second)
		}
	}

	return nil
}
<|endoftext|>


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

// package hi exposes a few Go functions to be wrapped and used from Python.
package hi

import (
	"fmt"
)

// Hi prints hi from Go
func Hi() {
	fmt.Printf("hi from go\n")
}

// Hello prints a greeting from Go
func Hello(s string) {
	fmt.Printf("hello %s from go\n", s)
}

// Concat concatenates two strings together and returns the resulting string.
func Concat(s1, s2 string) string {
	return s1 + s2
}

// Add returns the sum of its arguments.
func Add(i, j int) int {
	return i + j
}

// Person is a simple struct
type Person struct {
	Name string
	Age  int
}

// NewPerson creates a new Person value
func NewPerson(name string, age int) Person {
	return Person{
		Name: name,
		Age:  age,
	}
}

// NewPersonWithAge creates a new Person with a specific age
func NewPersonWithAge(age int) Person {
	return Person{
		Name: "stranger",
		Age:  age,
	}
}

func (p Person) String() string {
	return fmt.Sprintf("hi.Person{Name=%q, Age=%d}", p.Name, p.Age)
}

// Greet sends greetings
func (p *Person) Greet() string {
	return p.greet()
}

// greet sends greetings
func (p *Person) greet() string {
	return fmt.Sprintf("Hello, I am %s", p.Name)
}

// Work makes a Person go to work for h hours
func (p *Person) Work(h int) error {
	fmt.Printf("working...\n")
	if h > 7 {
		return fmt.Errorf("can't work for %d hours!", h)
	}
	fmt.Printf("worked for %d hours\n", h)
	return nil
}

// Salary returns the expected gains after h hours of work
func (p *Person) Salary(h int) (int, error) {
	if h > 7 {
		return 0, fmt.Errorf("can't work for %d hours!", h)
	}
	return h * 10, nil
}
<commit_msg>test: add ctor with comma-err<commit_after>// Copyright 2015 The go-python Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// package hi exposes a few Go functions to be wrapped and used from Python.
package hi

import (
	"fmt"
)

// Hi prints hi from Go
func Hi() {
	fmt.Printf("hi from go\n")
}

// Hello prints a greeting from Go
func Hello(s string) {
	fmt.Printf("hello %s from go\n", s)
}

// Concat concatenates two strings together and returns the resulting string.
func Concat(s1, s2 string) string {
	return s1 + s2
}

// Add returns the sum of its arguments.
func Add(i, j int) int {
	return i + j
}

// Person is a simple struct
type Person struct {
	Name string
	Age  int
}

// NewPerson creates a new Person value
func NewPerson(name string, age int) Person {
	return Person{
		Name: name,
		Age:  age,
	}
}

// NewPersonWithAge creates a new Person with a specific age
func NewPersonWithAge(age int) Person {
	return Person{
		Name: "stranger",
		Age:  age,
	}
}

// NewActivePerson creates a new Person with a certain amount of work done.
func NewActivePerson(h int) (Person, error) {
	var p Person
	err := p.Work(h)
	return p, err
}

func (p Person) String() string {
	return fmt.Sprintf("hi.Person{Name=%q, Age=%d}", p.Name, p.Age)
}

// Greet sends greetings
func (p *Person) Greet() string {
	return p.greet()
}

// greet sends greetings
func (p *Person) greet() string {
	return fmt.Sprintf("Hello, I am %s", p.Name)
}

// Work makes a Person go to work for h hours
func (p *Person) Work(h int) error {
	fmt.Printf("working...\n")
	if h > 7 {
		return fmt.Errorf("can't work for %d hours!", h)
	}
	fmt.Printf("worked for %d hours\n", h)
	return nil
}

// Salary returns the expected gains after h hours of work
func (p *Person) Salary(h int) (int, error) {
	if h > 7 {
		return 0, fmt.Errorf("can't work for %d hours!", h)
	}
	return h * 10, nil
}
<|endoftext|>


<commit_before>// +build !providerless

/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package azure

import (
	"context"
	"testing"

	"github.com/Azure/go-autorest/autorest/to"
	"github.com/stretchr/testify/assert"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	fakeclient "k8s.io/client-go/kubernetes/fake"
	"k8s.io/legacy-cloud-providers/azure/auth"
	"sigs.k8s.io/yaml"
)

func getTestConfig() *Config {
	return &Config{
		AzureAuthConfig: auth.AzureAuthConfig{
			TenantID:        "TenantID",
			SubscriptionID:  "SubscriptionID",
			AADClientID:     "AADClientID",
			AADClientSecret: "AADClientSecret",
		},
		ResourceGroup:               "ResourceGroup",
		RouteTableName:              "RouteTableName",
		RouteTableResourceGroup:     "RouteTableResourceGroup",
		Location:                    "Location",
		SubnetName:                  "SubnetName",
		VnetName:                    "VnetName",
		PrimaryAvailabilitySetName:  "PrimaryAvailabilitySetName",
		PrimaryScaleSetName:         "PrimaryScaleSetName",
		LoadBalancerSku:             "LoadBalancerSku",
		ExcludeMasterFromStandardLB: to.BoolPtr(true),
	}
}

func getTestCloudConfigTypeSecretConfig() *Config {
	return &Config{
		AzureAuthConfig: auth.AzureAuthConfig{
			TenantID:       "TenantID",
			SubscriptionID: "SubscriptionID",
		},
		ResourceGroup:           "ResourceGroup",
		RouteTableName:          "RouteTableName",
		RouteTableResourceGroup: "RouteTableResourceGroup",
		SecurityGroupName:       "SecurityGroupName",
		CloudConfigType:         cloudConfigTypeSecret,
	}
}

func getTestCloudConfigTypeMergeConfig() *Config {
	return &Config{
		AzureAuthConfig: auth.AzureAuthConfig{
			TenantID:       "TenantID",
			SubscriptionID: "SubscriptionID",
		},
		ResourceGroup:           "ResourceGroup",
		RouteTableName:          "RouteTableName",
		RouteTableResourceGroup: "RouteTableResourceGroup",
		SecurityGroupName:       "SecurityGroupName",
		CloudConfigType:         cloudConfigTypeMerge,
	}
}

func getTestCloudConfigTypeMergeConfigExpected() *Config {
	config := getTestConfig()
	config.SecurityGroupName = "SecurityGroupName"
	config.CloudConfigType = cloudConfigTypeMerge
	return config
}

func TestGetConfigFromSecret(t *testing.T) {
	emptyConfig := &Config{}
	tests := []struct {
		name           string
		existingConfig *Config
		secretConfig   *Config
		expected       *Config
		expectErr      bool
	}{
		{
			name: "Azure config shouldn't be override when cloud config type is file",
			existingConfig: &Config{
				ResourceGroup:   "ResourceGroup1",
				CloudConfigType: cloudConfigTypeFile,
			},
			secretConfig: getTestConfig(),
			expected:     nil,
		},
		{
			name:           "Azure config should be override when cloud config type is secret",
			existingConfig: getTestCloudConfigTypeSecretConfig(),
			secretConfig:   getTestConfig(),
			expected:       getTestConfig(),
		},
		{
			name:           "Azure config should be override when cloud config type is merge",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   getTestConfig(),
			expected:       getTestCloudConfigTypeMergeConfigExpected(),
		},
		{
			name:           "Error should be reported when secret doesn't exists",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			expectErr:      true,
		},
		{
			name:           "Error should be reported when secret exists but cloud-config data is not provided",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   emptyConfig,
			expectErr:      true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			az := &Cloud{
				KubeClient: fakeclient.NewSimpleClientset(),
			}
			if test.existingConfig != nil {
				az.Config = *test.existingConfig
			}
			if test.secretConfig != nil {
				secret := &v1.Secret{
					Type: v1.SecretTypeOpaque,
					ObjectMeta: metav1.ObjectMeta{
						Name:      "azure-cloud-provider",
						Namespace: "kube-system",
					},
				}
				if test.secretConfig != emptyConfig {
					secretData, err := yaml.Marshal(test.secretConfig)
					assert.NoError(t, err, test.name)
					secret.Data = map[string][]byte{
						"cloud-config": secretData,
					}
				}
				_, err := az.KubeClient.CoreV1().Secrets(cloudConfigNamespace).Create(context.TODO(), secret, metav1.CreateOptions{})
				assert.NoError(t, err, test.name)
			}

			real, err := az.getConfigFromSecret()
			if test.expectErr {
				assert.Error(t, err, test.name)
				return
			}

			assert.NoError(t, err, test.name)
			assert.Equal(t, test.expected, real, test.name)
		})
	}
}
<commit_msg>Improves unittest CC for azure_config<commit_after>// +build !providerless

/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package azure

import (
	"context"
	"testing"

	"github.com/Azure/go-autorest/autorest/to"
	"github.com/stretchr/testify/assert"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	fakeclient "k8s.io/client-go/kubernetes/fake"
	"k8s.io/legacy-cloud-providers/azure/auth"
	"sigs.k8s.io/yaml"
)

func getTestConfig() *Config {
	return &Config{
		AzureAuthConfig: auth.AzureAuthConfig{
			TenantID:        "TenantID",
			SubscriptionID:  "SubscriptionID",
			AADClientID:     "AADClientID",
			AADClientSecret: "AADClientSecret",
		},
		ResourceGroup:               "ResourceGroup",
		RouteTableName:              "RouteTableName",
		RouteTableResourceGroup:     "RouteTableResourceGroup",
		Location:                    "Location",
		SubnetName:                  "SubnetName",
		VnetName:                    "VnetName",
		PrimaryAvailabilitySetName:  "PrimaryAvailabilitySetName",
		PrimaryScaleSetName:         "PrimaryScaleSetName",
		LoadBalancerSku:             "LoadBalancerSku",
		ExcludeMasterFromStandardLB: to.BoolPtr(true),
	}
}

func getTestCloudConfigTypeSecretConfig() *Config {
	return &Config{
		AzureAuthConfig: auth.AzureAuthConfig{
			TenantID:       "TenantID",
			SubscriptionID: "SubscriptionID",
		},
		ResourceGroup:           "ResourceGroup",
		RouteTableName:          "RouteTableName",
		RouteTableResourceGroup: "RouteTableResourceGroup",
		SecurityGroupName:       "SecurityGroupName",
		CloudConfigType:         cloudConfigTypeSecret,
	}
}

func getTestCloudConfigTypeMergeConfig() *Config {
	return &Config{
		AzureAuthConfig: auth.AzureAuthConfig{
			TenantID:       "TenantID",
			SubscriptionID: "SubscriptionID",
		},
		ResourceGroup:           "ResourceGroup",
		RouteTableName:          "RouteTableName",
		RouteTableResourceGroup: "RouteTableResourceGroup",
		SecurityGroupName:       "SecurityGroupName",
		CloudConfigType:         cloudConfigTypeMerge,
	}
}

func getTestCloudConfigTypeMergeConfigExpected() *Config {
	config := getTestConfig()
	config.SecurityGroupName = "SecurityGroupName"
	config.CloudConfigType = cloudConfigTypeMerge
	return config
}

func TestGetConfigFromSecret(t *testing.T) {
	emptyConfig := &Config{}
	badConfig := &Config{ResourceGroup: "DuplicateColumnsIncloud-config"}
	tests := []struct {
		name           string
		existingConfig *Config
		secretConfig   *Config
		expected       *Config
		expectErr      bool
	}{
		{
			name: "Azure config shouldn't be override when cloud config type is file",
			existingConfig: &Config{
				ResourceGroup:   "ResourceGroup1",
				CloudConfigType: cloudConfigTypeFile,
			},
			secretConfig: getTestConfig(),
			expected:     nil,
		},
		{
			name:           "Azure config should be override when cloud config type is secret",
			existingConfig: getTestCloudConfigTypeSecretConfig(),
			secretConfig:   getTestConfig(),
			expected:       getTestConfig(),
		},
		{
			name:           "Azure config should be override when cloud config type is merge",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   getTestConfig(),
			expected:       getTestCloudConfigTypeMergeConfigExpected(),
		},
		{
			name:           "Error should be reported when secret doesn't exists",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			expectErr:      true,
		},
		{
			name:           "Error should be reported when secret exists but cloud-config data is not provided",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   emptyConfig,
			expectErr:      true,
		},
		{
			name:           "Error should be reported when it failed to parse Azure cloud-config",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   badConfig,
			expectErr:      true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			az := &Cloud{
				KubeClient: fakeclient.NewSimpleClientset(),
			}
			if test.existingConfig != nil {
				az.Config = *test.existingConfig
			}
			if test.secretConfig != nil {
				secret := &v1.Secret{
					Type: v1.SecretTypeOpaque,
					ObjectMeta: metav1.ObjectMeta{
						Name:      "azure-cloud-provider",
						Namespace: "kube-system",
					},
				}
				if test.secretConfig != emptyConfig && test.secretConfig != badConfig {
					secretData, err := yaml.Marshal(test.secretConfig)
					assert.NoError(t, err, test.name)
					secret.Data = map[string][]byte{
						"cloud-config": secretData,
					}
				}
				if test.secretConfig == badConfig {
					secret.Data = map[string][]byte{"cloud-config": []byte(`unknown: "hello",unknown: "hello"`)}
				}
				_, err := az.KubeClient.CoreV1().Secrets(cloudConfigNamespace).Create(context.TODO(), secret, metav1.CreateOptions{})
				assert.NoError(t, err, test.name)
			}

			real, err := az.getConfigFromSecret()
			if test.expectErr {
				assert.Error(t, err, test.name)
				return
			}

			assert.NoError(t, err, test.name)
			assert.Equal(t, test.expected, real, test.name)
		})
	}
}

func TestInitializeCloudFromSecret(t *testing.T) {
	emptyConfig := &Config{}
	unknownConfigTypeConfig := getTestConfig()
	unknownConfigTypeConfig.CloudConfigType = "UnknownConfigType"
	tests := []struct {
		name           string
		existingConfig *Config
		secretConfig   *Config
		expected       *Config
		expectErr      bool
	}{
		{
			name: "Azure config shouldn't be override when cloud config type is file",
			existingConfig: &Config{
				ResourceGroup:   "ResourceGroup1",
				CloudConfigType: cloudConfigTypeFile,
			},
			secretConfig: getTestConfig(),
			expected:     nil,
		},
		{
			name: "Azure config shouldn't be override when cloud config type is unknown",
			existingConfig: &Config{
				ResourceGroup:   "ResourceGroup1",
				CloudConfigType: "UnknownConfigType",
			},
			secretConfig: unknownConfigTypeConfig,
			expected:     nil,
		},
		{
			name:           "Azure config should be override when cloud config type is secret",
			existingConfig: getTestCloudConfigTypeSecretConfig(),
			secretConfig:   getTestConfig(),
			expected:       getTestConfig(),
		},
		{
			name:           "Azure config should be override when cloud config type is merge",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   getTestConfig(),
			expected:       getTestCloudConfigTypeMergeConfigExpected(),
		},
		{
			name:           "Error should be reported when secret doesn't exists",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			expectErr:      true,
		},
		{
			name:           "Error should be reported when secret exists but cloud-config data is not provided",
			existingConfig: getTestCloudConfigTypeMergeConfig(),
			secretConfig:   emptyConfig,
			expectErr:      true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			az := &Cloud{
				KubeClient: fakeclient.NewSimpleClientset(),
			}
			if test.existingConfig != nil {
				az.Config = *test.existingConfig
			}
			if test.secretConfig != nil {
				secret := &v1.Secret{
					Type: v1.SecretTypeOpaque,
					ObjectMeta: metav1.ObjectMeta{
						Name:      "azure-cloud-provider",
						Namespace: "kube-system",
					},
				}
				if test.secretConfig != emptyConfig {
					secretData, err := yaml.Marshal(test.secretConfig)
					assert.NoError(t, err, test.name)
					secret.Data = map[string][]byte{
						"cloud-config": secretData,
					}
				}
				_, err := az.KubeClient.CoreV1().Secrets(cloudConfigNamespace).Create(context.TODO(), secret, metav1.CreateOptions{})
				assert.NoError(t, err, test.name)
			}

			az.InitializeCloudFromSecret()
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"testing"
	"time"
)

const exampleTimeString = "2012-08-28T21:24:35.37465188Z"
const milliAccuracy = "2012-08-28T21:24:35.374Z"
const secondAccuracy = "2012-08-28T21:24:35Z"

var exampleTime time.Time

func init() {
	var err error
	exampleTime, err = time.Parse(time.RFC3339Nano, exampleTimeString)
	if err != nil {
		panic(err)
	}
	if exampleTimeString != exampleTime.UTC().Format(time.RFC3339Nano) {
		log.Panicf("Expected %v, got %v", exampleTimeString,
			exampleTime.UTC().Format(time.RFC3339Nano))
	}
}

func TestTimeParsing(t *testing.T) {
	tests := []struct {
		input string
		exp   string
	}{
		{"1346189075374651880", exampleTimeString},
		{"1346189075374", milliAccuracy},
		{"1346189075", secondAccuracy},
		{"2012-08-28T21:24:35.37465188Z", exampleTimeString},
		{secondAccuracy, secondAccuracy},
		{"Tue, 28 Aug 2012 21:24:35 +0000", secondAccuracy},
		{"Tue, 28 Aug 2012 21:24:35 UTC", secondAccuracy},
		{"Tue Aug 28 21:24:35 UTC 2012", secondAccuracy},
		{"Tue Aug 28 21:24:35 2012", secondAccuracy},
		{"Tue Aug 28 21:24:35 +0000 2012", secondAccuracy},
		{"2012-08-28T21:24", "2012-08-28T21:24:00Z"},
		{"2012-08-28T21", "2012-08-28T21:00:00Z"},
		{"2012-08-28", "2012-08-28T00:00:00Z"},
		{"2012-08", "2012-08-01T00:00:00Z"},
		{"2012", "2012-01-01T00:00:00Z"},
	}

	for _, x := range tests {
		tm, err := parseTime(x.input)
		if err != nil {
			t.Errorf("Error on %v - %v", x.input, err)
			t.Fail()
		}
		got := tm.UTC().Format(time.RFC3339Nano)
		if x.exp != got {
			t.Errorf("Expected %v for %v, got %v", x.exp, x.input, got)
			t.Fail()
		}
	}
}

func TestCanonicalParser(t *testing.T) {
	tests := []struct {
		input string
		exp   string
	}{
		{"2012-08-28T21:24:35.374651883Z", ""},
		{"2012-08-28T21:24:35.37465188Z", ""},
		{"2012-08-28T21:24:35.3746518Z", ""},
		{"2012-08-28T21:24:35.374651Z", ""},
		{"2012-08-28T21:24:35.37465Z", ""},
		{"2012-08-28T21:24:35.3746Z", ""},
		{"2012-08-28T21:24:35.374Z", ""},
		{"2012-08-28T21:24:35.37Z", ""},
		{"2012-08-28T21:24:35.3Z", ""},
		{"2012-08-28T21:24:35.0Z", "2012-08-28T21:24:35Z"},
		{"2012-08-28T21:24:35.Z", "2012-08-28T21:24:35Z"},
		{"2012-08-28T21:24:35Z", ""},
	}

	for _, x := range tests {
		tm, err := parseCanonicalTime(x.input)
		if err != nil {
			t.Errorf("Error on %v - %v", x.input, err)
			t.Fail()
		}
		got := tm.UTC().Format(time.RFC3339Nano)
		exp := x.exp
		if exp == "" {
			exp = x.input
		}
		if exp != got {
			t.Errorf("Expected %v for %v, got %v", x.exp, x.input, got)
			t.Fail()
		}
	}
}

func benchTimeParsing(b *testing.B, input string) {
	for i := 0; i < b.N; i++ {
		_, err := parseTime(input)
		if err != nil {
			b.Fatalf("Error on %v - %v", input, err)
		}
	}
}

func BenchmarkParseTimeCanonicalDirect(b *testing.B) {
	input := "2012-08-28T21:24:35.37465188Z"
	for i := 0; i < b.N; i++ {
		_, err := parseCanonicalTime(input)
		if err != nil {
			b.Fatalf("Error on %v - %v", input, err)
		}
	}
}

func BenchmarkParseTimeCanonical(b *testing.B) {
	benchTimeParsing(b, "2012-08-28T21:24:35.37465188Z")
}

func BenchmarkParseTimeMisc(b *testing.B) {
	benchTimeParsing(b, "Tue, 28 Aug 2012 21:24:35 +0000")
}

func BenchmarkParseTimeIntNano(b *testing.B) {
	benchTimeParsing(b, "1346189075374651880")
}

func BenchmarkParseTimeIntMillis(b *testing.B) {
	benchTimeParsing(b, "1346189075374")
}

func BenchmarkParseTimeIntSecs(b *testing.B) {
	benchTimeParsing(b, "1346189075")
}
<commit_msg>Don't use RFC3339Nano to parse time.<commit_after>package main

import (
	"log"
	"testing"
	"time"
)

const exampleTimeString = "2012-08-28T21:24:35.37465188Z"
const milliAccuracy = "2012-08-28T21:24:35.374Z"
const secondAccuracy = "2012-08-28T21:24:35Z"

var exampleTime time.Time

func init() {
	var err error
	exampleTime, err = time.Parse(time.RFC3339, exampleTimeString)
	if err != nil {
		panic(err)
	}
	if exampleTimeString != exampleTime.UTC().Format(time.RFC3339Nano) {
		log.Panicf("Expected %v, got %v", exampleTimeString,
			exampleTime.UTC().Format(time.RFC3339Nano))
	}
}

func TestTimeParsing(t *testing.T) {
	tests := []struct {
		input string
		exp   string
	}{
		{"1346189075374651880", exampleTimeString},
		{"1346189075374", milliAccuracy},
		{"1346189075", secondAccuracy},
		{"2012-08-28T21:24:35.37465188Z", exampleTimeString},
		{secondAccuracy, secondAccuracy},
		{"Tue, 28 Aug 2012 21:24:35 +0000", secondAccuracy},
		{"Tue, 28 Aug 2012 21:24:35 UTC", secondAccuracy},
		{"Tue Aug 28 21:24:35 UTC 2012", secondAccuracy},
		{"Tue Aug 28 21:24:35 2012", secondAccuracy},
		{"Tue Aug 28 21:24:35 +0000 2012", secondAccuracy},
		{"2012-08-28T21:24", "2012-08-28T21:24:00Z"},
		{"2012-08-28T21", "2012-08-28T21:00:00Z"},
		{"2012-08-28", "2012-08-28T00:00:00Z"},
		{"2012-08", "2012-08-01T00:00:00Z"},
		{"2012", "2012-01-01T00:00:00Z"},
	}

	for _, x := range tests {
		tm, err := parseTime(x.input)
		if err != nil {
			t.Errorf("Error on %v - %v", x.input, err)
			t.Fail()
		}
		got := tm.UTC().Format(time.RFC3339Nano)
		if x.exp != got {
			t.Errorf("Expected %v for %v, got %v", x.exp, x.input, got)
			t.Fail()
		}
	}
}

func TestCanonicalParser(t *testing.T) {
	tests := []struct {
		input string
		exp   string
	}{
		{"2012-08-28T21:24:35.374651883Z", ""},
		{"2012-08-28T21:24:35.37465188Z", ""},
		{"2012-08-28T21:24:35.3746518Z", ""},
		{"2012-08-28T21:24:35.374651Z", ""},
		{"2012-08-28T21:24:35.37465Z", ""},
		{"2012-08-28T21:24:35.3746Z", ""},
		{"2012-08-28T21:24:35.374Z", ""},
		{"2012-08-28T21:24:35.37Z", ""},
		{"2012-08-28T21:24:35.3Z", ""},
		{"2012-08-28T21:24:35.0Z", "2012-08-28T21:24:35Z"},
		{"2012-08-28T21:24:35.Z", "2012-08-28T21:24:35Z"},
		{"2012-08-28T21:24:35Z", ""},
	}

	for _, x := range tests {
		tm, err := parseCanonicalTime(x.input)
		if err != nil {
			t.Errorf("Error on %v - %v", x.input, err)
			t.Fail()
		}
		got := tm.UTC().Format(time.RFC3339Nano)
		exp := x.exp
		if exp == "" {
			exp = x.input
		}
		if exp != got {
			t.Errorf("Expected %v for %v, got %v", x.exp, x.input, got)
			t.Fail()
		}
	}
}

func benchTimeParsing(b *testing.B, input string) {
	for i := 0; i < b.N; i++ {
		_, err := parseTime(input)
		if err != nil {
			b.Fatalf("Error on %v - %v", input, err)
		}
	}
}

func BenchmarkParseTimeCanonicalDirect(b *testing.B) {
	input := "2012-08-28T21:24:35.37465188Z"
	for i := 0; i < b.N; i++ {
		_, err := parseCanonicalTime(input)
		if err != nil {
			b.Fatalf("Error on %v - %v", input, err)
		}
	}
}

func BenchmarkParseTimeCanonical(b *testing.B) {
	benchTimeParsing(b, "2012-08-28T21:24:35.37465188Z")
}

func BenchmarkParseTimeMisc(b *testing.B) {
	benchTimeParsing(b, "Tue, 28 Aug 2012 21:24:35 +0000")
}

func BenchmarkParseTimeIntNano(b *testing.B) {
	benchTimeParsing(b, "1346189075374651880")
}

func BenchmarkParseTimeIntMillis(b *testing.B) {
	benchTimeParsing(b, "1346189075374")
}

func BenchmarkParseTimeIntSecs(b *testing.B) {
	benchTimeParsing(b, "1346189075")
}
<|endoftext|>


<commit_before>// Copyright 2018 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.

// +build go1.8

// The proxy package provides a record/replay HTTP proxy. It is designed to support
// both an in-memory API (cloud.google.com/go/httpreplay) and a standalone server
// (cloud.google.com/go/httpreplay/cmd/httpr).
package proxy

// See github.com/google/martian/cmd/proxy/main.go for the origin of much of this.

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/google/martian"
	"github.com/google/martian/fifo"
	"github.com/google/martian/httpspec"
	"github.com/google/martian/martianlog"
	"github.com/google/martian/mitm"
)

// A Proxy is an HTTP proxy that supports recording or replaying requests.
type Proxy struct {
	// The certificate that the proxy uses to participate in TLS.
	CACert *x509.Certificate

	// The URL of the proxy.
	URL *url.URL

	// Initial state of the client.
	Initial []byte

	mproxy   *martian.Proxy
	filename string  // for log
	logger   *Logger // for recording only
}

// ForRecording returns a Proxy configured to record.
func ForRecording(filename string, port int) (*Proxy, error) {
	p, err := newProxy(filename)
	if err != nil {
		return nil, err
	}
	// Configure the transport for the proxy's outgoing traffic. We MUST use
	// DialContext and not Dial. In Go 1.10, Setting Dial (but not DialContext)
	// disables HTTP2, and that gives different behavior than http.DefaultTransport.
	// (For example, GET
	// https://storage.googleapis.com/storage-library-test-bucket/gzipped-text.txt
	// with an "Accept-Encoding: gzip" header returns a Content-Length header with
	// HTTP2, but not HTTP1.)
	// We must also hide the type http.Transport from martian, because it looks for
	// http.Transport and sets the Dial field!
	p.mproxy.SetRoundTripper((*hideTransport)(&http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: time.Second,
	}))

	// Construct a group that performs the standard proxy stack of request/response
	// modifications.
	stack, _ := httpspec.NewStack("httpr") // second arg is an internal group that we don't need
	p.mproxy.SetRequestModifier(stack)
	p.mproxy.SetResponseModifier(stack)

	// Make a group for logging requests and responses.
	logGroup := fifo.NewGroup()
	skipAuth := skipLoggingByHost("accounts.google.com")
	logGroup.AddRequestModifier(skipAuth)
	logGroup.AddResponseModifier(skipAuth)
	p.logger = NewLogger()
	logGroup.AddRequestModifier(p.logger)
	logGroup.AddResponseModifier(p.logger)

	stack.AddRequestModifier(logGroup)
	stack.AddResponseModifier(logGroup)

	// Ordinary debug logging.
	logger := martianlog.NewLogger()
	logger.SetDecode(true)
	stack.AddRequestModifier(logger)
	stack.AddResponseModifier(logger)

	if err := p.start(port); err != nil {
		return nil, err
	}
	return p, nil
}

type hideTransport http.Transport

func (t *hideTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	return (*http.Transport)(t).RoundTrip(req)
}

func newProxy(filename string) (*Proxy, error) {
	mproxy := martian.NewProxy()
	// Set up a man-in-the-middle configuration with a CA certificate so the proxy can
	// participate in TLS.
	x509c, priv, err := mitm.NewAuthority("cloud.google.com/go/httpreplay", "HTTPReplay Authority", time.Hour)
	if err != nil {
		return nil, err
	}
	mc, err := mitm.NewConfig(x509c, priv)
	if err != nil {
		return nil, err
	}
	mc.SetValidity(time.Hour)
	mc.SetOrganization("cloud.google.com/go/httpreplay")
	mc.SkipTLSVerify(false)
	if err != nil {
		return nil, err
	}
	mproxy.SetMITM(mc)
	return &Proxy{
		mproxy:   mproxy,
		CACert:   x509c,
		filename: filename,
	}, nil
}

func (p *Proxy) start(port int) error {
	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return err
	}
	p.URL = &url.URL{Scheme: "http", Host: l.Addr().String()}
	go p.mproxy.Serve(l)
	return nil
}

// Transport returns an http.Transport for clients who want to talk to the proxy.
func (p *Proxy) Transport() *http.Transport {
	caCertPool := x509.NewCertPool()
	caCertPool.AddCert(p.CACert)
	return &http.Transport{
		TLSClientConfig: &tls.Config{RootCAs: caCertPool},
		Proxy:           func(*http.Request) (*url.URL, error) { return p.URL, nil },
	}
}

// Close closes the proxy. If the proxy is recording, it also writes the log.
func (p *Proxy) Close() error {
	p.mproxy.Close()
	if p.logger != nil {
		return p.writeLog()
	}
	return nil
}

func (p *Proxy) writeLog() error {
	lg := p.logger.Extract()
	lg.Initial = p.Initial
	bytes, err := json.MarshalIndent(lg, "", "  ")
	if err != nil {
		return err
	}
	return ioutil.WriteFile(p.filename, bytes, 0600) // only accessible by owner
}

// skipLoggingByHost disables logging for traffic to a particular host.
type skipLoggingByHost string

func (s skipLoggingByHost) ModifyRequest(req *http.Request) error {
	if strings.HasPrefix(req.Host, string(s)) {
		martian.NewContext(req).SkipLogging()
	}
	return nil
}

func (s skipLoggingByHost) ModifyResponse(res *http.Response) error {
	return s.ModifyRequest(res.Request)
}
<commit_msg>httpreplay: go back to using the default transport for martian<commit_after>// Copyright 2018 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.

// +build go1.8

// The proxy package provides a record/replay HTTP proxy. It is designed to support
// both an in-memory API (cloud.google.com/go/httpreplay) and a standalone server
// (cloud.google.com/go/httpreplay/cmd/httpr).
package proxy

// See github.com/google/martian/cmd/proxy/main.go for the origin of much of this.

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/google/martian"
	"github.com/google/martian/fifo"
	"github.com/google/martian/httpspec"
	"github.com/google/martian/martianlog"
	"github.com/google/martian/mitm"
)

// A Proxy is an HTTP proxy that supports recording or replaying requests.
type Proxy struct {
	// The certificate that the proxy uses to participate in TLS.
	CACert *x509.Certificate

	// The URL of the proxy.
	URL *url.URL

	// Initial state of the client.
	Initial []byte

	mproxy   *martian.Proxy
	filename string  // for log
	logger   *Logger // for recording only
}

// ForRecording returns a Proxy configured to record.
func ForRecording(filename string, port int) (*Proxy, error) {
	p, err := newProxy(filename)
	if err != nil {
		return nil, err
	}

	// Construct a group that performs the standard proxy stack of request/response
	// modifications.
	stack, _ := httpspec.NewStack("httpr") // second arg is an internal group that we don't need
	p.mproxy.SetRequestModifier(stack)
	p.mproxy.SetResponseModifier(stack)

	// Make a group for logging requests and responses.
	logGroup := fifo.NewGroup()
	skipAuth := skipLoggingByHost("accounts.google.com")
	logGroup.AddRequestModifier(skipAuth)
	logGroup.AddResponseModifier(skipAuth)
	p.logger = NewLogger()
	logGroup.AddRequestModifier(p.logger)
	logGroup.AddResponseModifier(p.logger)

	stack.AddRequestModifier(logGroup)
	stack.AddResponseModifier(logGroup)

	// Ordinary debug logging.
	logger := martianlog.NewLogger()
	logger.SetDecode(true)
	stack.AddRequestModifier(logger)
	stack.AddResponseModifier(logger)

	if err := p.start(port); err != nil {
		return nil, err
	}
	return p, nil
}

type hideTransport http.Transport

func (t *hideTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	return (*http.Transport)(t).RoundTrip(req)
}

func newProxy(filename string) (*Proxy, error) {
	mproxy := martian.NewProxy()
	// Set up a man-in-the-middle configuration with a CA certificate so the proxy can
	// participate in TLS.
	x509c, priv, err := mitm.NewAuthority("cloud.google.com/go/httpreplay", "HTTPReplay Authority", time.Hour)
	if err != nil {
		return nil, err
	}
	mc, err := mitm.NewConfig(x509c, priv)
	if err != nil {
		return nil, err
	}
	mc.SetValidity(time.Hour)
	mc.SetOrganization("cloud.google.com/go/httpreplay")
	mc.SkipTLSVerify(false)
	if err != nil {
		return nil, err
	}
	mproxy.SetMITM(mc)
	return &Proxy{
		mproxy:   mproxy,
		CACert:   x509c,
		filename: filename,
	}, nil
}

func (p *Proxy) start(port int) error {
	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return err
	}
	p.URL = &url.URL{Scheme: "http", Host: l.Addr().String()}
	go p.mproxy.Serve(l)
	return nil
}

// Transport returns an http.Transport for clients who want to talk to the proxy.
func (p *Proxy) Transport() *http.Transport {
	caCertPool := x509.NewCertPool()
	caCertPool.AddCert(p.CACert)
	return &http.Transport{
		TLSClientConfig: &tls.Config{RootCAs: caCertPool},
		Proxy:           func(*http.Request) (*url.URL, error) { return p.URL, nil },
	}
}

// Close closes the proxy. If the proxy is recording, it also writes the log.
func (p *Proxy) Close() error {
	p.mproxy.Close()
	if p.logger != nil {
		return p.writeLog()
	}
	return nil
}

func (p *Proxy) writeLog() error {
	lg := p.logger.Extract()
	lg.Initial = p.Initial
	bytes, err := json.MarshalIndent(lg, "", "  ")
	if err != nil {
		return err
	}
	return ioutil.WriteFile(p.filename, bytes, 0600) // only accessible by owner
}

// skipLoggingByHost disables logging for traffic to a particular host.
type skipLoggingByHost string

func (s skipLoggingByHost) ModifyRequest(req *http.Request) error {
	if strings.HasPrefix(req.Host, string(s)) {
		martian.NewContext(req).SkipLogging()
	}
	return nil
}

func (s skipLoggingByHost) ModifyResponse(res *http.Response) error {
	return s.ModifyRequest(res.Request)
}
<|endoftext|>


<commit_before>package metrics

import (
	"fmt"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pkg/uuid"
	"github.com/pachyderm/pachyderm/src/client/version"
	db "github.com/pachyderm/pachyderm/src/server/pfs/db"

	"github.com/dancannon/gorethink"
	"github.com/segmentio/analytics-go"
	"go.pedge.io/lion"
	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	kube "k8s.io/kubernetes/pkg/client/unversioned"
)

//Reporter is used to submit user & cluster metrics to segment
type Reporter struct {
	segmentClient *analytics.Client
	clusterID     string
	kubeClient    *kube.Client
	dbClient      *gorethink.Session
	pfsDbName     string
	ppsDbName     string
}

// NewReporter creates a new reporter and kicks off the loop to report cluster
// metrics
func NewReporter(clusterID string, kubeClient *kube.Client, address string, pfsDbName string, ppsDbName string) *Reporter {

	dbClient, err := db.DbConnect(address)
	if err != nil {
		lion.Errorf("error connected to DB when reporting metrics: %v\n", err)
		return nil
	}
	reporter := &Reporter{
		segmentClient: newPersistentClient(),
		clusterID:     clusterID,
		kubeClient:    kubeClient,
		dbClient:      dbClient,
		pfsDbName:     pfsDbName,
		ppsDbName:     ppsDbName,
	}
	go reporter.reportClusterMetrics()
	return reporter
}

//ReportUserAction pushes the action into a queue for reporting,
// and reports the start, finish, and error conditions
func ReportUserAction(ctx context.Context, r *Reporter, action string) func(time.Time, error) {
	if r == nil {
		// This happens when stubbing out metrics for testing, e.g. src/server/pfs/server/server_test.go
		return func(time.Time, error) {}
	}
	// If we report nil, segment sees it, but mixpanel omits the field
	r.reportUserAction(ctx, fmt.Sprintf("%vStarted", action), 1)
	return func(start time.Time, err error) {
		if err == nil {
			r.reportUserAction(ctx, fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			r.reportUserAction(ctx, fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func getKeyFromMD(md metadata.MD, key string) (string, error) {
	if md[key] != nil && len(md[key]) > 0 {
		return md[key][0], nil
	}
	return "", fmt.Errorf("error extracting userid from metadata. userid is empty")
}

func (r *Reporter) reportUserAction(ctx context.Context, action string, value interface{}) {
	md, ok := metadata.FromContext(ctx)
	if ok {
		// metadata API downcases all the key names
		userID, err := getKeyFromMD(md, "userid")
		if err != nil {
			lion.Errorln(err)
			return
		}
		prefix, err := getKeyFromMD(md, "prefix")
		if err != nil {
			lion.Errorln(err)
			return
		}
		reportUserMetricsToSegment(
			r.segmentClient,
			userID,
			prefix,
			action,
			value,
			r.clusterID,
		)
	} else {
		lion.Errorf("Error extracting userid metadata from context: %v\n", ctx)
	}
}

// ReportAndFlushUserAction immediately reports the metric
// It is used in the few places we need to report metrics from the client.
// It handles reporting the start, finish, and error conditions of the action
func ReportAndFlushUserAction(action string) func(time.Time, error) {
	// If we report nil, segment sees it, but mixpanel omits the field
	reportAndFlushUserAction(fmt.Sprintf("%vStarted", action), 1)
	return func(start time.Time, err error) {
		if err == nil {
			reportAndFlushUserAction(fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			reportAndFlushUserAction(fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func reportAndFlushUserAction(action string, value interface{}) {
	client := newSegmentClient()
	defer client.Close()
	cfg, err := config.Read()
	if err != nil {
		lion.Errorf("Error reading userid from ~/.pachyderm/config: %v\n", err)
		// metrics errors are non fatal
		return
	}
	reportUserMetricsToSegment(client, cfg.UserID, "user", action, value, "")
}

func (r *Reporter) dbMetrics(metrics *Metrics) {
	cursor, err := gorethink.Object(
		"Repos",
		gorethink.DB(r.pfsDbName).Table("Repos").Count(),
		"Commits",
		gorethink.DB(r.pfsDbName).Table("Commits").Count(),
		"Diffs",
		gorethink.DB(r.pfsDbName).Table("Diffs").Count(),
		"Jobs",
		gorethink.DB(r.ppsDbName).Table("JobInfos").Count(),
		"Pipelines",
		gorethink.DB(r.ppsDbName).Table("PipelineInfos").Count(),
	).Run(r.dbClient)
	if err != nil {
		lion.Errorf("Error Fetching Metrics:%+v", err)
	}
	cursor.One(&metrics)
}

func (r *Reporter) reportClusterMetrics() {
	for {
		time.Sleep(reportingInterval)
		metrics := &Metrics{}
		r.dbMetrics(metrics)
		externalMetrics(r.kubeClient, metrics)
		metrics.ClusterID = r.clusterID
		metrics.PodID = uuid.NewWithoutDashes()
		metrics.Version = version.PrettyPrintVersion(version.Version)
		reportClusterMetricsToSegment(r.segmentClient, metrics)
	}
}
<commit_msg>Silence error from normal usage<commit_after>package metrics

import (
	"fmt"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pkg/uuid"
	"github.com/pachyderm/pachyderm/src/client/version"
	db "github.com/pachyderm/pachyderm/src/server/pfs/db"

	"github.com/dancannon/gorethink"
	"github.com/segmentio/analytics-go"
	"go.pedge.io/lion"
	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	kube "k8s.io/kubernetes/pkg/client/unversioned"
)

//Reporter is used to submit user & cluster metrics to segment
type Reporter struct {
	segmentClient *analytics.Client
	clusterID     string
	kubeClient    *kube.Client
	dbClient      *gorethink.Session
	pfsDbName     string
	ppsDbName     string
}

// NewReporter creates a new reporter and kicks off the loop to report cluster
// metrics
func NewReporter(clusterID string, kubeClient *kube.Client, address string, pfsDbName string, ppsDbName string) *Reporter {

	dbClient, err := db.DbConnect(address)
	if err != nil {
		lion.Errorf("error connected to DB when reporting metrics: %v\n", err)
		return nil
	}
	reporter := &Reporter{
		segmentClient: newPersistentClient(),
		clusterID:     clusterID,
		kubeClient:    kubeClient,
		dbClient:      dbClient,
		pfsDbName:     pfsDbName,
		ppsDbName:     ppsDbName,
	}
	go reporter.reportClusterMetrics()
	return reporter
}

//ReportUserAction pushes the action into a queue for reporting,
// and reports the start, finish, and error conditions
func ReportUserAction(ctx context.Context, r *Reporter, action string) func(time.Time, error) {
	if r == nil {
		// This happens when stubbing out metrics for testing, e.g. src/server/pfs/server/server_test.go
		return func(time.Time, error) {}
	}
	// If we report nil, segment sees it, but mixpanel omits the field
	r.reportUserAction(ctx, fmt.Sprintf("%vStarted", action), 1)
	return func(start time.Time, err error) {
		if err == nil {
			r.reportUserAction(ctx, fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			r.reportUserAction(ctx, fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func getKeyFromMD(md metadata.MD, key string) (string, error) {
	if md[key] != nil && len(md[key]) > 0 {
		return md[key][0], nil
	}
	return "", fmt.Errorf("error extracting userid from metadata. userid is empty")
}

func (r *Reporter) reportUserAction(ctx context.Context, action string, value interface{}) {
	md, ok := metadata.FromContext(ctx)
	if ok {
		// metadata API downcases all the key names
		userID, err := getKeyFromMD(md, "userid")
		if err != nil {
			// The FUSE client will never have a userID, so normal usage will produce a lot of these errors
			return
		}
		prefix, err := getKeyFromMD(md, "prefix")
		if err != nil {
			lion.Errorln(err)
			return
		}
		reportUserMetricsToSegment(
			r.segmentClient,
			userID,
			prefix,
			action,
			value,
			r.clusterID,
		)
	} else {
		lion.Errorf("Error extracting userid metadata from context: %v\n", ctx)
	}
}

// ReportAndFlushUserAction immediately reports the metric
// It is used in the few places we need to report metrics from the client.
// It handles reporting the start, finish, and error conditions of the action
func ReportAndFlushUserAction(action string) func(time.Time, error) {
	// If we report nil, segment sees it, but mixpanel omits the field
	reportAndFlushUserAction(fmt.Sprintf("%vStarted", action), 1)
	return func(start time.Time, err error) {
		if err == nil {
			reportAndFlushUserAction(fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			reportAndFlushUserAction(fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func reportAndFlushUserAction(action string, value interface{}) {
	client := newSegmentClient()
	defer client.Close()
	cfg, err := config.Read()
	if err != nil {
		lion.Errorf("Error reading userid from ~/.pachyderm/config: %v\n", err)
		// metrics errors are non fatal
		return
	}
	reportUserMetricsToSegment(client, cfg.UserID, "user", action, value, "")
}

func (r *Reporter) dbMetrics(metrics *Metrics) {
	cursor, err := gorethink.Object(
		"Repos",
		gorethink.DB(r.pfsDbName).Table("Repos").Count(),
		"Commits",
		gorethink.DB(r.pfsDbName).Table("Commits").Count(),
		"Diffs",
		gorethink.DB(r.pfsDbName).Table("Diffs").Count(),
		"Jobs",
		gorethink.DB(r.ppsDbName).Table("JobInfos").Count(),
		"Pipelines",
		gorethink.DB(r.ppsDbName).Table("PipelineInfos").Count(),
	).Run(r.dbClient)
	if err != nil {
		lion.Errorf("Error Fetching Metrics:%+v", err)
	}
	cursor.One(&metrics)
}

func (r *Reporter) reportClusterMetrics() {
	for {
		time.Sleep(reportingInterval)
		metrics := &Metrics{}
		r.dbMetrics(metrics)
		externalMetrics(r.kubeClient, metrics)
		metrics.ClusterID = r.clusterID
		metrics.PodID = uuid.NewWithoutDashes()
		metrics.Version = version.PrettyPrintVersion(version.Version)
		reportClusterMetricsToSegment(r.segmentClient, metrics)
	}
}
<|endoftext|>


<commit_before>package example

import (
	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	ppsclient "github.com/pachyderm/pachyderm/src/client/pps"
)

func CreateJobRequest() *ppsclient.CreateJobRequest {
	return &ppsclient.CreateJobRequest{
		Transform: &ppsclient.Transform{
			Cmd: []string{"cmd", "args..."},
		},
		Parallelism: 1,
		Inputs: []*ppsclient.JobInput{
			{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{Name: "in_repo"},
					ID:   "10cf676b626044f9a405235bf7660959",
				},
				Method: client.MapMethod,
			},
		},
		ParentJob: &ppsclient.Job{
			ID: "a951ca06cfda4377b8ffaa050d1074df",
		},
	}
}

func CreatePipelineRequest() *ppsclient.CreatePipelineRequest {
	return &ppsclient.CreatePipelineRequest{
		Pipeline: &ppsclient.Pipeline{
			Name: "name",
		},
		Transform: &ppsclient.Transform{
			Cmd: []string{"cmd", "args..."},
		},
		Parallelism: 1,
		Inputs: []*ppsclient.PipelineInput{
			{
				Repo:   &pfs.Repo{Name: "in_repo"},
				Method: client.ReduceMethod,
			},
		},
	}
}

func RunPipelineSpec() *ppsclient.CreateJobRequest {
	return &ppsclient.CreateJobRequest{
		Inputs: []*ppsclient.JobInput{
			{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{Name: "in_repo"},
					ID:   "10cf676b626044f9a405235bf7660959",
				},
				Method: client.GlobalMethod,
			},
		},
		Parallelism: 3,
	}
}
<commit_msg>Adds AcceptReturnCode to example job.<commit_after>package example

import (
	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	ppsclient "github.com/pachyderm/pachyderm/src/client/pps"
)

func CreateJobRequest() *ppsclient.CreateJobRequest {
	return &ppsclient.CreateJobRequest{
		Transform: &ppsclient.Transform{
			Cmd:              []string{"cmd", "args..."},
			AcceptReturnCode: []int64{1},
		},
		Parallelism: 1,
		Inputs: []*ppsclient.JobInput{
			{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{Name: "in_repo"},
					ID:   "10cf676b626044f9a405235bf7660959",
				},
				Method: client.MapMethod,
			},
		},
		ParentJob: &ppsclient.Job{
			ID: "a951ca06cfda4377b8ffaa050d1074df",
		},
	}
}

func CreatePipelineRequest() *ppsclient.CreatePipelineRequest {
	return &ppsclient.CreatePipelineRequest{
		Pipeline: &ppsclient.Pipeline{
			Name: "name",
		},
		Transform: &ppsclient.Transform{
			Cmd:              []string{"cmd", "args..."},
			AcceptReturnCode: []int64{1},
		},
		Parallelism: 1,
		Inputs: []*ppsclient.PipelineInput{
			{
				Repo:   &pfs.Repo{Name: "in_repo"},
				Method: client.ReduceMethod,
			},
		},
	}
}

func RunPipelineSpec() *ppsclient.CreateJobRequest {
	return &ppsclient.CreateJobRequest{
		Inputs: []*ppsclient.JobInput{
			{
				Commit: &pfs.Commit{
					Repo: &pfs.Repo{Name: "in_repo"},
					ID:   "10cf676b626044f9a405235bf7660959",
				},
				Method: client.GlobalMethod,
			},
		},
		Parallelism: 3,
	}
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Zack Mullaly zmullaly@mozilla.com [:zack]

package actions

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/mozilla/mig"
)

// ListActions abstracts over operations that allow the MIG API to retrieve
// a list of actions that an agent should run. It allows for a limit to be set
// on the number of actions to return.
type ListActions interface {
	ListActions(uint) ([]mig.Action, error)
}

// List is an HTTP request handler that serves GET requests intended by agents
// to retrieve a list of actions that can be run.
//
// This request handler must be able to construct a means of retrieving actions
// given a queue location string and integer limit.
type List struct {
	actions func(string) ListActions
}

type operation struct {
	Module     string      `json:"module"`
	Parameters interface{} `json:"parameters"`
}

type action struct {
	Name          string      `json:"name"`
	Target        string      `json:"target"`
	ValidFrom     time.Time   `json:"validFrom"`
	ExpireAfter   time.Time   `json:"expireAfter"`
	Operations    []operation `json:"operations"`
	Signatures    []string    `json:"signatures"`
	Status        string      `json:"status"`
	SyntaxVersion uint        `json:"syntaxVersion"`
}

type listRequest struct {
	Queue string `json:"queue"`
	Limit uint   `json:"limit"`
}

type listResponse struct {
	Error   *string  `json:"error"`
	Actions []action `json:"actions"`
}

// NewList constructs a new List handler.
func NewList(listActionsConstructor func(string) ListActions) List {
	return List{
		actions: listActionsConstructor,
	}
}

// validate ensures that a request to list actions contains all of the data
// required to satisfy the request.
func (req listRequest) validate() error {
	if req.Queue == "" {
		return fmt.Errorf("missing queue field")
	}

	return nil
}

// fromMigAction converts a mig.Action loaded from the database into our
// limited representation for use by the API.
func (a *action) fromMigAction(act mig.Action) {
	*a = action{
		Name:          act.Name,
		Target:        act.Target,
		ValidFrom:     act.ValidFrom,
		ExpireAfter:   act.ExpireAfter,
		Operations:    []operation{},
		Signatures:    act.PGPSignatures,
		Status:        act.Status,
		SyntaxVersion: uint(act.SyntaxVersion),
	}

	for _, op := range act.Operations {
		a.Operations = append(a.Operations, operation{
			Module:     op.Module,
			Parameters: op.Parameters,
		})
	}
}

func (handler List) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	resEncoder := json.NewEncoder(response)

	response.Header().Set("Content-Type", "application/json")

	queryStringValues := request.URL.Query()

	var paramsErr error
	queue := queryStringValues.Get("queue")
	limit, parseErr := strconv.Atoi(queryStringValues.Get("limit"))
	if queue == "" {
		paramsErr = fmt.Errorf("missing parameter 'queue'")
	}
	if parseErr != nil {
		paramsErr = parseErr
	}
	if paramsErr != nil {
		errMsg := fmt.Sprintf("Missing or invalid request parameters: %s", paramsErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&listResponse{&errMsg, []action{}})
		return
	}

	reqData := listRequest{
		Queue: queue,
		Limit: uint(limit),
	}

	validateErr := reqData.validate()
	if validateErr != nil {
		errMsg := fmt.Sprintf("Missing or invalid data in request: %s", validateErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&listResponse{&errMsg, []action{}})
		return
	}

	list := handler.actions(reqData.Queue)
	actions, err := list.ListActions(reqData.Limit)
	if err != nil {
		errMsg := fmt.Sprintf("Failed to retrieve actions: %s", err.Error())
		response.WriteHeader(http.StatusInternalServerError)
		resEncoder.Encode(&listResponse{&errMsg, []action{}})
		return
	}

	respActions := make([]action, len(actions))
	for index, act := range actions {
		a := action{}
		a.fromMigAction(act)
		respActions[index] = a
	}
	resEncoder.Encode(&listResponse{nil, respActions})
}
<commit_msg>Made comment more accurate<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Zack Mullaly zmullaly@mozilla.com [:zack]

package actions

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/mozilla/mig"
)

// ListActions abstracts over operations that allow the MIG API to retrieve
// a list of actions that an agent should run. It allows for a limit to be set
// on the number of actions to return.
type ListActions interface {
	ListActions(uint) ([]mig.Action, error)
}

// List is an HTTP request handler that serves GET requests intended by agents
// to retrieve a list of actions that can be run.
//
// This request handler must be able to construct a means of retrieving actions
// given a queue location string.
type List struct {
	actions func(string) ListActions
}

type operation struct {
	Module     string      `json:"module"`
	Parameters interface{} `json:"parameters"`
}

type action struct {
	Name          string      `json:"name"`
	Target        string      `json:"target"`
	ValidFrom     time.Time   `json:"validFrom"`
	ExpireAfter   time.Time   `json:"expireAfter"`
	Operations    []operation `json:"operations"`
	Signatures    []string    `json:"signatures"`
	Status        string      `json:"status"`
	SyntaxVersion uint        `json:"syntaxVersion"`
}

type listRequest struct {
	Queue string `json:"queue"`
	Limit uint   `json:"limit"`
}

type listResponse struct {
	Error   *string  `json:"error"`
	Actions []action `json:"actions"`
}

// NewList constructs a new List handler.
func NewList(listActionsConstructor func(string) ListActions) List {
	return List{
		actions: listActionsConstructor,
	}
}

// validate ensures that a request to list actions contains all of the data
// required to satisfy the request.
func (req listRequest) validate() error {
	if req.Queue == "" {
		return fmt.Errorf("missing queue field")
	}

	return nil
}

// fromMigAction converts a mig.Action loaded from the database into our
// limited representation for use by the API.
func (a *action) fromMigAction(act mig.Action) {
	*a = action{
		Name:          act.Name,
		Target:        act.Target,
		ValidFrom:     act.ValidFrom,
		ExpireAfter:   act.ExpireAfter,
		Operations:    []operation{},
		Signatures:    act.PGPSignatures,
		Status:        act.Status,
		SyntaxVersion: uint(act.SyntaxVersion),
	}

	for _, op := range act.Operations {
		a.Operations = append(a.Operations, operation{
			Module:     op.Module,
			Parameters: op.Parameters,
		})
	}
}

func (handler List) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	resEncoder := json.NewEncoder(response)

	response.Header().Set("Content-Type", "application/json")

	queryStringValues := request.URL.Query()

	var paramsErr error
	queue := queryStringValues.Get("queue")
	limit, parseErr := strconv.Atoi(queryStringValues.Get("limit"))
	if queue == "" {
		paramsErr = fmt.Errorf("missing parameter 'queue'")
	}
	if parseErr != nil {
		paramsErr = parseErr
	}
	if paramsErr != nil {
		errMsg := fmt.Sprintf("Missing or invalid request parameters: %s", paramsErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&listResponse{&errMsg, []action{}})
		return
	}

	reqData := listRequest{
		Queue: queue,
		Limit: uint(limit),
	}

	validateErr := reqData.validate()
	if validateErr != nil {
		errMsg := fmt.Sprintf("Missing or invalid data in request: %s", validateErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&listResponse{&errMsg, []action{}})
		return
	}

	list := handler.actions(reqData.Queue)
	actions, err := list.ListActions(reqData.Limit)
	if err != nil {
		errMsg := fmt.Sprintf("Failed to retrieve actions: %s", err.Error())
		response.WriteHeader(http.StatusInternalServerError)
		resEncoder.Encode(&listResponse{&errMsg, []action{}})
		return
	}

	respActions := make([]action, len(actions))
	for index, act := range actions {
		a := action{}
		a.fromMigAction(act)
		respActions[index] = a
	}
	resEncoder.Encode(&listResponse{nil, respActions})
}
<|endoftext|>


<commit_before>// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package frontend

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/julienschmidt/httprouter"
	"go.chromium.org/luci/auth/identity"
	"go.chromium.org/luci/buildbucket/deprecated"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/server"
	"go.chromium.org/luci/server/auth"
	"go.chromium.org/luci/server/auth/openid"
	"go.chromium.org/luci/server/auth/xsrf"
	"go.chromium.org/luci/server/middleware"
	"go.chromium.org/luci/server/router"
	"go.chromium.org/luci/server/templates"

	"go.chromium.org/luci/milo/buildsource/buildbucket"
	"go.chromium.org/luci/milo/buildsource/swarming"
	"go.chromium.org/luci/milo/common"
)

// Run sets up all the routes and runs the server.
func Run(srv *server.Server, templatePath string) {
	appVersionID := "unknown"
	if idx := strings.LastIndex(srv.Options.ContainerImageID, ":"); idx != -1 {
		appVersionID = srv.Options.ContainerImageID[idx+1:]
	}

	// Register plain ol' http handlers.
	r := srv.Routes

	baseMW := router.NewMiddlewareChain()
	baseAuthMW := baseMW.Extend(
		middleware.WithContextTimeout(time.Minute),
		auth.Authenticate(srv.CookieAuth),
	)
	htmlMW := baseAuthMW.Extend(
		withAccessClientMiddleware, // This must be called after the auth.Authenticate middleware.
		withGitMiddleware,
		withBuildbucketBuildsClient,
		withBuildbucketBuildersClient,
		templates.WithTemplates(getTemplateBundle(templatePath, appVersionID, srv.Options.Prod)),
	)
	xsrfMW := htmlMW.Extend(xsrf.WithTokenCheck)
	projectMW := htmlMW.Extend(buildProjectACLMiddleware(false))
	optionalProjectMW := htmlMW.Extend(buildProjectACLMiddleware(true))

	r.GET("/", htmlMW, frontpageHandler)
	r.GET("/p", baseMW, movedPermanently("/"))
	r.GET("/search", htmlMW, searchHandler)
	r.GET("/opensearch.xml", baseMW, searchXMLHandler)

	// Artifacts.
	r.GET("/artifact/*path", baseMW, redirect("/ui/artifact/*path", http.StatusFound))

	// Invocations.
	r.GET("/inv/*path", baseMW, redirect("/ui/inv/*path", http.StatusFound))

	// Builds.
	r.GET("/b/:id", htmlMW, handleError(redirectLUCIBuild))
	r.GET("/p/:project/builds/b:id", baseMW, movedPermanently("/b/:id"))

	buildPageMW := router.NewMiddlewareChain(func(c *router.Context, next router.Handler) {
		shouldShowNewBuildPage := getShowNewBuildPageCookie(c)
		if shouldShowNewBuildPage {
			redirect("/ui/p/:project/builders/:bucket/:builder/:numberOrId", http.StatusFound)(c)
		} else {
			next(c)
		}
	}).Extend(optionalProjectMW...)
	r.GET("/p/:project/builders/:bucket/:builder/:numberOrId", buildPageMW, handleError(handleLUCIBuild))
	// TODO(crbug/1108198): remvoe this route once we turned down the old build page.
	r.GET("/old/p/:project/builders/:bucket/:builder/:numberOrId", optionalProjectMW, handleError(handleLUCIBuild))

	// Only the new build page can take path suffix, redirect to the new build page.
	r.GET("/b/:id/*path", baseMW, redirect("/ui/b/:id/*path", http.StatusFound))
	r.GET("/p/:project/builds/b:id/*path", baseMW, redirect("/ui/b/:id/*path", http.StatusFound))
	r.GET("/p/:project/builders/:bucket/:builder/:numberOrId/*path", baseMW, redirect("/ui/p/:project/builders/:bucket/:builder/:numberOrId/*path", http.StatusFound))

	// Console
	r.GET("/p/:project", projectMW, handleError(func(c *router.Context) error {
		return ConsolesHandler(c, c.Params.ByName("project"))
	}))
	r.GET("/p/:project/", baseMW, movedPermanently("/p/:project"))
	r.GET("/p/:project/g", baseMW, movedPermanently("/p/:project"))
	r.GET("/p/:project/g/:group/console", projectMW, handleError(ConsoleHandler))
	r.GET("/p/:project/g/:group", projectMW, redirect("/p/:project/g/:group/console", http.StatusFound))
	r.GET("/p/:project/g/:group/", baseMW, movedPermanently("/p/:project/g/:group"))

	// Builder list
	// Redirects to the lit-element implementation.
	r.GET("/p/:project/builders", baseMW, redirect("/ui/p/:project/builders", http.StatusFound))
	r.GET("/p/:project/g/:group/builders", baseMW, redirect("/ui/p/:project/g/:group/builders", http.StatusFound))

	// Swarming
	r.GET(swarming.URLBase+"/:id/steps/*logname", htmlMW, handleError(HandleSwarmingLog))
	r.GET(swarming.URLBase+"/:id", htmlMW, handleError(handleSwarmingBuild))
	// Backward-compatible URLs for Swarming:
	r.GET("/swarming/prod/:id/steps/*logname", htmlMW, handleError(HandleSwarmingLog))
	r.GET("/swarming/prod/:id", htmlMW, handleError(handleSwarmingBuild))

	// Buildbucket
	// If these routes change, also change links in common/model/build_summary.go:getLinkFromBuildID
	// and common/model/builder_summary.go:SelfLink.
	r.GET("/p/:project/builders/:bucket/:builder", optionalProjectMW, handleError(BuilderHandler))

	r.GET("/buildbucket/:bucket/:builder", baseMW, redirectFromProjectlessBuilder)

	// LogDog Milo Annotation Streams.
	// This mimics the `logdog://logdog_host/project/*path` url scheme seen on
	// swarming tasks.
	r.GET("/raw/build/:logdog_host/:project/*path", htmlMW, handleError(handleRawPresentationBuild))

	pubsubMW := router.NewMiddlewareChain(
		auth.Authenticate(&openid.GoogleIDTokenAuthMethod{
			AudienceCheck: openid.AudienceMatchesHost,
		}),
		withBuildbucketBuildsClient,
	)
	pusherID := identity.Identity(fmt.Sprintf("user:buildbucket-pubsub@%s.iam.gserviceaccount.com", srv.Options.CloudProject))

	// PubSub subscription endpoints.
	r.POST("/push-handlers/buildbucket", pubsubMW, func(ctx *router.Context) {
		if got := auth.CurrentIdentity(ctx.Context); got != pusherID {
			logging.Errorf(ctx.Context, "Expecting ID token of %q, got %q", pusherID, got)
			ctx.Writer.WriteHeader(403)
		} else {
			buildbucket.PubSubHandler(ctx)
		}
	})

	r.POST("/actions/cancel_build", xsrfMW, handleError(cancelBuildHandler))
	r.POST("/actions/retry_build", xsrfMW, handleError(retryBuildHandler))

	r.GET("/internal_widgets/related_builds/:id", htmlMW, handleError(handleGetRelatedBuildsTable))

	// Config for ResultUI frontend.
	r.GET("/configs.js", baseMW, handleError(configsJSHandler))

	r.GET("/auth-state", baseAuthMW, handleError(getAuthState))
}

// handleError is a wrapper for a handler so that the handler can return an error
// rather than call ErrorHandler directly.
// This should be used for handlers that render webpages.
func handleError(handler func(c *router.Context) error) func(c *router.Context) {
	return func(c *router.Context) {
		if err := handler(c); err != nil {
			ErrorHandler(c, err)
		}
	}
}

// cronHandler is a wrapper for cron handlers which do not require template rendering.
func cronHandler(handler func(c context.Context) error) func(c *router.Context) {
	return func(ctx *router.Context) {
		if err := handler(ctx.Context); err != nil {
			logging.WithError(err).Errorf(ctx.Context, "failed to run")
			ctx.Writer.WriteHeader(http.StatusInternalServerError)
			return
		}
		ctx.Writer.WriteHeader(http.StatusOK)
	}
}

// redirect returns a handler that responds with given HTTP status
// with a location specified by the pathTemplate.
func redirect(pathTemplate string, status int) router.Handler {
	if !strings.HasPrefix(pathTemplate, "/") {
		panic("pathTemplate must start with /")
	}

	interpolator := createInterpolator(pathTemplate)
	return func(c *router.Context) {
		path := interpolator(c.Params)
		http.Redirect(c.Writer, c.Request, path, status)
	}
}

// createInterpolator returns a function that can replace the variables in the
// pathTemplate with the provided params.
func createInterpolator(pathTemplate string) func(params httprouter.Params) string {
	templateParts := strings.Split(pathTemplate, "/")

	return func(params httprouter.Params) string {
		components := make([]string, 0, len(templateParts))

		for _, p := range templateParts {
			if strings.HasPrefix(p, ":") {
				components = append(components, params.ByName(p[1:]))
			} else if strings.HasPrefix(p, "*_") {
				// httprouter uses the decoded URL path to perform routing
				// (which defeats the whole purpose of encoding), so we have to
				// use '*' to capture a path component containing %2F.
				// "*_" is a special syntax to signal that although we are
				// capturing all characters till the end of the path, the
				// captured value should be treated as a single path component,
				// therefore '/' should also be encoded.
				//
				// Caveat: because '*' is used, this hack only works for the
				// last path component.
				//
				// https://github.com/julienschmidt/httprouter/issues/284
				component := params.ByName(p[1:])
				component = strings.TrimPrefix(component, "/")
				components = append(components, component)
			} else if strings.HasPrefix(p, "*") {
				path := params.ByName(p[1:])
				path = strings.TrimPrefix(path, "/")

				// Split the path into components before passing them to
				// url.PathEscape. Otherwise url.PathEscape will encode "/" into
				// "%2F" because it escapes all non-safe characters in a path
				// component (it should be renamed to url.PathComponentEscape).
				components = append(components, strings.Split(path, "/")...)
			} else {
				components = append(components, p)
			}
		}

		// Escape the path components ourselves.
		// url.URL.String() should not be used because it escapes everything
		// automatically except '/' making it impossible to have %2F (encoded
		// '/') in a path component ('%2F' will be double encoded to '%252F'
		// while '/' won't be encoded at all).
		for i, p := range components {
			components[i] = url.PathEscape(p)
		}
		return strings.Join(components, "/")
	}
}

// movedPermanently is a special instance of redirect, returning a handler
// that responds with HTTP 301 (Moved Permanently) with a location specified
// by the pathTemplate.
//
// TODO(nodir,iannucci): delete all usages.
func movedPermanently(pathTemplate string) router.Handler {
	return redirect(pathTemplate, http.StatusMovedPermanently)
}

func redirectFromProjectlessBuilder(c *router.Context) {
	bucket := c.Params.ByName("bucket")
	builder := c.Params.ByName("builder")

	project, _ := deprecated.BucketNameToV2(bucket)
	u := *c.Request.URL
	u.Path = fmt.Sprintf("/p/%s/builders/%s/%s", project, bucket, builder)
	http.Redirect(c.Writer, c.Request, u.String(), http.StatusMovedPermanently)
}

// configsJSHandler serves /configs.js used by ResultUI frontend code.
func configsJSHandler(c *router.Context) error {
	template, err := template.ParseFiles("templates/configs.template.js")
	if err != nil {
		logging.Errorf(c.Context, "Failed to load configs.template.js: %s", err)
		return err
	}

	settings := common.GetSettings(c.Context)

	header := c.Writer.Header()
	header.Set("content-type", "application/javascript")

	// The configs file rarely changes, and may block other scripts from running.
	// Set max-age to one hour, stale-while-revalidate to 7 days to improve
	// performance.
	header.Set("cache-control", "max-age=3600,stale-while-revalidate=604800")
	err = template.Execute(c.Writer, map[string]interface{}{
		"ResultDB": map[string]string{
			"Host": settings.GetResultdb().GetHost(),
		},
		"Buildbucket": map[string]string{
			"Host": settings.GetBuildbucket().GetHost(),
		},
	})

	if err != nil {
		logging.Errorf(c.Context, "Failed to execute configs.template.js: %s", err)
		return err
	}

	return nil
}
<commit_msg>[milo] remove unused cronHandler wrapper<commit_after>// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package frontend

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

	"github.com/julienschmidt/httprouter"
	"go.chromium.org/luci/auth/identity"
	"go.chromium.org/luci/buildbucket/deprecated"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/server"
	"go.chromium.org/luci/server/auth"
	"go.chromium.org/luci/server/auth/openid"
	"go.chromium.org/luci/server/auth/xsrf"
	"go.chromium.org/luci/server/middleware"
	"go.chromium.org/luci/server/router"
	"go.chromium.org/luci/server/templates"

	"go.chromium.org/luci/milo/buildsource/buildbucket"
	"go.chromium.org/luci/milo/buildsource/swarming"
	"go.chromium.org/luci/milo/common"
)

// Run sets up all the routes and runs the server.
func Run(srv *server.Server, templatePath string) {
	appVersionID := "unknown"
	if idx := strings.LastIndex(srv.Options.ContainerImageID, ":"); idx != -1 {
		appVersionID = srv.Options.ContainerImageID[idx+1:]
	}

	// Register plain ol' http handlers.
	r := srv.Routes

	baseMW := router.NewMiddlewareChain()
	baseAuthMW := baseMW.Extend(
		middleware.WithContextTimeout(time.Minute),
		auth.Authenticate(srv.CookieAuth),
	)
	htmlMW := baseAuthMW.Extend(
		withAccessClientMiddleware, // This must be called after the auth.Authenticate middleware.
		withGitMiddleware,
		withBuildbucketBuildsClient,
		withBuildbucketBuildersClient,
		templates.WithTemplates(getTemplateBundle(templatePath, appVersionID, srv.Options.Prod)),
	)
	xsrfMW := htmlMW.Extend(xsrf.WithTokenCheck)
	projectMW := htmlMW.Extend(buildProjectACLMiddleware(false))
	optionalProjectMW := htmlMW.Extend(buildProjectACLMiddleware(true))

	r.GET("/", htmlMW, frontpageHandler)
	r.GET("/p", baseMW, movedPermanently("/"))
	r.GET("/search", htmlMW, searchHandler)
	r.GET("/opensearch.xml", baseMW, searchXMLHandler)

	// Artifacts.
	r.GET("/artifact/*path", baseMW, redirect("/ui/artifact/*path", http.StatusFound))

	// Invocations.
	r.GET("/inv/*path", baseMW, redirect("/ui/inv/*path", http.StatusFound))

	// Builds.
	r.GET("/b/:id", htmlMW, handleError(redirectLUCIBuild))
	r.GET("/p/:project/builds/b:id", baseMW, movedPermanently("/b/:id"))

	buildPageMW := router.NewMiddlewareChain(func(c *router.Context, next router.Handler) {
		shouldShowNewBuildPage := getShowNewBuildPageCookie(c)
		if shouldShowNewBuildPage {
			redirect("/ui/p/:project/builders/:bucket/:builder/:numberOrId", http.StatusFound)(c)
		} else {
			next(c)
		}
	}).Extend(optionalProjectMW...)
	r.GET("/p/:project/builders/:bucket/:builder/:numberOrId", buildPageMW, handleError(handleLUCIBuild))
	// TODO(crbug/1108198): remvoe this route once we turned down the old build page.
	r.GET("/old/p/:project/builders/:bucket/:builder/:numberOrId", optionalProjectMW, handleError(handleLUCIBuild))

	// Only the new build page can take path suffix, redirect to the new build page.
	r.GET("/b/:id/*path", baseMW, redirect("/ui/b/:id/*path", http.StatusFound))
	r.GET("/p/:project/builds/b:id/*path", baseMW, redirect("/ui/b/:id/*path", http.StatusFound))
	r.GET("/p/:project/builders/:bucket/:builder/:numberOrId/*path", baseMW, redirect("/ui/p/:project/builders/:bucket/:builder/:numberOrId/*path", http.StatusFound))

	// Console
	r.GET("/p/:project", projectMW, handleError(func(c *router.Context) error {
		return ConsolesHandler(c, c.Params.ByName("project"))
	}))
	r.GET("/p/:project/", baseMW, movedPermanently("/p/:project"))
	r.GET("/p/:project/g", baseMW, movedPermanently("/p/:project"))
	r.GET("/p/:project/g/:group/console", projectMW, handleError(ConsoleHandler))
	r.GET("/p/:project/g/:group", projectMW, redirect("/p/:project/g/:group/console", http.StatusFound))
	r.GET("/p/:project/g/:group/", baseMW, movedPermanently("/p/:project/g/:group"))

	// Builder list
	// Redirects to the lit-element implementation.
	r.GET("/p/:project/builders", baseMW, redirect("/ui/p/:project/builders", http.StatusFound))
	r.GET("/p/:project/g/:group/builders", baseMW, redirect("/ui/p/:project/g/:group/builders", http.StatusFound))

	// Swarming
	r.GET(swarming.URLBase+"/:id/steps/*logname", htmlMW, handleError(HandleSwarmingLog))
	r.GET(swarming.URLBase+"/:id", htmlMW, handleError(handleSwarmingBuild))
	// Backward-compatible URLs for Swarming:
	r.GET("/swarming/prod/:id/steps/*logname", htmlMW, handleError(HandleSwarmingLog))
	r.GET("/swarming/prod/:id", htmlMW, handleError(handleSwarmingBuild))

	// Buildbucket
	// If these routes change, also change links in common/model/build_summary.go:getLinkFromBuildID
	// and common/model/builder_summary.go:SelfLink.
	r.GET("/p/:project/builders/:bucket/:builder", optionalProjectMW, handleError(BuilderHandler))

	r.GET("/buildbucket/:bucket/:builder", baseMW, redirectFromProjectlessBuilder)

	// LogDog Milo Annotation Streams.
	// This mimics the `logdog://logdog_host/project/*path` url scheme seen on
	// swarming tasks.
	r.GET("/raw/build/:logdog_host/:project/*path", htmlMW, handleError(handleRawPresentationBuild))

	pubsubMW := router.NewMiddlewareChain(
		auth.Authenticate(&openid.GoogleIDTokenAuthMethod{
			AudienceCheck: openid.AudienceMatchesHost,
		}),
		withBuildbucketBuildsClient,
	)
	pusherID := identity.Identity(fmt.Sprintf("user:buildbucket-pubsub@%s.iam.gserviceaccount.com", srv.Options.CloudProject))

	// PubSub subscription endpoints.
	r.POST("/push-handlers/buildbucket", pubsubMW, func(ctx *router.Context) {
		if got := auth.CurrentIdentity(ctx.Context); got != pusherID {
			logging.Errorf(ctx.Context, "Expecting ID token of %q, got %q", pusherID, got)
			ctx.Writer.WriteHeader(403)
		} else {
			buildbucket.PubSubHandler(ctx)
		}
	})

	r.POST("/actions/cancel_build", xsrfMW, handleError(cancelBuildHandler))
	r.POST("/actions/retry_build", xsrfMW, handleError(retryBuildHandler))

	r.GET("/internal_widgets/related_builds/:id", htmlMW, handleError(handleGetRelatedBuildsTable))

	// Config for ResultUI frontend.
	r.GET("/configs.js", baseMW, handleError(configsJSHandler))

	r.GET("/auth-state", baseAuthMW, handleError(getAuthState))
}

// handleError is a wrapper for a handler so that the handler can return an error
// rather than call ErrorHandler directly.
// This should be used for handlers that render webpages.
func handleError(handler func(c *router.Context) error) func(c *router.Context) {
	return func(c *router.Context) {
		if err := handler(c); err != nil {
			ErrorHandler(c, err)
		}
	}
}

// redirect returns a handler that responds with given HTTP status
// with a location specified by the pathTemplate.
func redirect(pathTemplate string, status int) router.Handler {
	if !strings.HasPrefix(pathTemplate, "/") {
		panic("pathTemplate must start with /")
	}

	interpolator := createInterpolator(pathTemplate)
	return func(c *router.Context) {
		path := interpolator(c.Params)
		http.Redirect(c.Writer, c.Request, path, status)
	}
}

// createInterpolator returns a function that can replace the variables in the
// pathTemplate with the provided params.
func createInterpolator(pathTemplate string) func(params httprouter.Params) string {
	templateParts := strings.Split(pathTemplate, "/")

	return func(params httprouter.Params) string {
		components := make([]string, 0, len(templateParts))

		for _, p := range templateParts {
			if strings.HasPrefix(p, ":") {
				components = append(components, params.ByName(p[1:]))
			} else if strings.HasPrefix(p, "*_") {
				// httprouter uses the decoded URL path to perform routing
				// (which defeats the whole purpose of encoding), so we have to
				// use '*' to capture a path component containing %2F.
				// "*_" is a special syntax to signal that although we are
				// capturing all characters till the end of the path, the
				// captured value should be treated as a single path component,
				// therefore '/' should also be encoded.
				//
				// Caveat: because '*' is used, this hack only works for the
				// last path component.
				//
				// https://github.com/julienschmidt/httprouter/issues/284
				component := params.ByName(p[1:])
				component = strings.TrimPrefix(component, "/")
				components = append(components, component)
			} else if strings.HasPrefix(p, "*") {
				path := params.ByName(p[1:])
				path = strings.TrimPrefix(path, "/")

				// Split the path into components before passing them to
				// url.PathEscape. Otherwise url.PathEscape will encode "/" into
				// "%2F" because it escapes all non-safe characters in a path
				// component (it should be renamed to url.PathComponentEscape).
				components = append(components, strings.Split(path, "/")...)
			} else {
				components = append(components, p)
			}
		}

		// Escape the path components ourselves.
		// url.URL.String() should not be used because it escapes everything
		// automatically except '/' making it impossible to have %2F (encoded
		// '/') in a path component ('%2F' will be double encoded to '%252F'
		// while '/' won't be encoded at all).
		for i, p := range components {
			components[i] = url.PathEscape(p)
		}
		return strings.Join(components, "/")
	}
}

// movedPermanently is a special instance of redirect, returning a handler
// that responds with HTTP 301 (Moved Permanently) with a location specified
// by the pathTemplate.
//
// TODO(nodir,iannucci): delete all usages.
func movedPermanently(pathTemplate string) router.Handler {
	return redirect(pathTemplate, http.StatusMovedPermanently)
}

func redirectFromProjectlessBuilder(c *router.Context) {
	bucket := c.Params.ByName("bucket")
	builder := c.Params.ByName("builder")

	project, _ := deprecated.BucketNameToV2(bucket)
	u := *c.Request.URL
	u.Path = fmt.Sprintf("/p/%s/builders/%s/%s", project, bucket, builder)
	http.Redirect(c.Writer, c.Request, u.String(), http.StatusMovedPermanently)
}

// configsJSHandler serves /configs.js used by ResultUI frontend code.
func configsJSHandler(c *router.Context) error {
	template, err := template.ParseFiles("templates/configs.template.js")
	if err != nil {
		logging.Errorf(c.Context, "Failed to load configs.template.js: %s", err)
		return err
	}

	settings := common.GetSettings(c.Context)

	header := c.Writer.Header()
	header.Set("content-type", "application/javascript")

	// The configs file rarely changes, and may block other scripts from running.
	// Set max-age to one hour, stale-while-revalidate to 7 days to improve
	// performance.
	header.Set("cache-control", "max-age=3600,stale-while-revalidate=604800")
	err = template.Execute(c.Writer, map[string]interface{}{
		"ResultDB": map[string]string{
			"Host": settings.GetResultdb().GetHost(),
		},
		"Buildbucket": map[string]string{
			"Host": settings.GetBuildbucket().GetHost(),
		},
	})

	if err != nil {
		logging.Errorf(c.Context, "Failed to execute configs.template.js: %s", err)
		return err
	}

	return nil
}
<|endoftext|>