<commit_before>package dns

import (
	"fmt"
	"io"
	"strconv"
	"strings"
	"text/scanner"
)

// Only used when debugging the parser itself.
var _DEBUG = false

// Tokinize a RFC 1035 zone file. The tokenizer will normalize it:
// * Add ownernames if they are left blank;
// * Suppress sequences of spaces;
// * Make each RR fit on one line (NEWLINE is send as last)
// * Handle comments: ;
// * Handle braces.
const (
	// Zonefile
	_EOF = iota // Don't let it start with zero
	_STRING
	_BLANK
	_NEWLINE
	_RRTYPE
	_OWNER
	_CLASS
	_DIRORIGIN  // $ORIGIN
	_DIRTTL     // $TTL
	_DIRINCLUDE // $INCLUDE

	// Privatekey file
	_VALUE
	_KEY

	_EXPECT_OWNER_DIR      // Ownername
	_EXPECT_OWNER_BL       // Whitespace after the ownername
	_EXPECT_ANY            // Expect rrtype, ttl or class
	_EXPECT_ANY_NOCLASS    // Expect rrtype or ttl
	_EXPECT_ANY_NOCLASS_BL // The Whitespace after _EXPECT_ANY_NOCLASS
	_EXPECT_ANY_NOTTL      // Expect rrtype or class
	_EXPECT_ANY_NOTTL_BL   // Whitespace after _EXPECT_ANY_NOTTL
	_EXPECT_RRTYPE         // Expect rrtype
	_EXPECT_RRTYPE_BL      // Whitespace BEFORE rrtype
	_EXPECT_RDATA          // The first element of the rdata
	_EXPECT_DIRTTL_BL      // Space after directive $TTL
	_EXPECT_DIRTTL         // Directive $TTL
)

// ParseError contains the parse error and the location in the io.Reader
// where the error occured.
type ParseError struct {
	err string
	lex lex
}

func (e *ParseError) Error() string {
	va := strconv.Itoa(e.lex.value)
	s := e.err + ": `" + e.lex.token + "' (value: " + va + ") at line: " +
		strconv.Itoa(e.lex.line) + " and column: " +
		strconv.Itoa(e.lex.column)
	return s
}

type lex struct {
	token  string // Text of the token
	err    string // Error text when the lexer detects it. Not used by the grammar
	value  int    // Value: _STRING, _BLANK, etc.
	line   int    // Line in the file
	column int    // Column in the fil
}

type Token struct {
	Rr    RR          // the scanned resource record
	Error *ParseError // when an error occured, this is the specifics
}

// NewRR parses the string s and returns the RR contained in there. If the string
// contains more than one RR, only the first is returned. If an error is detected
// that error is returned.
// If the class is not specified, the IN class is assumed. If the TTL is not
// specified DefaultTtl is assumed.
func NewRR(s string) (RR, error) {
	t := make(chan Token)
	if s[len(s)-1] != '\n' { // We need a closing newline
		t = ParseZone(strings.NewReader(s+"\n"))
	} else {
		t= ParseZone(strings.NewReader(s))
	}
	r := <-t
	if r.Error != nil {
		return nil, r.Error
	}
	return r.Rr, nil
}

// ParseZone reads a RFC 1035 zone from r. It returns each parsed RR or on error
// on the returned channel. The channel t is closed by ParseZone when the end of r is reached.
func ParseZone(r io.Reader) (chan Token) {
        t := make(chan Token)
        go parseZone(r, t)
        return t
}

func parseZone(r io.Reader, t chan Token) {
	defer close(t)
	var s scanner.Scanner
	c := make(chan lex)
	s.Init(r)
	s.Mode = 0
	s.Whitespace = 0
	// Start the lexer
	go zlexer(s, c)
	// 5 possible beginnings of a line, _ is a space
	// 1. _OWNER _ _RRTYPE                     -> class/ttl omitted
	// 2. _OWNER _ _STRING _ _RRTYPE           -> class omitted
	// 3. _OWNER _ _STRING _ _CLASS  _ _RRTYPE -> ttl/class
	// 4. _OWNER _ _CLASS  _ _RRTYPE           -> ttl omitted
	// 5. _OWNER _ _CLASS  _ _STRING _ _RRTYPE -> class/ttl (reversed)
	// After detecting these, we know the _RRTYPE so we can jump to functions
	// handling the rdata for each of these types.
	st := _EXPECT_OWNER_DIR
	var h RR_Header
	var ok bool
	var defttl uint32 = DefaultTtl
	for l := range c {
		if _DEBUG {
			fmt.Printf("[%v]\n", l)
		}
		// Lexer spotted an error already
		if l.err != "" {
			t <- Token{Error: &ParseError{l.err, l}}
			return

		}
		switch st {
		case _EXPECT_OWNER_DIR:
			// We can also expect a directive, like $TTL or $ORIGIN
			h.Ttl = defttl
			h.Class = ClassINET
			switch l.value {
			case _NEWLINE: // Empty line
				st = _EXPECT_OWNER_DIR
			case _OWNER:
				h.Name = l.token
				st = _EXPECT_OWNER_BL
			case _DIRTTL:
				st = _EXPECT_DIRTTL_BL
			default:
				t <- Token{Error: &ParseError{"Error at the start", l}}
				return
			}
		case _EXPECT_DIRTTL_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank after $-directive", l}}
				return
			}
			st = _EXPECT_DIRTTL
		case _EXPECT_DIRTTL:
			if l.value != _STRING {
				t <- Token{Error: &ParseError{"Expecting $TTL value, not this...", l}}
				return
			}
			if ttl, ok := stringToTtl(l, t); !ok {
				return
			} else {
				defttl = ttl
			}

			st = _EXPECT_OWNER_DIR
		case _EXPECT_OWNER_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank after owner", l}}
				return
			}
			st = _EXPECT_ANY
		case _EXPECT_ANY:
			switch l.value {
			case _RRTYPE:
				h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
				st = _EXPECT_RDATA
			case _CLASS:
				h.Class, ok = Str_class[strings.ToUpper(l.token)]
				if !ok {
					t <- Token{Error: &ParseError{"Unknown class", l}}
					return
				}
				st = _EXPECT_ANY_NOCLASS_BL
			case _STRING: // TTL is this case
				if ttl, ok := stringToTtl(l, t); !ok {
					return
				} else {
					h.Ttl = ttl
				}
				st = _EXPECT_ANY_NOTTL_BL
			default:
				t <- Token{Error: &ParseError{"Expecting RR type, TTL or class, not this...", l}}
				return
			}
		case _EXPECT_ANY_NOCLASS_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank before NOCLASS", l}}
				return
			}
			st = _EXPECT_ANY_NOCLASS
		case _EXPECT_ANY_NOTTL_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank before NOTTL", l}}
				return
			}
			st = _EXPECT_ANY_NOTTL
		case _EXPECT_ANY_NOTTL:
			switch l.value {
			case _CLASS:
				h.Class, ok = Str_class[strings.ToUpper(l.token)]
				if !ok {
					t <- Token{Error: &ParseError{"Unknown class", l}}
					return
				}
				st = _EXPECT_RRTYPE_BL
			case _RRTYPE:
				h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
				st = _EXPECT_RDATA
			}
		case _EXPECT_ANY_NOCLASS:
			switch l.value {
			case _STRING: // TTL
				if ttl, ok := stringToTtl(l, t); !ok {
					return
				} else {
					h.Ttl = ttl
				}
				st = _EXPECT_RRTYPE_BL
			case _RRTYPE:
				h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
				st = _EXPECT_RDATA
			default:
				t <- Token{Error: &ParseError{"Expecting RR type or TTL, not this...", l}}
				return
			}
		case _EXPECT_RRTYPE_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank after", l}}
				return
			}
			st = _EXPECT_RRTYPE
		case _EXPECT_RRTYPE:
			if l.value != _RRTYPE {
				t <- Token{Error: &ParseError{"Unknown RR type", l}}
				return
			}
			h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
			st = _EXPECT_RDATA
		case _EXPECT_RDATA:
			// I could save my token here...? l
			r, e := setRR(h, c)
			if e != nil {
				// If e.lex is nil than we have encounter a unknown RR type
				// in that case we substitute our current lex token
				if e.lex.token == "" && e.lex.value == 0 {
					e.lex = l // Uh, dirty
				}
				t <- Token{Error: e}
				return
			}
			t <- Token{Rr: r}
			st = _EXPECT_OWNER_DIR
		}
	}
}

func (l lex) String() string {
	switch l.value {
	case _STRING:
		return "S:" + l.token + "$"
	case _BLANK:
		return "_"
	case _NEWLINE:
		return "|\n"
	case _RRTYPE:
		return "R:" + l.token + "$"
	case _OWNER:
		return "O:" + l.token + "$"
	case _CLASS:
		return "C:" + l.token + "$"
	case _DIRTTL:
		return "T:" + l.token + "$"
	}
	return ""
}

// zlexer scans the sourcefile and returns tokens on the channel c.
func zlexer(s scanner.Scanner, c chan lex) {
	var l lex
	str := "" // Hold the current read text
	quote := false
	escape := false
	space := false
	commt := false
	rrtype := false
	owner := true
	brace := 0
	tok := s.Scan()
	defer close(c)
	for tok != scanner.EOF {
		l.column = s.Position.Column
		l.line = s.Position.Line
		switch x := s.TokenText(); x {
		case " ", "\t":
                        escape = false
			if commt {
				break
			}
			if str == "" {
				//l.value = _BLANK
				//l.token = " "
			} else if owner {
				// If we have a string and its the first, make it an owner
				l.value = _OWNER
				l.token = str
                                // escape $... start with a \ not a $, so this will work
				if str == "$TTL" {
					l.value = _DIRTTL
				}
				if str == "$ORIGIN" {
					l.value = _DIRORIGIN
				}
				c <- l
			} else {
				l.value = _STRING
				l.token = str

				if !rrtype {
					if _, ok := Str_rr[strings.ToUpper(l.token)]; ok {
						l.value = _RRTYPE
						rrtype = true
					}
					if _, ok := Str_class[strings.ToUpper(l.token)]; ok {
						l.value = _CLASS
					}
				}
				c <- l
			}
			str = ""
			if !space && !commt {
				l.value = _BLANK
				l.token = " "
				c <- l
			}
			owner = false
			space = true
		case ";":
                        if escape {
                                escape = false
                                str += ";"
                                break
                        }
			if quote {
				// Inside quoted text we allow ;
				str += ";"
				break
			}
			commt = true
		case "\n":
                        // Hmmm, escape newline
                        escape = false
			if commt {
				// Reset a comment
				commt = false
				rrtype = false
				str = ""
				// If not in a brace this ends the comment AND the RR
				if brace == 0 {
					owner = true
					l.value = _NEWLINE
					l.token = "\n"
					c <- l
				}
				break
			}
			if str != "" {
				l.value = _STRING
				l.token = str
				if !rrtype {
					if _, ok := Str_rr[strings.ToUpper(l.token)]; ok {
						l.value = _RRTYPE
						rrtype = true
					}
				}
				c <- l
			}
			if brace > 0 {
				l.value = _BLANK
				l.token = " "
				if !space {
					c <- l
				}
			} else {
				l.value = _NEWLINE
				l.token = "\n"
				c <- l
			}
			if l.value == _BLANK {
				space = true
			}

			str = ""
			commt = false
			rrtype = false
			owner = true
		case "\\":
			if commt {
				break
			}
                        if escape {
                                str += "\\"
                                escape = false
                                break
                        }
			escape = true
		case "\"":
			if commt {
				break
			}
                        if escape {
                                str += "\""
                                escape = false
                                break
                        }
			// str += "\"" don't add quoted quotes
			quote = !quote
		case "(":
			if commt {
				break
			}
                        if escape {
                                str += "("
                                escape = false
                                break
                        }
			brace++
		case ")":
			if commt {
				break
			}
                        if escape {
                                str += ")"
                                escape = false
                                break
                        }
			brace--
			if brace < 0 {
				l.err = "Extra closing brace"
				c <- l
				return
			}
		default:
			if commt {
				break
			}
                        escape = false
			str += x
			space = false
		}
		tok = s.Scan()
	}
	// Hmm.
	if len(str) > 0 {
		// Send remainder
		l.token = str
		l.value = _STRING
		c <- l
	}
}

func stringToTtl(l lex, t chan Token) (uint32, bool) {
	if ttl, ok := strconv.Atoi(l.token); ok != nil {
		t <- Token{Error: &ParseError{"Not a TTL", l}}
		return 0, false
	} else {
		return uint32(ttl), true
	}
	panic("not reached")
}
<commit_msg>partially working code<commit_after>package dns

import (
	"fmt"
	"io"
	"strconv"
	"strings"
)

// Only used when debugging the parser itself.
var _DEBUG = true

// Tokinize a RFC 1035 zone file. The tokenizer will normalize it:
// * Add ownernames if they are left blank;
// * Suppress sequences of spaces;
// * Make each RR fit on one line (NEWLINE is send as last)
// * Handle comments: ;
// * Handle braces.
const (
	// Zonefile
	_EOF = iota // Don't let it start with zero
	_STRING
	_BLANK
	_NEWLINE
	_RRTYPE
	_OWNER
	_CLASS
	_DIRORIGIN  // $ORIGIN
	_DIRTTL     // $TTL
	_DIRINCLUDE // $INCLUDE

	// Privatekey file
	_VALUE
	_KEY

	_EXPECT_OWNER_DIR      // Ownername
	_EXPECT_OWNER_BL       // Whitespace after the ownername
	_EXPECT_ANY            // Expect rrtype, ttl or class
	_EXPECT_ANY_NOCLASS    // Expect rrtype or ttl
	_EXPECT_ANY_NOCLASS_BL // The Whitespace after _EXPECT_ANY_NOCLASS
	_EXPECT_ANY_NOTTL      // Expect rrtype or class
	_EXPECT_ANY_NOTTL_BL   // Whitespace after _EXPECT_ANY_NOTTL
	_EXPECT_RRTYPE         // Expect rrtype
	_EXPECT_RRTYPE_BL      // Whitespace BEFORE rrtype
	_EXPECT_RDATA          // The first element of the rdata
	_EXPECT_DIRTTL_BL      // Space after directive $TTL
	_EXPECT_DIRTTL         // Directive $TTL
)

// ParseError contains the parse error and the location in the io.Reader
// where the error occured.
type ParseError struct {
	err string
	lex lex
}

func (e *ParseError) Error() string {
	va := strconv.Itoa(e.lex.value)
	s := e.err + ": `" + e.lex.token + "' (value: " + va + ") at line: " +
		strconv.Itoa(e.lex.line) + " and column: " +
		strconv.Itoa(e.lex.column)
	return s
}

type lex struct {
	token  string // Text of the token
	err    string // Error text when the lexer detects it. Not used by the grammar
	value  int    // Value: _STRING, _BLANK, etc.
	line   int    // Line in the file
	column int    // Column in the fil
}

type Token struct {
	Rr    RR          // the scanned resource record
	Error *ParseError // when an error occured, this is the specifics
}

// NewRR parses the string s and returns the RR contained in there. If the string
// contains more than one RR, only the first is returned. If an error is detected
// that error is returned.
// If the class is not specified, the IN class is assumed. If the TTL is not
// specified DefaultTtl is assumed.
func NewRR(s string) (RR, error) {
	t := make(chan Token)
	if s[len(s)-1] != '\n' { // We need a closing newline
		t = ParseZone(strings.NewReader(s + "\n"))
	} else {
		t = ParseZone(strings.NewReader(s))
	}
	r := <-t
	if r.Error != nil {
		return nil, r.Error
	}
	return r.Rr, nil
}

// ParseZone reads a RFC 1035 zone from r. It returns each parsed RR or on error
// on the returned channel. The channel t is closed by ParseZone when the end of r is reached.
func ParseZone(r io.Reader) chan Token {
	t := make(chan Token)
	go parseZone(r, t)
	return t
}

func parseZone(r io.Reader, t chan Token) {
	defer close(t)
	c := make(chan lex)
	// Start the lexer
	go zlexer(r, c)
	// 5 possible beginnings of a line, _ is a space
	// 1. _OWNER _ _RRTYPE                     -> class/ttl omitted
	// 2. _OWNER _ _STRING _ _RRTYPE           -> class omitted
	// 3. _OWNER _ _STRING _ _CLASS  _ _RRTYPE -> ttl/class
	// 4. _OWNER _ _CLASS  _ _RRTYPE           -> ttl omitted
	// 5. _OWNER _ _CLASS  _ _STRING _ _RRTYPE -> class/ttl (reversed)
	// After detecting these, we know the _RRTYPE so we can jump to functions
	// handling the rdata for each of these types.
	st := _EXPECT_OWNER_DIR
	var h RR_Header
	var ok bool
	var defttl uint32 = DefaultTtl
	for l := range c {
		if _DEBUG {
			fmt.Printf("[%v]\n", l)
		}
		// Lexer spotted an error already
		if l.err != "" {
			t <- Token{Error: &ParseError{l.err, l}}
			return

		}
		switch st {
		case _EXPECT_OWNER_DIR:
			// We can also expect a directive, like $TTL or $ORIGIN
			h.Ttl = defttl
			h.Class = ClassINET
			switch l.value {
			case _NEWLINE: // Empty line
				st = _EXPECT_OWNER_DIR
			case _OWNER:
				h.Name = l.token
				st = _EXPECT_OWNER_BL
			case _DIRTTL:
				st = _EXPECT_DIRTTL_BL
			default:
				t <- Token{Error: &ParseError{"Error at the start", l}}
				return
			}
		case _EXPECT_DIRTTL_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank after $-directive", l}}
				return
			}
			st = _EXPECT_DIRTTL
		case _EXPECT_DIRTTL:
			if l.value != _STRING {
				t <- Token{Error: &ParseError{"Expecting $TTL value, not this...", l}}
				return
			}
			if ttl, ok := stringToTtl(l, t); !ok {
				return
			} else {
				defttl = ttl
			}

			st = _EXPECT_OWNER_DIR
		case _EXPECT_OWNER_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank after owner", l}}
				return
			}
			st = _EXPECT_ANY
		case _EXPECT_ANY:
			switch l.value {
			case _RRTYPE:
				h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
				st = _EXPECT_RDATA
			case _CLASS:
				h.Class, ok = Str_class[strings.ToUpper(l.token)]
				if !ok {
					t <- Token{Error: &ParseError{"Unknown class", l}}
					return
				}
				st = _EXPECT_ANY_NOCLASS_BL
			case _STRING: // TTL is this case
				if ttl, ok := stringToTtl(l, t); !ok {
					return
				} else {
					h.Ttl = ttl
				}
				st = _EXPECT_ANY_NOTTL_BL
			default:
				t <- Token{Error: &ParseError{"Expecting RR type, TTL or class, not this...", l}}
				return
			}
		case _EXPECT_ANY_NOCLASS_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank before NOCLASS", l}}
				return
			}
			st = _EXPECT_ANY_NOCLASS
		case _EXPECT_ANY_NOTTL_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank before NOTTL", l}}
				return
			}
			st = _EXPECT_ANY_NOTTL
		case _EXPECT_ANY_NOTTL:
			switch l.value {
			case _CLASS:
				h.Class, ok = Str_class[strings.ToUpper(l.token)]
				if !ok {
					t <- Token{Error: &ParseError{"Unknown class", l}}
					return
				}
				st = _EXPECT_RRTYPE_BL
			case _RRTYPE:
				h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
				st = _EXPECT_RDATA
			}
		case _EXPECT_ANY_NOCLASS:
			switch l.value {
			case _STRING: // TTL
				if ttl, ok := stringToTtl(l, t); !ok {
					return
				} else {
					h.Ttl = ttl
				}
				st = _EXPECT_RRTYPE_BL
			case _RRTYPE:
				h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
				st = _EXPECT_RDATA
			default:
				t <- Token{Error: &ParseError{"Expecting RR type or TTL, not this...", l}}
				return
			}
		case _EXPECT_RRTYPE_BL:
			if l.value != _BLANK {
				t <- Token{Error: &ParseError{"No blank after", l}}
				return
			}
			st = _EXPECT_RRTYPE
		case _EXPECT_RRTYPE:
			if l.value != _RRTYPE {
				t <- Token{Error: &ParseError{"Unknown RR type", l}}
				return
			}
			h.Rrtype, _ = Str_rr[strings.ToUpper(l.token)]
			st = _EXPECT_RDATA
		case _EXPECT_RDATA:
			// I could save my token here...? l
			r, e := setRR(h, c)
			if e != nil {
				// If e.lex is nil than we have encounter a unknown RR type
				// in that case we substitute our current lex token
				if e.lex.token == "" && e.lex.value == 0 {
					e.lex = l // Uh, dirty
				}
				t <- Token{Error: e}
				return
			}
			t <- Token{Rr: r}
			st = _EXPECT_OWNER_DIR
		}
	}
}

func (l lex) String() string {
	switch l.value {
	case _STRING:
		return "S:" + l.token + "$"
	case _BLANK:
		return "_"
	case _NEWLINE:
		return "|\n"
	case _RRTYPE:
		return "R:" + l.token + "$"
	case _OWNER:
		return "O:" + l.token + "$"
	case _CLASS:
		return "C:" + l.token + "$"
	case _DIRTTL:
		return "T:" + l.token + "$"
	}
	return ""
}

// zlexer scans the sourcefile and returns tokens on the channel c.
func zlexer(r io.Reader, c chan lex) {
	var l lex
	defer close(c)
	quote := false
	escape := false
	space := false
	commt := false
	rrtype := false
	owner := true
	brace := 0
	p, q := 0, 0
	buf := make([]byte, 4096)
	n, err := r.Read(buf)
	for err != io.EOF {
		l.column = 0
		l.line = 0
		switch buf[q] {
		case ' ', '\t':
			escape = false
			if commt {
				p++
				break
			}
			if p == q {
				//l.value = _BLANK
				//l.token = " "
			} else if owner {
				// If we have a string and its the first, make it an owner
				l.value = _OWNER
				println(p, q)
				l.token = string(buf[p:q])
				// escape $... start with a \ not a $, so this will work
				if l.token == "$TTL" {
					l.value = _DIRTTL
				}
				if l.token == "$ORIGIN" {
					l.value = _DIRORIGIN
				}
				c <- l
				p = q + 1
			} else {
				l.value = _STRING
				l.token = string(buf[p:q])

				if !rrtype {
					if _, ok := Str_rr[strings.ToUpper(l.token)]; ok {
						l.value = _RRTYPE
						rrtype = true
					}
					if _, ok := Str_class[strings.ToUpper(l.token)]; ok {
						l.value = _CLASS
					}
				}
				c <- l
				p = q + 1
			}
			if !space && !commt {
				l.value = _BLANK
				l.token = " "
				c <- l
				p = q + 1
			}
			if space || commt {
				p++
			}
			owner = false
			space = true
			println("upping", p, q)
		case ';':
			if escape {
				escape = false
				break
			}
			if quote {
				// Inside quoted text we allow ;
				break
			}
			p++
			commt = true
		case '\n':
			// Hmmm, escape newline
			escape = false
			if commt {
				// Reset a comment
				commt = false
				rrtype = false
				p++
				// If not in a brace this ends the comment AND the RR
				if brace == 0 {
					owner = true
					l.value = _NEWLINE
					l.token = "\n"
					c <- l
					p = q + 1
				}
				break
			}
			if p != q {
				l.value = _STRING
				l.token = string(buf[p:q])
				if !rrtype {
					if _, ok := Str_rr[strings.ToUpper(l.token)]; ok {
						l.value = _RRTYPE
						rrtype = true
					}
				}
				c <- l
				p = q + 1
			}
			if brace > 0 {
				l.value = _BLANK
				p++
				if !space {
					c <- l
					p = q + 1
				}
			} else {
				l.value = _NEWLINE
				l.token = "\n"
				c <- l
				p = q + 1
			}
			if l.value == _BLANK {
				space = true
			}

			p = q + 1
			println("hallo")
			commt = false
			rrtype = false
			owner = true
		case '\\':
			if commt {
				p++
				break
			}
			if escape {
				escape = false
				break
			}
			escape = true
		case '"':
			if commt {
				p++
				break
			}
			if escape {
				escape = false
				break
			}
			quote = !quote
		case '(':
			if commt {
				p++
				break
			}
			if escape {
				escape = false
				break
			}
			brace++
		case ')':
			if commt {
				p++
				break
			}
			if escape {
				escape = false
				break
			}
			brace--
			if brace < 0 {
				l.err = "Extra closing brace"
				c <- l
				p = q + 1
				return
			}
		default:
			if commt {
				p++
				break
			}
			escape = false
			space = false
		}
		// tok, err = r.ReadByte() read extra bytes
		q++
		if q > n {
			break
		}
	}
	println("We crashen gewoon hier", p, q)
        // It this need anymore???
        /*
	if p != q {
		// Send remainder
		l.token = string(buf[p:q])
		l.value = _STRING
		c <- l
	}
        */
}

func stringToTtl(l lex, t chan Token) (uint32, bool) {
	if ttl, ok := strconv.Atoi(l.token); ok != nil {
		t <- Token{Error: &ParseError{"Not a TTL", l}}
		return 0, false
	} else {
		return uint32(ttl), true
	}
	panic("not reached")
}
<|endoftext|>


<commit_before>package auth

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

	"sync"

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

const (
	sessionTokenName = "kapow"
	claimExpires     = "exp"
	claimNotBefore   = "nbf"
	claimCSRFToken   = "csrf"
	claimUserID      = "uid"
	claimGroups      = "grp"
)

// Context is a holder for the currently authenticated user's information
type Context interface {
	User() string
	Groups() []string
}

// Authenticator is a pluggable interface for authentication providers
type Authenticator interface {
	// Name returns the url-safe unique name of this authenticator
	Name() string
	// Description returns the user-friendly description of this authenticator
	Description() string
	// Type returns the type of this authenticator
	Type() string
	// GetHandlers returns the handlers for this authenticator; the set of
	// handlers must include a "login" handler which will be triggered
	// GetHandlers() map[string]http.HandlerFunc
	// LoginURL returns the initial login URL for this handler
	Authenticate(w http.ResponseWriter, r *http.Request) (*SessionToken, error)

	LoginURL() string
	// IconURL returns an icon URL to signify this login method; empty string implies a default can be used
	IconURL() string
	// PostWithCredentials returns true if this authenticator expects username/password credentials be POST'd
	PostWithCredentials() bool
}

// Delegate is a function which requires user and group information along with
// the standard http request/response parameters
type Delegate func(w http.ResponseWriter, r *http.Request, authentication Context)

// NewAuthDelegate returns a new http.Handler func which delegates to the
// provided AuthDelegate, passing the resolved user,group information form
// the session where found, otherwise returning 401 Unauthorized.
func (m *Manager) NewAuthDelegate(delegate Delegate) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := m.ParseSessionToken(r)
		if session == nil {
			if err != nil {
				log.Warnf("Authentication error: %v", err)
			} else {
				log.Infof("No existing/valid session")
			}
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		m.keepSessionAlive(session, w)
		delegate(w, r, session)
	}
}

// Manager manages supported authentication mechanisms
type Manager struct {
	authenticators       map[string]Authenticator
	loginMethodsResponse []byte
	mutex                sync.Mutex
	hmac                 []byte
}

// NewAuthManager creates a new authentication manager instance
func NewAuthManager() (*Manager, error) {

	hmc, _ := uuid.NewV4()
	m := &Manager{
		authenticators: make(map[string]Authenticator),
		hmac:           []byte(hmc.String()),
	}

	m.loginMethodsResponse, _ = m.buildLoginMethodsResponse()
	http.HandleFunc("/auth/login_methods", m.listLoginMethods)
	http.HandleFunc("/auth/user_info", m.userInfo)

	return m, nil
}

func (m *Manager) buildLoginMethodsResponse() ([]byte, error) {
	loginMethods := []map[string]interface{}{}
	for _, authN := range m.authenticators {
		loginMethods = append(loginMethods, map[string]interface{}{
			"name":       authN.Name(),
			"desc":       authN.Description(),
			"type":       authN.Type(),
			"url":        authN.LoginURL(),
			"icon":       authN.IconURL(),
			"post_creds": authN.PostWithCredentials(),
		})
	}
	resp := map[string]interface{}{
		"login_methods": loginMethods,
	}
	return json.Marshal(&resp)
}

// RegisterAuthenticator registers an authentication provider
func (m *Manager) RegisterAuthenticator(authn Authenticator) error {
	m.mutex.Lock()
	if _, ok := m.authenticators[authn.Name()]; ok {
		return fmt.Errorf("An authenticator already exists with name '%s'", authn.Name())
	}
	m.authenticators[authn.Name()] = authn

	var handlerFunc http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
		sessionToken, err := authn.Authenticate(w, r)
		if err != nil {
			log.Error(err)
		} else if sessionToken != nil {
			m.completeAuthentication(sessionToken, w, r)
		}
	}
	http.HandleFunc(authn.LoginURL(), handlerFunc)

	var err error
	m.loginMethodsResponse, err = m.buildLoginMethodsResponse()
	if err != nil {
		return fmt.Errorf("Problem marshalling cached authenticators response: %v", err)
	}
	log.Infof("Enabled %s authenticator: %s => %s", authn.Type(), authn.Name(), authn.LoginURL())
	m.mutex.Unlock()
	return nil
}

func (m *Manager) listLoginMethods(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(m.loginMethodsResponse)))
	w.Write(m.loginMethodsResponse)
}

type userInfo struct {
	User string `json:"user,omitempty"`
}

func (m *Manager) userInfo(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
	}

	session, _ := m.ParseSessionToken(r)
	m.keepSessionAlive(session, w)
	m.respondWithUserInfo(session, w)
}

func (m *Manager) respondWithUserInfo(session *SessionToken, w http.ResponseWriter) {
	var resp userInfo
	if session != nil {
		resp.User = session.User()
	}
	data, err := json.Marshal(resp)
	if err != nil {
		http.Error(w, "Failed marshalling user response", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
	w.Write(data)
}

func (m *Manager) keepSessionAlive(session *SessionToken, w http.ResponseWriter) {
	if session != nil && session.Valid && int(session.claims[claimExpires].(float64)) < 120 {
		session = NewSessionToken(session.User(), []string{}, session.claims)
		m.writeSessionCookie(session, w)
	}
}

func (m *Manager) writeSessionCookie(session *SessionToken, w http.ResponseWriter) {
	signedJWT, err := session.SignedString(m.hmac)
	if err != nil {
		http.Error(w, "Failed to sign JWT: "+err.Error(), http.StatusInternalServerError)
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:     sessionTokenName,
		Value:    signedJWT,
		HttpOnly: true,
		Path:     "/",
	})
}

// TODO: this should be re-worked so that it wraps authenticators' authenticate methods
func (m *Manager) completeAuthentication(session *SessionToken, w http.ResponseWriter, r *http.Request) {

	m.writeSessionCookie(session, w)

	targetURL := r.URL.Query().Get("target")
	if len(targetURL) > 0 {
		http.Redirect(w, r, targetURL, http.StatusFound)
	} else {
		m.respondWithUserInfo(session, w)
	}
}

// SessionToken is a wrapper around JWT with methods for easy user/group access
type SessionToken struct {
	*jwt.Token
	claims jwt.MapClaims
}

// User recovers the userID from a session token
func (s *SessionToken) User() string {
	return s.claims[claimUserID].(string)
}

// Groups recovers the groups from a session token
func (s *SessionToken) Groups() []string {
	return strings.Split(s.claims[claimGroups].(string), ",")
}

// NewSessionToken generates a new auth token suitable for storing user session state
func NewSessionToken(user string, groups []string, additionalClaims map[string]interface{}) *SessionToken {
	csrfToken, _ := uuid.NewV4()
	claims := jwt.MapClaims{
		claimNotBefore: time.Now().Unix(),
		claimExpires:   time.Now().Add(time.Minute * 15).Unix(),
		claimCSRFToken: csrfToken.String(),
		claimUserID:    user,
		claimGroups:    strings.Join(groups, ","),
	}
	if additionalClaims != nil {
		for k, v := range additionalClaims {
			claims[k] = v
		}
	}
	return &SessionToken{Token: jwt.NewWithClaims(jwt.SigningMethodHS256, claims), claims: claims}
}

// ParseSessionToken recovers the session
func (m *Manager) ParseSessionToken(r *http.Request) (*SessionToken, error) {

	cookie, err := r.Cookie(sessionTokenName)
	if cookie == nil || err != nil {
		return nil, nil
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Found cookie for %s; %s: %s", r.URL, sessionTokenName, cookie.Value)
	}

	token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return m.hmac, nil
	})

	if err != nil {
		return nil, err
	} else if claims, ok := token.Claims.(jwt.MapClaims); ok {
		st := &SessionToken{Token: token, claims: claims}
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resolved session token %v", st)
		}
		return st, nil
	}
	return nil, fmt.Errorf("Failed to parse token claims")
}
<commit_msg>added session_expires to userInfo; fixed automatic session renewal on active session<commit_after>package auth

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

	"sync"

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

const (
	sessionTokenName = "kapow"
	claimExpires     = "exp"
	claimNotBefore   = "nbf"
	claimCSRFToken   = "csrf"
	claimUserID      = "uid"
	claimGroups      = "grp"
)

// Context is a holder for the currently authenticated user's information
type Context interface {
	User() string
	Groups() []string
}

// Authenticator is a pluggable interface for authentication providers
type Authenticator interface {
	// Name returns the url-safe unique name of this authenticator
	Name() string
	// Description returns the user-friendly description of this authenticator
	Description() string
	// Type returns the type of this authenticator
	Type() string
	// GetHandlers returns the handlers for this authenticator; the set of
	// handlers must include a "login" handler which will be triggered
	// GetHandlers() map[string]http.HandlerFunc
	// LoginURL returns the initial login URL for this handler
	Authenticate(w http.ResponseWriter, r *http.Request) (*SessionToken, error)

	LoginURL() string
	// IconURL returns an icon URL to signify this login method; empty string implies a default can be used
	IconURL() string
	// PostWithCredentials returns true if this authenticator expects username/password credentials be POST'd
	PostWithCredentials() bool
}

// Delegate is a function which requires user and group information along with
// the standard http request/response parameters
type Delegate func(w http.ResponseWriter, r *http.Request, authentication Context)

// NewAuthDelegate returns a new http.Handler func which delegates to the
// provided AuthDelegate, passing the resolved user,group information form
// the session where found, otherwise returning 401 Unauthorized.
func (m *Manager) NewAuthDelegate(delegate Delegate) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		session, err := m.ParseSessionToken(r)
		if session == nil {
			if err != nil {
				log.Warnf("Authentication error: %v", err)
			} else {
				log.Infof("No existing/valid session")
			}
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		m.keepSessionAlive(session, w)
		delegate(w, r, session)
	}
}

// Manager manages supported authentication mechanisms
type Manager struct {
	authenticators       map[string]Authenticator
	loginMethodsResponse []byte
	mutex                sync.Mutex
	hmac                 []byte
}

// NewAuthManager creates a new authentication manager instance
func NewAuthManager() (*Manager, error) {

	hmc, _ := uuid.NewV4()
	m := &Manager{
		authenticators: make(map[string]Authenticator),
		hmac:           []byte(hmc.String()),
	}

	m.loginMethodsResponse, _ = m.buildLoginMethodsResponse()
	http.HandleFunc("/auth/login_methods", m.listLoginMethods)
	http.HandleFunc("/auth/user_info", m.userInfo)

	return m, nil
}

func (m *Manager) buildLoginMethodsResponse() ([]byte, error) {
	loginMethods := []map[string]interface{}{}
	for _, authN := range m.authenticators {
		loginMethods = append(loginMethods, map[string]interface{}{
			"name":       authN.Name(),
			"desc":       authN.Description(),
			"type":       authN.Type(),
			"url":        authN.LoginURL(),
			"icon":       authN.IconURL(),
			"post_creds": authN.PostWithCredentials(),
		})
	}
	resp := map[string]interface{}{
		"login_methods": loginMethods,
	}
	return json.Marshal(&resp)
}

// RegisterAuthenticator registers an authentication provider
func (m *Manager) RegisterAuthenticator(authn Authenticator) error {
	m.mutex.Lock()
	if _, ok := m.authenticators[authn.Name()]; ok {
		return fmt.Errorf("An authenticator already exists with name '%s'", authn.Name())
	}
	m.authenticators[authn.Name()] = authn

	var handlerFunc http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
		sessionToken, err := authn.Authenticate(w, r)
		if err != nil {
			log.Error(err)
		} else if sessionToken != nil {
			m.completeAuthentication(sessionToken, w, r)
		}
	}
	http.HandleFunc(authn.LoginURL(), handlerFunc)

	var err error
	m.loginMethodsResponse, err = m.buildLoginMethodsResponse()
	if err != nil {
		return fmt.Errorf("Problem marshalling cached authenticators response: %v", err)
	}
	log.Infof("Enabled %s authenticator: %s => %s", authn.Type(), authn.Name(), authn.LoginURL())
	m.mutex.Unlock()
	return nil
}

func (m *Manager) listLoginMethods(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
	}

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(m.loginMethodsResponse)))
	w.Write(m.loginMethodsResponse)
}

type userInfo struct {
	User           string `json:"user,omitempty"`
	SessionExpires string `json:"session_expires,omitempty"`
}

func (m *Manager) userInfo(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
	}

	session, _ := m.ParseSessionToken(r)
	m.keepSessionAlive(session, w)
	m.respondWithUserInfo(session, w)
}

func (m *Manager) respondWithUserInfo(session *SessionToken, w http.ResponseWriter) {
	var resp userInfo
	if session != nil {
		resp.User = session.User()
		resp.SessionExpires = time.Unix(session.Expires(), 0).Format(time.RFC3339)
	}
	data, err := json.Marshal(resp)
	if err != nil {
		http.Error(w, "Failed marshalling user response", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
	w.Write(data)
}

func (m *Manager) keepSessionAlive(session *SessionToken, w http.ResponseWriter) {
	// If the session expires in less than 1 minute, renew it
	if session != nil && session.Valid && session.Expires() < (time.Now().Unix()-int64(time.Minute.Seconds())) {
		session = NewSessionToken(session.User(), []string{}, session.claims)
		m.writeSessionCookie(session, w)
	}
}

func (m *Manager) writeSessionCookie(session *SessionToken, w http.ResponseWriter) {
	signedJWT, err := session.SignedString(m.hmac)
	if err != nil {
		http.Error(w, "Failed to sign JWT: "+err.Error(), http.StatusInternalServerError)
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:     sessionTokenName,
		Value:    signedJWT,
		HttpOnly: true,
		Path:     "/",
	})
}

// TODO: this should be re-worked so that it wraps authenticators' authenticate methods
func (m *Manager) completeAuthentication(session *SessionToken, w http.ResponseWriter, r *http.Request) {

	m.writeSessionCookie(session, w)

	targetURL := r.URL.Query().Get("target")
	if len(targetURL) > 0 {
		http.Redirect(w, r, targetURL, http.StatusFound)
	} else {
		m.respondWithUserInfo(session, w)
	}
}

// SessionToken is a wrapper around JWT with methods for easy user/group access
type SessionToken struct {
	*jwt.Token
	claims jwt.MapClaims
}

// User recovers the userID from a session token
func (s *SessionToken) User() string {
	return s.claims[claimUserID].(string)
}

// Expires returns the expiration of the token in Unix time, the number of seconds elapsed
// since January 1, 1970 UTC.
func (s *SessionToken) Expires() int64 {
	expires := s.claims[claimExpires]
	if intVal, ok := expires.(int64); ok {
		return intVal
	}
	return int64(expires.(float64))
}

// Groups recovers the groups from a session token
func (s *SessionToken) Groups() []string {
	return strings.Split(s.claims[claimGroups].(string), ",")
}

// NewSessionToken generates a new auth token suitable for storing user session state
func NewSessionToken(user string, groups []string, additionalClaims map[string]interface{}) *SessionToken {
	csrfToken, _ := uuid.NewV4()
	claims := jwt.MapClaims{
		claimNotBefore: time.Now().Unix(),
		claimExpires:   time.Now().Add(time.Minute * 15).Unix(),
		claimCSRFToken: csrfToken.String(),
		claimUserID:    user,
		claimGroups:    strings.Join(groups, ","),
	}
	if additionalClaims != nil {
		for k, v := range additionalClaims {
			claims[k] = v
		}
	}
	return &SessionToken{Token: jwt.NewWithClaims(jwt.SigningMethodHS256, claims), claims: claims}
}

// ParseSessionToken recovers the session
func (m *Manager) ParseSessionToken(r *http.Request) (*SessionToken, error) {

	cookie, err := r.Cookie(sessionTokenName)
	if cookie == nil || err != nil {
		return nil, nil
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Found cookie for %s; %s: %s", r.URL, sessionTokenName, cookie.Value)
	}

	token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return m.hmac, nil
	})

	if err != nil {
		return nil, err
	} else if claims, ok := token.Claims.(jwt.MapClaims); ok {
		st := &SessionToken{Token: token, claims: claims}
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Resolved session token %v", st)
		}
		return st, nil
	}
	return nil, fmt.Errorf("Failed to parse token claims")
}
<|endoftext|>


<commit_before>package cmds

import (
	"fmt"
	"io"
	"strings"

	"github.com/appscode/go/types"
	"github.com/k8sdb/apimachinery/pkg/docker"
	"github.com/k8sdb/cli/pkg/kube"
	"github.com/k8sdb/cli/pkg/util"
	"github.com/spf13/cobra"
	kerr "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

var (
	init_long = templates.LongDesc(`
		Install or upgrade unified operator for kubedb databases.`)

	init_example = templates.Examples(`
		# Install latest released operator.
		kubedb init

		# Upgrade operator to use another version.
		kubedb init --version=0.2.0 --upgrade`)
)

func NewCmdInit(out io.Writer, errOut io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:     "init",
		Short:   "Create or upgrade unified operator",
		Long:    init_long,
		Example: init_example,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(RunInit(cmd, out, errOut))
		},
	}

	util.AddInitFlags(cmd)
	return cmd
}

func RunInit(cmd *cobra.Command, out, errOut io.Writer) error {
	upgrade := cmdutil.GetFlagBool(cmd, "upgrade")
	namespace := cmdutil.GetFlagString(cmd, "operator-namespace")
	serviceAccount := cmdutil.GetFlagString(cmd, "operator-service-account")
	version := cmdutil.GetFlagString(cmd, "version")

	client, err := kube.NewKubeClient(cmd)
	if err != nil {
		return err
	}

	if upgrade {
		deployment, err := getOperatorDeployment(client, namespace)
		if err != nil {
			if kerr.IsNotFound(err) {
				message := fmt.Sprintf("Operator deployment \"%v\" not found.\n\n"+
					"Create operator using following commnad:\n"+
					"kubedb init --version=%v --operator-namespace=%v", docker.OperatorName, version, namespace)

				fmt.Fprintln(errOut, message)
				return nil
			}

			return err
		}

		containers := deployment.Spec.Template.Spec.Containers
		if len(containers) == 0 {
			fmt.Fprintln(errOut, fmt.Sprintf(`Invalid operator deployment "%v"`, docker.OperatorName))
			return nil
		}

		items := strings.Split(containers[0].Image, ":")

		image := items[0]
		tag := items[1]

		if image != docker.ImageOperator {
			fmt.Fprintln(errOut, fmt.Sprintf(`Operator image mismatch. Can't upgrade to version "%v"`, version))
			return nil
		}

		if tag == version {
			fmt.Fprintln(out, "Operator deployment is already using this version.")
			return nil
		}

		if err := docker.CheckDockerImageVersion(docker.ImageOperator, version); err != nil {
			fmt.Fprintln(errOut, fmt.Sprintf(`Operator image %v:%v not found.`, docker.ImageOperator, version))
			return nil
		}

		deployment.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("%v:%v", docker.ImageOperator, version)

		if err := updateOperatorDeployment(client, deployment); err != nil {
			return err
		}

		fmt.Fprintln(out, "Successfully upgraded operator deployment.")
	} else {
		if err := docker.CheckDockerImageVersion(docker.ImageOperator, version); err != nil {
			fmt.Fprintln(errOut, fmt.Sprintf(`Operator image %v:%v not found.`, docker.ImageOperator, version))
			return nil
		}

		if err := createOperatorDeployment(client, namespace, serviceAccount, version); err != nil {
			if kerr.IsAlreadyExists(err) {
				fmt.Fprintln(errOut, "Operator deployment already exists.")
			} else {
				return err
			}
		} else {
			fmt.Fprintln(out, "Successfully created operator deployment.")
		}

		if err := createOperatorService(client, namespace); err != nil {
			if kerr.IsAlreadyExists(err) {
				fmt.Fprintln(errOut, "Operator service already exists.")
			} else {
				return err
			}
		} else {
			fmt.Fprintln(out, "Successfully created operator service.")
		}
	}

	return nil
}

func getOperatorDeployment(client kubernetes.Interface, namespace string) (*extensions.Deployment, error) {
	return client.ExtensionsV1beta1().Deployments(namespace).Get(docker.OperatorName, metav1.GetOptions{})
}

var operatorLabel = map[string]string{
	"app": docker.OperatorName,
}

func createOperatorDeployment(client kubernetes.Interface, namespace, serviceAccount, version string) error {
	deployment := &extensions.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      docker.OperatorName,
			Namespace: namespace,
		},
		Spec: extensions.DeploymentSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: operatorLabel,
			},
			Replicas: types.Int32P(1),
			Template: apiv1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: operatorLabel,
				},
				Spec: apiv1.PodSpec{
					Containers: []apiv1.Container{
						{
							Name:  docker.OperatorContainer,
							Image: fmt.Sprintf("%v:%v", docker.ImageOperator, version),
							Args: []string{
								"run",
								fmt.Sprintf("--address=:%v", docker.OperatorPortNumber),
								"--v=3",
							},
							Env: []apiv1.EnvVar{
								{
									Name: "OPERATOR_NAMESPACE",
									ValueFrom: &apiv1.EnvVarSource{
										FieldRef: &apiv1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.namespace",
										},
									},
								},
								{
									Name: "OPERATOR_SERVICE_ACCOUNT",
									ValueFrom: &apiv1.EnvVarSource{
										FieldRef: &apiv1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "spec.serviceAccountName",
										},
									},
								},
							},
							Ports: []apiv1.ContainerPort{
								{
									Name:          docker.OperatorPortName,
									Protocol:      apiv1.ProtocolTCP,
									ContainerPort: docker.OperatorPortNumber,
								},
							},
						},
					},
					ServiceAccountName: serviceAccount,
				},
			},
		},
	}

	_, err := client.ExtensionsV1beta1().Deployments(namespace).Create(deployment)
	return err
}

func createOperatorService(client kubernetes.Interface, namespace string) error {
	svc := &apiv1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      docker.OperatorName,
			Namespace: namespace,
		},
		Spec: apiv1.ServiceSpec{
			Type: apiv1.ServiceTypeClusterIP,
			Ports: []apiv1.ServicePort{
				{
					Name:       docker.OperatorPortName,
					Port:       docker.OperatorPortNumber,
					Protocol:   apiv1.ProtocolTCP,
					TargetPort: intstr.FromString(docker.OperatorPortName),
				},
			},
			Selector: operatorLabel,
		},
	}

	_, err := client.CoreV1().Services(namespace).Create(svc)
	return err
}

func updateOperatorDeployment(client kubernetes.Interface, deployment *extensions.Deployment) error {
	_, err := client.ExtensionsV1beta1().Deployments(deployment.Namespace).Update(deployment)
	return err
}
<commit_msg>Change KubeDB labels (#78)<commit_after>package cmds

import (
	"fmt"
	"io"
	"strings"

	"github.com/appscode/go/types"
	"github.com/k8sdb/apimachinery/pkg/docker"
	"github.com/k8sdb/cli/pkg/kube"
	"github.com/k8sdb/cli/pkg/util"
	"github.com/spf13/cobra"
	kerr "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

var (
	init_long = templates.LongDesc(`
		Install or upgrade unified operator for kubedb databases.`)

	init_example = templates.Examples(`
		# Install latest released operator.
		kubedb init

		# Upgrade operator to use another version.
		kubedb init --version=0.2.0 --upgrade`)
)

func NewCmdInit(out io.Writer, errOut io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:     "init",
		Short:   "Create or upgrade unified operator",
		Long:    init_long,
		Example: init_example,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(RunInit(cmd, out, errOut))
		},
	}

	util.AddInitFlags(cmd)
	return cmd
}

func RunInit(cmd *cobra.Command, out, errOut io.Writer) error {
	upgrade := cmdutil.GetFlagBool(cmd, "upgrade")
	namespace := cmdutil.GetFlagString(cmd, "operator-namespace")
	serviceAccount := cmdutil.GetFlagString(cmd, "operator-service-account")
	version := cmdutil.GetFlagString(cmd, "version")

	client, err := kube.NewKubeClient(cmd)
	if err != nil {
		return err
	}

	if upgrade {
		deployment, err := getOperatorDeployment(client, namespace)
		if err != nil {
			if kerr.IsNotFound(err) {
				message := fmt.Sprintf("Operator deployment \"%v\" not found.\n\n"+
					"Create operator using following commnad:\n"+
					"kubedb init --version=%v --operator-namespace=%v", docker.OperatorName, version, namespace)

				fmt.Fprintln(errOut, message)
				return nil
			}

			return err
		}

		containers := deployment.Spec.Template.Spec.Containers
		if len(containers) == 0 {
			fmt.Fprintln(errOut, fmt.Sprintf(`Invalid operator deployment "%v"`, docker.OperatorName))
			return nil
		}

		items := strings.Split(containers[0].Image, ":")

		image := items[0]
		tag := items[1]

		if image != docker.ImageOperator {
			fmt.Fprintln(errOut, fmt.Sprintf(`Operator image mismatch. Can't upgrade to version "%v"`, version))
			return nil
		}

		if tag == version {
			fmt.Fprintln(out, "Operator deployment is already using this version.")
			return nil
		}

		if err := docker.CheckDockerImageVersion(docker.ImageOperator, version); err != nil {
			fmt.Fprintln(errOut, fmt.Sprintf(`Operator image %v:%v not found.`, docker.ImageOperator, version))
			return nil
		}

		deployment.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("%v:%v", docker.ImageOperator, version)

		if err := updateOperatorDeployment(client, deployment); err != nil {
			return err
		}

		fmt.Fprintln(out, "Successfully upgraded operator deployment.")
	} else {
		if err := docker.CheckDockerImageVersion(docker.ImageOperator, version); err != nil {
			fmt.Fprintln(errOut, fmt.Sprintf(`Operator image %v:%v not found.`, docker.ImageOperator, version))
			return nil
		}

		if err := createOperatorDeployment(client, namespace, serviceAccount, version); err != nil {
			if kerr.IsAlreadyExists(err) {
				fmt.Fprintln(errOut, "Operator deployment already exists.")
			} else {
				return err
			}
		} else {
			fmt.Fprintln(out, "Successfully created operator deployment.")
		}

		if err := createOperatorService(client, namespace); err != nil {
			if kerr.IsAlreadyExists(err) {
				fmt.Fprintln(errOut, "Operator service already exists.")
			} else {
				return err
			}
		} else {
			fmt.Fprintln(out, "Successfully created operator service.")
		}
	}

	return nil
}

func getOperatorDeployment(client kubernetes.Interface, namespace string) (*extensions.Deployment, error) {
	return client.ExtensionsV1beta1().Deployments(namespace).Get(docker.OperatorName, metav1.GetOptions{})
}

var operatorLabel = map[string]string{
	"app": "kubedb",
}

func createOperatorDeployment(client kubernetes.Interface, namespace, serviceAccount, version string) error {
	deployment := &extensions.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      docker.OperatorName,
			Namespace: namespace,
			Labels: operatorLabel,
		},
		Spec: extensions.DeploymentSpec{
			Replicas: types.Int32P(1),
			Template: apiv1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: operatorLabel,
				},
				Spec: apiv1.PodSpec{
					Containers: []apiv1.Container{
						{
							Name:  docker.OperatorContainer,
							Image: fmt.Sprintf("%v:%v", docker.ImageOperator, version),
							Args: []string{
								"run",
								fmt.Sprintf("--address=:%v", docker.OperatorPortNumber),
								"--v=3",
							},
							Env: []apiv1.EnvVar{
								{
									Name: "OPERATOR_NAMESPACE",
									ValueFrom: &apiv1.EnvVarSource{
										FieldRef: &apiv1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.namespace",
										},
									},
								},
								{
									Name: "OPERATOR_SERVICE_ACCOUNT",
									ValueFrom: &apiv1.EnvVarSource{
										FieldRef: &apiv1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "spec.serviceAccountName",
										},
									},
								},
							},
							Ports: []apiv1.ContainerPort{
								{
									Name:          docker.OperatorPortName,
									Protocol:      apiv1.ProtocolTCP,
									ContainerPort: docker.OperatorPortNumber,
								},
							},
						},
					},
					ServiceAccountName: serviceAccount,
				},
			},
		},
	}

	_, err := client.ExtensionsV1beta1().Deployments(namespace).Create(deployment)
	return err
}

func createOperatorService(client kubernetes.Interface, namespace string) error {
	svc := &apiv1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      docker.OperatorName,
			Namespace: namespace,
			Labels: operatorLabel,
		},
		Spec: apiv1.ServiceSpec{
			Type: apiv1.ServiceTypeClusterIP,
			Ports: []apiv1.ServicePort{
				{
					Name:       docker.OperatorPortName,
					Port:       docker.OperatorPortNumber,
					Protocol:   apiv1.ProtocolTCP,
					TargetPort: intstr.FromString(docker.OperatorPortName),
				},
			},
			Selector: operatorLabel,
		},
	}

	_, err := client.CoreV1().Services(namespace).Create(svc)
	return err
}

func updateOperatorDeployment(client kubernetes.Interface, deployment *extensions.Deployment) error {
	_, err := client.ExtensionsV1beta1().Deployments(deployment.Namespace).Update(deployment)
	return err
}
<|endoftext|>


<commit_before>/*
Copyright The Helm 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 kube // import "k8s.io/helm/pkg/kube"

import (
	"time"

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

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

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

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

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

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

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

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

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

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

func isPodReady(pod *v1.Pod) bool {
	if &pod.Status != nil && len(pod.Status.Conditions) > 0 {
		for _, condition := range pod.Status.Conditions {
			if condition.Type == v1.PodReady &&
				condition.Status == v1.ConditionTrue {
				return true
			}
		}
	}
	return false
}
<commit_msg>Wait for ingress host to be ready<commit_after>/*
Copyright The Helm 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 kube // import "k8s.io/helm/pkg/kube"

import (
	"time"

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

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

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

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

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

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

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

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

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

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

func isPodReady(pod *v1.Pod) bool {
	if &pod.Status != nil && len(pod.Status.Conditions) > 0 {
		for _, condition := range pod.Status.Conditions {
			if condition.Type == v1.PodReady &&
				condition.Status == v1.ConditionTrue {
				return true
			}
		}
	}
	return false
}

func (c *Client) ingressesReady(ingresses []extensions.Ingress) bool {
	for _, ingress := range ingresses {
		if &ingress.Status == nil && len(ingress.Status.LoadBalancer.Ingress) > 0 {
			c.Log("Ingress is not ready: %s/%s", ingress.GetNamespace(), ingress.GetName())
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>//go:build linux
// +build linux

/*
   Copyright The containerd 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 v2

import (
	"context"
	"fmt"

	cgroupsv2 "github.com/containerd/cgroups/v2"
	eventstypes "github.com/containerd/containerd/api/events"
	"github.com/containerd/containerd/pkg/oom"
	"github.com/containerd/containerd/runtime"
	"github.com/containerd/containerd/runtime/v2/shim"
	"github.com/sirupsen/logrus"
)

// New returns an implementation that listens to OOM events
// from a container's cgroups.
func New(publisher shim.Publisher) (oom.Watcher, error) {
	return &watcher{
		itemCh:    make(chan item),
		publisher: publisher,
	}, nil
}

// watcher implementation for handling OOM events from a container's cgroup
type watcher struct {
	itemCh    chan item
	publisher shim.Publisher
}

type item struct {
	id  string
	ev  cgroupsv2.Event
	err error
}

// Close closes the watcher
func (w *watcher) Close() error {
	return nil
}

// Run the loop
func (w *watcher) Run(ctx context.Context) {
	lastOOMMap := make(map[string]uint64) // key: id, value: ev.OOM
	for {
		select {
		case <-ctx.Done():
			w.Close()
			return
		case i := <-w.itemCh:
			if i.err != nil {
				delete(lastOOMMap, i.id)
				continue
			}
			lastOOM := lastOOMMap[i.id]
			if i.ev.OOM > lastOOM {
				if err := w.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &eventstypes.TaskOOM{
					ContainerID: i.id,
				}); err != nil {
					logrus.WithError(err).Error("publish OOM event")
				}
			}
			if i.ev.OOM > 0 {
				lastOOMMap[i.id] = i.ev.OOM
			}
		}
	}
}

// Add cgroups.Cgroup to the epoll monitor
func (w *watcher) Add(id string, cgx interface{}) error {
	cg, ok := cgx.(*cgroupsv2.Manager)
	if !ok {
		return fmt.Errorf("expected *cgroupsv2.Manager, got: %T", cgx)
	}
	// FIXME: cgroupsv2.Manager does not support closing eventCh routine currently.
	// The routine shuts down when an error happens, mostly when the cgroup is deleted.
	eventCh, errCh := cg.EventChan()
	go func() {
		for {
			i := item{id: id}
			select {
			case ev := <-eventCh:
				i.ev = ev
				w.itemCh <- i
			case err := <-errCh:
				i.err = err
				w.itemCh <- i
				// we no longer get any event/err when we got an err
				logrus.WithError(err).Warn("error from *cgroupsv2.Manager.EventChan")
				return
			}
		}
	}()
	return nil
}
<commit_msg>pkg/oom/v2: handle EventChan routine shutdown quietly<commit_after>//go:build linux
// +build linux

/*
   Copyright The containerd 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 v2

import (
	"context"
	"fmt"

	cgroupsv2 "github.com/containerd/cgroups/v2"
	eventstypes "github.com/containerd/containerd/api/events"
	"github.com/containerd/containerd/pkg/oom"
	"github.com/containerd/containerd/runtime"
	"github.com/containerd/containerd/runtime/v2/shim"
	"github.com/sirupsen/logrus"
)

// New returns an implementation that listens to OOM events
// from a container's cgroups.
func New(publisher shim.Publisher) (oom.Watcher, error) {
	return &watcher{
		itemCh:    make(chan item),
		publisher: publisher,
	}, nil
}

// watcher implementation for handling OOM events from a container's cgroup
type watcher struct {
	itemCh    chan item
	publisher shim.Publisher
}

type item struct {
	id  string
	ev  cgroupsv2.Event
	err error
}

// Close closes the watcher
func (w *watcher) Close() error {
	return nil
}

// Run the loop
func (w *watcher) Run(ctx context.Context) {
	lastOOMMap := make(map[string]uint64) // key: id, value: ev.OOM
	for {
		select {
		case <-ctx.Done():
			w.Close()
			return
		case i := <-w.itemCh:
			if i.err != nil {
				delete(lastOOMMap, i.id)
				continue
			}
			lastOOM := lastOOMMap[i.id]
			if i.ev.OOM > lastOOM {
				if err := w.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &eventstypes.TaskOOM{
					ContainerID: i.id,
				}); err != nil {
					logrus.WithError(err).Error("publish OOM event")
				}
			}
			if i.ev.OOM > 0 {
				lastOOMMap[i.id] = i.ev.OOM
			}
		}
	}
}

// Add cgroups.Cgroup to the epoll monitor
func (w *watcher) Add(id string, cgx interface{}) error {
	cg, ok := cgx.(*cgroupsv2.Manager)
	if !ok {
		return fmt.Errorf("expected *cgroupsv2.Manager, got: %T", cgx)
	}
	// FIXME: cgroupsv2.Manager does not support closing eventCh routine currently.
	// The routine shuts down when an error happens, mostly when the cgroup is deleted.
	eventCh, errCh := cg.EventChan()
	go func() {
		for {
			i := item{id: id}
			select {
			case ev := <-eventCh:
				i.ev = ev
				w.itemCh <- i
			case err := <-errCh:
				// channel is closed when cgroup gets deleted
				if err != nil {
					i.err = err
					w.itemCh <- i
					// we no longer get any event/err when we got an err
					logrus.WithError(err).Warn("error from *cgroupsv2.Manager.EventChan")
				}
				return
			}
		}
	}()
	return nil
}
<|endoftext|>


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

// Package site define HTTP handlers.
package site

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

	"github.com/google/triage-party/pkg/provider"

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

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

	"k8s.io/klog/v2"
)

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

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

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

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

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

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

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

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

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

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

	AverageResponseLatency time.Duration
	TotalPullRequests      int
	TotalIssues            int

	ClosedPerDay float64

	Collection  triage.Collection
	Collections []triage.Collection

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

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

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

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

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

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

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

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

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

func parseHexColor(s string) (c color.RGBA, err error) {
	c.A = 0xff

	if s[0] != '#' {
		return c, fmt.Errorf("%q is not a valid hex color", s)
	}

	hexToByte := func(b byte) byte {
		switch {
		case b >= '0' && b <= '9':
			return b - '0'
		case b >= 'a' && b <= 'f':
			return b - 'a' + 10
		case b >= 'A' && b <= 'F':
			return b - 'A' + 10
		}
		err = fmt.Errorf("%q is not a parseable hex color", s)
		return 0
	}

	switch len(s) {
	case 7:
		c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
		c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
		c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
	case 4:
		c.R = hexToByte(s[1]) * 17
		c.G = hexToByte(s[2]) * 17
		c.B = hexToByte(s[3]) * 17
	default:
		err = fmt.Errorf("%q is not a proper hex color", s)
	}
	return
}

// pick an appropriate text color given a background color
func textColor(s string) template.CSS {
	color, err := parseHexColor(fmt.Sprintf("#%s", strings.TrimPrefix(s, "#")))
	if err != nil {
		klog.Errorf("parse hex color failed: %v", err)
		return "f00"
	}

	// human eye is most sensitive to green
	lum := (0.299*float64(color.R) + 0.587*float64(color.G) + 0.114*float64(color.B)) / 255
	if lum > 0.5 {
		return "111"
	}
	return "fff"
}

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

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

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

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

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

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

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

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

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

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

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

	return ds
}

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

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

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

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

		for _, i := range o.Items {
			if (i.ID % players) == (player - 1) {
				cs = append(cs, i)
			}
		}

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

	return triage.SummarizeCollectionResult(result.Collection, os)
}

// Healthz returns a dummy healthz page - it's always happy here!
func (h *Handlers) Healthz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(fmt.Sprintf("ok: %s", h.updater.Status())))
	}
}

// Threadz returns a threadz page
func (h *Handlers) Threadz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		klog.Infof("GET %s: %v", r.URL.Path, r.Header)
		w.WriteHeader(http.StatusOK)
		w.Write(stack())
	}
}

// stack returns a formatted stack trace of all goroutines
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func stack() []byte {
	buf := make([]byte, 1024)
	for {
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			return buf[:n]
		}
		buf = make([]byte, 2*len(buf))
	}
}
<commit_msg>Bump to v1.4.0-beta.2<commit_after>// Copyright 2020 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package site define HTTP handlers.
package site

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

	"github.com/google/triage-party/pkg/provider"

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

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

	"k8s.io/klog/v2"
)

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

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

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

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

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

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

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

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

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

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

	AverageResponseLatency time.Duration
	TotalPullRequests      int
	TotalIssues            int

	ClosedPerDay float64

	Collection  triage.Collection
	Collections []triage.Collection

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

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

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

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

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

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

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

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

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

func parseHexColor(s string) (c color.RGBA, err error) {
	c.A = 0xff

	if s[0] != '#' {
		return c, fmt.Errorf("%q is not a valid hex color", s)
	}

	hexToByte := func(b byte) byte {
		switch {
		case b >= '0' && b <= '9':
			return b - '0'
		case b >= 'a' && b <= 'f':
			return b - 'a' + 10
		case b >= 'A' && b <= 'F':
			return b - 'A' + 10
		}
		err = fmt.Errorf("%q is not a parseable hex color", s)
		return 0
	}

	switch len(s) {
	case 7:
		c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
		c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
		c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
	case 4:
		c.R = hexToByte(s[1]) * 17
		c.G = hexToByte(s[2]) * 17
		c.B = hexToByte(s[3]) * 17
	default:
		err = fmt.Errorf("%q is not a proper hex color", s)
	}
	return
}

// pick an appropriate text color given a background color
func textColor(s string) template.CSS {
	color, err := parseHexColor(fmt.Sprintf("#%s", strings.TrimPrefix(s, "#")))
	if err != nil {
		klog.Errorf("parse hex color failed: %v", err)
		return "f00"
	}

	// human eye is most sensitive to green
	lum := (0.299*float64(color.R) + 0.587*float64(color.G) + 0.114*float64(color.B)) / 255
	if lum > 0.5 {
		return "111"
	}
	return "fff"
}

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

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

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

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

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

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

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

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

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

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

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

	return ds
}

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

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

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

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

		for _, i := range o.Items {
			if (i.ID % players) == (player - 1) {
				cs = append(cs, i)
			}
		}

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

	return triage.SummarizeCollectionResult(result.Collection, os)
}

// Healthz returns a dummy healthz page - it's always happy here!
func (h *Handlers) Healthz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(fmt.Sprintf("ok: %s", h.updater.Status())))
	}
}

// Threadz returns a threadz page
func (h *Handlers) Threadz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		klog.Infof("GET %s: %v", r.URL.Path, r.Header)
		w.WriteHeader(http.StatusOK)
		w.Write(stack())
	}
}

// stack returns a formatted stack trace of all goroutines
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func stack() []byte {
	buf := make([]byte, 1024)
	for {
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			return buf[:n]
		}
		buf = make([]byte, 2*len(buf))
	}
}
<|endoftext|>