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

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"text/template"
)

func (s *Server) serveHTTP(l net.Listener) error {
	httpMux := http.NewServeMux()
	httpMux.HandleFunc("/_/ipxe", s.handleIpxe)
	httpMux.HandleFunc("/_/file", s.handleFile)
	if err := http.Serve(l, httpMux); err != nil {
		return fmt.Errorf("HTTP server shut down: %s", err)
	}
	return nil
}

func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) {
	macStr := r.URL.Query().Get("mac")
	if macStr == "" {
		s.debug("HTTP", "Bad request %q from %s, missing MAC address", r.URL, r.RemoteAddr)
		http.Error(w, "missing MAC address parameter", http.StatusBadRequest)
		return
	}
	archStr := r.URL.Query().Get("arch")
	if archStr == "" {
		s.debug("HTTP", "Bad request %q from %s, missing architecture", r.URL, r.RemoteAddr)
		http.Error(w, "missing architecture parameter", http.StatusBadRequest)
		return
	}

	mac, err := net.ParseMAC(macStr)
	if err != nil {
		s.debug("HTTP", "Bad request %q from %s, invalid MAC address %q (%s)", r.URL, r.RemoteAddr, macStr, err)
		http.Error(w, "invalid MAC address", http.StatusBadRequest)
		return
	}

	i, err := strconv.Atoi(archStr)
	if err != nil {
		s.debug("HTTP", "Bad request %q from %s, invalid architecture %q (%s)", r.URL, r.RemoteAddr, archStr, err)
		http.Error(w, "invalid architecture", http.StatusBadRequest)
		return
	}
	arch := Architecture(i)
	switch arch {
	case ArchIA32, ArchX64:
	default:
		s.debug("HTTP", "Bad request %q from %s, unknown architecture %q", r.URL, r.RemoteAddr, arch)
		http.Error(w, "unknown architecture", http.StatusBadRequest)
		return
	}

	mach := Machine{
		MAC:  mac,
		Arch: arch,
	}
	spec, err := s.Booter.BootSpec(mach)
	if err != nil {
		s.log("HTTP", "Couldn't get a bootspec for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
		http.Error(w, "couldn't get a bootspec", http.StatusInternalServerError)
		return
	}
	if spec == nil {
		// TODO: make ipxe abort netbooting so it can fall through to
		// other boot options - unsure if that's possible.
		s.debug("HTTP", "No boot spec for %s (query %q from %s), ignoring boot request", mac, r.URL, r.RemoteAddr)
		http.Error(w, "you don't netboot", http.StatusNotFound)
		return
	}
	script, err := ipxeScript(spec, r.Host)
	if err != nil {
		s.log("HTTP", "Failed to assemble ipxe script for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
		http.Error(w, "couldn't get a boot script", http.StatusInternalServerError)
		return
	}

	s.log("HTTP", "Sending ipxe boot script to %s", r.RemoteAddr)
	w.Header().Set("Content-Type", "text/plain")
	w.Write(script)
}

func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) {
	name := r.URL.Query().Get("name")
	if name == "" {
		s.debug("HTTP", "Bad request %q from %s, missing filename", r.URL, r.RemoteAddr)
		http.Error(w, "missing filename", http.StatusBadRequest)
	}

	f, sz, err := s.Booter.ReadBootFile(ID(name))
	if err != nil {
		s.log("HTTP", "Error getting file %q (query %q from %s): %s", name, r.URL, r.RemoteAddr, err)
		http.Error(w, "couldn't get file", http.StatusInternalServerError)
		return
	}
	defer f.Close()
	if sz >= 0 {
		w.Header().Set("Content-Length", strconv.FormatInt(sz, 10))
	}
	if _, err = io.Copy(w, f); err != nil {
		s.log("HTTP", "Copy of %q to %s (query %q) failed: %s", name, r.RemoteAddr, r.URL, err)
		return
	}
	s.log("HTTP", "Sent file %q to %s", name, r.RemoteAddr)
}

func ipxeScript(spec *Spec, serverHost string) ([]byte, error) {
	if spec.Kernel == "" {
		return nil, errors.New("spec is missing Kernel")
	}

	urlPrefix := fmt.Sprintf("http://%s/_/file?name=", serverHost)
	var b bytes.Buffer
	b.WriteString("#!ipxe\n")
	fmt.Fprintf(&b, "kernel --name kernel %s%s\n", urlPrefix, url.QueryEscape(string(spec.Kernel)))
	for i, initrd := range spec.Initrd {
		fmt.Fprintf(&b, "initrd --name initrd%d %s%s\n", i, urlPrefix, url.QueryEscape(string(initrd)))
	}
	b.WriteString("boot kernel ")
	for i := range spec.Initrd {
		fmt.Fprintf(&b, "initrd=initrd%d ", i)
	}

	f := func(id string) string {
		return fmt.Sprintf("http://%s/_/file?name=%s", serverHost, url.QueryEscape(id))
	}
	cmdline, err := expandCmdline(spec.Cmdline, template.FuncMap{"ID": f})
	if err != nil {
		return nil, fmt.Errorf("expanding cmdline %q: %s", spec.Cmdline, err)
	}
	b.WriteString(cmdline)

	b.WriteByte('\n')
	return b.Bytes(), nil
}
<commit_msg>pixiecore: Add a warning that no Content-Length will massively slow down booting.<commit_after>// Copyright 2016 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 pixiecore

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"text/template"
)

func (s *Server) serveHTTP(l net.Listener) error {
	httpMux := http.NewServeMux()
	httpMux.HandleFunc("/_/ipxe", s.handleIpxe)
	httpMux.HandleFunc("/_/file", s.handleFile)
	if err := http.Serve(l, httpMux); err != nil {
		return fmt.Errorf("HTTP server shut down: %s", err)
	}
	return nil
}

func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) {
	macStr := r.URL.Query().Get("mac")
	if macStr == "" {
		s.debug("HTTP", "Bad request %q from %s, missing MAC address", r.URL, r.RemoteAddr)
		http.Error(w, "missing MAC address parameter", http.StatusBadRequest)
		return
	}
	archStr := r.URL.Query().Get("arch")
	if archStr == "" {
		s.debug("HTTP", "Bad request %q from %s, missing architecture", r.URL, r.RemoteAddr)
		http.Error(w, "missing architecture parameter", http.StatusBadRequest)
		return
	}

	mac, err := net.ParseMAC(macStr)
	if err != nil {
		s.debug("HTTP", "Bad request %q from %s, invalid MAC address %q (%s)", r.URL, r.RemoteAddr, macStr, err)
		http.Error(w, "invalid MAC address", http.StatusBadRequest)
		return
	}

	i, err := strconv.Atoi(archStr)
	if err != nil {
		s.debug("HTTP", "Bad request %q from %s, invalid architecture %q (%s)", r.URL, r.RemoteAddr, archStr, err)
		http.Error(w, "invalid architecture", http.StatusBadRequest)
		return
	}
	arch := Architecture(i)
	switch arch {
	case ArchIA32, ArchX64:
	default:
		s.debug("HTTP", "Bad request %q from %s, unknown architecture %q", r.URL, r.RemoteAddr, arch)
		http.Error(w, "unknown architecture", http.StatusBadRequest)
		return
	}

	mach := Machine{
		MAC:  mac,
		Arch: arch,
	}
	spec, err := s.Booter.BootSpec(mach)
	if err != nil {
		s.log("HTTP", "Couldn't get a bootspec for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
		http.Error(w, "couldn't get a bootspec", http.StatusInternalServerError)
		return
	}
	if spec == nil {
		// TODO: make ipxe abort netbooting so it can fall through to
		// other boot options - unsure if that's possible.
		s.debug("HTTP", "No boot spec for %s (query %q from %s), ignoring boot request", mac, r.URL, r.RemoteAddr)
		http.Error(w, "you don't netboot", http.StatusNotFound)
		return
	}
	script, err := ipxeScript(spec, r.Host)
	if err != nil {
		s.log("HTTP", "Failed to assemble ipxe script for %s (query %q from %s): %s", mac, r.URL, r.RemoteAddr, err)
		http.Error(w, "couldn't get a boot script", http.StatusInternalServerError)
		return
	}

	s.log("HTTP", "Sending ipxe boot script to %s", r.RemoteAddr)
	w.Header().Set("Content-Type", "text/plain")
	w.Write(script)
}

func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) {
	name := r.URL.Query().Get("name")
	if name == "" {
		s.debug("HTTP", "Bad request %q from %s, missing filename", r.URL, r.RemoteAddr)
		http.Error(w, "missing filename", http.StatusBadRequest)
	}

	f, sz, err := s.Booter.ReadBootFile(ID(name))
	if err != nil {
		s.log("HTTP", "Error getting file %q (query %q from %s): %s", name, r.URL, r.RemoteAddr, err)
		http.Error(w, "couldn't get file", http.StatusInternalServerError)
		return
	}
	defer f.Close()
	if sz >= 0 {
		w.Header().Set("Content-Length", strconv.FormatInt(sz, 10))
	} else {
		s.log("HTTP", "Unknown file size for %q, boot will be VERY slow (can your Booter provide file sizes?")
	}
	if _, err = io.Copy(w, f); err != nil {
		s.log("HTTP", "Copy of %q to %s (query %q) failed: %s", name, r.RemoteAddr, r.URL, err)
		return
	}
	s.log("HTTP", "Sent file %q to %s", name, r.RemoteAddr)
}

func ipxeScript(spec *Spec, serverHost string) ([]byte, error) {
	if spec.Kernel == "" {
		return nil, errors.New("spec is missing Kernel")
	}

	urlPrefix := fmt.Sprintf("http://%s/_/file?name=", serverHost)
	var b bytes.Buffer
	b.WriteString("#!ipxe\n")
	fmt.Fprintf(&b, "kernel --name kernel %s%s\n", urlPrefix, url.QueryEscape(string(spec.Kernel)))
	for i, initrd := range spec.Initrd {
		fmt.Fprintf(&b, "initrd --name initrd%d %s%s\n", i, urlPrefix, url.QueryEscape(string(initrd)))
	}
	b.WriteString("boot kernel ")
	for i := range spec.Initrd {
		fmt.Fprintf(&b, "initrd=initrd%d ", i)
	}

	f := func(id string) string {
		return fmt.Sprintf("http://%s/_/file?name=%s", serverHost, url.QueryEscape(id))
	}
	cmdline, err := expandCmdline(spec.Cmdline, template.FuncMap{"ID": f})
	if err != nil {
		return nil, fmt.Errorf("expanding cmdline %q: %s", spec.Cmdline, err)
	}
	b.WriteString(cmdline)

	b.WriteByte('\n')
	return b.Bytes(), nil
}
<|endoftext|>


<commit_before>package cli

var RunEnv string = "dev"
<commit_msg>[ci skip] Add comment.<commit_after>package cli

var RunEnv string = "dev" // same as default value in cmdline
<|endoftext|>


<commit_before>//   Copyright 2020 MSolution.IO
//
//   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 utils

import (
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"time"
)

// GenerateBulkID generates a unique ID for a Tagging Report
func GenerateBulkID(doc TaggingReportDocument) (string, error) {
	ji, err := json.Marshal(struct {
		Account    string    `json:"account"`
		ReportDate time.Time `json:"reportDate"`
		ID         string    `json:"id"`
	}{
		doc.Account,
		doc.ReportDate,
		doc.ResourceID,
	})
	if err != nil {
		return "", err
	}
	hash := md5.Sum(ji)
	hash64 := base64.URLEncoding.EncodeToString(hash[:])
	return hash64, nil
}
<commit_msg>update-tags: Fixed rare hash collision w.r.t. buckets<commit_after>//   Copyright 2020 MSolution.IO
//
//   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 utils

import (
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"time"
)

// GenerateBulkID generates a unique ID for a Tagging Report
func GenerateBulkID(doc TaggingReportDocument) (string, error) {
	ji, err := json.Marshal(struct {
		Account      string    `json:"account"`
		ReportDate   time.Time `json:"reportDate"`
		ResourceID   string    `json:"resourceID"`
		Region       string    `json:"region"`
		ResourceType string    `json:"resourceType"`
	}{
		doc.Account,
		doc.ReportDate,
		doc.ResourceID,
		doc.Region,
		doc.ResourceType,
	})
	if err != nil {
		return "", err
	}
	hash := md5.Sum(ji)
	hash64 := base64.URLEncoding.EncodeToString(hash[:])
	return hash64, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"io"
	"os"
	"fmt"
	"unicode/utf8"
	"strings"
)

//TODO fsm transitions to be declared here somehow
const (
	NormalChar = iota
	Escape
	EscapeX
	EscapeFirst
	EscapeSecond
)

type AnalyseResult struct {
	charsOfCode       int
	charsOfValue      int
	inputString       string
	totalEncodedChars int
}

type State struct {
	id int
}

type Result struct {
	encodedResult int
	escapedResult int
}

func main() {
	calculateResult()
}

func calculateResult() Result {
	fmt.Println("Advent of code: Golang\nTask 8")

	file, err := os.Open("../input.txt")
	if err != nil {
		fmt.Println("Error opening input file:", err)
		os.Exit(1)
	}
	defer file.Close()

	return handleFile(file)
}

func handleFile(file io.Reader) Result {
	resultChan := make(chan AnalyseResult)

	linesCount := 0
	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		go handleString(scanner.Text(), resultChan)
		linesCount++
	}
	fmt.Println("linesCount:", linesCount)

	totalCharsOfCode := 0
	totalCharsOfValue := 0
	totalEncodedChars := 0
	for i := 0; i < linesCount; i++ {
		result := <-resultChan
		//fmt.Printf("read from results: %+v\n", result)

		totalCharsOfCode += result.charsOfCode
		totalCharsOfValue += result.charsOfValue
		totalEncodedChars += result.totalEncodedChars
	}

	fmt.Println("totalCharsOfCode:", totalCharsOfCode)
	fmt.Println("totalCharsOfValue:", totalCharsOfValue)

	encodedResult := totalCharsOfCode - totalCharsOfValue
	escapedResult := totalEncodedChars - totalCharsOfCode

	fmt.Println("encodedResult:", encodedResult)
	fmt.Println("escapedResult:", escapedResult)

	return Result{encodedResult: encodedResult, escapedResult: escapedResult}
}

func handleString(inputString string, resultChan chan AnalyseResult) {
	state := State{id:NormalChar}

	inputStringLen := utf8.RuneCountInString(inputString)
	charsOfValue := 0

	if inputStringLen == 2 {
		//empty string passed
		resultChan <- AnalyseResult{charsOfCode:2, charsOfValue:0, inputString:inputString, totalEncodedChars:6}
		return
	}

	workingString := inputString[1:inputStringLen - 1] //ignore quotes

	for _, char := range workingString {
		updateState(&state, char)

		if state.id == NormalChar || state.id == EscapeSecond {
			charsOfValue++
		}
	}

	resultChan <- AnalyseResult{charsOfCode:inputStringLen,
		charsOfValue:charsOfValue,
		inputString:inputString,
		totalEncodedChars:encodedCharsCount(inputString)}
}

func encodedCharsCount(inputString string) int {
	newString := strings.Replace(inputString, "\\", "\\\\", -1)
	newString = strings.Replace(newString, "\"", "\\\"", -1)

	return utf8.RuneCountInString(newString) + 2 //outer quotes
}

func updateState(oldState *State, char rune) {
	if oldState.id == NormalChar && char != '\\' {
		return
	}

	if oldState.id == Escape {
		if char == 'x' {
			//char code sequence starting
			oldState.id = EscapeX
		} else {
			//just escaped char
			oldState.id = NormalChar
		}
	} else if oldState.id == EscapeX {
		oldState.id = EscapeFirst
	} else if oldState.id == EscapeFirst {
		oldState.id = EscapeSecond
	} else if oldState.id == EscapeSecond {
		if char == '\\' {
			oldState.id = Escape
		} else {
			oldState.id = NormalChar
		}
	} else if oldState.id == NormalChar {
		if char == '\\' {
			oldState.id = Escape
		}
	}
}
<commit_msg>Raw strings usage<commit_after>package main

import (
	"bufio"
	"io"
	"os"
	"fmt"
	"unicode/utf8"
	"strings"
)

//TODO fsm transitions to be declared here somehow
const (
	NormalChar = iota
	Escape
	EscapeX
	EscapeFirst
	EscapeSecond
)

type AnalyseResult struct {
	charsOfCode       int
	charsOfValue      int
	inputString       string
	totalEncodedChars int
}

type State struct {
	id int
}

type Result struct {
	encodedResult int
	escapedResult int
}

func main() {
	calculateResult()
}

func calculateResult() Result {
	fmt.Println("Advent of code: Golang\nTask 8")

	file, err := os.Open("../input.txt")
	if err != nil {
		fmt.Println("Error opening input file:", err)
		os.Exit(1)
	}
	defer file.Close()

	return handleFile(file)
}

func handleFile(file io.Reader) Result {
	resultChan := make(chan AnalyseResult)

	linesCount := 0
	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		go handleString(scanner.Text(), resultChan)
		linesCount++
	}
	fmt.Println("linesCount:", linesCount)

	totalCharsOfCode := 0
	totalCharsOfValue := 0
	totalEncodedChars := 0
	for i := 0; i < linesCount; i++ {
		result := <-resultChan
		//fmt.Printf("read from results: %+v\n", result)

		totalCharsOfCode += result.charsOfCode
		totalCharsOfValue += result.charsOfValue
		totalEncodedChars += result.totalEncodedChars
	}

	fmt.Println("totalCharsOfCode:", totalCharsOfCode)
	fmt.Println("totalCharsOfValue:", totalCharsOfValue)

	encodedResult := totalCharsOfCode - totalCharsOfValue
	escapedResult := totalEncodedChars - totalCharsOfCode

	fmt.Println("encodedResult:", encodedResult)
	fmt.Println("escapedResult:", escapedResult)

	return Result{encodedResult: encodedResult, escapedResult: escapedResult}
}

func handleString(inputString string, resultChan chan AnalyseResult) {
	state := State{id:NormalChar}

	inputStringLen := utf8.RuneCountInString(inputString)
	charsOfValue := 0

	if inputStringLen == 2 {
		//empty string passed
		resultChan <- AnalyseResult{charsOfCode:2, charsOfValue:0, inputString:inputString, totalEncodedChars:6}
		return
	}

	workingString := inputString[1:inputStringLen - 1] //ignore quotes

	for _, char := range workingString {
		updateState(&state, char)

		if state.id == NormalChar || state.id == EscapeSecond {
			charsOfValue++
		}
	}

	resultChan <- AnalyseResult{charsOfCode:inputStringLen,
		charsOfValue:charsOfValue,
		inputString:inputString,
		totalEncodedChars:encodedCharsCount(inputString)}
}

func encodedCharsCount(inputString string) int {
	newString := strings.Replace(inputString, `\`, `\\`, -1)
	newString = strings.Replace(newString, `"`, `\"`, -1)

	return utf8.RuneCountInString(newString) + 2 //outer quotes
}

func updateState(oldState *State, char rune) {
	if oldState.id == NormalChar && char != '\\' {
		return
	}

	if oldState.id == Escape {
		if char == 'x' {
			//char code sequence starting
			oldState.id = EscapeX
		} else {
			//just escaped char
			oldState.id = NormalChar
		}
	} else if oldState.id == EscapeX {
		oldState.id = EscapeFirst
	} else if oldState.id == EscapeFirst {
		oldState.id = EscapeSecond
	} else if oldState.id == EscapeSecond {
		if char == '\\' {
			oldState.id = Escape
		} else {
			oldState.id = NormalChar
		}
	} else if oldState.id == NormalChar {
		if char == '\\' {
			oldState.id = Escape
		}
	}
}
<|endoftext|>


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

package xhttp

import (
	"gopkg.in/goyy/goyy.v0/comm/profile"
	"gopkg.in/goyy/goyy.v0/comm/xtype"
	"html/template"
)

var Conf = &conf{
	Addr:    ":9090",
	Actives: []string{profile.DEV},
	Static: &staticOptions{
		Dir:        "static",
		Assets:     "/static",
		Consumers:  "/static",
		Operations: "/static",
	},
	Templates: &templateOptions{
		Directory:  "templates",
		Extensions: []string{"tmpl"},
		Funcs:      []template.FuncMap{},
		Delims: templateDelims{
			Left:  "{{",
			Right: "}}",
		},
		Reloaded: true,
	},
	Session: &sessionOptions{
		Addr: ":6379",
		Options: &Options{
			Path:     "",
			Domain:   "",
			MaxAge:   30 * 60,
			Secure:   false,
			HttpOnly: true,
		},
	},
	Secures: &secureOptions{
		LoginUrl:   "/login",
		SuccessUrl: "/",
		Filters: []xtype.Map{
			{"/**", "anon"},
		},
	},
}

type conf struct {
	Addr      string           // the TCP network address
	Actives   []string         // Active profile
	Session   *sessionOptions  // the session TCP network address
	Static    *staticOptions   // Static resource options
	Templates *templateOptions // template options
	Secures   *secureOptions
}

type sessionOptions struct {
	*Options
	Addr string
}

type staticOptions struct {
	Dir        string // Static resource directory
	Assets     string // Static resource URL
	Consumers  string // Consumer uploaded static resource URL
	Operations string // Operations uploaded static resource URL
}

type secureOptions struct {
	LoginUrl   string
	SuccessUrl string
	Filters    []xtype.Map
}

// Options is a struct for specifying configuration options for the html render
type templateOptions struct {
	// Directory to load templates. Default is "templates"
	Directory string
	// Extensions to parse template files from. Defaults to ["tmpl"]
	Extensions []string
	// Funcs is a slice of FuncMaps to apply to the template upon compilation.
	// This is useful for helper functions. Defaults to [].
	Funcs []template.FuncMap
	// Delims sets the action delimiters to the specified strings in the templateDelims struct.
	Delims templateDelims
	// Reloaded sets up the template for each reload
	Reloaded bool
}

// templateDelims represents a set of Left and Right delimiters for HTML template rendering
type templateDelims struct {
	// Left delimiter, defaults to {{
	Left string
	// Right delimiter, defaults to }}
	Right string
}
<commit_msg>add Conf.Static.Apis<commit_after>// Copyright 2014 The goyy Authors.  All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package xhttp

import (
	"gopkg.in/goyy/goyy.v0/comm/profile"
	"gopkg.in/goyy/goyy.v0/comm/xtype"
	"html/template"
)

var Conf = &conf{
	Addr:    ":9090",
	Actives: []string{profile.DEV},
	Static: &staticOptions{
		Dir:        "static",
		Apis:       "/api",
		Assets:     "/static",
		Consumers:  "/static",
		Operations: "/static",
	},
	Templates: &templateOptions{
		Directory:  "templates",
		Extensions: []string{"tmpl"},
		Funcs:      []template.FuncMap{},
		Delims: templateDelims{
			Left:  "{{",
			Right: "}}",
		},
		Reloaded: true,
	},
	Session: &sessionOptions{
		Addr: ":6379",
		Options: &Options{
			Path:     "",
			Domain:   "",
			MaxAge:   30 * 60,
			Secure:   false,
			HttpOnly: true,
		},
	},
	Secures: &secureOptions{
		LoginUrl:   "/login",
		SuccessUrl: "/",
		Filters: []xtype.Map{
			{"/**", "anon"},
		},
	},
}

type conf struct {
	Addr      string           // the TCP network address
	Actives   []string         // Active profile
	Session   *sessionOptions  // the session TCP network address
	Static    *staticOptions   // Static resource options
	Templates *templateOptions // template options
	Secures   *secureOptions
}

type sessionOptions struct {
	*Options
	Addr string
}

type staticOptions struct {
	Dir        string // Static resource directory
	Apis       string // APIs URL
	Assets     string // Static resource URL
	Consumers  string // Consumer uploaded static resource URL
	Operations string // Operations uploaded static resource URL
}

type secureOptions struct {
	LoginUrl   string
	SuccessUrl string
	Filters    []xtype.Map
}

// Options is a struct for specifying configuration options for the html render
type templateOptions struct {
	// Directory to load templates. Default is "templates"
	Directory string
	// Extensions to parse template files from. Defaults to ["tmpl"]
	Extensions []string
	// Funcs is a slice of FuncMaps to apply to the template upon compilation.
	// This is useful for helper functions. Defaults to [].
	Funcs []template.FuncMap
	// Delims sets the action delimiters to the specified strings in the templateDelims struct.
	Delims templateDelims
	// Reloaded sets up the template for each reload
	Reloaded bool
}

// templateDelims represents a set of Left and Right delimiters for HTML template rendering
type templateDelims struct {
	// Left delimiter, defaults to {{
	Left string
	// Right delimiter, defaults to }}
	Right string
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package api

import (
	"reflect"
	"testing"
)

func runTest(t *testing.T, source interface{}) {
	name := reflect.TypeOf(source).Name()
	data, err := Encode(source)
	if err != nil {
		t.Errorf("%v: %v", name, err)
		return
	}
	obj2, err := Decode(data)
	if err != nil {
		t.Errorf("%v: %v", name, err)
		return
	}
	if !reflect.DeepEqual(source, obj2) {
		t.Errorf("%v: wanted %#v, got %#v", name, source, obj2)
		return
	}
	obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
	err = DecodeInto(data, obj3)
	if err != nil {
		t.Errorf("%v: %v", name, err)
		return
	}
	if !reflect.DeepEqual(source, obj3) {
		t.Errorf("%v: wanted %#v, got %#v", name, source, obj3)
		return
	}
}

func TestTypes(t *testing.T) {
	// TODO: auto-fill all fields.
	table := []interface{}{
		&Pod{
			JSONBase: JSONBase{
				ID: "mylittlepod",
			},
			Labels: map[string]string{
				"name": "pinky",
			},
		},
		&Service{},
		&ServiceList{
			Items: []Service{
				{
					Labels: map[string]string{
						"foo": "bar",
					},
				}, {
					Labels: map[string]string{
						"foo": "baz",
					},
				},
			},
		},
		&ReplicationControllerList{},
		&ReplicationController{},
		&PodList{},
	}
	for _, item := range table {
		runTest(t, item)
	}
}

func TestNonPtr(t *testing.T) {
	obj := interface{}(Pod{Labels: map[string]string{"name": "foo"}})
	data, err := Encode(obj)
	obj2, err2 := Decode(data)
	if err != nil || err2 != nil {
		t.Errorf("Failure: %v %v", err2, err2)
	}
	if _, ok := obj2.(*Pod); !ok {
		t.Errorf("Got wrong type")
	}
	if !reflect.DeepEqual(obj2, &Pod{Labels: map[string]string{"name": "foo"}}) {
		t.Errorf("Something changed: %#v", obj2)
	}
}

func TestPtr(t *testing.T) {
	obj := interface{}(&Pod{Labels: map[string]string{"name": "foo"}})
	data, err := Encode(obj)
	obj2, err2 := Decode(data)
	if err != nil || err2 != nil {
		t.Errorf("Failure: %v %v", err2, err2)
	}
	if _, ok := obj2.(*Pod); !ok {
		t.Errorf("Got wrong type")
	}
	if !reflect.DeepEqual(obj2, &Pod{Labels: map[string]string{"name": "foo"}}) {
		t.Errorf("Something changed: %#v", obj2)
	}
}

// TODO: test rejection of bad JSON.
<commit_msg>Test rejection of bad JSON.<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package api

import (
	"reflect"
	"testing"
)

func runTest(t *testing.T, source interface{}) {
	name := reflect.TypeOf(source).Name()
	data, err := Encode(source)
	if err != nil {
		t.Errorf("%v: %v", name, err)
		return
	}
	obj2, err := Decode(data)
	if err != nil {
		t.Errorf("%v: %v", name, err)
		return
	}
	if !reflect.DeepEqual(source, obj2) {
		t.Errorf("%v: wanted %#v, got %#v", name, source, obj2)
		return
	}
	obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
	err = DecodeInto(data, obj3)
	if err != nil {
		t.Errorf("%v: %v", name, err)
		return
	}
	if !reflect.DeepEqual(source, obj3) {
		t.Errorf("%v: wanted %#v, got %#v", name, source, obj3)
		return
	}
}

func TestTypes(t *testing.T) {
	// TODO: auto-fill all fields.
	table := []interface{}{
		&Pod{
			JSONBase: JSONBase{
				ID: "mylittlepod",
			},
			Labels: map[string]string{
				"name": "pinky",
			},
		},
		&Service{},
		&ServiceList{
			Items: []Service{
				{
					Labels: map[string]string{
						"foo": "bar",
					},
				}, {
					Labels: map[string]string{
						"foo": "baz",
					},
				},
			},
		},
		&ReplicationControllerList{},
		&ReplicationController{},
		&PodList{},
	}
	for _, item := range table {
		runTest(t, item)
	}
}

func TestNonPtr(t *testing.T) {
	obj := interface{}(Pod{Labels: map[string]string{"name": "foo"}})
	data, err := Encode(obj)
	obj2, err2 := Decode(data)
	if err != nil || err2 != nil {
		t.Errorf("Failure: %v %v", err2, err2)
	}
	if _, ok := obj2.(*Pod); !ok {
		t.Errorf("Got wrong type")
	}
	if !reflect.DeepEqual(obj2, &Pod{Labels: map[string]string{"name": "foo"}}) {
		t.Errorf("Something changed: %#v", obj2)
	}
}

func TestPtr(t *testing.T) {
	obj := interface{}(&Pod{Labels: map[string]string{"name": "foo"}})
	data, err := Encode(obj)
	obj2, err2 := Decode(data)
	if err != nil || err2 != nil {
		t.Errorf("Failure: %v %v", err2, err2)
	}
	if _, ok := obj2.(*Pod); !ok {
		t.Errorf("Got wrong type")
	}
	if !reflect.DeepEqual(obj2, &Pod{Labels: map[string]string{"name": "foo"}}) {
		t.Errorf("Something changed: %#v", obj2)
	}
}

func TestBadJSONRejection(t *testing.T) {
	badJSONMissingKind := []byte(`{ }`)
	if _, err := Decode(badJSONMissingKind); err == nil {
		t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind)
	}
	badJSONUnknownType := []byte(`{"kind": "bar"}`)
	if _, err1 := Decode(badJSONUnknownType); err1 == nil {
		t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType)
	}
	badJSONKindMismatch := []byte(`{"kind": "Pod"}`)
	if err2 := DecodeInto(badJSONKindMismatch, &Minion{}); err2 == nil {
		t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
	}
}
<|endoftext|>


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

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

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

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

package rest

import (
	"fmt"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/api/meta"
	"k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/validation/field"
)

// RESTUpdateStrategy defines the minimum validation, accepted input, and
// name generation behavior to update an object that follows Kubernetes
// API conventions. A resource may have many UpdateStrategies, depending on
// the call pattern in use.
type RESTUpdateStrategy interface {
	runtime.ObjectTyper
	// NamespaceScoped returns true if the object must be within a namespace.
	NamespaceScoped() bool
	// AllowCreateOnUpdate returns true if the object can be created by a PUT.
	AllowCreateOnUpdate() bool
	// PrepareForUpdate is invoked on update before validation to normalize
	// the object.  For example: remove fields that are not to be persisted,
	// sort order-insensitive list fields, etc.  This should not remove fields
	// whose presence would be considered a validation error.
	PrepareForUpdate(obj, old runtime.Object)
	// ValidateUpdate is invoked after default fields in the object have been
	// filled in before the object is persisted.  This method should not mutate
	// the object.
	ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList
	// Canonicalize is invoked after validation has succeeded but before the
	// object has been persisted.  This method may mutate the object.
	Canonicalize(obj runtime.Object)
	// AllowUnconditionalUpdate returns true if the object can be updated
	// unconditionally (irrespective of the latest resource version), when
	// there is no resource version specified in the object.
	AllowUnconditionalUpdate() bool
}

// TODO: add other common fields that require global validation.
func validateCommonFields(obj, old runtime.Object) (field.ErrorList, error) {
	allErrs := field.ErrorList{}
	objectMeta, err := api.ObjectMetaFor(obj)
	if err != nil {
		return nil, fmt.Errorf("failed to get new object metadata: %v", err)
	}
	oldObjectMeta, err := api.ObjectMetaFor(old)
	if err != nil {
		return nil, fmt.Errorf("failed to get old object metadata: %v", err)
	}
	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...)

	return allErrs, nil
}

// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns
// errors that can be converted to api.Status. It will invoke update validation with the provided existing
// and updated objects.
func BeforeUpdate(strategy RESTUpdateStrategy, ctx api.Context, obj, old runtime.Object) error {
	objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
	if kerr != nil {
		return kerr
	}
	if strategy.NamespaceScoped() {
		if !api.ValidNamespace(ctx, objectMeta) {
			return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
		}
	} else {
		objectMeta.Namespace = api.NamespaceNone
	}

	strategy.PrepareForUpdate(obj, old)

	// Ensure some common fields, like UID, are validated for all resources.
	errs, err := validateCommonFields(obj, old)
	if err != nil {
		return errors.NewInternalError(err)
	}

	errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...)
	if len(errs) > 0 {
		return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
	}

	strategy.Canonicalize(obj)

	return nil
}

// TransformFunc is a function to transform and return newObj
type TransformFunc func(ctx api.Context, newObj runtime.Object, oldObj runtime.Object) (transformedNewObj runtime.Object, err error)

// defaultUpdatedObjectInfo implements UpdatedObjectInfo
type defaultUpdatedObjectInfo struct {
	// obj is the updated object
	obj runtime.Object

	// copier makes a copy of the object before returning it.
	// this allows repeated calls to UpdatedObject() to return
	// pristine data, even if the returned value is mutated.
	copier runtime.ObjectCopier

	// transformers is an optional list of transforming functions that modify or
	// replace obj using information from the context, old object, or other sources.
	transformers []TransformFunc
}

// DefaultUpdatedObjectInfo returns an UpdatedObjectInfo impl based on the specified object.
func DefaultUpdatedObjectInfo(obj runtime.Object, copier runtime.ObjectCopier, transformers ...TransformFunc) UpdatedObjectInfo {
	return &defaultUpdatedObjectInfo{obj, copier, transformers}
}

// Preconditions satisfies the UpdatedObjectInfo interface.
func (i *defaultUpdatedObjectInfo) Preconditions() *api.Preconditions {
	// Attempt to get the UID out of the object
	accessor, err := meta.Accessor(i.obj)
	if err != nil {
		// If no UID can be read, no preconditions are possible
		return nil
	}

	// If empty, no preconditions needed
	uid := accessor.GetUID()
	if len(uid) == 0 {
		return nil
	}

	return &api.Preconditions{UID: &uid}
}

// UpdatedObject satisfies the UpdatedObjectInfo interface.
// It returns a copy of the held obj, passed through any configured transformers.
func (i *defaultUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) {
	var err error
	// Start with the configured object
	newObj := i.obj

	// If the original is non-nil (might be nil if the first transformer builds the object from the oldObj), make a copy,
	// so we don't return the original. BeforeUpdate can mutate the returned object, doing things like clearing ResourceVersion.
	// If we're re-called, we need to be able to return the pristine version.
	if newObj != nil {
		newObj, err = i.copier.Copy(newObj)
		if err != nil {
			return nil, err
		}
	}

	// Allow any configured transformers to update the new object
	for _, transformer := range i.transformers {
		newObj, err = transformer(ctx, newObj, oldObj)
		if err != nil {
			return nil, err
		}
	}

	return newObj, nil
}
<commit_msg>Add WrapUpdatedObjectInfo helper<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package rest

import (
	"fmt"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/api/meta"
	"k8s.io/kubernetes/pkg/api/validation"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/validation/field"
)

// RESTUpdateStrategy defines the minimum validation, accepted input, and
// name generation behavior to update an object that follows Kubernetes
// API conventions. A resource may have many UpdateStrategies, depending on
// the call pattern in use.
type RESTUpdateStrategy interface {
	runtime.ObjectTyper
	// NamespaceScoped returns true if the object must be within a namespace.
	NamespaceScoped() bool
	// AllowCreateOnUpdate returns true if the object can be created by a PUT.
	AllowCreateOnUpdate() bool
	// PrepareForUpdate is invoked on update before validation to normalize
	// the object.  For example: remove fields that are not to be persisted,
	// sort order-insensitive list fields, etc.  This should not remove fields
	// whose presence would be considered a validation error.
	PrepareForUpdate(obj, old runtime.Object)
	// ValidateUpdate is invoked after default fields in the object have been
	// filled in before the object is persisted.  This method should not mutate
	// the object.
	ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList
	// Canonicalize is invoked after validation has succeeded but before the
	// object has been persisted.  This method may mutate the object.
	Canonicalize(obj runtime.Object)
	// AllowUnconditionalUpdate returns true if the object can be updated
	// unconditionally (irrespective of the latest resource version), when
	// there is no resource version specified in the object.
	AllowUnconditionalUpdate() bool
}

// TODO: add other common fields that require global validation.
func validateCommonFields(obj, old runtime.Object) (field.ErrorList, error) {
	allErrs := field.ErrorList{}
	objectMeta, err := api.ObjectMetaFor(obj)
	if err != nil {
		return nil, fmt.Errorf("failed to get new object metadata: %v", err)
	}
	oldObjectMeta, err := api.ObjectMetaFor(old)
	if err != nil {
		return nil, fmt.Errorf("failed to get old object metadata: %v", err)
	}
	allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...)

	return allErrs, nil
}

// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns
// errors that can be converted to api.Status. It will invoke update validation with the provided existing
// and updated objects.
func BeforeUpdate(strategy RESTUpdateStrategy, ctx api.Context, obj, old runtime.Object) error {
	objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
	if kerr != nil {
		return kerr
	}
	if strategy.NamespaceScoped() {
		if !api.ValidNamespace(ctx, objectMeta) {
			return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
		}
	} else {
		objectMeta.Namespace = api.NamespaceNone
	}

	strategy.PrepareForUpdate(obj, old)

	// Ensure some common fields, like UID, are validated for all resources.
	errs, err := validateCommonFields(obj, old)
	if err != nil {
		return errors.NewInternalError(err)
	}

	errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...)
	if len(errs) > 0 {
		return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
	}

	strategy.Canonicalize(obj)

	return nil
}

// TransformFunc is a function to transform and return newObj
type TransformFunc func(ctx api.Context, newObj runtime.Object, oldObj runtime.Object) (transformedNewObj runtime.Object, err error)

// defaultUpdatedObjectInfo implements UpdatedObjectInfo
type defaultUpdatedObjectInfo struct {
	// obj is the updated object
	obj runtime.Object

	// copier makes a copy of the object before returning it.
	// this allows repeated calls to UpdatedObject() to return
	// pristine data, even if the returned value is mutated.
	copier runtime.ObjectCopier

	// transformers is an optional list of transforming functions that modify or
	// replace obj using information from the context, old object, or other sources.
	transformers []TransformFunc
}

// DefaultUpdatedObjectInfo returns an UpdatedObjectInfo impl based on the specified object.
func DefaultUpdatedObjectInfo(obj runtime.Object, copier runtime.ObjectCopier, transformers ...TransformFunc) UpdatedObjectInfo {
	return &defaultUpdatedObjectInfo{obj, copier, transformers}
}

// Preconditions satisfies the UpdatedObjectInfo interface.
func (i *defaultUpdatedObjectInfo) Preconditions() *api.Preconditions {
	// Attempt to get the UID out of the object
	accessor, err := meta.Accessor(i.obj)
	if err != nil {
		// If no UID can be read, no preconditions are possible
		return nil
	}

	// If empty, no preconditions needed
	uid := accessor.GetUID()
	if len(uid) == 0 {
		return nil
	}

	return &api.Preconditions{UID: &uid}
}

// UpdatedObject satisfies the UpdatedObjectInfo interface.
// It returns a copy of the held obj, passed through any configured transformers.
func (i *defaultUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) {
	var err error
	// Start with the configured object
	newObj := i.obj

	// If the original is non-nil (might be nil if the first transformer builds the object from the oldObj), make a copy,
	// so we don't return the original. BeforeUpdate can mutate the returned object, doing things like clearing ResourceVersion.
	// If we're re-called, we need to be able to return the pristine version.
	if newObj != nil {
		newObj, err = i.copier.Copy(newObj)
		if err != nil {
			return nil, err
		}
	}

	// Allow any configured transformers to update the new object
	for _, transformer := range i.transformers {
		newObj, err = transformer(ctx, newObj, oldObj)
		if err != nil {
			return nil, err
		}
	}

	return newObj, nil
}

// wrappedUpdatedObjectInfo allows wrapping an existing objInfo and
// chaining additional transformations/checks on the result of UpdatedObject()
type wrappedUpdatedObjectInfo struct {
	// obj is the updated object
	objInfo UpdatedObjectInfo

	// transformers is an optional list of transforming functions that modify or
	// replace obj using information from the context, old object, or other sources.
	transformers []TransformFunc
}

// WrapUpdatedObjectInfo returns an UpdatedObjectInfo impl that delegates to
// the specified objInfo, then calls the passed transformers
func WrapUpdatedObjectInfo(objInfo UpdatedObjectInfo, transformers ...TransformFunc) UpdatedObjectInfo {
	return &wrappedUpdatedObjectInfo{objInfo, transformers}
}

// Preconditions satisfies the UpdatedObjectInfo interface.
func (i *wrappedUpdatedObjectInfo) Preconditions() *api.Preconditions {
	return i.objInfo.Preconditions()
}

// UpdatedObject satisfies the UpdatedObjectInfo interface.
// It delegates to the wrapped objInfo and passes the result through any configured transformers.
func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx api.Context, oldObj runtime.Object) (runtime.Object, error) {
	newObj, err := i.objInfo.UpdatedObject(ctx, oldObj)
	if err != nil {
		return newObj, err
	}

	// Allow any configured transformers to update the new object or error
	for _, transformer := range i.transformers {
		newObj, err = transformer(ctx, newObj, oldObj)
		if err != nil {
			return nil, err
		}
	}

	return newObj, nil
}
<|endoftext|>


<commit_before>package certgen

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"math/big"
	"net"
	"time"
)

type Params struct {
	Hosts []string
	IsCA  bool
	CA    *Certificate
}

type Certificate struct {
	Pin string
	PEM string
	DER []byte

	KeyPEM string
	Key    *rsa.PrivateKey
}

func Generate(p Params) (*Certificate, error) {
	var err error
	cert := &Certificate{}
	cert.Key, err = rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, err
	}

	notBefore := time.Now()
	notAfter := notBefore.Add(5 * 365 * 24 * time.Hour)

	template := &x509.Certificate{
		SerialNumber: serialNumber,
		Subject:      pkix.Name{Organization: []string{"Flynn"}},
		NotBefore:    notBefore,
		NotAfter:     notAfter,

		BasicConstraintsValid: true,
		IsCA: p.IsCA,
	}
	if p.IsCA {
		template.Subject.OrganizationalUnit = []string{"CA"}
		template.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
	} else {
		template.Subject.CommonName = p.Hosts[0]
		template.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature
		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
	}

	for _, host := range p.Hosts {
		if ip := net.ParseIP(host); ip != nil {
			template.IPAddresses = append(template.IPAddresses, ip)
		} else {
			template.DNSNames = append(template.DNSNames, host)
		}
	}

	parent := template
	var parentKey interface{} = cert.Key
	if p.CA != nil {
		ca, err := x509.ParseCertificate(p.CA.DER)
		if err != nil {
			return nil, err
		}
		parent = ca
		parentKey = p.CA.Key
	}
	cert.DER, err = x509.CreateCertificate(rand.Reader, template, parent, &cert.Key.PublicKey, parentKey)
	if err != nil {
		return nil, err
	}

	h := sha256.New()
	h.Write(cert.DER)
	cert.Pin = base64.StdEncoding.EncodeToString(h.Sum(nil))

	var buf bytes.Buffer

	pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.DER})
	cert.PEM = buf.String()

	buf.Reset()

	pem.Encode(&buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(cert.Key)})
	cert.KeyPEM = buf.String()

	return cert, nil
}
<commit_msg>pkg/certgen: Make CA certificate name more clear<commit_after>package certgen

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"math/big"
	"net"
	"time"
)

type Params struct {
	Hosts []string
	IsCA  bool
	CA    *Certificate
}

type Certificate struct {
	Pin string
	PEM string
	DER []byte

	KeyPEM string
	Key    *rsa.PrivateKey
}

func Generate(p Params) (*Certificate, error) {
	var err error
	cert := &Certificate{}
	cert.Key, err = rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, err
	}

	notBefore := time.Now()
	notAfter := notBefore.Add(5 * 365 * 24 * time.Hour)

	template := &x509.Certificate{
		SerialNumber: serialNumber,
		Subject:      pkix.Name{Organization: []string{"Flynn"}},
		NotBefore:    notBefore,
		NotAfter:     notAfter,

		BasicConstraintsValid: true,
		IsCA: p.IsCA,
	}
	if p.IsCA {
		template.Subject.OrganizationalUnit = []string{"Flynn Ephemeral CA"}
		template.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
	} else {
		template.Subject.CommonName = p.Hosts[0]
		template.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature
		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
	}

	for _, host := range p.Hosts {
		if ip := net.ParseIP(host); ip != nil {
			template.IPAddresses = append(template.IPAddresses, ip)
		} else {
			template.DNSNames = append(template.DNSNames, host)
		}
	}

	parent := template
	var parentKey interface{} = cert.Key
	if p.CA != nil {
		ca, err := x509.ParseCertificate(p.CA.DER)
		if err != nil {
			return nil, err
		}
		parent = ca
		parentKey = p.CA.Key
	}
	cert.DER, err = x509.CreateCertificate(rand.Reader, template, parent, &cert.Key.PublicKey, parentKey)
	if err != nil {
		return nil, err
	}

	h := sha256.New()
	h.Write(cert.DER)
	cert.Pin = base64.StdEncoding.EncodeToString(h.Sum(nil))

	var buf bytes.Buffer

	pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.DER})
	cert.PEM = buf.String()

	buf.Reset()

	pem.Encode(&buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(cert.Key)})
	cert.KeyPEM = buf.String()

	return cert, nil
}
<|endoftext|>


<commit_before>package contain

import (
	"fmt"
	"github.com/docker/docker/pkg/reexec"
	"github.com/mrosset/util/file"
	"github.com/mrosset/via/pkg"
	"gopkg.in/urfave/cli.v2"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

const (
	BIND_RO = syscall.MS_BIND | syscall.MS_RDONLY | syscall.MS_REC
	BIND_RW = syscall.MS_BIND | syscall.MS_REC
)

var (
	elog   = log.New(os.Stderr, "error: ", log.Lshortfile)
	config = via.GetConfig()
)

func init() {
	reexec.Register("init", initialize)
	if reexec.Init() {
		os.Exit(0)
	}

}

func Append(app *cli.App) {
	cmd := &cli.Command{
		Name:   "contain",
		Action: contain,
		Hidden: false,
	}
	app.Commands = append(app.Commands, cmd)
}

// instead of linking, bind sh to bash to avoid cross linking across
// devices
func bindsh(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin", "bash")
		bin    = filepath.Join(root, "bin")
		target = filepath.Join(bin, "sh")
	)
	if err := os.MkdirAll(bin, 07550); err != nil {
		return err
	}
	if err := file.Touch(target); err != nil {
		return err
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func initialize() {
	fmt.Println(os.Args)
	root, err := ioutil.TempDir("", "via-build")
	if err != nil {
		elog.Fatal(err)
	}

	if err := os.MkdirAll(root, 0700); err != nil {
		elog.Fatal(err)
	}
	// setup busybox
	// if err := busybox(root); err != nil {
	//	elog.Fatal(err)
	// }

	// setup all our mounts
	if err := mount(root); err != nil {
		elog.Fatal(err)
	}
	if err := bindsh(root); err != nil {
		elog.Fatal(err)
	}
	// finally pivot our root
	if err := pivot(root); err != nil {
		elog.Fatal(err)
	}
	run()
}

func run() {

	cmd := exec.Command(filepath.Join(config.Prefix, "bin", "bash"))
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.Env = []string{
		"HOME=/home/mrosset",
		"GOPATH=/home/mrosset/gocode",
		"PATH=/bin:/opt/via/bin:/home/mrosset/gocode/bin",
		"PS1=-[via-build]- # ",
	}

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 1000,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 1001,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}

	if err := cmd.Run(); err != nil {
		elog.Fatal(err)
	}
}

func contain(ctx *cli.Context) error {
	cmd := reexec.Command("init", ctx.Command.Name)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

type FileSystem struct {
	Source string
	Type   string
	Flags  int
	Data   string
	MakeFn func(string) error
}

func (fs FileSystem) Mount(root string) error {

	target := filepath.Join(root, fs.Source)
	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}
	return syscall.Mount(
		fs.Source,
		target,
		fs.Type,
		uintptr(fs.Flags),
		fs.Data,
	)
}

func mkdir(path string) error {
	return os.MkdirAll(path, 0755)
}

func busybox(root string) error {
	bin := filepath.Join(root, "bin")
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	bpath := "/bin/busybox"
	cmd := exec.Cmd{
		Path:   bpath,
		Args:   []string{"busybox", "--install", "-s", bin},
		Stderr: os.Stderr,
		Stdout: os.Stdout,
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	out, err := os.OpenFile(filepath.Join(bin, "busybox"), os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	defer out.Close()
	return file.Copy(out, bpath)
}

func bind(source, root string) error {
	target := filepath.Join(root, source)
	stat, err := os.Stat(source)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		os.MkdirAll(target, 0755)
	} else {
		dir := filepath.Dir(target)
		os.MkdirAll(dir, 0755)
		if err := file.Touch(target); err != nil {
			return err
		}
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func mount(root string) error {
	// our binds
	binds := []string{
		"/dev",
		"/etc/resolv.conf",
		"/etc/ssl",
		"/etc/passwd",
		os.ExpandEnv("$HOME/.ccache"),
		config.Cache.String(),
		config.Plans,
		config.Repo,
		filepath.Join(os.Getenv("GOPATH"), "bin/via"),
		config.Prefix,
	}
	// our filesystems
	fs := []FileSystem{
		{
			Source: "proc",
			Type:   "proc",
		},
		{
			Source: "tmpfs",
			Type:   "tmpfs",
		},
	}
	// mount our binds
	for _, source := range binds {
		if err := bind(source, root); err != nil {
			elog.Printf("binding %s to %s", source, filepath.Join(root, source))
			return err
		}
	}
	// mount our filesystems
	for _, m := range fs {
		if err := m.Mount(root); err != nil {
			elog.Printf("mounting %s to %s", m.Source, filepath.Join(root, m.Source))
			return err
		}
	}
	return nil
}

func pivot(newroot string) error {
	oldroot := filepath.Join(newroot, "/.root")

	// bind mount newroot to itself - this is a slight hack
	// needed to work around a pivot_root requirement
	if err := syscall.Mount(
		newroot,
		newroot,
		"",
		BIND_RO,
		"",
	); err != nil {
		return err
	}

	// create oldroot directory
	if err := os.MkdirAll(oldroot, 0700); err != nil {
		return err
	}

	// call pivot_root
	if err := syscall.PivotRoot(newroot, oldroot); err != nil {
		return err
	}

	// ensure current working directory is set to new root
	if err := os.Chdir("/"); err != nil {
		return err
	}

	// umount oldroot, which now lives at /.pivot_root
	oldroot = "/.root"
	if err := syscall.Unmount(
		oldroot,
		syscall.MNT_DETACH,
	); err != nil {
		return err
	}

	return os.RemoveAll(oldroot)
}
<commit_msg>contain: get bin PATH from config<commit_after>package contain

import (
	"fmt"
	"github.com/docker/docker/pkg/reexec"
	"github.com/mrosset/util/file"
	"github.com/mrosset/via/pkg"
	"gopkg.in/urfave/cli.v2"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
)

const (
	BIND_RO = syscall.MS_BIND | syscall.MS_RDONLY | syscall.MS_REC
	BIND_RW = syscall.MS_BIND | syscall.MS_REC
)

var (
	elog   = log.New(os.Stderr, "error: ", log.Lshortfile)
	config = via.GetConfig()
)

func init() {
	reexec.Register("init", initialize)
	if reexec.Init() {
		os.Exit(0)
	}

}

func Append(app *cli.App) {
	cmd := &cli.Command{
		Name:   "contain",
		Action: contain,
		Hidden: false,
	}
	app.Commands = append(app.Commands, cmd)
}

// instead of linking, bind sh to bash to avoid cross linking across
// devices
func bindsh(root string) error {
	var (
		source = filepath.Join(config.Prefix, "bin", "bash")
		bin    = filepath.Join(root, "bin")
		target = filepath.Join(bin, "sh")
	)
	if err := os.MkdirAll(bin, 07550); err != nil {
		return err
	}
	if err := file.Touch(target); err != nil {
		return err
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func initialize() {
	fmt.Println(os.Args)
	root, err := ioutil.TempDir("", "via-build")
	if err != nil {
		elog.Fatal(err)
	}

	if err := os.MkdirAll(root, 0700); err != nil {
		elog.Fatal(err)
	}
	// setup busybox
	// if err := busybox(root); err != nil {
	//	elog.Fatal(err)
	// }

	// setup all our mounts
	if err := mount(root); err != nil {
		elog.Fatal(err)
	}
	if err := bindsh(root); err != nil {
		elog.Fatal(err)
	}
	// finally pivot our root
	if err := pivot(root); err != nil {
		elog.Fatal(err)
	}
	run()
}

func run() {

	cmd := exec.Command(filepath.Join(config.Prefix, "bin", "bash"))
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.Env = []string{
		"HOME=/home/mrosset",
		"GOPATH=/home/mrosset/gocode",
		fmt.Sprintf("PATH=/bin:%s/bin:/home/mrosset/gocode/bin", config.Prefix),
		"PS1=-[via-build]- # ",
	}

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 1000,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 1001,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}

	if err := cmd.Run(); err != nil {
		elog.Fatal(err)
	}
}

func contain(ctx *cli.Context) error {
	cmd := reexec.Command("init", ctx.Command.Name)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

type FileSystem struct {
	Source string
	Type   string
	Flags  int
	Data   string
	MakeFn func(string) error
}

func (fs FileSystem) Mount(root string) error {

	target := filepath.Join(root, fs.Source)
	if err := os.MkdirAll(target, 0755); err != nil {
		return err
	}
	return syscall.Mount(
		fs.Source,
		target,
		fs.Type,
		uintptr(fs.Flags),
		fs.Data,
	)
}

func mkdir(path string) error {
	return os.MkdirAll(path, 0755)
}

func busybox(root string) error {
	bin := filepath.Join(root, "bin")
	if err := os.MkdirAll(bin, 0755); err != nil {
		return err
	}
	bpath := "/bin/busybox"
	cmd := exec.Cmd{
		Path:   bpath,
		Args:   []string{"busybox", "--install", "-s", bin},
		Stderr: os.Stderr,
		Stdout: os.Stdout,
	}
	if err := cmd.Run(); err != nil {
		return err
	}
	out, err := os.OpenFile(filepath.Join(bin, "busybox"), os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	defer out.Close()
	return file.Copy(out, bpath)
}

func bind(source, root string) error {
	target := filepath.Join(root, source)
	stat, err := os.Stat(source)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		os.MkdirAll(target, 0755)
	} else {
		dir := filepath.Dir(target)
		os.MkdirAll(dir, 0755)
		if err := file.Touch(target); err != nil {
			return err
		}
	}
	return syscall.Mount(
		source,
		target,
		"",
		BIND_RO,
		"",
	)
}

func mount(root string) error {
	// our binds
	binds := []string{
		"/dev",
		"/etc/resolv.conf",
		"/etc/ssl",
		"/etc/passwd",
		os.ExpandEnv("$HOME/.ccache"),
		config.Cache.String(),
		config.Plans,
		config.Repo,
		filepath.Join(os.Getenv("GOPATH"), "bin/via"),
		config.Prefix,
	}
	// our filesystems
	fs := []FileSystem{
		{
			Source: "proc",
			Type:   "proc",
		},
		{
			Source: "tmpfs",
			Type:   "tmpfs",
		},
	}
	// mount our binds
	for _, source := range binds {
		if err := bind(source, root); err != nil {
			elog.Printf("binding %s to %s", source, filepath.Join(root, source))
			return err
		}
	}
	// mount our filesystems
	for _, m := range fs {
		if err := m.Mount(root); err != nil {
			elog.Printf("mounting %s to %s", m.Source, filepath.Join(root, m.Source))
			return err
		}
	}
	return nil
}

func pivot(newroot string) error {
	oldroot := filepath.Join(newroot, "/.root")

	// bind mount newroot to itself - this is a slight hack
	// needed to work around a pivot_root requirement
	if err := syscall.Mount(
		newroot,
		newroot,
		"",
		BIND_RO,
		"",
	); err != nil {
		return err
	}

	// create oldroot directory
	if err := os.MkdirAll(oldroot, 0700); err != nil {
		return err
	}

	// call pivot_root
	if err := syscall.PivotRoot(newroot, oldroot); err != nil {
		return err
	}

	// ensure current working directory is set to new root
	if err := os.Chdir("/"); err != nil {
		return err
	}

	// umount oldroot, which now lives at /.pivot_root
	oldroot = "/.root"
	if err := syscall.Unmount(
		oldroot,
		syscall.MNT_DETACH,
	); err != nil {
		return err
	}

	return os.RemoveAll(oldroot)
}
<|endoftext|>


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

package hubbub

import (
	"context"
	"fmt"
	"github.com/google/triage-party/pkg/provider"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/tag"
	"k8s.io/klog/v2"
)

func (h *Engine) cachedTimeline(ctx context.Context, sp provider.SearchParams) ([]*provider.Timeline, error) {
	sp.SearchKey = fmt.Sprintf("%s-%s-%d-timeline", sp.Repo.Organization, sp.Repo.Project, sp.IssueNumber)
	klog.V(1).Infof("Need timeline for %s as of %s", sp.SearchKey, sp.NewerThan)

	if x := h.cache.GetNewerThan(sp.SearchKey, sp.NewerThan); x != nil {
		return x.Timeline, nil
	}

	klog.Infof("cache miss for %s newer than %s (fetch=%v)", sp.SearchKey, sp.NewerThan, sp.Fetch)
	if !sp.Fetch {
		return nil, nil
	}
	return h.updateTimeline(ctx, sp)
}

func (h *Engine) updateTimeline(ctx context.Context, sp provider.SearchParams) ([]*provider.Timeline, error) {
	//	klog.Infof("Downloading event timeline for %s/%s #%d", org, project, num)

	sp.ListOptions = provider.ListOptions{
		PerPage: 100,
	}
	var allEvents []*provider.Timeline
	for {

		pr := provider.ResolveProviderByHost(sp.Repo.Host)
		evs, resp, err := pr.IssuesListIssueTimeline(ctx, sp)
		if err != nil {
			return nil, err
		}
		h.logRate(resp.Rate)

		for _, ev := range evs {
			h.updateMtimeLong(sp.Repo.Organization, sp.Repo.Project, sp.IssueNumber, ev.GetCreatedAt())
		}

		allEvents = append(allEvents, evs...)
		if resp.NextPage == 0 {
			break
		}
		sp.ListOptions.Page = resp.NextPage
	}

	if err := h.cache.Set(sp.SearchKey, &provider.Thing{Timeline: allEvents}); err != nil {
		klog.Errorf("set %q failed: %v", sp.SearchKey, err)
	}

	return allEvents, nil
}

// Add events to the conversation summary if useful
func (h *Engine) addEvents(ctx context.Context, sp provider.SearchParams, co *Conversation, timeline []*provider.Timeline) {
	priority := ""
	for _, l := range co.Labels {
		if strings.HasPrefix(l.GetName(), "priority") {
			klog.V(1).Infof("found priority: %s", l.GetName())
			priority = l.GetName()
			break
		}
	}
	assignedTo := map[string]bool{}
	for _, a := range co.Assignees {
		assignedTo[a.GetLogin()] = true
	}

	thisRepo := fmt.Sprintf("%s/%s", co.Organization, co.Project)

	for _, t := range timeline {
		if h.debug[co.ID] {
			klog.Errorf("debug timeline event %q: %s", t.GetEvent(), formatStruct(t))
		}

		if t.GetEvent() == "labeled" && t.GetLabel().GetName() == priority {
			co.Prioritized = t.GetCreatedAt()
		}

		if t.GetEvent() == "cross-referenced" {
			if assignedTo[t.GetActor().GetLogin()] {
				if t.GetCreatedAt().After(co.LatestAssigneeResponse) {
					co.LatestAssigneeResponse = t.GetCreatedAt()
					co.Tags[tag.AssigneeUpdated] = true
				}
			}

			ri := t.GetSource().GetIssue()

			// Push the item timestamps as far forwards as possible for the best possible timeline fetch
			h.updateCoMtime(co, t.GetCreatedAt())
			h.updateCoMtime(co, ri.GetUpdatedAt())
			h.updateMtime(ri, t.GetCreatedAt())
			h.updateMtime(ri, ri.GetUpdatedAt())
			h.updateMtime(ri, co.Updated)

			if co.Type == Issue && ri.IsPullRequest() {
				refRepo := ri.GetRepository().GetFullName()
				// Filter out PR's that are part of other repositories for now
				if refRepo != thisRepo {
					klog.V(1).Infof("PR#%d is in %s, rather than %s", ri.GetNumber(), refRepo, thisRepo)
					continue
				}

				klog.V(1).Infof("Found cross-referenced PR: #%d, updating PR ref", ri.GetNumber())

				sp.Age = h.mtimeCo(co)

				ref := h.prRef(ctx, sp, ri)
				co.UpdatePullRequestRefs(ref)
				refTag := reviewStateTag(ref.ReviewState)
				refTag.ID = fmt.Sprintf("pr-%s", refTag.ID)
				refTag.Desc = fmt.Sprintf("cross-referenced PR: %s", refTag.Desc)
				co.Tags[refTag] = true
			} else {
				co.UpdateIssueRefs(h.issueRef(t.GetSource().GetIssue(), co.Seen))
			}
		}
	}
}

func (h *Engine) prRef(ctx context.Context, sp provider.SearchParams, pr provider.IItem) *RelatedConversation {
	if pr == nil {
		klog.Errorf("PR is nil")
		return nil
	}

	sp.NewerThan = sp.Age
	if h.mtime(pr).After(sp.NewerThan) {
		sp.NewerThan = h.mtime(pr)
	}

	if !pr.GetClosedAt().IsZero() {
		sp.NewerThan = pr.GetClosedAt()
	}

	klog.V(1).Infof("Creating PR reference for #%d, updated at %s(state=%s)", pr.GetNumber(), pr.GetUpdatedAt(), pr.GetState())

	co := h.createConversation(pr, nil, sp.Age)
	rel := makeRelated(co)

	sp.Repo.Organization = co.Organization
	sp.Repo.Project = co.Project
	sp.IssueNumber = pr.GetNumber()

	timeline, err := h.cachedTimeline(ctx, sp)
	if err != nil {
		klog.Errorf("timeline: %v", err)
	}

	// mtime may have been updated by fetching tthe timeline
	if h.mtime(pr).After(sp.NewerThan) {
		sp.NewerThan = h.mtime(pr)
	}

	var reviews []*provider.PullRequestReview
	if pr.GetState() != "closed" {
		reviews, _, err = h.cachedReviews(ctx, sp)
		if err != nil {
			klog.Errorf("reviews: %v", err)
		}
	} else {
		klog.V(1).Infof("PR #%d is closed, won't fetch review state", pr.GetNumber())
	}

	rel.ReviewState = reviewState(pr, timeline, reviews)
	klog.V(1).Infof("Determined PR #%d to be in review state %q", pr.GetNumber(), rel.ReviewState)
	return rel
}

func (h *Engine) updateLinkedPRs(ctx context.Context, sp provider.SearchParams, parent *Conversation) []*RelatedConversation {
	newRefs := []*RelatedConversation{}

	for _, ref := range parent.PullRequestRefs {
		if h.mtimeRef(ref).After(sp.NewerThan) {
			sp.NewerThan = h.mtimeRef(ref)
		}
	}

	for _, ref := range parent.PullRequestRefs {
		if sp.NewerThan.Before(ref.Seen) || sp.NewerThan == ref.Seen {
			newRefs = append(newRefs, ref)
			continue
		}

		klog.V(1).Infof("updating PR ref: %s/%s #%d from %s to %s",
			ref.Organization, ref.Project, ref.ID, ref.Seen, sp.NewerThan)

		sp.Repo.Organization = ref.Organization
		sp.Repo.Project = ref.Project
		sp.IssueNumber = ref.ID

		pr, age, err := h.cachedPR(ctx, sp)
		if err != nil {
			klog.Errorf("error updating cached PR: %v", err)
			newRefs = append(newRefs, ref)
			continue
		}
		// unable to fetch
		if pr == nil {
			klog.Warningf("Unable to update PR ref for %s/%s #%d (data not yet available)", ref.Organization, ref.Project, ref.ID)
			newRefs = append(newRefs, ref)
			continue
		}

		sp.Age = age

		newRefs = append(newRefs, h.prRef(ctx, sp, pr))
	}

	return newRefs
}

func (h *Engine) issueRef(i *provider.Issue, age time.Time) *RelatedConversation {
	co := h.createConversation(i, nil, age)
	return makeRelated(co)
}
<commit_msg>Prevent case loading the same page over and over<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hubbub

import (
	"context"
	"fmt"
	"github.com/google/triage-party/pkg/provider"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/tag"
	"k8s.io/klog/v2"
)

func (h *Engine) cachedTimeline(ctx context.Context, sp provider.SearchParams) ([]*provider.Timeline, error) {
	sp.SearchKey = fmt.Sprintf("%s-%s-%d-timeline", sp.Repo.Organization, sp.Repo.Project, sp.IssueNumber)
	klog.V(1).Infof("Need timeline for %s as of %s", sp.SearchKey, sp.NewerThan)

	if x := h.cache.GetNewerThan(sp.SearchKey, sp.NewerThan); x != nil {
		return x.Timeline, nil
	}

	klog.Infof("cache miss for %s newer than %s (fetch=%v)", sp.SearchKey, sp.NewerThan, sp.Fetch)
	if !sp.Fetch {
		return nil, nil
	}
	return h.updateTimeline(ctx, sp)
}

func (h *Engine) updateTimeline(ctx context.Context, sp provider.SearchParams) ([]*provider.Timeline, error) {
	//	klog.Infof("Downloading event timeline for %s/%s #%d", org, project, num)

	sp.ListOptions = provider.ListOptions{
		PerPage: 100,
	}
	var allEvents []*provider.Timeline
	for {

		pr := provider.ResolveProviderByHost(sp.Repo.Host)
		evs, resp, err := pr.IssuesListIssueTimeline(ctx, sp)
		if err != nil {
			return nil, err
		}
		h.logRate(resp.Rate)

		for _, ev := range evs {
			h.updateMtimeLong(sp.Repo.Organization, sp.Repo.Project, sp.IssueNumber, ev.GetCreatedAt())
		}

		allEvents = append(allEvents, evs...)
		if resp.NextPage == 0 || sp.ListOptions.Page == resp.NextPage {
			break
		}
		sp.ListOptions.Page = resp.NextPage
	}

	if err := h.cache.Set(sp.SearchKey, &provider.Thing{Timeline: allEvents}); err != nil {
		klog.Errorf("set %q failed: %v", sp.SearchKey, err)
	}

	return allEvents, nil
}

// Add events to the conversation summary if useful
func (h *Engine) addEvents(ctx context.Context, sp provider.SearchParams, co *Conversation, timeline []*provider.Timeline) {
	priority := ""
	for _, l := range co.Labels {
		if strings.HasPrefix(l.GetName(), "priority") {
			klog.V(1).Infof("found priority: %s", l.GetName())
			priority = l.GetName()
			break
		}
	}
	assignedTo := map[string]bool{}
	for _, a := range co.Assignees {
		assignedTo[a.GetLogin()] = true
	}

	thisRepo := fmt.Sprintf("%s/%s", co.Organization, co.Project)

	for _, t := range timeline {
		if h.debug[co.ID] {
			klog.Errorf("debug timeline event %q: %s", t.GetEvent(), formatStruct(t))
		}

		if t.GetEvent() == "labeled" && t.GetLabel().GetName() == priority {
			co.Prioritized = t.GetCreatedAt()
		}

		if t.GetEvent() == "cross-referenced" {
			if assignedTo[t.GetActor().GetLogin()] {
				if t.GetCreatedAt().After(co.LatestAssigneeResponse) {
					co.LatestAssigneeResponse = t.GetCreatedAt()
					co.Tags[tag.AssigneeUpdated] = true
				}
			}

			ri := t.GetSource().GetIssue()

			// Push the item timestamps as far forwards as possible for the best possible timeline fetch
			h.updateCoMtime(co, t.GetCreatedAt())
			h.updateCoMtime(co, ri.GetUpdatedAt())
			h.updateMtime(ri, t.GetCreatedAt())
			h.updateMtime(ri, ri.GetUpdatedAt())
			h.updateMtime(ri, co.Updated)

			if co.Type == Issue && ri.IsPullRequest() {
				refRepo := ri.GetRepository().GetFullName()
				// Filter out PR's that are part of other repositories for now
				if refRepo != thisRepo {
					klog.V(1).Infof("PR#%d is in %s, rather than %s", ri.GetNumber(), refRepo, thisRepo)
					continue
				}

				klog.V(1).Infof("Found cross-referenced PR: #%d, updating PR ref", ri.GetNumber())

				sp.Age = h.mtimeCo(co)

				ref := h.prRef(ctx, sp, ri)
				co.UpdatePullRequestRefs(ref)
				refTag := reviewStateTag(ref.ReviewState)
				refTag.ID = fmt.Sprintf("pr-%s", refTag.ID)
				refTag.Desc = fmt.Sprintf("cross-referenced PR: %s", refTag.Desc)
				co.Tags[refTag] = true
			} else {
				co.UpdateIssueRefs(h.issueRef(t.GetSource().GetIssue(), co.Seen))
			}
		}
	}
}

func (h *Engine) prRef(ctx context.Context, sp provider.SearchParams, pr provider.IItem) *RelatedConversation {
	if pr == nil {
		klog.Errorf("PR is nil")
		return nil
	}

	sp.NewerThan = sp.Age
	if h.mtime(pr).After(sp.NewerThan) {
		sp.NewerThan = h.mtime(pr)
	}

	if !pr.GetClosedAt().IsZero() {
		sp.NewerThan = pr.GetClosedAt()
	}

	klog.V(1).Infof("Creating PR reference for #%d, updated at %s(state=%s)", pr.GetNumber(), pr.GetUpdatedAt(), pr.GetState())

	co := h.createConversation(pr, nil, sp.Age)
	rel := makeRelated(co)

	sp.Repo.Organization = co.Organization
	sp.Repo.Project = co.Project
	sp.IssueNumber = pr.GetNumber()

	timeline, err := h.cachedTimeline(ctx, sp)
	if err != nil {
		klog.Errorf("timeline: %v", err)
	}

	// mtime may have been updated by fetching tthe timeline
	if h.mtime(pr).After(sp.NewerThan) {
		sp.NewerThan = h.mtime(pr)
	}

	var reviews []*provider.PullRequestReview
	if pr.GetState() != "closed" {
		reviews, _, err = h.cachedReviews(ctx, sp)
		if err != nil {
			klog.Errorf("reviews: %v", err)
		}
	} else {
		klog.V(1).Infof("PR #%d is closed, won't fetch review state", pr.GetNumber())
	}

	rel.ReviewState = reviewState(pr, timeline, reviews)
	klog.V(1).Infof("Determined PR #%d to be in review state %q", pr.GetNumber(), rel.ReviewState)
	return rel
}

func (h *Engine) updateLinkedPRs(ctx context.Context, sp provider.SearchParams, parent *Conversation) []*RelatedConversation {
	newRefs := []*RelatedConversation{}

	for _, ref := range parent.PullRequestRefs {
		if h.mtimeRef(ref).After(sp.NewerThan) {
			sp.NewerThan = h.mtimeRef(ref)
		}
	}

	for _, ref := range parent.PullRequestRefs {
		if sp.NewerThan.Before(ref.Seen) || sp.NewerThan == ref.Seen {
			newRefs = append(newRefs, ref)
			continue
		}

		klog.V(1).Infof("updating PR ref: %s/%s #%d from %s to %s",
			ref.Organization, ref.Project, ref.ID, ref.Seen, sp.NewerThan)

		sp.Repo.Organization = ref.Organization
		sp.Repo.Project = ref.Project
		sp.IssueNumber = ref.ID

		pr, age, err := h.cachedPR(ctx, sp)
		if err != nil {
			klog.Errorf("error updating cached PR: %v", err)
			newRefs = append(newRefs, ref)
			continue
		}
		// unable to fetch
		if pr == nil {
			klog.Warningf("Unable to update PR ref for %s/%s #%d (data not yet available)", ref.Organization, ref.Project, ref.ID)
			newRefs = append(newRefs, ref)
			continue
		}

		sp.Age = age

		newRefs = append(newRefs, h.prRef(ctx, sp, pr))
	}

	return newRefs
}

func (h *Engine) issueRef(i *provider.Issue, age time.Time) *RelatedConversation {
	co := h.createConversation(i, nil, age)
	return makeRelated(co)
}
<|endoftext|>


<commit_before>// Copyright 2016 The prometheus-operator 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 k8sutil

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"

	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
	"github.com/hashicorp/go-version"
	"github.com/pkg/errors"
	yaml "gopkg.in/yaml.v2"
	v1 "k8s.io/api/core/v1"
	extensionsobj "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/validation"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/discovery"
	clientv1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// KubeConfigEnv (optionally) specify the location of kubeconfig file
const KubeConfigEnv = "KUBECONFIG"

var invalidDNS1123Characters = regexp.MustCompile("[^-a-z0-9]+")

// CustomResourceDefinitionTypeMeta set the default kind/apiversion of CRD
var CustomResourceDefinitionTypeMeta metav1.TypeMeta = metav1.TypeMeta{
	Kind:       "CustomResourceDefinition",
	APIVersion: "apiextensions.k8s.io/v1beta1",
}

// WaitForCRDReady waits for a custom resource definition to be available for use.
func WaitForCRDReady(listFunc func(opts metav1.ListOptions) (runtime.Object, error)) error {
	err := wait.Poll(3*time.Second, 10*time.Minute, func() (bool, error) {
		_, err := listFunc(metav1.ListOptions{})
		if err != nil {
			if se, ok := err.(*apierrors.StatusError); ok {
				if se.Status().Code == http.StatusNotFound {
					return false, nil
				}
			}
			return false, errors.Wrap(err, "failed to list CRD")
		}
		return true, nil
	})

	return errors.Wrap(err, fmt.Sprintf("timed out waiting for Custom Resource"))
}

// PodRunningAndReady returns whether a pod is running and each container has
// passed it's ready state.
func PodRunningAndReady(pod v1.Pod) (bool, error) {
	switch pod.Status.Phase {
	case v1.PodFailed, v1.PodSucceeded:
		return false, fmt.Errorf("pod completed")
	case v1.PodRunning:
		for _, cond := range pod.Status.Conditions {
			if cond.Type != v1.PodReady {
				continue
			}
			return cond.Status == v1.ConditionTrue, nil
		}
		return false, fmt.Errorf("pod ready condition not found")
	}
	return false, nil
}

func NewClusterConfig(host string, tlsInsecure bool, tlsConfig *rest.TLSClientConfig) (*rest.Config, error) {
	var cfg *rest.Config
	var err error

	kubeconfigFile := os.Getenv(KubeConfigEnv)
	if kubeconfigFile != "" {
		cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfigFile)
		if err != nil {
			return nil, fmt.Errorf("Error creating config from specified file: %s %v\n", kubeconfigFile, err)
		}
	} else {
		if len(host) == 0 {
			if cfg, err = rest.InClusterConfig(); err != nil {
				return nil, err
			}
		} else {
			cfg = &rest.Config{
				Host: host,
			}
			hostURL, err := url.Parse(host)
			if err != nil {
				return nil, fmt.Errorf("error parsing host url %s : %v", host, err)
			}
			if hostURL.Scheme == "https" {
				cfg.TLSClientConfig = *tlsConfig
				cfg.Insecure = tlsInsecure
			}
		}
	}

	cfg.QPS = 100
	cfg.Burst = 100

	return cfg, nil
}

func IsResourceNotFoundError(err error) bool {
	se, ok := err.(*apierrors.StatusError)
	if !ok {
		return false
	}
	if se.Status().Code == http.StatusNotFound && se.Status().Reason == metav1.StatusReasonNotFound {
		return true
	}
	return false
}

func CreateOrUpdateService(sclient clientv1.ServiceInterface, svc *v1.Service) error {
	service, err := sclient.Get(svc.Name, metav1.GetOptions{})
	if err != nil && !apierrors.IsNotFound(err) {
		return errors.Wrap(err, "retrieving service object failed")
	}

	if apierrors.IsNotFound(err) {
		_, err = sclient.Create(svc)
		if err != nil {
			return errors.Wrap(err, "creating service object failed")
		}
	} else {
		svc.ResourceVersion = service.ResourceVersion
		svc.SetOwnerReferences(mergeOwnerReferences(service.GetOwnerReferences(), svc.GetOwnerReferences()))
		_, err := sclient.Update(svc)
		if err != nil && !apierrors.IsNotFound(err) {
			return errors.Wrap(err, "updating service object failed")
		}
	}

	return nil
}

func CreateOrUpdateEndpoints(eclient clientv1.EndpointsInterface, eps *v1.Endpoints) error {
	endpoints, err := eclient.Get(eps.Name, metav1.GetOptions{})
	if err != nil && !apierrors.IsNotFound(err) {
		return errors.Wrap(err, "retrieving existing kubelet endpoints object failed")
	}

	if apierrors.IsNotFound(err) {
		_, err = eclient.Create(eps)
		if err != nil {
			return errors.Wrap(err, "creating kubelet endpoints object failed")
		}
	} else {
		eps.ResourceVersion = endpoints.ResourceVersion
		_, err = eclient.Update(eps)
		if err != nil {
			return errors.Wrap(err, "updating kubelet endpoints object failed")
		}
	}

	return nil
}

// GetMinorVersion returns the minor version as an integer
func GetMinorVersion(dclient discovery.DiscoveryInterface) (int, error) {
	v, err := dclient.ServerVersion()
	if err != nil {
		return 0, err
	}

	ver, err := version.NewVersion(v.String())
	if err != nil {
		return 0, err
	}

	return ver.Segments()[1], nil
}

// NewCustomResourceDefinition creates a CustomResourceDefinition by unmarshalling
// the associated yaml asset
func NewCustomResourceDefinition(crdKind monitoringv1.CrdKind, group string, labels map[string]string, validation bool) *extensionsobj.CustomResourceDefinition {
	crdName := strings.ToLower(crdKind.Plural)
	assetPath := "example/prometheus-operator-crd/" + group + "_" + crdName + ".yaml"
	data := monitoringv1.MustAsset(assetPath)
	crd := &extensionsobj.CustomResourceDefinition{}
	err := yaml.Unmarshal(data, crd)
	if err != nil {
		panic("unable to unmarshal crd asset for " + assetPath + ": " + err.Error())
	}
	crd.ObjectMeta.Name = crd.Spec.Names.Plural + "." + crd.Spec.Group
	crd.ObjectMeta.Labels = labels
	crd.Spec.Group = group
	return crd
}

// SanitizeVolumeName ensures that the given volume name is a valid DNS-1123 label
// accepted by Kubernetes.
func SanitizeVolumeName(name string) string {
	name = strings.ToLower(name)
	name = invalidDNS1123Characters.ReplaceAllString(name, "-")
	if len(name) > validation.DNS1123LabelMaxLength {
		name = name[0:validation.DNS1123LabelMaxLength]
	}
	return strings.Trim(name, "-")
}

func mergeOwnerReferences(old []metav1.OwnerReference, new []metav1.OwnerReference) []metav1.OwnerReference {
	existing := make(map[metav1.OwnerReference]bool)
	for _, ownerRef := range old {
		existing[ownerRef] = true
	}
	for _, ownerRef := range new {
		if _, ok := existing[ownerRef]; !ok {
			old = append(old, ownerRef)
		}
	}
	return old
}
<commit_msg>use ghodss/yaml for proper CRD unmarshalling<commit_after>// Copyright 2016 The prometheus-operator 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 k8sutil

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"

	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
	yaml "github.com/ghodss/yaml"
	"github.com/hashicorp/go-version"
	"github.com/pkg/errors"
	v1 "k8s.io/api/core/v1"
	extensionsobj "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/validation"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/discovery"
	clientv1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// KubeConfigEnv (optionally) specify the location of kubeconfig file
const KubeConfigEnv = "KUBECONFIG"

var invalidDNS1123Characters = regexp.MustCompile("[^-a-z0-9]+")

// CustomResourceDefinitionTypeMeta set the default kind/apiversion of CRD
var CustomResourceDefinitionTypeMeta metav1.TypeMeta = metav1.TypeMeta{
	Kind:       "CustomResourceDefinition",
	APIVersion: "apiextensions.k8s.io/v1beta1",
}

// WaitForCRDReady waits for a custom resource definition to be available for use.
func WaitForCRDReady(listFunc func(opts metav1.ListOptions) (runtime.Object, error)) error {
	err := wait.Poll(3*time.Second, 10*time.Minute, func() (bool, error) {
		_, err := listFunc(metav1.ListOptions{})
		if err != nil {
			if se, ok := err.(*apierrors.StatusError); ok {
				if se.Status().Code == http.StatusNotFound {
					return false, nil
				}
			}
			return false, errors.Wrap(err, "failed to list CRD")
		}
		return true, nil
	})

	return errors.Wrap(err, fmt.Sprintf("timed out waiting for Custom Resource"))
}

// PodRunningAndReady returns whether a pod is running and each container has
// passed it's ready state.
func PodRunningAndReady(pod v1.Pod) (bool, error) {
	switch pod.Status.Phase {
	case v1.PodFailed, v1.PodSucceeded:
		return false, fmt.Errorf("pod completed")
	case v1.PodRunning:
		for _, cond := range pod.Status.Conditions {
			if cond.Type != v1.PodReady {
				continue
			}
			return cond.Status == v1.ConditionTrue, nil
		}
		return false, fmt.Errorf("pod ready condition not found")
	}
	return false, nil
}

func NewClusterConfig(host string, tlsInsecure bool, tlsConfig *rest.TLSClientConfig) (*rest.Config, error) {
	var cfg *rest.Config
	var err error

	kubeconfigFile := os.Getenv(KubeConfigEnv)
	if kubeconfigFile != "" {
		cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfigFile)
		if err != nil {
			return nil, fmt.Errorf("Error creating config from specified file: %s %v\n", kubeconfigFile, err)
		}
	} else {
		if len(host) == 0 {
			if cfg, err = rest.InClusterConfig(); err != nil {
				return nil, err
			}
		} else {
			cfg = &rest.Config{
				Host: host,
			}
			hostURL, err := url.Parse(host)
			if err != nil {
				return nil, fmt.Errorf("error parsing host url %s : %v", host, err)
			}
			if hostURL.Scheme == "https" {
				cfg.TLSClientConfig = *tlsConfig
				cfg.Insecure = tlsInsecure
			}
		}
	}

	cfg.QPS = 100
	cfg.Burst = 100

	return cfg, nil
}

func IsResourceNotFoundError(err error) bool {
	se, ok := err.(*apierrors.StatusError)
	if !ok {
		return false
	}
	if se.Status().Code == http.StatusNotFound && se.Status().Reason == metav1.StatusReasonNotFound {
		return true
	}
	return false
}

func CreateOrUpdateService(sclient clientv1.ServiceInterface, svc *v1.Service) error {
	service, err := sclient.Get(svc.Name, metav1.GetOptions{})
	if err != nil && !apierrors.IsNotFound(err) {
		return errors.Wrap(err, "retrieving service object failed")
	}

	if apierrors.IsNotFound(err) {
		_, err = sclient.Create(svc)
		if err != nil {
			return errors.Wrap(err, "creating service object failed")
		}
	} else {
		svc.ResourceVersion = service.ResourceVersion
		svc.SetOwnerReferences(mergeOwnerReferences(service.GetOwnerReferences(), svc.GetOwnerReferences()))
		_, err := sclient.Update(svc)
		if err != nil && !apierrors.IsNotFound(err) {
			return errors.Wrap(err, "updating service object failed")
		}
	}

	return nil
}

func CreateOrUpdateEndpoints(eclient clientv1.EndpointsInterface, eps *v1.Endpoints) error {
	endpoints, err := eclient.Get(eps.Name, metav1.GetOptions{})
	if err != nil && !apierrors.IsNotFound(err) {
		return errors.Wrap(err, "retrieving existing kubelet endpoints object failed")
	}

	if apierrors.IsNotFound(err) {
		_, err = eclient.Create(eps)
		if err != nil {
			return errors.Wrap(err, "creating kubelet endpoints object failed")
		}
	} else {
		eps.ResourceVersion = endpoints.ResourceVersion
		_, err = eclient.Update(eps)
		if err != nil {
			return errors.Wrap(err, "updating kubelet endpoints object failed")
		}
	}

	return nil
}

// GetMinorVersion returns the minor version as an integer
func GetMinorVersion(dclient discovery.DiscoveryInterface) (int, error) {
	v, err := dclient.ServerVersion()
	if err != nil {
		return 0, err
	}

	ver, err := version.NewVersion(v.String())
	if err != nil {
		return 0, err
	}

	return ver.Segments()[1], nil
}

// NewCustomResourceDefinition creates a CustomResourceDefinition by unmarshalling
// the associated yaml asset
func NewCustomResourceDefinition(crdKind monitoringv1.CrdKind, group string, labels map[string]string, validation bool) *extensionsobj.CustomResourceDefinition {
	crdName := strings.ToLower(crdKind.Plural)
	assetPath := "example/prometheus-operator-crd/" + group + "_" + crdName + ".yaml"
	data := monitoringv1.MustAsset(assetPath)
	crd := &extensionsobj.CustomResourceDefinition{}
	err := yaml.Unmarshal(data, crd)
	if err != nil {
		panic("unable to unmarshal crd asset for " + assetPath + ": " + err.Error())
	}
	crd.ObjectMeta.Name = crd.Spec.Names.Plural + "." + group
	crd.ObjectMeta.Labels = labels
	crd.Spec.Group = group
	return crd
}

// SanitizeVolumeName ensures that the given volume name is a valid DNS-1123 label
// accepted by Kubernetes.
func SanitizeVolumeName(name string) string {
	name = strings.ToLower(name)
	name = invalidDNS1123Characters.ReplaceAllString(name, "-")
	if len(name) > validation.DNS1123LabelMaxLength {
		name = name[0:validation.DNS1123LabelMaxLength]
	}
	return strings.Trim(name, "-")
}

func mergeOwnerReferences(old []metav1.OwnerReference, new []metav1.OwnerReference) []metav1.OwnerReference {
	existing := make(map[metav1.OwnerReference]bool)
	for _, ownerRef := range old {
		existing[ownerRef] = true
	}
	for _, ownerRef := range new {
		if _, ok := existing[ownerRef]; !ok {
			old = append(old, ownerRef)
		}
	}
	return old
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package kepval

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/blang/semver/v4"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"k8s.io/enhancements/api"
)

func ValidatePRR(kep *api.Proposal, h *api.PRRHandler, prrDir string) error {
	requiredPRRApproval, _, err := isPRRRequired(kep)
	if err != nil {
		return errors.Wrap(err, "checking if PRR is required")
	}

	if !requiredPRRApproval {
		logrus.Debugf("PRR review is not required for %s", kep.Number)
		return nil
	}

	prrFilename := kep.Number + ".yaml"
	prrFilepath := filepath.Join(
		prrDir,
		kep.OwningSIG,
		prrFilename,
	)

	logrus.Infof("PRR file: %s", prrFilepath)

	prrFile, err := os.Open(prrFilepath)
	if os.IsNotExist(err) {
		// TODO: Is this actually the error we want to return here?
		return err //needsPRRApproval(stageMilestone, kep.Stage, prrFilepath)
	}

	if err != nil {
		return errors.Wrapf(err, "could not open file %s", prrFilepath)
	}

	// TODO: Create a context to hold the parsers
	prr, prrParseErr := h.Parse(prrFile)
	if prrParseErr != nil {
		return errors.Wrap(prrParseErr, "parsing PRR approval file")
	}

	// TODO: This shouldn't be required once we push the errors into the
	//       parser struct
	if prr.Error != nil {
		return errors.Wrapf(prr.Error, "%v has an error", prrFilepath)
	}

	// TODO: Check for edge cases
	var stageMilestone string
	switch kep.Stage {
	case "alpha":
		stageMilestone = kep.Milestone.Alpha
	case "beta":
		stageMilestone = kep.Milestone.Beta
	case "stable":
		stageMilestone = kep.Milestone.Stable
	}

	stagePRRApprover := prr.ApproverForStage(stageMilestone)
	validApprover := api.IsOneOf(stagePRRApprover, h.PRRApprovers)
	if !validApprover {
		return errors.New(
			fmt.Sprintf(
				"this contributor (%s) is not a PRR approver (%v)",
				stagePRRApprover,
				h.PRRApprovers,
			),
		)
	}

	return nil
}

func isPRRRequired(kep *api.Proposal) (required, missingMilestone bool, err error) {
	logrus.Infof("checking if PRR is required")

	required = true
	missingMilestone = kep.IsMissingMilestone()

	if kep.Status != "implementable" {
		required = false
		return required, missingMilestone, nil
	}

	if missingMilestone {
		required = false
		logrus.Warnf("KEP %s is missing the latest milestone field. This will become a validation error in future releases.", kep.Number)

		return required, missingMilestone, nil
	}

	// TODO: Consider making this a function
	prrRequiredAtSemVer, err := semver.ParseTolerant("v1.21")
	if err != nil {
		return required, missingMilestone, errors.Wrap(err, "creating a SemVer object for PRRs")
	}

	latestSemVer, err := semver.ParseTolerant(kep.LatestMilestone)
	if err != nil {
		return required, missingMilestone, errors.Wrap(err, "creating a SemVer object for latest milestone")
	}

	if latestSemVer.LT(prrRequiredAtSemVer) {
		required = false
		return required, missingMilestone, nil
	}

	return required, missingMilestone, nil
}
<commit_msg>kepval: Fix PRR approver search<commit_after>/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package kepval

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/blang/semver/v4"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"k8s.io/enhancements/api"
)

func ValidatePRR(kep *api.Proposal, h *api.PRRHandler, prrDir string) error {
	requiredPRRApproval, _, err := isPRRRequired(kep)
	if err != nil {
		return errors.Wrap(err, "checking if PRR is required")
	}

	if !requiredPRRApproval {
		logrus.Debugf("PRR review is not required for %s", kep.Number)
		return nil
	}

	prrFilename := kep.Number + ".yaml"
	prrFilepath := filepath.Join(
		prrDir,
		kep.OwningSIG,
		prrFilename,
	)

	logrus.Infof("PRR file: %s", prrFilepath)

	prrFile, err := os.Open(prrFilepath)
	if os.IsNotExist(err) {
		// TODO: Is this actually the error we want to return here?
		return err //needsPRRApproval(stageMilestone, kep.Stage, prrFilepath)
	}

	if err != nil {
		return errors.Wrapf(err, "could not open file %s", prrFilepath)
	}

	// TODO: Create a context to hold the parsers
	prr, prrParseErr := h.Parse(prrFile)
	if prrParseErr != nil {
		return errors.Wrap(prrParseErr, "parsing PRR approval file")
	}

	// TODO: This shouldn't be required once we push the errors into the
	//       parser struct
	if prr.Error != nil {
		return errors.Wrapf(prr.Error, "%v has an error", prrFilepath)
	}

	stagePRRApprover := prr.ApproverForStage(kep.Stage)
	validApprover := api.IsOneOf(stagePRRApprover, h.PRRApprovers)
	if !validApprover {
		return errors.New(
			fmt.Sprintf(
				"this contributor (%s) is not a PRR approver (%v)",
				stagePRRApprover,
				h.PRRApprovers,
			),
		)
	}

	return nil
}

func isPRRRequired(kep *api.Proposal) (required, missingMilestone bool, err error) {
	logrus.Debug("checking if PRR is required")

	required = true
	missingMilestone = kep.IsMissingMilestone()

	if kep.Status != "implementable" {
		required = false
		return required, missingMilestone, nil
	}

	if missingMilestone {
		required = false
		logrus.Warnf("KEP %s is missing the latest milestone field. This will become a validation error in future releases.", kep.Number)

		return required, missingMilestone, nil
	}

	// TODO: Consider making this a function
	prrRequiredAtSemVer, err := semver.ParseTolerant("v1.21")
	if err != nil {
		return required, missingMilestone, errors.Wrap(err, "creating a SemVer object for PRRs")
	}

	latestSemVer, err := semver.ParseTolerant(kep.LatestMilestone)
	if err != nil {
		return required, missingMilestone, errors.Wrap(err, "creating a SemVer object for latest milestone")
	}

	if latestSemVer.LT(prrRequiredAtSemVer) {
		required = false
		return required, missingMilestone, nil
	}

	return required, missingMilestone, nil
}
<|endoftext|>