<commit_before>package alg

import (
	"graph"
	"container/heap"
	"container/list"
)

func isInList(w []graph.Way, s graph.Node) bool {
	for _, way := range w {
		if s == way.Node() {
			return true
		}
	}
	return false
}

// A slightly optimized version of dijkstras algorithm
// Takes an graph as argument and returns an list of vertices in order
// of the path
func Dijkstra(s, t []graph.Way) (float64, *list.List, *list.List) {
	d := make(map[graph.Node]float64)                // I assume distance is an integer
	p := make(map[graph.Node]graph.Node)               // Predecessor list
	ep := make(map[graph.Node]graph.Edge) // Edge Predecessors
	q := NewPriorityQueue(100 /* initialCapacity */) // 100 is just a first guess
	final := make(map[graph.Node]bool)
	for _, tar := range t {
		final[tar.Node()] = false
	}
	for _, str := range s {
		priority := str.Length()
		x := NewElement(str.Node(), int(priority)) // TODO check this cast
		heap.Push(&q, x)
		d[str.Node()] = priority
	}
	for !q.Empty() {
		currElem := (heap.Pop(&q)).(*Element) // Get the first element
		curr := currElem.Value.(graph.Node)            // Unbox the node
		if isfinal, _ := final[curr]; isfinal {
			final[curr] = true
			finished := true
			for _, node := range final {
				finished = finished && node
			}
			if !finished {
				break
			}
		}
		currDist := d[curr]
		for _, e := range curr.Edges() {
			n := e.EndPoint()
			if dist, ok := d[n]; ok {
				if tmpDist := currDist + e.Length(); tmpDist < dist {
					q.ChangePriority(currElem, int(tmpDist)) // TODO again check cast
					d[n] = tmpDist
					p[n] = curr
					ep[n]=e
				}
			} else {
				d[n] = currDist + e.Length()
				p[n] = curr
				elem := NewElement(n, int(currDist))  // TODO again check cast
				heap.Push(&q, elem)
			}
		}
	}
	path := list.New()
	edges := list.New()
	// Construct the list by moving from t to s
	first := true
	var curr graph.Node
	var currdist float64
	for _, targetnode := range t {
		tmpnode := targetnode.Node()
		tmpdist := d[tmpnode] + targetnode.Length()
		if first {
			curr = tmpnode
			currdist = tmpdist
		} else {
			if tmpdist<currdist {
				curr = tmpnode
				currdist = tmpdist
			}
		}
	}
	for isInList(s, curr) {
		path.PushFront(curr)
		curr = p[curr]
		edges.PushFront(p[curr])
	}
	path.PushFront(p[curr])
	// TODO fix, t[0] is not necessarily optimal
	return d[t[0].Node()], path, edges
}
<commit_msg>adjusted dijkstra<commit_after>package alg

import (
	"graph"
	"container/heap"
	"container/list"
)

func isInList(w []graph.Way, s graph.Node) bool {
	for _, way := range w {
		if s == way.Node {
			return true
		}
	}
	return false
}

// A slightly optimized version of dijkstras algorithm
// Takes an graph as argument and returns an list of vertices in order
// of the path
func Dijkstra(s, t []graph.Way) (float64, *list.List, *list.List) {
	d := make(map[graph.Node]float64)                // I assume distance is an integer
	p := make(map[graph.Node]graph.Node)               // Predecessor list
	ep := make(map[graph.Node]graph.Edge) // Edge Predecessors
	q := NewPriorityQueue(100 /* initialCapacity */) // 100 is just a first guess
	final := make(map[graph.Node]bool)
	for _, tar := range t {
		final[tar.Node] = false
	}
	for _, str := range s {
		priority := str.Length
		x := NewElement(str.Node, int(priority)) // TODO check this cast
		heap.Push(&q, x)
		d[str.Node] = priority
	}
	for !q.Empty() {
		currElem := (heap.Pop(&q)).(*Element) // Get the first element
		curr := currElem.Value.(graph.Node)            // Unbox the node
		if isfinal, _ := final[curr]; isfinal {
			final[curr] = true
			finished := true
			for _, node := range final {
				finished = finished && node
			}
			if !finished {
				break
			}
		}
		currDist := d[curr]
		for _, e := range curr.Edges() {
			n := e.EndPoint()
			if dist, ok := d[n]; ok {
				if tmpDist := currDist + e.Length(); tmpDist < dist {
					q.ChangePriority(currElem, int(tmpDist)) // TODO again check cast
					d[n] = tmpDist
					p[n] = curr
					ep[n]=e
				}
			} else {
				d[n] = currDist + e.Length()
				p[n] = curr
				elem := NewElement(n, int(currDist))  // TODO again check cast
				heap.Push(&q, elem)
			}
		}
	}
	path := list.New()
	edges := list.New()
	// Construct the list by moving from t to s
	first := true
	var curr graph.Node
	var currdist float64
	for _, targetnode := range t {
		tmpnode := targetnode.Node
		tmpdist := d[tmpnode] + targetnode.Length
		if first {
			curr = tmpnode
			currdist = tmpdist
		} else {
			if tmpdist<currdist {
				curr = tmpnode
				currdist = tmpdist
			}
		}
	}
	for isInList(s, curr) {
		path.PushFront(curr)
		curr = p[curr]
		edges.PushFront(p[curr])
	}
	path.PushFront(p[curr])
	// TODO fix, t[0] is not necessarily optimal
	return d[t[0].Node], path, edges
}
<|endoftext|>


<commit_before>package chain

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"strings"
)

// Chain contains sentence fragments that can be recombined.
type Chain struct {
	Nodes     []*Node
	PrefixLen int `json:"prefix_len"`
}

type Node struct {
	Key       string
	Fragments []string
}

func NewChain(prefixLen int) *Chain {
	return &Chain{[]*Node{}, prefixLen}
}

// FindNode locates the node with the given key.
// If that node doesn't already exist, then a new node will
// be created for that key before it is returned.
func (c *Chain) FindNode(key string) *Node {
	for _, n := range c.Nodes {
		if n.Key == key {
			return n
		}
	}
	n := new(Node)
	n.Key = key
	c.Nodes = append(c.Nodes, n)
	return n
}

func (c *Chain) Build(r io.Reader) {
	br := bufio.NewReader(r)
	p := prefix(make([]string, c.PrefixLen))
	var s string
	for {
		if _, err := fmt.Fscan(br, &s); err != nil {
			break
		}
		key := p.key()
		node := c.FindNode(key)
		node.Fragments = append(node.Fragments, s)
		p.shift(s)
	}
}

func wordCount() int {
	numbers := []int{13, 21, 34, 55, 89, 144}
	return numbers[rand.Intn(len(numbers))]
}

func paragraphCount() int {
	numbers := []int{1, 2, 2, 3, 3, 3, 4, 4, 5}
	return numbers[rand.Intn(len(numbers))]
}

func (c *Chain) Generate() string {
	n := paragraphCount()
	paragraphs := make([]string, n, n)
	for i := 0; i < n; i++ {
		paragraphs = append(paragraphs, c.GenerateParagraph())
	}
	return strings.TrimLeft(strings.Join(paragraphs, "\n\n"), "\n ")
}

func completesSentence(s string) bool {
	return strings.LastIndexAny(s, "?!.") == len(s)-1
}

func (c *Chain) GenerateParagraph() string {
	p := make(prefix, c.PrefixLen)
	n := wordCount()

	var words []string
	for {
		choices := c.FindNode(p.key()).Fragments
		if len(choices) == 0 {
			break
		}
		next := choices[rand.Intn(len(choices))]
		words = append(words, next)
		p.shift(next)
		if len(choices) > n && completesSentence(next) {
			break
		}
	}
	return strings.Join(words, " ")
}

func (c Chain) ToFile(path string) {
	bytes, err := json.Marshal(c)
	if err != nil {
		fmt.Printf("Failed to marshal: %v\n", err)
	}

	err = ioutil.WriteFile(path, bytes, 0644)
	if err != nil {
		fmt.Printf("Unable to write to %s: %v\n", path, err)
	}
}

func FromFile(path string) (c Chain) {
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Printf("Unable to read file: %s\n", path)
		return
	}

	err = json.Unmarshal(bytes, &c)
	if err != nil {
		fmt.Printf("Cannot unmarshall: %v\n", err)
		return
	}
	return
}
<commit_msg>Introduce AppendNode method on Chain<commit_after>package chain

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"strings"
)

// Chain contains sentence fragments that can be recombined.
type Chain struct {
	Nodes     []*Node
	PrefixLen int `json:"prefix_len"`
}

type Node struct {
	Key       string
	Fragments []string
}

func NewChain(prefixLen int) *Chain {
	return &Chain{[]*Node{}, prefixLen}
}

// FindNode locates the node with the given key.
// If that node doesn't already exist, then a new node will
// be created for that key before it is returned.
func (c *Chain) FindNode(key string) *Node {
	for _, n := range c.Nodes {
		if n.Key == key {
			return n
		}
	}
	return nil
}

func (c *Chain) AppendFragment(key, fragment string) {
	node := c.FindNode(key)
	if node == nil {
		node = new(Node)
		node.Key = key
		c.Nodes = append(c.Nodes, node)
	}
	node.Fragments = append(node.Fragments, fragment)
}

func (c *Chain) Build(r io.Reader) {
	br := bufio.NewReader(r)
	p := prefix(make([]string, c.PrefixLen))
	var s string
	for {
		if _, err := fmt.Fscan(br, &s); err != nil {
			break
		}
		key := p.key()
		c.AppendFragment(key, s)
		p.shift(s)
	}
}

func wordCount() int {
	numbers := []int{13, 21, 34, 55, 89, 144}
	return numbers[rand.Intn(len(numbers))]
}

func paragraphCount() int {
	numbers := []int{1, 2, 2, 3, 3, 3, 4, 4, 5}
	return numbers[rand.Intn(len(numbers))]
}

func (c *Chain) Generate() string {
	n := paragraphCount()
	paragraphs := make([]string, n, n)
	for i := 0; i < n; i++ {
		paragraphs = append(paragraphs, c.GenerateParagraph())
	}
	return strings.TrimLeft(strings.Join(paragraphs, "\n\n"), "\n ")
}

func completesSentence(s string) bool {
	return strings.LastIndexAny(s, "?!.") == len(s)-1
}

func (c *Chain) GenerateParagraph() string {
	p := make(prefix, c.PrefixLen)
	n := wordCount()

	var words []string
	for {
		choices := c.FindNode(p.key()).Fragments
		if len(choices) == 0 {
			break
		}
		next := choices[rand.Intn(len(choices))]
		words = append(words, next)
		p.shift(next)
		if len(choices) > n && completesSentence(next) {
			break
		}
	}
	return strings.Join(words, " ")
}

func (c Chain) ToFile(path string) {
	bytes, err := json.Marshal(c)
	if err != nil {
		fmt.Printf("Failed to marshal: %v\n", err)
	}

	err = ioutil.WriteFile(path, bytes, 0644)
	if err != nil {
		fmt.Printf("Unable to write to %s: %v\n", path, err)
	}
}

func FromFile(path string) (c Chain) {
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Printf("Unable to read file: %s\n", path)
		return
	}

	err = json.Unmarshal(bytes, &c)
	if err != nil {
		fmt.Printf("Cannot unmarshall: %v\n", err)
		return
	}
	return
}
<|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 errors

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

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
)

// HTTP Status codes not in the golang http package.
const (
	StatusUnprocessableEntity = 422
	StatusTooManyRequests     = 429
	// HTTP recommendations are for servers to define 5xx error codes
	// for scenarios not covered by behavior. In this case, ServerTimeout
	// is an indication that a transient server error has occured and the
	// client *should* retry, with an optional Retry-After header to specify
	// the back off window.
	StatusServerTimeout = 504
)

// StatusError is an error intended for consumption by a REST API server; it can also be
// reconstructed by clients from a REST response. Public to allow easy type switches.
type StatusError struct {
	ErrStatus api.Status
}

var _ error = &StatusError{}

// Error implements the Error interface.
func (e *StatusError) Error() string {
	return e.ErrStatus.Message
}

// Status allows access to e's status without having to know the detailed workings
// of StatusError. Used by pkg/apiserver.
func (e *StatusError) Status() api.Status {
	return e.ErrStatus
}

// DebugError reports extended info about the error to debug output.
func (e *StatusError) DebugError() (string, []interface{}) {
	if out, err := json.MarshalIndent(e.ErrStatus, "", "  "); err == nil {
		return "server response object: %s", []interface{}{string(out)}
	}
	return "server response object: %#v", []interface{}{e.ErrStatus}
}

// UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
type UnexpectedObjectError struct {
	Object runtime.Object
}

// Error returns an error message describing 'u'.
func (u *UnexpectedObjectError) Error() string {
	return fmt.Sprintf("unexpected object: %v", u.Object)
}

// FromObject generates an StatusError from an api.Status, if that is the type of obj; otherwise,
// returns an UnexpecteObjectError.
func FromObject(obj runtime.Object) error {
	switch t := obj.(type) {
	case *api.Status:
		return &StatusError{*t}
	}
	return &UnexpectedObjectError{obj}
}

// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
func NewNotFound(kind, name string) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusNotFound,
		Reason: api.StatusReasonNotFound,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q not found", kind, name),
	}}
}

// NewAlreadyExists returns an error indicating the item requested exists by that identifier.
func NewAlreadyExists(kind, name string) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusConflict,
		Reason: api.StatusReasonAlreadyExists,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q already exists", kind, name),
	}}
}

// NewUnauthorized returns an error indicating the client is not authorized to perform the requested
// action.
func NewUnauthorized(reason string) error {
	message := reason
	if len(message) == 0 {
		message = "not authorized"
	}
	return &StatusError{api.Status{
		Status:  api.StatusFailure,
		Code:    http.StatusUnauthorized,
		Reason:  api.StatusReasonUnauthorized,
		Message: message,
	}}
}

// NewForbidden returns an error indicating the requested action was forbidden
func NewForbidden(kind, name string, err error) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusForbidden,
		Reason: api.StatusReasonForbidden,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q is forbidden: %v", kind, name, err),
	}}
}

// NewConflict returns an error indicating the item can't be updated as provided.
func NewConflict(kind, name string, err error) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusConflict,
		Reason: api.StatusReasonConflict,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q cannot be updated: %v", kind, name, err),
	}}
}

// NewInvalid returns an error indicating the item is invalid and cannot be processed.
func NewInvalid(kind, name string, errs fielderrors.ValidationErrorList) error {
	causes := make([]api.StatusCause, 0, len(errs))
	for i := range errs {
		if err, ok := errs[i].(*fielderrors.ValidationError); ok {
			causes = append(causes, api.StatusCause{
				Type:    api.CauseType(err.Type),
				Message: err.Error(),
				Field:   err.Field,
			})
		}
	}
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   StatusUnprocessableEntity, // RFC 4918: StatusUnprocessableEntity
		Reason: api.StatusReasonInvalid,
		Details: &api.StatusDetails{
			Kind:   kind,
			ID:     name,
			Causes: causes,
		},
		Message: fmt.Sprintf("%s %q is invalid: %v", kind, name, errors.NewAggregate(errs)),
	}}
}

// NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
func NewBadRequest(reason string) error {
	return &StatusError{api.Status{
		Status:  api.StatusFailure,
		Code:    http.StatusBadRequest,
		Reason:  api.StatusReasonBadRequest,
		Message: reason,
	}}
}

// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
func NewMethodNotSupported(kind, action string) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusMethodNotAllowed,
		Reason: api.StatusReasonMethodNotAllowed,
		Details: &api.StatusDetails{
			Kind: kind,
		},
		Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, kind),
	}}
}

// NewServerTimeout returns an error indicating the requested action could not be completed due to a
// transient error, and the client should try again.
func NewServerTimeout(kind, operation string, retryAfterSeconds int) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusInternalServerError,
		Reason: api.StatusReasonServerTimeout,
		Details: &api.StatusDetails{
			Kind:              kind,
			ID:                operation,
			RetryAfterSeconds: retryAfterSeconds,
		},
		Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, kind),
	}}
}

// NewInternalError returns an error indicating the item is invalid and cannot be processed.
func NewInternalError(err error) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusInternalServerError,
		Reason: api.StatusReasonInternalError,
		Details: &api.StatusDetails{
			Causes: []api.StatusCause{{Message: err.Error()}},
		},
		Message: fmt.Sprintf("Internal error occurred: %v", err),
	}}
}

// NewTimeoutError returns an error indicating that a timeout occurred before the request
// could be completed.  Clients may retry, but the operation may still complete.
func NewTimeoutError(message string, retryAfterSeconds int) error {
	return &StatusError{api.Status{
		Status:  api.StatusFailure,
		Code:    StatusServerTimeout,
		Reason:  api.StatusReasonTimeout,
		Message: fmt.Sprintf("Timeout: %s", message),
		Details: &api.StatusDetails{
			RetryAfterSeconds: retryAfterSeconds,
		},
	}}
}

// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
func NewGenericServerResponse(code int, verb, kind, name, serverMessage string, retryAfterSeconds int) error {
	reason := api.StatusReasonUnknown
	message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
	switch code {
	case http.StatusConflict:
		if verb == "POST" {
			reason = api.StatusReasonAlreadyExists
		} else {
			reason = api.StatusReasonConflict
		}
		message = "the server reported a conflict"
	case http.StatusNotFound:
		reason = api.StatusReasonNotFound
		message = "the server could not find the requested resource"
	case http.StatusBadRequest:
		reason = api.StatusReasonBadRequest
		message = "the server rejected our request for an unknown reason"
	case http.StatusUnauthorized:
		reason = api.StatusReasonUnauthorized
		message = "the server has asked for the client to provide credentials"
	case http.StatusForbidden:
		reason = api.StatusReasonForbidden
		message = "the server does not allow access to the requested resource"
	case StatusUnprocessableEntity:
		reason = api.StatusReasonInvalid
		message = "the server rejected our request due to an error in our request"
	case StatusServerTimeout:
		reason = api.StatusReasonServerTimeout
		message = "the server cannot complete the requested operation at this time, try again later"
	case StatusTooManyRequests:
		reason = api.StatusReasonTimeout
		message = "the server has received too many requests and has asked us to try again later"
	default:
		if code >= 500 {
			reason = api.StatusReasonInternalError
			message = "an error on the server has prevented the request from succeeding"
		}
	}
	switch {
	case len(kind) > 0 && len(name) > 0:
		message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), kind, name)
	case len(kind) > 0:
		message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), kind)
	}
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   code,
		Reason: reason,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,

			Causes: []api.StatusCause{
				{
					Type:    api.CauseTypeUnexpectedServerResponse,
					Message: serverMessage,
				},
			},

			RetryAfterSeconds: retryAfterSeconds,
		},
		Message: message,
	}}
}

// IsNotFound returns true if the specified error was created by NewNotFoundErr.
func IsNotFound(err error) bool {
	return reasonForError(err) == api.StatusReasonNotFound
}

// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
func IsAlreadyExists(err error) bool {
	return reasonForError(err) == api.StatusReasonAlreadyExists
}

// IsConflict determines if the err is an error which indicates the provided update conflicts.
func IsConflict(err error) bool {
	return reasonForError(err) == api.StatusReasonConflict
}

// IsInvalid determines if the err is an error which indicates the provided resource is not valid.
func IsInvalid(err error) bool {
	return reasonForError(err) == api.StatusReasonInvalid
}

// IsMethodNotSupported determines if the err is an error which indicates the provided action could not
// be performed because it is not supported by the server.
func IsMethodNotSupported(err error) bool {
	return reasonForError(err) == api.StatusReasonMethodNotAllowed
}

// IsBadRequest determines if err is an error which indicates that the request is invalid.
func IsBadRequest(err error) bool {
	return reasonForError(err) == api.StatusReasonBadRequest
}

// IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
// requires authentication by the user.
func IsUnauthorized(err error) bool {
	return reasonForError(err) == api.StatusReasonUnauthorized
}

// IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
// be completed as requested.
func IsForbidden(err error) bool {
	return reasonForError(err) == api.StatusReasonForbidden
}

// IsServerTimeout determines if err is an error which indicates that the request needs to be retried
// by the client.
func IsServerTimeout(err error) bool {
	return reasonForError(err) == api.StatusReasonServerTimeout
}

// IsUnexpectedServerError returns true if the server response was not in the expected API format,
// and may be the result of another HTTP actor.
func IsUnexpectedServerError(err error) bool {
	switch t := err.(type) {
	case *StatusError:
		if d := t.Status().Details; d != nil {
			for _, cause := range d.Causes {
				if cause.Type == api.CauseTypeUnexpectedServerResponse {
					return true
				}
			}
		}
	}
	return false
}

// IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
func IsUnexpectedObjectError(err error) bool {
	_, ok := err.(*UnexpectedObjectError)
	return err != nil && ok
}

// SuggestsClientDelay returns true if this error suggests a client delay as well as the
// suggested seconds to wait, or false if the error does not imply a wait.
func SuggestsClientDelay(err error) (int, bool) {
	switch t := err.(type) {
	case *StatusError:
		if t.Status().Details != nil {
			switch t.Status().Reason {
			case api.StatusReasonServerTimeout, api.StatusReasonTimeout:
				return t.Status().Details.RetryAfterSeconds, true
			}
		}
	}
	return 0, false
}

func reasonForError(err error) api.StatusReason {
	switch t := err.(type) {
	case *StatusError:
		return t.ErrStatus.Reason
	}
	return api.StatusReasonUnknown
}
<commit_msg>Add a more descriptive message for 405 errors<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 errors

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

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
)

// HTTP Status codes not in the golang http package.
const (
	StatusUnprocessableEntity = 422
	StatusTooManyRequests     = 429
	// HTTP recommendations are for servers to define 5xx error codes
	// for scenarios not covered by behavior. In this case, ServerTimeout
	// is an indication that a transient server error has occured and the
	// client *should* retry, with an optional Retry-After header to specify
	// the back off window.
	StatusServerTimeout = 504
)

// StatusError is an error intended for consumption by a REST API server; it can also be
// reconstructed by clients from a REST response. Public to allow easy type switches.
type StatusError struct {
	ErrStatus api.Status
}

var _ error = &StatusError{}

// Error implements the Error interface.
func (e *StatusError) Error() string {
	return e.ErrStatus.Message
}

// Status allows access to e's status without having to know the detailed workings
// of StatusError. Used by pkg/apiserver.
func (e *StatusError) Status() api.Status {
	return e.ErrStatus
}

// DebugError reports extended info about the error to debug output.
func (e *StatusError) DebugError() (string, []interface{}) {
	if out, err := json.MarshalIndent(e.ErrStatus, "", "  "); err == nil {
		return "server response object: %s", []interface{}{string(out)}
	}
	return "server response object: %#v", []interface{}{e.ErrStatus}
}

// UnexpectedObjectError can be returned by FromObject if it's passed a non-status object.
type UnexpectedObjectError struct {
	Object runtime.Object
}

// Error returns an error message describing 'u'.
func (u *UnexpectedObjectError) Error() string {
	return fmt.Sprintf("unexpected object: %v", u.Object)
}

// FromObject generates an StatusError from an api.Status, if that is the type of obj; otherwise,
// returns an UnexpecteObjectError.
func FromObject(obj runtime.Object) error {
	switch t := obj.(type) {
	case *api.Status:
		return &StatusError{*t}
	}
	return &UnexpectedObjectError{obj}
}

// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found.
func NewNotFound(kind, name string) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusNotFound,
		Reason: api.StatusReasonNotFound,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q not found", kind, name),
	}}
}

// NewAlreadyExists returns an error indicating the item requested exists by that identifier.
func NewAlreadyExists(kind, name string) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusConflict,
		Reason: api.StatusReasonAlreadyExists,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q already exists", kind, name),
	}}
}

// NewUnauthorized returns an error indicating the client is not authorized to perform the requested
// action.
func NewUnauthorized(reason string) error {
	message := reason
	if len(message) == 0 {
		message = "not authorized"
	}
	return &StatusError{api.Status{
		Status:  api.StatusFailure,
		Code:    http.StatusUnauthorized,
		Reason:  api.StatusReasonUnauthorized,
		Message: message,
	}}
}

// NewForbidden returns an error indicating the requested action was forbidden
func NewForbidden(kind, name string, err error) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusForbidden,
		Reason: api.StatusReasonForbidden,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q is forbidden: %v", kind, name, err),
	}}
}

// NewConflict returns an error indicating the item can't be updated as provided.
func NewConflict(kind, name string, err error) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusConflict,
		Reason: api.StatusReasonConflict,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,
		},
		Message: fmt.Sprintf("%s %q cannot be updated: %v", kind, name, err),
	}}
}

// NewInvalid returns an error indicating the item is invalid and cannot be processed.
func NewInvalid(kind, name string, errs fielderrors.ValidationErrorList) error {
	causes := make([]api.StatusCause, 0, len(errs))
	for i := range errs {
		if err, ok := errs[i].(*fielderrors.ValidationError); ok {
			causes = append(causes, api.StatusCause{
				Type:    api.CauseType(err.Type),
				Message: err.Error(),
				Field:   err.Field,
			})
		}
	}
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   StatusUnprocessableEntity, // RFC 4918: StatusUnprocessableEntity
		Reason: api.StatusReasonInvalid,
		Details: &api.StatusDetails{
			Kind:   kind,
			ID:     name,
			Causes: causes,
		},
		Message: fmt.Sprintf("%s %q is invalid: %v", kind, name, errors.NewAggregate(errs)),
	}}
}

// NewBadRequest creates an error that indicates that the request is invalid and can not be processed.
func NewBadRequest(reason string) error {
	return &StatusError{api.Status{
		Status:  api.StatusFailure,
		Code:    http.StatusBadRequest,
		Reason:  api.StatusReasonBadRequest,
		Message: reason,
	}}
}

// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind.
func NewMethodNotSupported(kind, action string) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusMethodNotAllowed,
		Reason: api.StatusReasonMethodNotAllowed,
		Details: &api.StatusDetails{
			Kind: kind,
		},
		Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, kind),
	}}
}

// NewServerTimeout returns an error indicating the requested action could not be completed due to a
// transient error, and the client should try again.
func NewServerTimeout(kind, operation string, retryAfterSeconds int) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusInternalServerError,
		Reason: api.StatusReasonServerTimeout,
		Details: &api.StatusDetails{
			Kind:              kind,
			ID:                operation,
			RetryAfterSeconds: retryAfterSeconds,
		},
		Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, kind),
	}}
}

// NewInternalError returns an error indicating the item is invalid and cannot be processed.
func NewInternalError(err error) error {
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   http.StatusInternalServerError,
		Reason: api.StatusReasonInternalError,
		Details: &api.StatusDetails{
			Causes: []api.StatusCause{{Message: err.Error()}},
		},
		Message: fmt.Sprintf("Internal error occurred: %v", err),
	}}
}

// NewTimeoutError returns an error indicating that a timeout occurred before the request
// could be completed.  Clients may retry, but the operation may still complete.
func NewTimeoutError(message string, retryAfterSeconds int) error {
	return &StatusError{api.Status{
		Status:  api.StatusFailure,
		Code:    StatusServerTimeout,
		Reason:  api.StatusReasonTimeout,
		Message: fmt.Sprintf("Timeout: %s", message),
		Details: &api.StatusDetails{
			RetryAfterSeconds: retryAfterSeconds,
		},
	}}
}

// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
func NewGenericServerResponse(code int, verb, kind, name, serverMessage string, retryAfterSeconds int) error {
	reason := api.StatusReasonUnknown
	message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code)
	switch code {
	case http.StatusConflict:
		if verb == "POST" {
			reason = api.StatusReasonAlreadyExists
		} else {
			reason = api.StatusReasonConflict
		}
		message = "the server reported a conflict"
	case http.StatusNotFound:
		reason = api.StatusReasonNotFound
		message = "the server could not find the requested resource"
	case http.StatusBadRequest:
		reason = api.StatusReasonBadRequest
		message = "the server rejected our request for an unknown reason"
	case http.StatusUnauthorized:
		reason = api.StatusReasonUnauthorized
		message = "the server has asked for the client to provide credentials"
	case http.StatusForbidden:
		reason = api.StatusReasonForbidden
		message = "the server does not allow access to the requested resource"
	case http.StatusMethodNotAllowed:
		reason = api.StatusReasonMethodNotAllowed
		message = "the server does not allow this method on the requested resource"
	case StatusUnprocessableEntity:
		reason = api.StatusReasonInvalid
		message = "the server rejected our request due to an error in our request"
	case StatusServerTimeout:
		reason = api.StatusReasonServerTimeout
		message = "the server cannot complete the requested operation at this time, try again later"
	case StatusTooManyRequests:
		reason = api.StatusReasonTimeout
		message = "the server has received too many requests and has asked us to try again later"
	default:
		if code >= 500 {
			reason = api.StatusReasonInternalError
			message = "an error on the server has prevented the request from succeeding"
		}
	}
	switch {
	case len(kind) > 0 && len(name) > 0:
		message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), kind, name)
	case len(kind) > 0:
		message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), kind)
	}
	return &StatusError{api.Status{
		Status: api.StatusFailure,
		Code:   code,
		Reason: reason,
		Details: &api.StatusDetails{
			Kind: kind,
			ID:   name,

			Causes: []api.StatusCause{
				{
					Type:    api.CauseTypeUnexpectedServerResponse,
					Message: serverMessage,
				},
			},

			RetryAfterSeconds: retryAfterSeconds,
		},
		Message: message,
	}}
}

// IsNotFound returns true if the specified error was created by NewNotFoundErr.
func IsNotFound(err error) bool {
	return reasonForError(err) == api.StatusReasonNotFound
}

// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
func IsAlreadyExists(err error) bool {
	return reasonForError(err) == api.StatusReasonAlreadyExists
}

// IsConflict determines if the err is an error which indicates the provided update conflicts.
func IsConflict(err error) bool {
	return reasonForError(err) == api.StatusReasonConflict
}

// IsInvalid determines if the err is an error which indicates the provided resource is not valid.
func IsInvalid(err error) bool {
	return reasonForError(err) == api.StatusReasonInvalid
}

// IsMethodNotSupported determines if the err is an error which indicates the provided action could not
// be performed because it is not supported by the server.
func IsMethodNotSupported(err error) bool {
	return reasonForError(err) == api.StatusReasonMethodNotAllowed
}

// IsBadRequest determines if err is an error which indicates that the request is invalid.
func IsBadRequest(err error) bool {
	return reasonForError(err) == api.StatusReasonBadRequest
}

// IsUnauthorized determines if err is an error which indicates that the request is unauthorized and
// requires authentication by the user.
func IsUnauthorized(err error) bool {
	return reasonForError(err) == api.StatusReasonUnauthorized
}

// IsForbidden determines if err is an error which indicates that the request is forbidden and cannot
// be completed as requested.
func IsForbidden(err error) bool {
	return reasonForError(err) == api.StatusReasonForbidden
}

// IsServerTimeout determines if err is an error which indicates that the request needs to be retried
// by the client.
func IsServerTimeout(err error) bool {
	return reasonForError(err) == api.StatusReasonServerTimeout
}

// IsUnexpectedServerError returns true if the server response was not in the expected API format,
// and may be the result of another HTTP actor.
func IsUnexpectedServerError(err error) bool {
	switch t := err.(type) {
	case *StatusError:
		if d := t.Status().Details; d != nil {
			for _, cause := range d.Causes {
				if cause.Type == api.CauseTypeUnexpectedServerResponse {
					return true
				}
			}
		}
	}
	return false
}

// IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
func IsUnexpectedObjectError(err error) bool {
	_, ok := err.(*UnexpectedObjectError)
	return err != nil && ok
}

// SuggestsClientDelay returns true if this error suggests a client delay as well as the
// suggested seconds to wait, or false if the error does not imply a wait.
func SuggestsClientDelay(err error) (int, bool) {
	switch t := err.(type) {
	case *StatusError:
		if t.Status().Details != nil {
			switch t.Status().Reason {
			case api.StatusReasonServerTimeout, api.StatusReasonTimeout:
				return t.Status().Details.RetryAfterSeconds, true
			}
		}
	}
	return 0, false
}

func reasonForError(err error) api.StatusReason {
	switch t := err.(type) {
	case *StatusError:
		return t.ErrStatus.Reason
	}
	return api.StatusReasonUnknown
}
<|endoftext|>


<commit_before>package artnet

import (
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/jsimonetti/go-artnet"
	"github.com/sirupsen/logrus"

	"github.com/StageAutoControl/controller/pkg/cntl"
	"github.com/StageAutoControl/controller/pkg/internal/logging"
)

// Controller is a transport for the ArtNet protocol (DMX over UDP/IP)
type controller struct {
	logger      logging.Logger
	sender      *artnet.Controller
	state       *State
	sendTrigger chan UniverseStateMap
	context     context.Context
}

// NewController returns a artnet Controller as an anonymous interface
func NewController(logger logging.Logger) (Controller, error) {
	ip, err := FindArtNetIP()
	if err != nil {
		return nil, fmt.Errorf("failed to find the art-net IP: %v", err)
	}

	if len(ip) == 0 {
		return nil, errors.New("failed to find the art-net IP: No interface found")
	}

	host, err := os.Hostname()
	if err != nil {
		return nil, fmt.Errorf("failed to resolve hostname: %v", err)
	}

	host = strings.ToLower(strings.Split(host, ".")[0])
	logger.Infof("Using ArtNet IP %s and hostname %s", ip.String(), host)

	senderLogger := artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet"))
	control := &controller{
		logger:      logger,
		sender:      artnet.NewController(host, ip, senderLogger, artnet.MaxFPS(40)),
		state:       NewState(),
		sendTrigger: make(chan UniverseStateMap, 100),
	}

	return control, nil
}

// Start the controller
func (c *controller) Start(ctx context.Context) error {
	if err := c.sender.Start(); err != nil {
		return fmt.Errorf("failed to start Controller: %v", err)
	}

	c.context = ctx
	go c.sendBackground()
	go c.debugDevices()

	return nil
}

// Stop the controller
func (c *controller) Stop() {
	close(c.sendTrigger)
	c.sender.Stop()
}

func (c *controller) SetDMXChannelValue(value ChannelValue) {
	c.state.SetChannel(value.Universe, value.Channel, value.Value)
	c.triggerSend()
}

func (c *controller) SetDMXChannelValues(values []ChannelValue) {
	c.state.SetChannelValues(values)
	c.triggerSend()
}

// Write implements the playback.TransportWriter interface to compatibility :)
func (c *controller) Write(cmd cntl.Command) error {
	values := make([]ChannelValue, len(cmd.DMXCommands))
	for i, dmxCmd := range cmd.DMXCommands {
		values[i].Universe = uint16(dmxCmd.Universe)
		values[i].Channel = uint16(dmxCmd.Channel)
		values[i].Value = dmxCmd.Value.Uint8()
	}

	c.SetDMXChannelValues(values)

	return nil
}

func (c *controller) triggerSend() {
	c.sendTrigger <- c.state.Get()
}

func (c *controller) sendBackground() {
	for data := range c.sendTrigger {
		for u, dmx := range data {
			c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u))
		}
	}
}

// universeToAddress converts a dmx universe to a artnet address
// Code stolen from https://play.golang.org/p/pdQPC5u7JX
func (c *controller) universeToAddress(universe uint16) artnet.Address {
	v := make([]uint8, 2)
	binary.BigEndian.PutUint16(v, universe)

	return artnet.Address{
		Net:    v[0],
		SubUni: v[1],
	}
}

func (c *controller) debugDevices() {
	t := time.NewTicker(30 * time.Second)
	for range t.C {
		c.logger.Debugf("Currently %d devices are registered: %+s", len(c.sender.Nodes), ips(c.sender.Nodes))
	}
}

func ips(nodes []*artnet.ControlledNode) (ips []string) {
	for _, n := range nodes {
		ips = append(ips, NodeToString(n))
	}
	return
}
<commit_msg>Remove the MaxFPS controller option<commit_after>package artnet

import (
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/jsimonetti/go-artnet"
	"github.com/sirupsen/logrus"

	"github.com/StageAutoControl/controller/pkg/cntl"
	"github.com/StageAutoControl/controller/pkg/internal/logging"
)

// Controller is a transport for the ArtNet protocol (DMX over UDP/IP)
type controller struct {
	logger      logging.Logger
	sender      *artnet.Controller
	state       *State
	sendTrigger chan UniverseStateMap
	context     context.Context
}

// NewController returns a artnet Controller as an anonymous interface
func NewController(logger logging.Logger) (Controller, error) {
	ip, err := FindArtNetIP()
	if err != nil {
		return nil, fmt.Errorf("failed to find the art-net IP: %v", err)
	}

	if len(ip) == 0 {
		return nil, errors.New("failed to find the art-net IP: No interface found")
	}

	host, err := os.Hostname()
	if err != nil {
		return nil, fmt.Errorf("failed to resolve hostname: %v", err)
	}

	host = strings.ToLower(strings.Split(host, ".")[0])
	logger.Infof("Using ArtNet IP %s and hostname %s", ip.String(), host)

	senderLogger := artnet.NewLogger(logger.(*logrus.Entry).WithField("module", "artnet"))
	control := &controller{
		logger:      logger,
		sender:      artnet.NewController(host, ip, senderLogger), // , artnet.MaxFPS(26)),
		state:       NewState(),
		sendTrigger: make(chan UniverseStateMap, 100),
	}

	return control, nil
}

// Start the controller
func (c *controller) Start(ctx context.Context) error {
	if err := c.sender.Start(); err != nil {
		return fmt.Errorf("failed to start Controller: %v", err)
	}

	c.context = ctx
	go c.sendBackground()
	go c.debugDevices()

	return nil
}

// Stop the controller
func (c *controller) Stop() {
	close(c.sendTrigger)
	c.sender.Stop()
}

func (c *controller) SetDMXChannelValue(value ChannelValue) {
	c.state.SetChannel(value.Universe, value.Channel, value.Value)
	c.triggerSend()
}

func (c *controller) SetDMXChannelValues(values []ChannelValue) {
	c.state.SetChannelValues(values)
	c.triggerSend()
}

// Write implements the playback.TransportWriter interface to compatibility :)
func (c *controller) Write(cmd cntl.Command) error {
	values := make([]ChannelValue, len(cmd.DMXCommands))
	for i, dmxCmd := range cmd.DMXCommands {
		values[i].Universe = uint16(dmxCmd.Universe)
		values[i].Channel = uint16(dmxCmd.Channel)
		values[i].Value = dmxCmd.Value.Uint8()
	}

	c.SetDMXChannelValues(values)

	return nil
}

func (c *controller) triggerSend() {
	c.sendTrigger <- c.state.Get()
}

func (c *controller) sendBackground() {
	for data := range c.sendTrigger {
		for u, dmx := range data {
			c.sender.SendDMXToAddress(dmx.toByteSlice(), c.universeToAddress(u))
		}
	}
}

// universeToAddress converts a dmx universe to a artnet address
// Code stolen from https://play.golang.org/p/pdQPC5u7JX
func (c *controller) universeToAddress(universe uint16) artnet.Address {
	v := make([]uint8, 2)
	binary.BigEndian.PutUint16(v, universe)

	return artnet.Address{
		Net:    v[0],
		SubUni: v[1],
	}
}

func (c *controller) debugDevices() {
	t := time.NewTicker(30 * time.Second)
	for range t.C {
		c.logger.Debugf("Currently %d devices are registered: %+s", len(c.sender.Nodes), ips(c.sender.Nodes))
	}
}

func ips(nodes []*artnet.ControlledNode) (ips []string) {
	for _, n := range nodes {
		ips = append(ips, NodeToString(n))
	}
	return
}
<|endoftext|>


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

package fit

import (
	"testing"

	"github.com/u-root/u-root/pkg/boot"
)

func TestLoadConfig(t *testing.T) {
	i, err := New("testdata/fitimage.itb")
	if err != nil {
		t.Fatal(err)
	}

	kn, rn, err := i.LoadConfig(true)

	if err != nil {
		t.Fatal(err)
	}

	t.Logf("kernel name: %s", kn)
	t.Logf("ramdisk name: %s", rn)
	if kn != "kernel@0" {
		t.Fatal(err)
	}
	if rn != "ramdisk@0" {
		t.Fatal(err)
	}
}

func TestLoad(t *testing.T) {
	i, err := New("testdata/fitimage.itb")
	if err != nil {
		t.Fatal(err)
	}

	i.Kernel, i.InitRAMFS = "kernel@0", "ramdisk@0"

	// Save kexecReboot function and restore at the end
	defer func(old func() error) { kexecReboot = old }(kexecReboot)
	defer func(old func(i *boot.LinuxImage, verbose bool) error) { loadImage = old }(loadImage)
	kexecReboot = func() error {
		t.Log("mock reboot")
		return nil
	}

	loadImage = func(i *boot.LinuxImage, verbose bool) error {
		t.Log("mock load image")
		return nil
	}

	if err = i.Load(true); err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Added additional FIT test for failure coverage<commit_after>// Copyright 2020 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fit

import (
	"testing"

	"github.com/u-root/u-root/pkg/boot"
)

func TestLoadConfig(t *testing.T) {
	i, err := New("testdata/fitimage.itb")
	if err != nil {
		t.Fatal(err)
	}

	kn, rn, err := i.LoadConfig(true)

	if err != nil {
		t.Fatal(err)
	}

	t.Logf("kernel name: %s", kn)
	t.Logf("ramdisk name: %s", rn)
	if kn != "kernel@0" {
		t.Fatal(err)
	}
	if rn != "ramdisk@0" {
		t.Fatal(err)
	}
}

func TestLoadConfigMiss(t *testing.T) {
	i, err := New("testdata/fitimage.itb")
	if err != nil {
		t.Fatal(err)
	}

	i.ConfigOverride = "MagicNonExistentConfig"
	kn, rn, err := i.LoadConfig(true)

	if kn != "" {
		t.Fatalf("Kernel %s returned on expected config miss", kn)
	}

	if rn != "" {
		t.Fatalf("Initramfs %s returned on expected config miss", rn)
	}

	if err == nil {
		t.Fatal("Expected error message for miss on FIT config, got nil")
	}
}

func TestLoad(t *testing.T) {
	i, err := New("testdata/fitimage.itb")
	if err != nil {
		t.Fatal(err)
	}

	i.Kernel, i.InitRAMFS = "kernel@0", "ramdisk@0"

	// Save kexecReboot function and restore at the end
	defer func(old func() error) { kexecReboot = old }(kexecReboot)
	defer func(old func(i *boot.LinuxImage, verbose bool) error) { loadImage = old }(loadImage)
	kexecReboot = func() error {
		t.Log("mock reboot")
		return nil
	}

	loadImage = func(i *boot.LinuxImage, verbose bool) error {
		t.Log("mock load image")
		return nil
	}

	if err = i.Load(true); err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package auth

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/google/uuid"
	"github.com/pkg/browser"
	"golang.org/x/oauth2"

	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/cache"
)

const (
	callbackPath = "/callback"
)

type oauth2Callback struct {
	Code             string
	Error            string
	ErrorDescription string
}

type loginExecutor struct {
	Oauth2AuthUrl    string
	Oauth2TokenUrl   string
	CompletionUrl    string
	Oauth2ClientId   string
	UserInfoUrl      string
	SaveTokenFunc    func(context.Context, *oauth2.Token) error
	SaveUserInfoFunc func(context.Context, *cache.UserInfo) error
	OpenURLFunc      func(string) error
	Scout            *client.Scout
}

// LoginExecutor controls the execution of a login flow
type LoginExecutor interface {
	LoginFlow(ctx context.Context, stdout, stderr io.Writer) error
}

// NewLoginExecutor returns an instance of LoginExecutor
func NewLoginExecutor(oauth2AuthUrl string,
	oauth2TokenUrl string,
	oauth2ClientId string,
	completionUrl string,
	userInfoUrl string,
	saveTokenFunc func(context.Context, *oauth2.Token) error,
	saveUserInfoFunc func(context.Context, *cache.UserInfo) error,
	openURLFunc func(string) error,
	scout *client.Scout) LoginExecutor {
	return &loginExecutor{
		Oauth2AuthUrl:    oauth2AuthUrl,
		Oauth2TokenUrl:   oauth2TokenUrl,
		CompletionUrl:    completionUrl,
		Oauth2ClientId:   oauth2ClientId,
		UserInfoUrl:      userInfoUrl,
		SaveTokenFunc:    saveTokenFunc,
		SaveUserInfoFunc: saveUserInfoFunc,
		OpenURLFunc:      openURLFunc,
		Scout:            scout,
	}
}

// EnsureLoggedIn will check if the user is logged in and if not initiate the login flow.
func EnsureLoggedIn(ctx context.Context, stdout, stderr io.Writer) error {
	if token, _ := cache.LoadTokenFromUserCache(ctx); token != nil {
		return nil
	}

	return Login(ctx, stdout, stderr)
}

func Login(ctx context.Context, stdout, stderr io.Writer) error {
	env, err := client.LoadEnv(ctx)
	if err != nil {
		return err
	}

	l := NewLoginExecutor(
		env.LoginAuthURL,
		env.LoginTokenURL,
		env.LoginClientID,
		env.LoginCompletionURL,
		env.UserInfoURL,
		cache.SaveTokenToUserCache,
		cache.SaveUserInfoToUserCache,
		browser.OpenURL,
		client.NewScout(ctx, "cli"),
	)
	return l.LoginFlow(ctx, stdout, stderr)
}

// LoginFlow tries logging the user by opening a browser window and authenticating against the
// configured OAuth2 provider user a code flow. An HTTP server is started in the background during
// authentication to handle the callback url. Once the callback url is invoked, the login flow will
// invoke the token endpoint to get the user's access & refresh tokens and persist them with the
// SaveTokenFunc (which would usually write to user cache).
// If login succeeds, the login flow will then try invoking the userinfo endpoint and persisting it
// using SaveUserInfoFunc (which would usually write to user cache).
func (l *loginExecutor) LoginFlow(ctx context.Context, stdout, stderr io.Writer) error {
	// oauth2Callback chan that will receive the callback info
	callbacks := make(chan oauth2Callback)
	// also listen for interruption to cancel the flow
	interrupts := make(chan os.Signal, 1)
	signal.Notify(interrupts, syscall.SIGINT, syscall.SIGTERM)

	// start the background server on which we'll be listening for the OAuth2 callback
	backgroundServer, err := startBackgroundServer(callbacks, stderr, l.CompletionUrl)
	defer func() {
		err := backgroundServer.Shutdown(context.Background())
		if err != nil {
			fmt.Fprintf(stderr, "error shutting down callback server: %v", err)
		}
	}()
	if err != nil {
		return err
	}
	oauth2Config := oauth2.Config{
		ClientID:    l.Oauth2ClientId,
		RedirectURL: fmt.Sprintf("http://%v%v", backgroundServer.Addr, callbackPath),
		Endpoint: oauth2.Endpoint{
			AuthURL:  l.Oauth2AuthUrl,
			TokenURL: l.Oauth2TokenUrl,
		},
		Scopes: []string{"openid", "profile", "email"},
	}

	// create OAuth2 authentication code flow URL
	state := uuid.New().String()
	pkceVerifier, err := NewCodeVerifier()
	if err != nil {
		return err
	}
	url := oauth2Config.AuthCodeURL(
		state,
		oauth2.SetAuthURLParam("code_challenge", pkceVerifier.CodeChallengeS256()),
		oauth2.SetAuthURLParam("code_challenge_method", PKCEChallengeMethodS256),
	)

	fmt.Fprintln(stdout, "Launching browser authentication flow...")
	if err := l.OpenURLFunc(url); err != nil {
		fmt.Fprintf(stdout, "Could not open browser, please access this URL: %v\n", url)
	}

	// wait for callback completion or interruption
	select {
	case callback := <-callbacks:
		token, err := l.handleCallback(ctx, stdout, callback, oauth2Config, pkceVerifier)
		if err != nil {
			_ = l.Scout.Report(ctx, "login_failure", client.ScoutMeta{Key: "error", Value: err.Error()})
		} else {
			_ = l.retrieveUserInfo(ctx, token)
			_ = l.Scout.Report(ctx, "login_success")
		}
		return err
	case <-interrupts:
		fmt.Fprintln(stdout, "Login aborted.")
		_ = l.Scout.Report(ctx, "login_interrupted")
		return nil
	}
}

func (l *loginExecutor) handleCallback(
	ctx context.Context, stdout io.Writer,
	callback oauth2Callback, oauth2Config oauth2.Config, pkceVerifier CodeVerifier,
) (*oauth2.Token, error) {
	if callback.Error != "" {
		return nil, fmt.Errorf("%v error returned on OAuth2 callback: %v", callback.Error, callback.ErrorDescription)
	}

	// retrieve access token from callback code
	token, err := oauth2Config.Exchange(
		ctx,
		callback.Code,
		oauth2.SetAuthURLParam("code_verifier", pkceVerifier.String()),
	)
	if err != nil {
		return nil, fmt.Errorf("error while exchanging code for token: %w", err)
	}

	err = l.SaveTokenFunc(ctx, token)
	if err != nil {
		return nil, fmt.Errorf("could not save access token to user cache: %w", err)
	}

	fmt.Fprintln(stdout, "Login successful.")
	return token, nil
}

func (l *loginExecutor) retrieveUserInfo(ctx context.Context, token *oauth2.Token) error {
	var userInfo cache.UserInfo
	req, err := http.NewRequest("GET", l.UserInfoUrl, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status %v from user info endpoint", resp.StatusCode)
	}
	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(content, &userInfo)
	if err != nil {
		return err
	}
	return l.SaveUserInfoFunc(ctx, &userInfo)
}

func startBackgroundServer(callbacks chan oauth2Callback, stderr io.Writer, completionUrl string) (*http.Server, error) {
	// start listening on the next available port
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		return &http.Server{}, err
	}
	port := listener.Addr().(*net.TCPAddr).Port
	handler := http.NewServeMux()
	handler.HandleFunc(callbackPath, newCallbackHandlerFunc(callbacks, stderr, completionUrl))
	server := &http.Server{
		Addr:    fmt.Sprintf("localhost:%v", port),
		Handler: handler,
	}
	go func() {
		if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
			callbacks <- oauth2Callback{
				Code:             "",
				Error:            "Could not start callback server",
				ErrorDescription: err.Error(),
			}
		}
	}()
	return server, nil
}

func newCallbackHandlerFunc(callbacks chan oauth2Callback, stderr io.Writer, completionUrl string) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		query := r.URL.Query()
		code := query.Get("code")
		errorName := query.Get("error")
		errorDescription := query.Get("error_description")

		var sb strings.Builder
		sb.WriteString("<!DOCTYPE html><title>Authentication Successful</title>")
		if errorName == "" && code != "" {
			w.Header().Set("Location", completionUrl)
			w.WriteHeader(http.StatusTemporaryRedirect)
			sb.WriteString("
Authentication Successful
")
			sb.WriteString("
You can now close this tab and resume on the CLI.
")
		} else {
			sb.WriteString("
Authentication Error
")
			sb.WriteString(fmt.Sprintf("
%s: %s
", errorName, errorDescription))
			w.WriteHeader(http.StatusInternalServerError)
		}
		sb.WriteString("")

		w.Header().Set("Content-Type", "text/html")
		if _, err := io.WriteString(w, sb.String()); err != nil {
			fmt.Fprintf(stderr, "Error writing callback response body: %v", err)
		}

		callbacks <- oauth2Callback{
			Code:             code,
			Error:            errorName,
			ErrorDescription: errorDescription,
		}
	}
}
<commit_msg>pkg/client/auth: Move some logging around<commit_after>package auth

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/google/uuid"
	"github.com/pkg/browser"
	"golang.org/x/oauth2"

	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/cache"
)

const (
	callbackPath = "/callback"
)

type oauth2Callback struct {
	Code             string
	Error            string
	ErrorDescription string
}

type loginExecutor struct {
	Oauth2AuthUrl    string
	Oauth2TokenUrl   string
	CompletionUrl    string
	Oauth2ClientId   string
	UserInfoUrl      string
	SaveTokenFunc    func(context.Context, *oauth2.Token) error
	SaveUserInfoFunc func(context.Context, *cache.UserInfo) error
	OpenURLFunc      func(string) error
	Scout            *client.Scout
}

// LoginExecutor controls the execution of a login flow
type LoginExecutor interface {
	LoginFlow(ctx context.Context, stdout, stderr io.Writer) error
}

// NewLoginExecutor returns an instance of LoginExecutor
func NewLoginExecutor(oauth2AuthUrl string,
	oauth2TokenUrl string,
	oauth2ClientId string,
	completionUrl string,
	userInfoUrl string,
	saveTokenFunc func(context.Context, *oauth2.Token) error,
	saveUserInfoFunc func(context.Context, *cache.UserInfo) error,
	openURLFunc func(string) error,
	scout *client.Scout) LoginExecutor {
	return &loginExecutor{
		Oauth2AuthUrl:    oauth2AuthUrl,
		Oauth2TokenUrl:   oauth2TokenUrl,
		CompletionUrl:    completionUrl,
		Oauth2ClientId:   oauth2ClientId,
		UserInfoUrl:      userInfoUrl,
		SaveTokenFunc:    saveTokenFunc,
		SaveUserInfoFunc: saveUserInfoFunc,
		OpenURLFunc:      openURLFunc,
		Scout:            scout,
	}
}

// EnsureLoggedIn will check if the user is logged in and if not initiate the login flow.
func EnsureLoggedIn(ctx context.Context, stdout, stderr io.Writer) error {
	if token, _ := cache.LoadTokenFromUserCache(ctx); token != nil {
		return nil
	}

	return Login(ctx, stdout, stderr)
}

func Login(ctx context.Context, stdout, stderr io.Writer) error {
	env, err := client.LoadEnv(ctx)
	if err != nil {
		return err
	}

	l := NewLoginExecutor(
		env.LoginAuthURL,
		env.LoginTokenURL,
		env.LoginClientID,
		env.LoginCompletionURL,
		env.UserInfoURL,
		cache.SaveTokenToUserCache,
		cache.SaveUserInfoToUserCache,
		browser.OpenURL,
		client.NewScout(ctx, "cli"),
	)
	return l.LoginFlow(ctx, stdout, stderr)
}

// LoginFlow tries logging the user by opening a browser window and authenticating against the
// configured OAuth2 provider user a code flow. An HTTP server is started in the background during
// authentication to handle the callback url. Once the callback url is invoked, the login flow will
// invoke the token endpoint to get the user's access & refresh tokens and persist them with the
// SaveTokenFunc (which would usually write to user cache).
// If login succeeds, the login flow will then try invoking the userinfo endpoint and persisting it
// using SaveUserInfoFunc (which would usually write to user cache).
func (l *loginExecutor) LoginFlow(ctx context.Context, stdout, stderr io.Writer) error {
	// oauth2Callback chan that will receive the callback info
	callbacks := make(chan oauth2Callback)
	// also listen for interruption to cancel the flow
	interrupts := make(chan os.Signal, 1)
	signal.Notify(interrupts, syscall.SIGINT, syscall.SIGTERM)

	// start the background server on which we'll be listening for the OAuth2 callback
	backgroundServer, err := startBackgroundServer(callbacks, stderr, l.CompletionUrl)
	defer func() {
		err := backgroundServer.Shutdown(context.Background())
		if err != nil {
			fmt.Fprintf(stderr, "error shutting down callback server: %v", err)
		}
	}()
	if err != nil {
		return err
	}
	oauth2Config := oauth2.Config{
		ClientID:    l.Oauth2ClientId,
		RedirectURL: fmt.Sprintf("http://%v%v", backgroundServer.Addr, callbackPath),
		Endpoint: oauth2.Endpoint{
			AuthURL:  l.Oauth2AuthUrl,
			TokenURL: l.Oauth2TokenUrl,
		},
		Scopes: []string{"openid", "profile", "email"},
	}

	// create OAuth2 authentication code flow URL
	state := uuid.New().String()
	pkceVerifier, err := NewCodeVerifier()
	if err != nil {
		return err
	}
	url := oauth2Config.AuthCodeURL(
		state,
		oauth2.SetAuthURLParam("code_challenge", pkceVerifier.CodeChallengeS256()),
		oauth2.SetAuthURLParam("code_challenge_method", PKCEChallengeMethodS256),
	)

	fmt.Fprintln(stdout, "Launching browser authentication flow...")
	if err := l.OpenURLFunc(url); err != nil {
		fmt.Fprintf(stdout, "Could not open browser, please access this URL: %v\n", url)
	}

	// wait for callback completion or interruption
	select {
	case callback := <-callbacks:
		token, err := l.handleCallback(ctx, callback, oauth2Config, pkceVerifier)
		if err != nil {
			_ = l.Scout.Report(ctx, "login_failure", client.ScoutMeta{Key: "error", Value: err.Error()})
		} else {
			fmt.Fprintln(stdout, "Login successful.")
			_ = l.retrieveUserInfo(ctx, token)
			_ = l.Scout.Report(ctx, "login_success")
		}
		return err
	case <-interrupts:
		fmt.Fprintln(stdout, "Login aborted.")
		_ = l.Scout.Report(ctx, "login_interrupted")
		return nil
	}
}

func (l *loginExecutor) handleCallback(
	ctx context.Context,
	callback oauth2Callback, oauth2Config oauth2.Config, pkceVerifier CodeVerifier,
) (*oauth2.Token, error) {
	if callback.Error != "" {
		return nil, fmt.Errorf("%v error returned on OAuth2 callback: %v", callback.Error, callback.ErrorDescription)
	}

	// retrieve access token from callback code
	token, err := oauth2Config.Exchange(
		ctx,
		callback.Code,
		oauth2.SetAuthURLParam("code_verifier", pkceVerifier.String()),
	)
	if err != nil {
		return nil, fmt.Errorf("error while exchanging code for token: %w", err)
	}

	err = l.SaveTokenFunc(ctx, token)
	if err != nil {
		return nil, fmt.Errorf("could not save access token to user cache: %w", err)
	}

	return token, nil
}

func (l *loginExecutor) retrieveUserInfo(ctx context.Context, token *oauth2.Token) error {
	var userInfo cache.UserInfo
	req, err := http.NewRequest("GET", l.UserInfoUrl, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status %v from user info endpoint", resp.StatusCode)
	}
	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(content, &userInfo)
	if err != nil {
		return err
	}
	return l.SaveUserInfoFunc(ctx, &userInfo)
}

func startBackgroundServer(callbacks chan oauth2Callback, stderr io.Writer, completionUrl string) (*http.Server, error) {
	// start listening on the next available port
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		return &http.Server{}, err
	}
	port := listener.Addr().(*net.TCPAddr).Port
	handler := http.NewServeMux()
	handler.HandleFunc(callbackPath, newCallbackHandlerFunc(callbacks, stderr, completionUrl))
	server := &http.Server{
		Addr:    fmt.Sprintf("localhost:%v", port),
		Handler: handler,
	}
	go func() {
		if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
			callbacks <- oauth2Callback{
				Code:             "",
				Error:            "Could not start callback server",
				ErrorDescription: err.Error(),
			}
		}
	}()
	return server, nil
}

func newCallbackHandlerFunc(callbacks chan oauth2Callback, stderr io.Writer, completionUrl string) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		query := r.URL.Query()
		code := query.Get("code")
		errorName := query.Get("error")
		errorDescription := query.Get("error_description")

		var sb strings.Builder
		sb.WriteString("<!DOCTYPE html><title>Authentication Successful</title>")
		if errorName == "" && code != "" {
			w.Header().Set("Location", completionUrl)
			w.WriteHeader(http.StatusTemporaryRedirect)
			sb.WriteString("
Authentication Successful
")
			sb.WriteString("
You can now close this tab and resume on the CLI.
")
		} else {
			sb.WriteString("
Authentication Error
")
			sb.WriteString(fmt.Sprintf("
%s: %s
", errorName, errorDescription))
			w.WriteHeader(http.StatusInternalServerError)
		}
		sb.WriteString("")

		w.Header().Set("Content-Type", "text/html")
		if _, err := io.WriteString(w, sb.String()); err != nil {
			fmt.Fprintf(stderr, "Error writing callback response body: %v", err)
		}

		callbacks <- oauth2Callback{
			Code:             code,
			Error:            errorName,
			ErrorDescription: errorDescription,
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

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

package getter

import (
	"bytes"
	"crypto/tls"
	"io"
	"net/http"
	"net/url"

	"github.com/pkg/errors"

	"helm.sh/helm/v3/internal/tlsutil"
	"helm.sh/helm/v3/internal/urlutil"
	"helm.sh/helm/v3/internal/version"
)

// HTTPGetter is the default HTTP(/S) backend handler
type HTTPGetter struct {
	opts options
}

// Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
	for _, opt := range options {
		opt(&g.opts)
	}
	return g.get(href)
}

func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
	buf := bytes.NewBuffer(nil)

	// Set a helm specific user agent so that a repo server and metrics can
	// separate helm calls from other tools interacting with repos.
	req, err := http.NewRequest("GET", href, nil)
	if err != nil {
		return buf, err
	}

	req.Header.Set("User-Agent", version.GetUserAgent())
	if g.opts.userAgent != "" {
		req.Header.Set("User-Agent", g.opts.userAgent)
	}

	// Before setting the basic auth credentials, make sure the URL associated
	// with the basic auth is the one being fetched.
	u1, err := url.Parse(g.opts.url)
	if err != nil {
		return buf, errors.Wrap(err, "Unable to parse getter URL")
	}
	u2, err := url.Parse(href)
	if err != nil {
		return buf, errors.Wrap(err, "Unable to parse URL getting from")
	}

	// Host on URL (returned from url.Parse) contains the port if present.
	// This check ensures credentials are not passed between different
	// services on different ports.
	if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
		if g.opts.username != "" && g.opts.password != "" {
			req.SetBasicAuth(g.opts.username, g.opts.password)
		}
	}

	client, err := g.httpClient()
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return buf, err
	}
	if resp.StatusCode != 200 {
		return buf, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
	}

	_, err = io.Copy(buf, resp.Body)
	resp.Body.Close()
	return buf, err
}

// NewHTTPGetter constructs a valid http/https client as a Getter
func NewHTTPGetter(options ...Option) (Getter, error) {
	var client HTTPGetter

	for _, opt := range options {
		opt(&client.opts)
	}

	return &client, nil
}

func (g *HTTPGetter) httpClient() (*http.Client, error) {
	transport := &http.Transport{
		DisableCompression: true,
		Proxy:              http.ProxyFromEnvironment,
	}
	if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
		tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile)
		if err != nil {
			return nil, errors.Wrap(err, "can't create TLS config for client")
		}
		tlsConf.BuildNameToCertificate()

		sni, err := urlutil.ExtractHostname(g.opts.url)
		if err != nil {
			return nil, err
		}
		tlsConf.ServerName = sni

		transport.TLSClientConfig = tlsConf
	}

	if g.opts.insecureSkipVerifyTLS {
		if transport.TLSClientConfig == nil {
			transport.TLSClientConfig = &tls.Config{
				InsecureSkipVerify: true,
			}
		} else {
			transport.TLSClientConfig.InsecureSkipVerify = true
		}
	}

	client := &http.Client{
		Transport: transport,
		Timeout:   g.opts.timeout,
	}

	return client, nil
}
<commit_msg>refactor(getter): refine http GET request<commit_after>/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

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

package getter

import (
	"bytes"
	"crypto/tls"
	"io"
	"net/http"
	"net/url"

	"github.com/pkg/errors"

	"helm.sh/helm/v3/internal/tlsutil"
	"helm.sh/helm/v3/internal/urlutil"
	"helm.sh/helm/v3/internal/version"
)

// HTTPGetter is the default HTTP(/S) backend handler
type HTTPGetter struct {
	opts options
}

// Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
	for _, opt := range options {
		opt(&g.opts)
	}
	return g.get(href)
}

func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
	// Set a helm specific user agent so that a repo server and metrics can
	// separate helm calls from other tools interacting with repos.
	req, err := http.NewRequest(http.MethodGet, href, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("User-Agent", version.GetUserAgent())
	if g.opts.userAgent != "" {
		req.Header.Set("User-Agent", g.opts.userAgent)
	}

	// Before setting the basic auth credentials, make sure the URL associated
	// with the basic auth is the one being fetched.
	u1, err := url.Parse(g.opts.url)
	if err != nil {
		return nil, errors.Wrap(err, "Unable to parse getter URL")
	}
	u2, err := url.Parse(href)
	if err != nil {
		return nil, errors.Wrap(err, "Unable to parse URL getting from")
	}

	// Host on URL (returned from url.Parse) contains the port if present.
	// This check ensures credentials are not passed between different
	// services on different ports.
	if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
		if g.opts.username != "" && g.opts.password != "" {
			req.SetBasicAuth(g.opts.username, g.opts.password)
		}
	}

	client, err := g.httpClient()
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
	}

	buf := bytes.NewBuffer(nil)
	_, err = io.Copy(buf, resp.Body)
	return buf, err
}

// NewHTTPGetter constructs a valid http/https client as a Getter
func NewHTTPGetter(options ...Option) (Getter, error) {
	var client HTTPGetter

	for _, opt := range options {
		opt(&client.opts)
	}

	return &client, nil
}

func (g *HTTPGetter) httpClient() (*http.Client, error) {
	transport := &http.Transport{
		DisableCompression: true,
		Proxy:              http.ProxyFromEnvironment,
	}
	if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
		tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile)
		if err != nil {
			return nil, errors.Wrap(err, "can't create TLS config for client")
		}
		tlsConf.BuildNameToCertificate()

		sni, err := urlutil.ExtractHostname(g.opts.url)
		if err != nil {
			return nil, err
		}
		tlsConf.ServerName = sni

		transport.TLSClientConfig = tlsConf
	}

	if g.opts.insecureSkipVerifyTLS {
		if transport.TLSClientConfig == nil {
			transport.TLSClientConfig = &tls.Config{
				InsecureSkipVerify: true,
			}
		} else {
			transport.TLSClientConfig.InsecureSkipVerify = true
		}
	}

	client := &http.Client{
		Transport: transport,
		Timeout:   g.opts.timeout,
	}

	return client, nil
}
<|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 cmd

import (
	"fmt"
	"io"
	"strings"

	"github.com/evanphx/json-patch"
	"github.com/renstrom/dedent"
	"github.com/spf13/cobra"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/kubectl"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/kubernetes/pkg/util/strategicpatch"
	"k8s.io/kubernetes/pkg/util/yaml"
)

var patchTypes = map[string]api.PatchType{"json": api.JSONPatchType, "merge": api.MergePatchType, "strategic": api.StrategicMergePatchType}

// PatchOptions is the start of the data required to perform the operation.  As new fields are added, add them here instead of
// referencing the cmd.Flags()
type PatchOptions struct {
	Filenames []string
	Recursive bool
	Local     bool

	OutputFormat string
}

var (
	patch_long = dedent.Dedent(`
		Update field(s) of a resource using strategic merge patch

		JSON and YAML formats are accepted.

		Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`)
	patch_example = dedent.Dedent(`

		# Partially update a node using strategic merge patch
		kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'

		# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch
		kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'

		# Update a container's image; spec.containers[*].name is required because it's a merge key
		kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'

		# Update a container's image using a json patch with positional arrays
		kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'`)
)

func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
	options := &PatchOptions{}

	// retrieve a list of handled resources from printer as valid args
	validArgs, argAliases := []string{}, []string{}
	p, err := f.Printer(nil, false, false, false, false, false, false, []string{})
	cmdutil.CheckErr(err)
	if p != nil {
		validArgs = p.HandledResources()
		argAliases = kubectl.ResourceAliases(validArgs)
	}

	cmd := &cobra.Command{
		Use:     "patch (-f FILENAME | TYPE NAME) -p PATCH",
		Short:   "Update field(s) of a resource using strategic merge patch.",
		Long:    patch_long,
		Example: patch_example,
		Run: func(cmd *cobra.Command, args []string) {
			options.OutputFormat = cmdutil.GetFlagString(cmd, "output")
			err := RunPatch(f, out, cmd, args, options)
			cmdutil.CheckErr(err)
		},
		ValidArgs:  validArgs,
		ArgAliases: argAliases,
	}
	cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
	cmd.MarkFlagRequired("patch")
	cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
	cmdutil.AddPrinterFlags(cmd)
	cmdutil.AddRecordFlag(cmd)
	cmdutil.AddInclude3rdPartyFlags(cmd)

	usage := "Filename, directory, or URL to a file identifying the resource to update"
	kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
	cmdutil.AddRecursiveFlag(cmd, &options.Recursive)

	cmd.Flags().BoolVar(&options.Local, "local", false, "If true, patch will operate on the content of the file, not the server-side resource.")

	return cmd
}

func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error {
	switch {
	case options.Local && len(args) != 0:
		return fmt.Errorf("cannot specify --local and server resources")
	}

	cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
	if err != nil {
		return err
	}

	patchType := api.StrategicMergePatchType
	patchTypeString := strings.ToLower(cmdutil.GetFlagString(cmd, "type"))
	if len(patchTypeString) != 0 {
		ok := false
		patchType, ok = patchTypes[patchTypeString]
		if !ok {
			return cmdutil.UsageError(cmd, fmt.Sprintf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), patchTypeString))
		}
	}

	patch := cmdutil.GetFlagString(cmd, "patch")
	if len(patch) == 0 {
		return cmdutil.UsageError(cmd, "Must specify -p to patch")
	}
	patchBytes, err := yaml.ToJSON([]byte(patch))
	if err != nil {
		return fmt.Errorf("unable to parse %q: %v", patch, err)
	}

	mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
	r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
		ContinueOnError().
		NamespaceParam(cmdNamespace).DefaultNamespace().
		FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
		ResourceTypeOrNameArgs(false, args...).
		Flatten().
		Do()
	err = r.Err()
	if err != nil {
		return err
	}

	count := 0
	err = r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		name, namespace := info.Name, info.Namespace
		mapping := info.ResourceMapping()
		client, err := f.ClientForMapping(mapping)
		if err != nil {
			return err
		}

		if !options.Local {
			helper := resource.NewHelper(client, mapping)
			patchedObject, err := helper.Patch(namespace, name, patchType, patchBytes)
			if err != nil {
				return err
			}
			if cmdutil.ShouldRecord(cmd, info) {
				if err := cmdutil.RecordChangeCause(patchedObject, f.Command()); err == nil {
					// don't return an error on failure.  The patch itself succeeded, its only the hint for that change that failed
					// don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command
					// also, don't force the replacement.  If the replacement fails on a resourceVersion conflict, then it means this
					// record hint is likely to be invalid anyway, so avoid the bad hint
					resource.NewHelper(client, mapping).Replace(namespace, name, false, patchedObject)
				}
			}
			count++

			if options.OutputFormat == "name" || len(options.OutputFormat) == 0 {
				cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, "", name, "patched")
			}
			return nil
		}

		count++

		patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject)
		if err != nil {
			return err
		}
		originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(), info.VersionedObject.(runtime.Object))
		if err != nil {
			return err
		}
		originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object))
		if err != nil {
			return err
		}
		targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS)
		if err != nil {
			return err
		}
		// TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything
		// rawExtension := &runtime.Unknown{
		// 	Raw: originalPatchedObjJS,
		// }

		printer, err := f.PrinterForMapping(cmd, mapping, false)
		if err != nil {
			return err
		}
		if err := printer.PrintObj(targetObj, out); err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("no objects passed to patch")
	}
	return nil
}

func getPatchedJSON(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
	switch patchType {
	case api.JSONPatchType:
		patchObj, err := jsonpatch.DecodePatch(patchJS)
		if err != nil {
			return nil, err
		}
		return patchObj.Apply(originalJS)

	case api.MergePatchType:
		return jsonpatch.MergePatch(originalJS, patchJS)

	case api.StrategicMergePatchType:
		return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj)

	default:
		// only here as a safety net - go-restful filters content-type
		return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType)
	}
}
<commit_msg>Use strategic patch to replace changeCause in patch command<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 cmd

import (
	"fmt"
	"io"
	"strings"

	"github.com/evanphx/json-patch"
	"github.com/renstrom/dedent"
	"github.com/spf13/cobra"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/kubectl"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/kubernetes/pkg/util/strategicpatch"
	"k8s.io/kubernetes/pkg/util/yaml"
)

var patchTypes = map[string]api.PatchType{"json": api.JSONPatchType, "merge": api.MergePatchType, "strategic": api.StrategicMergePatchType}

// PatchOptions is the start of the data required to perform the operation.  As new fields are added, add them here instead of
// referencing the cmd.Flags()
type PatchOptions struct {
	Filenames []string
	Recursive bool
	Local     bool

	OutputFormat string
}

var (
	patch_long = dedent.Dedent(`
		Update field(s) of a resource using strategic merge patch

		JSON and YAML formats are accepted.

		Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.`)
	patch_example = dedent.Dedent(`

		# Partially update a node using strategic merge patch
		kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'

		# Partially update a node identified by the type and name specified in "node.json" using strategic merge patch
		kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'

		# Update a container's image; spec.containers[*].name is required because it's a merge key
		kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'

		# Update a container's image using a json patch with positional arrays
		kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new image"}]'`)
)

func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
	options := &PatchOptions{}

	// retrieve a list of handled resources from printer as valid args
	validArgs, argAliases := []string{}, []string{}
	p, err := f.Printer(nil, false, false, false, false, false, false, []string{})
	cmdutil.CheckErr(err)
	if p != nil {
		validArgs = p.HandledResources()
		argAliases = kubectl.ResourceAliases(validArgs)
	}

	cmd := &cobra.Command{
		Use:     "patch (-f FILENAME | TYPE NAME) -p PATCH",
		Short:   "Update field(s) of a resource using strategic merge patch.",
		Long:    patch_long,
		Example: patch_example,
		Run: func(cmd *cobra.Command, args []string) {
			options.OutputFormat = cmdutil.GetFlagString(cmd, "output")
			err := RunPatch(f, out, cmd, args, options)
			cmdutil.CheckErr(err)
		},
		ValidArgs:  validArgs,
		ArgAliases: argAliases,
	}
	cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.")
	cmd.MarkFlagRequired("patch")
	cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
	cmdutil.AddPrinterFlags(cmd)
	cmdutil.AddRecordFlag(cmd)
	cmdutil.AddInclude3rdPartyFlags(cmd)

	usage := "Filename, directory, or URL to a file identifying the resource to update"
	kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
	cmdutil.AddRecursiveFlag(cmd, &options.Recursive)

	cmd.Flags().BoolVar(&options.Local, "local", false, "If true, patch will operate on the content of the file, not the server-side resource.")

	return cmd
}

func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error {
	switch {
	case options.Local && len(args) != 0:
		return fmt.Errorf("cannot specify --local and server resources")
	}

	cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
	if err != nil {
		return err
	}

	patchType := api.StrategicMergePatchType
	patchTypeString := strings.ToLower(cmdutil.GetFlagString(cmd, "type"))
	if len(patchTypeString) != 0 {
		ok := false
		patchType, ok = patchTypes[patchTypeString]
		if !ok {
			return cmdutil.UsageError(cmd, fmt.Sprintf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), patchTypeString))
		}
	}

	patch := cmdutil.GetFlagString(cmd, "patch")
	if len(patch) == 0 {
		return cmdutil.UsageError(cmd, "Must specify -p to patch")
	}
	patchBytes, err := yaml.ToJSON([]byte(patch))
	if err != nil {
		return fmt.Errorf("unable to parse %q: %v", patch, err)
	}

	mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
	r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
		ContinueOnError().
		NamespaceParam(cmdNamespace).DefaultNamespace().
		FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
		ResourceTypeOrNameArgs(false, args...).
		Flatten().
		Do()
	err = r.Err()
	if err != nil {
		return err
	}

	count := 0
	err = r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		name, namespace := info.Name, info.Namespace
		mapping := info.ResourceMapping()
		client, err := f.ClientForMapping(mapping)
		if err != nil {
			return err
		}

		if !options.Local {
			helper := resource.NewHelper(client, mapping)
			_, err := helper.Patch(namespace, name, patchType, patchBytes)
			if err != nil {
				return err
			}
			if cmdutil.ShouldRecord(cmd, info) {
				// don't return an error on failure.  The patch itself succeeded, its only the hint for that change that failed
				// don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command
				// also, don't force the replacement.  If the replacement fails on a resourceVersion conflict, then it means this
				// record hint is likely to be invalid anyway, so avoid the bad hint
				patch, err := cmdutil.ChangeResourcePatch(info, f.Command())
				if err == nil {
					helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
				}
			}
			count++

			if options.OutputFormat == "name" || len(options.OutputFormat) == 0 {
				cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, "", name, "patched")
			}
			return nil
		}

		count++

		patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject)
		if err != nil {
			return err
		}
		originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(), info.VersionedObject.(runtime.Object))
		if err != nil {
			return err
		}
		originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object))
		if err != nil {
			return err
		}
		targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS)
		if err != nil {
			return err
		}
		// TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything
		// rawExtension := &runtime.Unknown{
		//	Raw: originalPatchedObjJS,
		// }

		printer, err := f.PrinterForMapping(cmd, mapping, false)
		if err != nil {
			return err
		}
		if err := printer.PrintObj(targetObj, out); err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("no objects passed to patch")
	}
	return nil
}

func getPatchedJSON(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
	switch patchType {
	case api.JSONPatchType:
		patchObj, err := jsonpatch.DecodePatch(patchJS)
		if err != nil {
			return nil, err
		}
		return patchObj.Apply(originalJS)

	case api.MergePatchType:
		return jsonpatch.MergePatch(originalJS, patchJS)

	case api.StrategicMergePatchType:
		return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj)

	default:
		// only here as a safety net - go-restful filters content-type
		return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"crypto/tls"
	"fmt"
	"net/http"
	"path/filepath"

	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/filemonitor"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/profile"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sirupsen/logrus"
)

func GetListenAndServeFunc(logger *logrus.Logger, tlsCertPath, tlsKeyPath, clientCAPath *string) (func() error, error) {
	mux := http.NewServeMux()
	profile.RegisterHandlers(mux)
	mux.Handle("/metrics", promhttp.Handler())
	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	s := http.Server{
		Handler: mux,
		Addr:    ":8080",
	}
	listenAndServe := s.ListenAndServe

	if *tlsCertPath != "" && *tlsKeyPath != "" {
		logger.Info("TLS keys set, using https for metrics")

		certStore, err := filemonitor.NewCertStore(*tlsCertPath, *tlsKeyPath)
		if err != nil {
			return nil, fmt.Errorf("Certificate monitoring for metrics (https) failed: %v", err)
		}

		csw, err := filemonitor.NewWatch(logger, []string{filepath.Dir(*tlsCertPath), filepath.Dir(*tlsKeyPath)}, certStore.HandleFilesystemUpdate)
		if err != nil {
			return nil, fmt.Errorf("error creating cert file watcher: %v", err)
		}
		csw.Run(context.Background())
		certPoolStore, err := filemonitor.NewCertPoolStore(*clientCAPath)
		cpsw, err := filemonitor.NewWatch(logger, []string{filepath.Dir(*clientCAPath)}, certPoolStore.HandleCABundleUpdate)
		if err != nil {
			return nil, fmt.Errorf("error creating cert file watcher: %v", err)
		}
		cpsw.Run(context.Background())

		s.Addr = ":8443"
		s.TLSConfig = &tls.Config{
			GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
				return certStore.GetCertificate(), nil
			},
			GetConfigForClient: func(_ *tls.ClientHelloInfo) (*tls.Config, error) {
				var certs []tls.Certificate
				if cert := certStore.GetCertificate(); cert != nil {
					certs = append(certs, *cert)
				}
				return &tls.Config{
					Certificates: certs,
					ClientCAs:    certPoolStore.GetCertPool(),
					ClientAuth:   tls.VerifyClientCertIfGiven,
				}, nil
			},
		}

		listenAndServe = func() error {
			return s.ListenAndServeTLS("", "")
		}
	} else if *tlsCertPath != "" || *tlsKeyPath != "" {
		return nil, fmt.Errorf("both --tls-key and --tls-crt must be provided for TLS to be enabled")
	} else {
		logger.Info("TLS keys not set, using non-https for metrics")
	}
	return listenAndServe, nil
}
<commit_msg>fail gracefully when client-ca file is not set or doesn't exist (#2358)<commit_after>package server

import (
	"context"
	"crypto/tls"
	"fmt"
	"net/http"
	"path/filepath"

	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/filemonitor"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/profile"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sirupsen/logrus"
)

func GetListenAndServeFunc(logger *logrus.Logger, tlsCertPath, tlsKeyPath, clientCAPath *string) (func() error, error) {
	mux := http.NewServeMux()
	profile.RegisterHandlers(mux)
	mux.Handle("/metrics", promhttp.Handler())
	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	s := http.Server{
		Handler: mux,
		Addr:    ":8080",
	}
	listenAndServe := s.ListenAndServe

	if *tlsCertPath != "" && *tlsKeyPath != "" {
		logger.Info("TLS keys set, using https for metrics")

		certStore, err := filemonitor.NewCertStore(*tlsCertPath, *tlsKeyPath)
		if err != nil {
			return nil, fmt.Errorf("certificate monitoring for metrics (https) failed: %v", err)
		}

		csw, err := filemonitor.NewWatch(logger, []string{filepath.Dir(*tlsCertPath), filepath.Dir(*tlsKeyPath)}, certStore.HandleFilesystemUpdate)
		if err != nil {
			return nil, fmt.Errorf("error creating cert file watcher: %v", err)
		}
		csw.Run(context.Background())
		certPoolStore, err := filemonitor.NewCertPoolStore(*clientCAPath)
		if err != nil {
			return nil, fmt.Errorf("certificate monitoring for client-ca failed: %v", err)
		}
		cpsw, err := filemonitor.NewWatch(logger, []string{filepath.Dir(*clientCAPath)}, certPoolStore.HandleCABundleUpdate)
		if err != nil {
			return nil, fmt.Errorf("error creating cert file watcher: %v", err)
		}
		cpsw.Run(context.Background())

		s.Addr = ":8443"
		s.TLSConfig = &tls.Config{
			GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
				return certStore.GetCertificate(), nil
			},
			GetConfigForClient: func(_ *tls.ClientHelloInfo) (*tls.Config, error) {
				var certs []tls.Certificate
				if cert := certStore.GetCertificate(); cert != nil {
					certs = append(certs, *cert)
				}
				return &tls.Config{
					Certificates: certs,
					ClientCAs:    certPoolStore.GetCertPool(),
					ClientAuth:   tls.VerifyClientCertIfGiven,
				}, nil
			},
		}

		listenAndServe = func() error {
			return s.ListenAndServeTLS("", "")
		}
	} else if *tlsCertPath != "" || *tlsKeyPath != "" {
		return nil, fmt.Errorf("both --tls-key and --tls-crt must be provided for TLS to be enabled")
	} else {
		logger.Info("TLS keys not set, using non-https for metrics")
	}
	return listenAndServe, nil
}
<|endoftext|>