<commit_before>// Copyright (c) 2013 Joshua Elliott
// Released under the MIT License
// http://opensource.org/licenses/MIT

package turnpike

import (
	"encoding/json"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
)

const (
	msgWelcome = iota
	msgPrefix
	msgCall
	msgCallResult
	msgCallError
	msgSubscribe
	msgUnsubscribe
	msgPublish
	msgEvent
)

const wampProtocolVersion = 1

var (
	typeReg = regexp.MustCompile("^\\s*\\[\\s*(\\d+)\\s*,")
)

// A WAMPError is returned when attempting to create a message that does not follow the WAMP
// protocol
type WAMPError struct {
	Msg string
}

var (
	// An ErrInvalidURI describes an invalid URI.
	ErrInvalidURI = &WAMPError{"invalid URI"}
	// An ErrInvalidNumArgs describes invalid number of arguments in a message.
	ErrInvalidNumArgs = &WAMPError{"invalid number of arguments in message"}
	// An ErrUnsupportedProtocol describes an unsupported WAMP protocol in a welcome message.
	ErrUnsupportedProtocol = &WAMPError{"unsupported protocol"}
)

// Error implements the error interface to provide a message.
func (e *WAMPError) Error() string {
	return "wamp: " + e.Msg
}

func parseMessageType(msg string) int {
	match := typeReg.FindStringSubmatch(msg)
	if match == nil {
		return -1
	}
	i, _ := strconv.Atoi(match[1])
	return i
}

func messageTypeString(typ int) string {
	types := []string{"WELCOME", "PREFIX", "CALL", "CALLRESULT", "CALLERROR", "SUBSCRIBE", "UNSUBSCRIBE", "PUBLISH", "EVENT"}
	if typ >= 0 && typ < 9 {
		return types[typ]
	}
	return ""
}

// WELCOME
type welcomeMsg struct {
	SessionId       string
	ProtocolVersion int
	ServerIdent     string
}

func (msg *welcomeMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 4 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.SessionId, ok = data[1].(string); !ok {
		return &WAMPError{"invalid session ID"}
	}
	if protocolVersion, ok := data[2].(float64); ok {
		msg.ProtocolVersion = int(protocolVersion)
	} else {
		return ErrUnsupportedProtocol
	}
	if msg.ServerIdent, ok = data[3].(string); !ok {
		return &WAMPError{"invalid server identity"}
	}
	return nil
}

// Welcome returns a json encoded WAMP 'WELCOME' message as a byte slice
// sessionId is a randomly generated string provided by the server, serverIdent
// is a string that identifies the WAMP server
func createWelcome(sessionId, serverIdent string) (string, error) {
	return createWAMPMessage(msgWelcome, sessionId, wampProtocolVersion, serverIdent)
}

// PREFIX
type prefixMsg struct {
	Prefix string
	URI    string
}

func (msg *prefixMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.Prefix, ok = data[1].(string); !ok {
		return &WAMPError{"invalid prefix"}
	}
	if msg.URI, ok = data[2].(string); !ok {
		return &WAMPError{"invalid URI"}
	}
	return nil
}

// Prefix returns a json encoded WAMP 'PREFIX' message as a byte slice
func createPrefix(prefix, URI string) (string, error) {
	if _, err := url.ParseRequestURI(URI); err != nil {
		return "", &WAMPError{"invalid URI: " + URI}
	}
	return createWAMPMessage(msgPrefix, prefix, URI)
}

// CALL
type callMsg struct {
	CallID   string
	ProcURI  string
	CallArgs []interface{}
}

func (msg *callMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) < 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.CallID, ok = data[1].(string); !ok {
		return &WAMPError{"invalid callID"}
	}
	if msg.ProcURI, ok = data[2].(string); !ok {
		return &WAMPError{"invalid procURI"}
	}
	if len(data) > 3 {
		msg.CallArgs = data[3:]
	}
	return nil
}

// Call returns a json encoded WAMP 'CALL' message as a byte slice
// callID must be a randomly generated string, procURI is the URI of the remote
// procedure to be called, followed by zero or more call arguments
func createCall(callID, procURI string, args ...interface{}) (string, error) {
	if _, err := url.ParseRequestURI(procURI); err != nil {
		return "", &WAMPError{"invalid URI: " + procURI}
	}
	var data []interface{}
	data = append(data, msgCall, callID, procURI)
	data = append(data, args...)
	b, err := json.Marshal(data)
	return string(b), err
}

// CALLRESULT
type callResultMsg struct {
	CallID string
	Result interface{}
}

func (msg *callResultMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.CallID, ok = data[1].(string); !ok {
		return &WAMPError{"invalid callID"}
	}
	msg.Result = data[2]

	return nil
}

// CallResult returns a json encoded WAMP 'CALLRESULT' message as a byte slice
// callID is the randomly generated string provided by the client
func createCallResult(callID string, result interface{}) (string, error) {
	return createWAMPMessage(msgCallResult, callID, result)
}

// CALLERROR
type callErrorMsg struct {
	CallID       string
	ErrorURI     string
	ErrorDesc    string
	ErrorDetails interface{}
}

func (msg *callErrorMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) < 4 || len(data) > 5 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.CallID, ok = data[1].(string); !ok {
		return &WAMPError{"invalid callID"}
	}
	if msg.ErrorURI, ok = data[2].(string); !ok {
		return &WAMPError{"invalid errorURI"}
	}
	if msg.ErrorDesc, ok = data[3].(string); !ok {
		return &WAMPError{"invalid error description"}
	}
	if len(data) == 5 {
		msg.ErrorDetails = data[4]
	}
	return nil
}

// CallError returns a json encoded WAMP 'CALLERROR' message as a byte slice
// callID is the randomly generated string provided by the client, errorURI is
// a URI identifying the error, errorDesc is a human-readable description of the
// error (for developers), errorDetails, if present, is a non-nil object
func createCallError(callID, errorURI, errorDesc string, errorDetails ...interface{}) (string, error) {
	if _, err := url.ParseRequestURI(errorURI); err != nil {
		return "", &WAMPError{"invalid URI: " + errorURI}
	}
	var data []interface{}
	filledInErrorDesc := fmt.Sprintf(errorDesc, errorDetails)
	data = append(data, msgCallError, callID, errorURI, filledInErrorDesc)
	data = append(data, errorDetails...)
	b, err := json.Marshal(data)
	return string(b), err
}

// SUBSCRIBE
type subscribeMsg struct {
	TopicURI string
}

func (msg *subscribeMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 2 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	return nil
}

// Subscribe returns a json encoded WAMP 'SUBSCRIBE' message as a byte slice
// topicURI is the topic that the client wants to subscribe to
func createSubscribe(topicURI string) (string, error) {
	return createWAMPMessagePubSub(msgSubscribe, topicURI)
}

// UNSUBSCRIBE
type unsubscribeMsg struct {
	TopicURI string
}

func (msg *unsubscribeMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 2 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	return nil
}

// Unsubscribe returns a json encoded WAMP 'UNSUBSCRIBE' message as a byte slice
// topicURI is the topic that the client wants to unsubscribe from
func createUnsubscribe(topicURI string) (string, error) {
	return createWAMPMessagePubSub(msgUnsubscribe, topicURI)
}

// PUBLISH
type publishMsg struct {
	TopicURI     string
	Event        interface{}
	ExcludeMe    bool
	ExcludeList  []string
	EligibleList []string
}

func (msg *publishMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) < 3 || len(data) > 5 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	msg.Event = data[2]
	if len(data) > 3 {
		if msg.ExcludeMe, ok = data[3].(bool); !ok {
			var arr []interface{}
			if arr, ok = data[3].([]interface{}); !ok && data[3] != nil {
				return &WAMPError{"invalid exclude argument"}
			}
			for _, v := range arr {
				if val, ok := v.(string); !ok {
					return &WAMPError{"invalid exclude list"}
				} else {
					msg.ExcludeList = append(msg.ExcludeList, val)
				}
			}
			if len(data) == 5 {
				if arr, ok = data[4].([]interface{}); !ok && data[3] != nil {
					return &WAMPError{"invalid eligable list"}
				}
				for _, v := range arr {
					if val, ok := v.(string); !ok {
						return &WAMPError{"invalid eligable list"}
					} else {
						msg.EligibleList = append(msg.EligibleList, val)
					}
				}
			}
		}
	}
	return nil
}

// Publish returns a json encoded WAMP 'PUBLISH' message as a byte slice
// arguments must be given in one of the following formats:
// [ topicURI, event ]
// [ topicURI, event, excludeMe ]
// [ topicURI, event, exclude ]
// [ topicURI, event, exclude, eligible ]
// event can be nil, a simple json type, or a complex json type
func createPublish(topicURI string, event interface{}, opts ...interface{}) (string, error) {
	var data []interface{}
	data = append(data, msgPublish, topicURI, event)
	data = append(data, opts...)
	return createWAMPMessagePubSub(data...)
}

// EVENT
type eventMsg struct {
	TopicURI string
	Event    interface{}
}

func (msg *eventMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	msg.Event = data[2]

	return nil
}

// Event returns a json encoded WAMP 'EVENT' message as a byte slice
// event can be nil, a simple json type, or a complex json type
func createEvent(topicURI string, event interface{}) (string, error) {
	return createWAMPMessagePubSub(msgEvent, topicURI, event)
}

// createWAMPMessagePubSub checks that the second argument (topicURI) is a valid
// URI and then passes the request on to createWAMPMessage
func createWAMPMessagePubSub(args ...interface{}) (string, error) {
	if _, err := url.ParseRequestURI(args[1].(string)); err != nil {
		return "", &WAMPError{"invalid URI: " + args[1].(string)}
	}
	return createWAMPMessage(args...)
}

// createWAMPMessage returns a JSON encoded list from all the arguments passed to it
func createWAMPMessage(args ...interface{}) (string, error) {
	var data []interface{}
	data = append(data, args...)
	b, err := json.Marshal(data)
	return string(b), err
}
<commit_msg>only pass variables if the array isn't 0 length.<commit_after>// Copyright (c) 2013 Joshua Elliott
// Released under the MIT License
// http://opensource.org/licenses/MIT

package turnpike

import (
	"encoding/json"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
)

const (
	msgWelcome = iota
	msgPrefix
	msgCall
	msgCallResult
	msgCallError
	msgSubscribe
	msgUnsubscribe
	msgPublish
	msgEvent
)

const wampProtocolVersion = 1

var (
	typeReg = regexp.MustCompile("^\\s*\\[\\s*(\\d+)\\s*,")
)

// A WAMPError is returned when attempting to create a message that does not follow the WAMP
// protocol
type WAMPError struct {
	Msg string
}

var (
	// An ErrInvalidURI describes an invalid URI.
	ErrInvalidURI = &WAMPError{"invalid URI"}
	// An ErrInvalidNumArgs describes invalid number of arguments in a message.
	ErrInvalidNumArgs = &WAMPError{"invalid number of arguments in message"}
	// An ErrUnsupportedProtocol describes an unsupported WAMP protocol in a welcome message.
	ErrUnsupportedProtocol = &WAMPError{"unsupported protocol"}
)

// Error implements the error interface to provide a message.
func (e *WAMPError) Error() string {
	return "wamp: " + e.Msg
}

func parseMessageType(msg string) int {
	match := typeReg.FindStringSubmatch(msg)
	if match == nil {
		return -1
	}
	i, _ := strconv.Atoi(match[1])
	return i
}

func messageTypeString(typ int) string {
	types := []string{"WELCOME", "PREFIX", "CALL", "CALLRESULT", "CALLERROR", "SUBSCRIBE", "UNSUBSCRIBE", "PUBLISH", "EVENT"}
	if typ >= 0 && typ < 9 {
		return types[typ]
	}
	return ""
}

// WELCOME
type welcomeMsg struct {
	SessionId       string
	ProtocolVersion int
	ServerIdent     string
}

func (msg *welcomeMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 4 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.SessionId, ok = data[1].(string); !ok {
		return &WAMPError{"invalid session ID"}
	}
	if protocolVersion, ok := data[2].(float64); ok {
		msg.ProtocolVersion = int(protocolVersion)
	} else {
		return ErrUnsupportedProtocol
	}
	if msg.ServerIdent, ok = data[3].(string); !ok {
		return &WAMPError{"invalid server identity"}
	}
	return nil
}

// Welcome returns a json encoded WAMP 'WELCOME' message as a byte slice
// sessionId is a randomly generated string provided by the server, serverIdent
// is a string that identifies the WAMP server
func createWelcome(sessionId, serverIdent string) (string, error) {
	return createWAMPMessage(msgWelcome, sessionId, wampProtocolVersion, serverIdent)
}

// PREFIX
type prefixMsg struct {
	Prefix string
	URI    string
}

func (msg *prefixMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.Prefix, ok = data[1].(string); !ok {
		return &WAMPError{"invalid prefix"}
	}
	if msg.URI, ok = data[2].(string); !ok {
		return &WAMPError{"invalid URI"}
	}
	return nil
}

// Prefix returns a json encoded WAMP 'PREFIX' message as a byte slice
func createPrefix(prefix, URI string) (string, error) {
	if _, err := url.ParseRequestURI(URI); err != nil {
		return "", &WAMPError{"invalid URI: " + URI}
	}
	return createWAMPMessage(msgPrefix, prefix, URI)
}

// CALL
type callMsg struct {
	CallID   string
	ProcURI  string
	CallArgs []interface{}
}

func (msg *callMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) < 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.CallID, ok = data[1].(string); !ok {
		return &WAMPError{"invalid callID"}
	}
	if msg.ProcURI, ok = data[2].(string); !ok {
		return &WAMPError{"invalid procURI"}
	}
	if len(data) > 3 {
		msg.CallArgs = data[3:]
	}
	return nil
}

// Call returns a json encoded WAMP 'CALL' message as a byte slice
// callID must be a randomly generated string, procURI is the URI of the remote
// procedure to be called, followed by zero or more call arguments
func createCall(callID, procURI string, args ...interface{}) (string, error) {
	if _, err := url.ParseRequestURI(procURI); err != nil {
		return "", &WAMPError{"invalid URI: " + procURI}
	}
	var data []interface{}
	data = append(data, msgCall, callID, procURI)
	data = append(data, args...)
	b, err := json.Marshal(data)
	return string(b), err
}

// CALLRESULT
type callResultMsg struct {
	CallID string
	Result interface{}
}

func (msg *callResultMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.CallID, ok = data[1].(string); !ok {
		return &WAMPError{"invalid callID"}
	}
	msg.Result = data[2]

	return nil
}

// CallResult returns a json encoded WAMP 'CALLRESULT' message as a byte slice
// callID is the randomly generated string provided by the client
func createCallResult(callID string, result interface{}) (string, error) {
	return createWAMPMessage(msgCallResult, callID, result)
}

// CALLERROR
type callErrorMsg struct {
	CallID       string
	ErrorURI     string
	ErrorDesc    string
	ErrorDetails interface{}
}

func (msg *callErrorMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) < 4 || len(data) > 5 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.CallID, ok = data[1].(string); !ok {
		return &WAMPError{"invalid callID"}
	}
	if msg.ErrorURI, ok = data[2].(string); !ok {
		return &WAMPError{"invalid errorURI"}
	}
	if msg.ErrorDesc, ok = data[3].(string); !ok {
		return &WAMPError{"invalid error description"}
	}
	if len(data) == 5 {
		msg.ErrorDetails = data[4]
	}
	return nil
}

// CallError returns a json encoded WAMP 'CALLERROR' message as a byte slice
// callID is the randomly generated string provided by the client, errorURI is
// a URI identifying the error, errorDesc is a human-readable description of the
// error (for developers), errorDetails, if present, is a non-nil object
func createCallError(callID, errorURI, errorDesc string, errorDetails ...interface{}) (string, error) {
	if _, err := url.ParseRequestURI(errorURI); err != nil {
		return "", &WAMPError{"invalid URI: " + errorURI}
	}
	var data []interface{}
	filledInErrorDesc := errorDesc
	if len(errorDetails) > 0 {
		filledInErrorDesc = fmt.Sprintf(errorDesc, errorDetails)
	}
	data = append(data, msgCallError, callID, errorURI, filledInErrorDesc)
	data = append(data, errorDetails...)
	b, err := json.Marshal(data)
	return string(b), err
}

// SUBSCRIBE
type subscribeMsg struct {
	TopicURI string
}

func (msg *subscribeMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 2 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	return nil
}

// Subscribe returns a json encoded WAMP 'SUBSCRIBE' message as a byte slice
// topicURI is the topic that the client wants to subscribe to
func createSubscribe(topicURI string) (string, error) {
	return createWAMPMessagePubSub(msgSubscribe, topicURI)
}

// UNSUBSCRIBE
type unsubscribeMsg struct {
	TopicURI string
}

func (msg *unsubscribeMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 2 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	return nil
}

// Unsubscribe returns a json encoded WAMP 'UNSUBSCRIBE' message as a byte slice
// topicURI is the topic that the client wants to unsubscribe from
func createUnsubscribe(topicURI string) (string, error) {
	return createWAMPMessagePubSub(msgUnsubscribe, topicURI)
}

// PUBLISH
type publishMsg struct {
	TopicURI     string
	Event        interface{}
	ExcludeMe    bool
	ExcludeList  []string
	EligibleList []string
}

func (msg *publishMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) < 3 || len(data) > 5 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	msg.Event = data[2]
	if len(data) > 3 {
		if msg.ExcludeMe, ok = data[3].(bool); !ok {
			var arr []interface{}
			if arr, ok = data[3].([]interface{}); !ok && data[3] != nil {
				return &WAMPError{"invalid exclude argument"}
			}
			for _, v := range arr {
				if val, ok := v.(string); !ok {
					return &WAMPError{"invalid exclude list"}
				} else {
					msg.ExcludeList = append(msg.ExcludeList, val)
				}
			}
			if len(data) == 5 {
				if arr, ok = data[4].([]interface{}); !ok && data[3] != nil {
					return &WAMPError{"invalid eligable list"}
				}
				for _, v := range arr {
					if val, ok := v.(string); !ok {
						return &WAMPError{"invalid eligable list"}
					} else {
						msg.EligibleList = append(msg.EligibleList, val)
					}
				}
			}
		}
	}
	return nil
}

// Publish returns a json encoded WAMP 'PUBLISH' message as a byte slice
// arguments must be given in one of the following formats:
// [ topicURI, event ]
// [ topicURI, event, excludeMe ]
// [ topicURI, event, exclude ]
// [ topicURI, event, exclude, eligible ]
// event can be nil, a simple json type, or a complex json type
func createPublish(topicURI string, event interface{}, opts ...interface{}) (string, error) {
	var data []interface{}
	data = append(data, msgPublish, topicURI, event)
	data = append(data, opts...)
	return createWAMPMessagePubSub(data...)
}

// EVENT
type eventMsg struct {
	TopicURI string
	Event    interface{}
}

func (msg *eventMsg) UnmarshalJSON(jsonData []byte) error {
	var data []interface{}
	err := json.Unmarshal(jsonData, &data)
	if err != nil {
		return err
	}
	if len(data) != 3 {
		return ErrInvalidNumArgs
	}
	var ok bool
	if msg.TopicURI, ok = data[1].(string); !ok {
		return &WAMPError{"invalid topicURI"}
	}
	msg.Event = data[2]

	return nil
}

// Event returns a json encoded WAMP 'EVENT' message as a byte slice
// event can be nil, a simple json type, or a complex json type
func createEvent(topicURI string, event interface{}) (string, error) {
	return createWAMPMessagePubSub(msgEvent, topicURI, event)
}

// createWAMPMessagePubSub checks that the second argument (topicURI) is a valid
// URI and then passes the request on to createWAMPMessage
func createWAMPMessagePubSub(args ...interface{}) (string, error) {
	if _, err := url.ParseRequestURI(args[1].(string)); err != nil {
		return "", &WAMPError{"invalid URI: " + args[1].(string)}
	}
	return createWAMPMessage(args...)
}

// createWAMPMessage returns a JSON encoded list from all the arguments passed to it
func createWAMPMessage(args ...interface{}) (string, error) {
	var data []interface{}
	data = append(data, args...)
	b, err := json.Marshal(data)
	return string(b), err
}
<|endoftext|>


<commit_before>package wave

import (
	"sync"
)

// Once prepares a wave that will only execute one time after Start is called
// on the returned handle.
func Once(concurrency int, vals []string, callback func(string)) *handle {
	h := newHandle()
	go func() {
		<-h.startChan
		doTheWave(concurrency, vals, callback, h)
		close(h.stopChan)
	}()
	return h
}

func doTheWave(concurrency int, vals []string, callback func(string), h *handle) {
	valChan := make(chan string, 10)
	go func() {
		for _, val := range vals {
			valChan <- val
		}
		close(valChan)
	}()
	wg := sync.WaitGroup{}
	for i := 0; i < concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case _, ok := <-h.interruptChan:
					if !ok {
						return
					}
				default:
					select {
					case _, ok := <-h.interruptChan:
						if !ok {
							return
						}
					case val, ok := <-valChan:
						if !ok {
							return
						}
						callback(val)
					}
				}
			}
		}()
	}
	wg.Wait()
	if _, ok := <-valChan; ok {
		h.trigger(h.interruptFuncs)
	} else {
		h.trigger(h.eachFuncs)
	}
}

// Continuous prepares a wave that will repeat indefinitely unless interrupted
// by one of the available event triggers. Start must be called on the returned
// handle to begin the wave.
func Continuous(concurrency int, vals []string, callback func(string)) *handle {
	h := newHandle()
	go func() {
		<-h.startChan
	loop:
		for {
			select {
			case _, ok := <-h.interruptChan:
				if !ok {
					break loop
				}
			case _, ok := <-h.finishChan:
				if !ok {
					break loop
				}
			default:
				doTheWave(concurrency, vals, callback, h)
			}
		}
		close(h.stopChan)
	}()
	return h
}

type handle struct {
	start, interrupt, finish sync.Once

	startChan      chan struct{} // Close to request start
	interruptChan  chan struct{} // Close to request interrupt
	finishChan     chan struct{} // Close to request finish
	stopChan       chan struct{} // Close when stopped
	stopFuncs      []func()
	interruptFuncs []func()
	eachFuncs      []func()
	funcsLock      sync.RWMutex // Guards all []func()
}

func newHandle() *handle {
	return &handle{
		startChan:      make(chan struct{}),
		interruptChan:  make(chan struct{}),
		finishChan:     make(chan struct{}),
		stopChan:       make(chan struct{}),
		stopFuncs:      []func(){},
		interruptFuncs: []func(){},
		eachFuncs:      []func(){},
	}
}

func (h *handle) trigger(fs []func()) {
	wg := sync.WaitGroup{}
	h.funcsLock.RLock()
	for _, f := range fs {
		wg.Add(1)
		go func() { f(); wg.Done() }()
	}
	h.funcsLock.RUnlock()
	wg.Wait()
}

// Start begins the wave. It must be called for the wave to proceed.
// For continuous waves, the first call to Start is all that is needed. The wave
// will loop continuously after that.
func (h *handle) Start() {
	h.start.Do(func() {
		close(h.startChan)
	})
}

// Interrupt allows running callbacks to finish while preventing the wave from
// continuing. It will block until all callbacks finish.
func (h *handle) Interrupt() {
	h.interrupt.Do(func() {
		close(h.interruptChan)
	})
	h.Wait()
}

// Finish will block until the current wave is completed, then no further waves
// will be started.
// For convenience, Finish starts the wave if it hasn't started yet.
// Note: if Interrupt is called, Finish will unblock whenever the wave stops,
// whether or not it truly finished an entire wave.  TODO return boolean
func (h *handle) Finish() {
	h.Start()
	h.finish.Do(func() {
		close(h.finishChan)
	})
	h.Wait()
}

// Wait blocks until the wave signals a Stop.
func (h *handle) Wait() {
	<-h.stopChan
}

// OnStop registers a function to be called when the wave has stopped for any
// reason.
func (h *handle) OnStop(f func()) {
	h.funcsLock.Lock()
	h.stopFuncs = append(h.stopFuncs, f)
	h.funcsLock.Unlock()
}

// OnInterrupt registers a function to be called if the wave is interrupted.
// The callback will be invoked after processing has finished.
func (h *handle) OnInterrupt(f func()) {
	h.funcsLock.Lock()
	h.interruptFuncs = append(h.interruptFuncs, f)
	h.funcsLock.Unlock()
}

// AfterEach returns a channel that will send after each full wave has completed.
// It will not send unless the whole set of values has been processed.
func (h *handle) AfterEach(f func()) {
	h.funcsLock.Lock()
	h.eachFuncs = append(h.eachFuncs, f)
	h.funcsLock.Unlock()
}
<commit_msg>Fix Continuous impl<commit_after>package wave

import (
	"sync"
)

// Once prepares a wave that will only execute one time after Start is called
// on the returned handle.
func Once(concurrency int, vals []string, callback func(string)) *handle {
	h := newHandle()
	go func() {
		<-h.startChan
		doTheWave(concurrency, vals, callback, h)
		close(h.stopChan)
	}()
	return h
}

func doTheWave(concurrency int, vals []string, callback func(string), h *handle) {
	valChan := make(chan string, 10)
	go func() {
		for _, val := range vals {
			valChan <- val
		}
		close(valChan)
	}()
	wg := sync.WaitGroup{}
	for i := 0; i < concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case <-h.interruptChan:
					return
				default:
					select {
					case <-h.interruptChan:
						return
					case val, ok := <-valChan:
						if !ok {
							return
						}
						callback(val)
					}
				}
			}
		}()
	}
	wg.Wait()
	if _, ok := <-valChan; ok {
		h.trigger(h.interruptFuncs)
	} else {
		h.trigger(h.eachFuncs)
	}
}

// Continuous prepares a wave that will repeat indefinitely unless interrupted
// by one of the available event triggers. Start must be called on the returned
// handle to begin the wave.
func Continuous(concurrency int, vals []string, callback func(string)) *handle {
	h := newHandle()
	go func() {
		<-h.startChan
		first := true
	loop:
		for {
			select {
			case <-h.interruptChan:
				break loop
			case <-h.finishChan:
				if first {
					doTheWave(concurrency, vals, callback, h)
					first = false
				}
				break loop
			default:
				doTheWave(concurrency, vals, callback, h)
				first = false
			}
		}
		close(h.stopChan)
	}()
	return h
}

type handle struct {
	start, interrupt, finish sync.Once

	startChan      chan struct{} // Close to request start
	interruptChan  chan struct{} // Close to request interrupt
	finishChan     chan struct{} // Close to request finish
	stopChan       chan struct{} // Close when stopped
	stopFuncs      []func()
	interruptFuncs []func()
	eachFuncs      []func()
	funcsLock      sync.RWMutex // Guards all []func()
}

func newHandle() *handle {
	return &handle{
		startChan:      make(chan struct{}),
		interruptChan:  make(chan struct{}),
		finishChan:     make(chan struct{}),
		stopChan:       make(chan struct{}),
		stopFuncs:      []func(){},
		interruptFuncs: []func(){},
		eachFuncs:      []func(){},
	}
}

func (h *handle) trigger(fs []func()) {
	wg := sync.WaitGroup{}
	h.funcsLock.RLock()
	for _, f := range fs {
		wg.Add(1)
		go func() { f(); wg.Done() }()
	}
	h.funcsLock.RUnlock()
	wg.Wait()
}

// Start begins the wave. It must be called for the wave to proceed.
// For continuous waves, the first call to Start is all that is needed. The wave
// will loop continuously after that.
func (h *handle) Start() {
	h.start.Do(func() {
		close(h.startChan)
	})
}

// Interrupt allows running callbacks to finish while preventing the wave from
// continuing. It will block until all callbacks finish.
func (h *handle) Interrupt() {
	h.interrupt.Do(func() {
		close(h.interruptChan)
	})
	h.Wait()
}

// Finish will block until the current wave is completed, then no further waves
// will be started.
// For convenience, Finish starts the wave if it hasn't started yet.
// Note: if Interrupt is called, Finish will unblock whenever the wave stops,
// whether or not it truly finished an entire wave.  TODO return boolean
func (h *handle) Finish() {
	h.Start()
	h.finish.Do(func() {
		close(h.finishChan)
	})
	h.Wait()
}

// Wait blocks until the wave signals a Stop.
func (h *handle) Wait() {
	<-h.stopChan
}

// OnStop registers a function to be called when the wave has stopped for any
// reason.
func (h *handle) OnStop(f func()) {
	h.funcsLock.Lock()
	h.stopFuncs = append(h.stopFuncs, f)
	h.funcsLock.Unlock()
}

// OnInterrupt registers a function to be called if the wave is interrupted.
// The callback will be invoked after processing has finished.
func (h *handle) OnInterrupt(f func()) {
	h.funcsLock.Lock()
	h.interruptFuncs = append(h.interruptFuncs, f)
	h.funcsLock.Unlock()
}

// AfterEach returns a channel that will send after each full wave has completed.
// It will not send unless the whole set of values has been processed.
func (h *handle) AfterEach(f func()) {
	h.funcsLock.Lock()
	h.eachFuncs = append(h.eachFuncs, f)
	h.funcsLock.Unlock()
}
<|endoftext|>


<commit_before>package review

import (
	"context"
	"fmt"
	"os"

	flowutil "github.com/codelingo/codelingo/sdk/flow"
	"github.com/codelingo/lingo/app/commands/verify"
	"github.com/codelingo/lingo/app/util"
	"github.com/codelingo/lingo/app/util/common/config"
	"github.com/codelingo/lingo/vcs"
	"github.com/codelingo/rpc/flow"
	"github.com/golang/protobuf/proto"
	"github.com/juju/errors"
	"github.com/urfave/cli"
)

const (
	vcsGit string = "git"
	vcsP4  string = "perforce"
)

var CLIApp = &flowutil.CLIApp{
	App: cli.App{

		Name:    "review",
		Usage:   "Review code following tenets in codelingo.yaml.",
		Version: "0.0.0",

		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  util.DiffFlg.String(),
				Usage: "Review only unstaged changes in the working tree.",
			},
			cli.StringFlag{
				Name:  util.OutputFlg.String(),
				Usage: "File to save found issues to.",
			},
			cli.StringFlag{
				Name:  util.FormatFlg.String(),
				Value: "json-pretty",
				Usage: "How to format the found issues. Possible values are: json, json-pretty.",
			},
			cli.BoolFlag{
				Name:  util.KeepAllFlg.String(),
				Usage: "Keep all issues and don't be prompted to confirm each issue.",
			},
			cli.StringFlag{
				Name:  util.DirectoryFlg.String(),
				Usage: "Review a given directory.",
			},
			cli.BoolFlag{
				Name:  "debug",
				Usage: "Display debug messages",
			},
			cli.BoolFlag{
				Name:   "insecure",
				Hidden: true,
				Usage:  "Review without TLS",
			},
			// cli.BoolFlag{
			// 	Name:  "all",
			// 	Usage: "review all files under all directories from pwd down",
			// },
		},
		// "$ lingo review" will review any unstaged changes from pwd down.
		// "$ lingo review [<filename>]" will review any unstaged changes in the named files.
		// "$ lingo review --all [<filename>]" will review all code in the named files.
	},
	Tagline: "Automated Code Reviews",
	Request: reviewAction,
}

func reviewRequire() error {
	reqs := []verify.Require{verify.VCSRq, verify.HomeRq, verify.AuthRq, verify.ConfigRq, verify.VersionRq}
	for _, req := range reqs {
		err := req.Verify()
		if err != nil {
			return errors.Trace(err)
		}
	}
	return nil
}

func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), error) {
	err := reviewRequire()
	if err != nil {
		return nil, nil, nil, err
	}

	defer util.Logger.Sync()
	if cliCtx.IsSet("debug") {
		util.Logger.Debugw("reviewCMD called")
	}
	dir := cliCtx.String("directory")
	if dir != "" {
		if err := os.Chdir(dir); err != nil {
			return nil, nil, nil, err
		}
	}

	insecure := cliCtx.IsSet("insecure")
	util.Logger.Debugf("insecure %t", insecure)

	dotlingo, err := ReadDotLingo(cliCtx)
	if err != nil {
		return nil, nil, nil, err

	}
	vcsType, repo, err := vcs.New()
	if err != nil {
		return nil, nil, nil, err

	}

	// TODO: replace this system with nfs-like communication.
	fmt.Println("Syncing your repo...")
	if err = vcs.SyncRepo(vcsType, repo); err != nil {
		return nil, nil, nil, err

	}

	owner, name, err := repo.OwnerAndNameFromRemote()
	if err != nil {
		return nil, nil, nil, err

	}

	sha, err := repo.CurrentCommitId()
	if err != nil {
		if flowutil.NoCommitErr(err) {
			return nil, nil, nil, errors.New(flowutil.NoCommitErrMsg)
		}

		return nil, nil, nil, err

	}

	patches, err := repo.Patches()
	if err != nil {
		return nil, nil, nil, err

	}
	var patchesSize int64
	for _, patch := range patches {
		patchesSize += int64(len([]byte(patch)))
	}
	if patchesSize >= 1024*1024 { // >= 1MB; default max GRPC msg size accepted by the servers is 4MB.
		util.UserFacingWarning("Warning: large diffs can be error prone. You may need to commit your changes.")
	}

	workingDir, err := repo.WorkingDir()
	if err != nil {
		return nil, nil, nil, err

	}

	cfg, err := config.Platform()
	if err != nil {
		return nil, nil, nil, err

	}
	vcsTypeStr, err := vcs.TypeToString(vcsType)
	if err != nil {
		return nil, nil, nil, err

	}

	ctx, cancel := util.UserCancelContext(context.Background())
	req := &flow.ReviewRequest{
		Repo:     name,
		Sha:      sha,
		Patches:  patches,
		Vcs:      vcsTypeStr,
		Dir:      workingDir,
		Dotlingo: dotlingo,
	}
	switch vcsTypeStr {
	case vcsGit:
		addr, err := cfg.GitServerAddr()
		if err != nil {
			return nil, nil, nil, err

		}
		hostname, err := cfg.GitRemoteName()
		if err != nil {
			return nil, nil, nil, err

		}

		req.Host = addr
		req.Hostname = hostname
		req.OwnerOrDepot = &flow.ReviewRequest_Owner{owner}
	case vcsP4:
		addr, err := cfg.P4ServerAddr()
		if err != nil {
			return nil, nil, nil, err

		}
		hostname, err := cfg.P4RemoteName()
		if err != nil {
			return nil, nil, nil, err

		}
		depot, err := cfg.P4RemoteDepotName()
		if err != nil {
			return nil, nil, nil, err

		}
		name = owner + "/" + name

		req.Host = addr
		req.Hostname = hostname
		req.OwnerOrDepot = &flow.ReviewRequest_Depot{depot}
		req.Repo = name
	default:
		return nil, nil, nil, errors.Errorf("Invalid VCS '%s'", vcsTypeStr)
	}

	fmt.Println("Running review flow...")
	resultc, errc, err := RequestReview(ctx, req, insecure)
	return resultc, errc, cancel, errors.Trace(err)
}
<commit_msg>Trace review errors.<commit_after>package review

import (
	"context"
	"fmt"
	"os"

	flowutil "github.com/codelingo/codelingo/sdk/flow"
	"github.com/codelingo/lingo/app/commands/verify"
	"github.com/codelingo/lingo/app/util"
	"github.com/codelingo/lingo/app/util/common/config"
	"github.com/codelingo/lingo/vcs"
	"github.com/codelingo/rpc/flow"
	"github.com/golang/protobuf/proto"
	"github.com/juju/errors"
	"github.com/urfave/cli"
)

const (
	vcsGit string = "git"
	vcsP4  string = "perforce"
)

var CLIApp = &flowutil.CLIApp{
	App: cli.App{

		Name:    "review",
		Usage:   "Review code following tenets in codelingo.yaml.",
		Version: "0.0.0",

		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  util.DiffFlg.String(),
				Usage: "Review only unstaged changes in the working tree.",
			},
			cli.StringFlag{
				Name:  util.OutputFlg.String(),
				Usage: "File to save found issues to.",
			},
			cli.StringFlag{
				Name:  util.FormatFlg.String(),
				Value: "json-pretty",
				Usage: "How to format the found issues. Possible values are: json, json-pretty.",
			},
			cli.BoolFlag{
				Name:  util.KeepAllFlg.String(),
				Usage: "Keep all issues and don't be prompted to confirm each issue.",
			},
			cli.StringFlag{
				Name:  util.DirectoryFlg.String(),
				Usage: "Review a given directory.",
			},
			cli.BoolFlag{
				Name:  "debug",
				Usage: "Display debug messages",
			},
			cli.BoolFlag{
				Name:   "insecure",
				Hidden: true,
				Usage:  "Review without TLS",
			},
			// cli.BoolFlag{
			// 	Name:  "all",
			// 	Usage: "review all files under all directories from pwd down",
			// },
		},
		// "$ lingo review" will review any unstaged changes from pwd down.
		// "$ lingo review [<filename>]" will review any unstaged changes in the named files.
		// "$ lingo review --all [<filename>]" will review all code in the named files.
	},
	Tagline: "Automated Code Reviews",
	Request: reviewAction,
}

func reviewRequire() error {
	reqs := []verify.Require{verify.VCSRq, verify.HomeRq, verify.AuthRq, verify.ConfigRq, verify.VersionRq}
	for _, req := range reqs {
		err := req.Verify()
		if err != nil {
			return errors.Trace(err)
		}
	}
	return nil
}

func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), error) {
	err := reviewRequire()
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}

	defer util.Logger.Sync()
	if cliCtx.IsSet("debug") {
		util.Logger.Debugw("reviewCMD called")
	}
	dir := cliCtx.String("directory")
	if dir != "" {
		if err := os.Chdir(dir); err != nil {
			return nil, nil, nil, errors.Trace(err)
		}
	}

	insecure := cliCtx.IsSet("insecure")
	util.Logger.Debugf("insecure %t", insecure)

	dotlingo, err := ReadDotLingo(cliCtx)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}
	vcsType, repo, err := vcs.New()
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}

	// TODO: replace this system with nfs-like communication.
	fmt.Println("Syncing your repo...")
	if err = vcs.SyncRepo(vcsType, repo); err != nil {
		return nil, nil, nil, errors.Trace(err)

	}

	owner, name, err := repo.OwnerAndNameFromRemote()
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}

	sha, err := repo.CurrentCommitId()
	if err != nil {
		if flowutil.NoCommitErr(err) {
			return nil, nil, nil, errors.Trace(err)ors.New(flowutil.NoCommitErrMsg)
		}

		return nil, nil, nil, errors.Trace(err)

	}

	patches, err := repo.Patches()
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}
	var patchesSize int64
	for _, patch := range patches {
		patchesSize += int64(len([]byte(patch)))
	}
	if patchesSize >= 1024*1024 { // >= 1MB; default max GRPC msg size accepted by the servers is 4MB.
		util.UserFacingWarning("Warning: large diffs can be error prone. You may need to commit your changes.")
	}

	workingDir, err := repo.WorkingDir()
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}

	cfg, err := config.Platform()
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}
	vcsTypeStr, err := vcs.TypeToString(vcsType)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)

	}

	ctx, cancel := util.UserCancelContext(context.Background())
	req := &flow.ReviewRequest{
		Repo:     name,
		Sha:      sha,
		Patches:  patches,
		Vcs:      vcsTypeStr,
		Dir:      workingDir,
		Dotlingo: dotlingo,
	}
	switch vcsTypeStr {
	case vcsGit:
		addr, err := cfg.GitServerAddr()
		if err != nil {
			return nil, nil, nil, errors.Trace(err)

		}
		hostname, err := cfg.GitRemoteName()
		if err != nil {
			return nil, nil, nil, errors.Trace(err)

		}

		req.Host = addr
		req.Hostname = hostname
		req.OwnerOrDepot = &flow.ReviewRequest_Owner{owner}
	case vcsP4:
		addr, err := cfg.P4ServerAddr()
		if err != nil {
			return nil, nil, nil, errors.Trace(err)

		}
		hostname, err := cfg.P4RemoteName()
		if err != nil {
			return nil, nil, nil, errors.Trace(err)

		}
		depot, err := cfg.P4RemoteDepotName()
		if err != nil {
			return nil, nil, nil, errors.Trace(err)

		}
		name = owner + "/" + name

		req.Host = addr
		req.Hostname = hostname
		req.OwnerOrDepot = &flow.ReviewRequest_Depot{depot}
		req.Repo = name
	default:
		return nil, nil, nil, errors.Trace(err)ors.Errorf("Invalid VCS '%s'", vcsTypeStr)
	}

	fmt.Println("Running review flow...")
	resultc, errc, err := RequestReview(ctx, req, insecure)
	return resultc, errc, cancel, errors.Trace(err)
}
<|endoftext|>


<commit_before>package common

import (
	"sort"
	"strings"

	"go.skia.org/infra/go/sklog"
)

const (
	TEST_HARNESS_NAME = "fuzz"

	UNKNOWN_FUNCTION    = "UNKNOWN"
	UNKNOWN_FILE        = "UNKNOWN"
	ASSEMBLY_CODE_FILE  = "[assembly code]"
	UNSYMBOLIZED_RESULT = "UNSYMBOLIZED"
	UNKNOWN_LINE        = -1

	ASAN_OPTIONS        = "ASAN_OPTIONS=detect_leaks=0 symbolize=1"
	STABLE_FUZZER       = "stable"
	EXPERIMENTAL_FUZZER = "experimental"
	FUZZER_NOT_FOUND    = "FUZZER_NOT_FOUND"

	UNCLAIMED = "<unclaimed>"
)

// The list of architectures we fuzz on
var ARCHITECTURES = []string{"linux_x64"}

// By default, allow a generous amount of RAM and set a flexible approach to timeouts.
var defaultGenerationArgs = []string{"-m", "5000", "-t", "500+"}

var commonImpl CommonImpl

// FuzzerInfo contains all the configuration needed to display, execute and analyze a fuzzer.
type FuzzerInfo struct {
	// PrettyName is the human readable name for this fuzzer.
	PrettyName string
	// Status should be STABLE_FUZZER or EXPERIMENTAL_FUZZER
	Status string
	// The Groomer is responsible for stabilizing experimental fuzzers and will be the point
	// of contact for the sheriff for triaging any bad fuzzes found in stable fuzzers.
	Groomer string
	// ExtraBugLabels are any additional labels that should be included when making a bug.
	ExtraBugLabels []string
	// ArgsAfterExecutable is a slice of arguments that come after the executable
	// and before the path to the bytes file, that will be fuzzed.
	ArgsAfterExecutable []string
	// GenerationArgs is a slice of arguments that are used to adjust timeouts/memory usage
	// when generating files.
	GenerationArgs []string
}

// fuzzers is a map of fuzzer_name -> FuzzerInfo for all registered fuzzers.  This should be a
// centralized location to add a new fuzzer, i.e. adding an entry and information here, combined
// with modifying the fuzzer-be.service should be sufficient to add a new fuzzer into the system.
var fuzzers = map[string]FuzzerInfo{
	"api_draw_functions": {
		PrettyName:          "API - CanvasDrawFunctions",
		Status:              STABLE_FUZZER,
		Groomer:             "hcm",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "DrawFunctions", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_gradient": {
		PrettyName:          "API - Gradients",
		Status:              STABLE_FUZZER,
		Groomer:             "fmalita",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "Gradients", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_image_filter": {
		PrettyName:          "API - SerializedImageFilter",
		Status:              STABLE_FUZZER,
		Groomer:             "robertphillips",
		ExtraBugLabels:      []string{"Area-ImageFilter"},
		ArgsAfterExecutable: []string{"--type", "api", "--name", "SerializedImageFilter", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_parse_path": {
		PrettyName:          "API - ParsePath",
		Status:              STABLE_FUZZER,
		Groomer:             "caryclark",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "ParsePath", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_path_measure": {
		PrettyName:          "API - PathMeasure",
		Status:              STABLE_FUZZER,
		Groomer:             "caryclark",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PathMeasure", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_pathop": {
		PrettyName:          "API - PathOp",
		Status:              STABLE_FUZZER,
		Groomer:             "caryclark",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "Pathop", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_pathop": {
		PrettyName:          "API - PolyUtils",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "jvanverth",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PolyUtils", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"color_deserialize": {
		PrettyName:          "SkColorSpace - Deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      []string{"Area-ImageDecoder"},
		ArgsAfterExecutable: []string{"--type", "color_deserialize", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"image_filter_deserialize": {
		PrettyName:          "FilterFuzz Stub",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "filter_fuzz", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "200+"},
	},
	"jpeg_encoder": {
		PrettyName:          "JPEG encoder",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "JPEGEncoder", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"mock_gpu_canvas": {
		PrettyName:          "Canvas to mock GL backend",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "robertphillips",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "MockGPUCanvas", "--bytes"},
		// For most of the canvases, some of the initial test cases take a long time. If they
		// time out (1000+ms ), afl-fuzz aborts during startup. To keep things moving, we
		// tell afl-fuzz to set the timout to 500ms and ignore any test cases that take longer
		// than that (the plus sign after 500).
		GenerationArgs: []string{"-m", "5000", "-t", "500+"},
	},
	"n32_canvas": {
		PrettyName:          "Canvas to raster n32 backend",
		Status:              STABLE_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "RasterN32Canvas", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "500+"},
	},
	"null_canvas": {
		PrettyName:          "Canvas to null canvas backend",
		Status:              STABLE_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "NullCanvas", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"null_gl_canvas": {
		PrettyName:          "Canvas to null gl canvas backend",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "robertphillips",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "NullGLCanvas", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "500+"},
	},
	"path_deserialize": {
		PrettyName:          "SkPath deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "path_deserialize", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"pdf_canvas": {
		PrettyName:          "Canvas to PDF backend",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PDFCanvas", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "500+"},
	},
	"png_encoder": {
		PrettyName:          "PNG encoder",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PNGEncoder", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"region_deserialize": {
		PrettyName:          "SkRegion deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "region_deserialize", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"region_set_path": {
		PrettyName:          "SkRegion set_path",
		Status:              STABLE_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "region_set_path", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skcodec_scale": {
		PrettyName:          "SkCodec (Scaling)",
		Status:              STABLE_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      []string{"Area-ImageDecoder"},
		ArgsAfterExecutable: []string{"--type", "image_scale", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skcodec_mode": {
		PrettyName:          "SkCodec (Modes)",
		Status:              STABLE_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      []string{"Area-ImageDecoder"},
		ArgsAfterExecutable: []string{"--type", "image_mode", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"sksl2glsl": {
		PrettyName:          "SKSL Compiler (GLSL)",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "ethannicholas",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "sksl2glsl", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skottie_json": {
		PrettyName:          "Skottie from JSON",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "fmalita",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "skottie_json", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skp": {
		PrettyName:          "SKP from ReadBuffer",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "skp", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"textblob": {
		PrettyName:          "TextBlob deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "fmalita",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "textblob", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"webp_encoder": {
		PrettyName:          "WEBP encoder",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "WEBPEncoder", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
}

// FUZZ_CATEGORIES is an alphabetized list of known fuzz categories.
var FUZZ_CATEGORIES = []string{}

var ANALYSIS_TYPES = []string{"ASAN_RELEASE", "ASAN_DEBUG", "CLANG_RELEASE", "CLANG_DEBUG"}

// TODO(kjlubick): Is it necessary to have two separate lists?
var STACKTRACE_ORDER = []string{"ASAN_RELEASE", "CLANG_RELEASE", "ASAN_DEBUG", "CLANG_DEBUG"}

func init() {
	commonImpl = &defaultImpl{}
	for k := range fuzzers {
		FUZZ_CATEGORIES = append(FUZZ_CATEGORIES, k)
	}
	sort.Strings(FUZZ_CATEGORIES)
}

func PrettifyCategory(category string) string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return FUZZER_NOT_FOUND
	}
	return f.PrettyName
}

func ExtraBugLabels(category string) []string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return nil
	}
	return f.ExtraBugLabels
}

// ReplicationArgs returns a space separated list of the args needed to replicate the crash
// of a fuzz of a given category.
func ReplicationArgs(category string) string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return FUZZER_NOT_FOUND
	}
	return strings.Join(f.ArgsAfterExecutable, " ")
}

// CategoryReminder returns a short string to remind a human what fuzz category
// a fuzz belongs to.  For example "skcodec_scale" -> "image_scale" and
// "pdf_canvas" -> "api-PDFCanvas"
func CategoryReminder(category string) string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return FUZZER_NOT_FOUND
	}
	// assume ['--type', type, {'--name', api_name}]
	cr := f.ArgsAfterExecutable[1]
	if cr == "api" {
		cr += "-" + f.ArgsAfterExecutable[3]
	}
	return cr
}

// HasCategory returns if a given string corresponds to a known fuzzer category.
func HasCategory(c string) bool {
	_, found := fuzzers[c]
	return found
}

// Status returns the status of a fuzz category (i.e. stable, experimental, etc)
func Status(c string) string {
	f, found := fuzzers[c]
	if !found {
		sklog.Errorf("Unknown category %s", c)
		return FUZZER_NOT_FOUND
	}
	return f.Status
}

// Groomer returns the groomer of a fuzz category
func Groomer(c string) string {
	f, found := fuzzers[c]
	if !found {
		sklog.Errorf("Unknown category %s", c)
		return FUZZER_NOT_FOUND
	}
	return f.Groomer
}

// Returns if fuzzer knows about a given architecture
func HasArchitecture(a string) bool {
	for _, ar := range ARCHITECTURES {
		if a == ar {
			return true
		}
	}
	return false
}

func SetMockCommon(c CommonImpl) {
	commonImpl = c
}

// Returns the Hostname of this machine. Clients should use this instead of
// os.Hostname because this function can be mocked out.
func Hostname() string {
	return commonImpl.Hostname()
}
<commit_msg>Fix typo<commit_after>package common

import (
	"sort"
	"strings"

	"go.skia.org/infra/go/sklog"
)

const (
	TEST_HARNESS_NAME = "fuzz"

	UNKNOWN_FUNCTION    = "UNKNOWN"
	UNKNOWN_FILE        = "UNKNOWN"
	ASSEMBLY_CODE_FILE  = "[assembly code]"
	UNSYMBOLIZED_RESULT = "UNSYMBOLIZED"
	UNKNOWN_LINE        = -1

	ASAN_OPTIONS        = "ASAN_OPTIONS=detect_leaks=0 symbolize=1"
	STABLE_FUZZER       = "stable"
	EXPERIMENTAL_FUZZER = "experimental"
	FUZZER_NOT_FOUND    = "FUZZER_NOT_FOUND"

	UNCLAIMED = "<unclaimed>"
)

// The list of architectures we fuzz on
var ARCHITECTURES = []string{"linux_x64"}

// By default, allow a generous amount of RAM and set a flexible approach to timeouts.
var defaultGenerationArgs = []string{"-m", "5000", "-t", "500+"}

var commonImpl CommonImpl

// FuzzerInfo contains all the configuration needed to display, execute and analyze a fuzzer.
type FuzzerInfo struct {
	// PrettyName is the human readable name for this fuzzer.
	PrettyName string
	// Status should be STABLE_FUZZER or EXPERIMENTAL_FUZZER
	Status string
	// The Groomer is responsible for stabilizing experimental fuzzers and will be the point
	// of contact for the sheriff for triaging any bad fuzzes found in stable fuzzers.
	Groomer string
	// ExtraBugLabels are any additional labels that should be included when making a bug.
	ExtraBugLabels []string
	// ArgsAfterExecutable is a slice of arguments that come after the executable
	// and before the path to the bytes file, that will be fuzzed.
	ArgsAfterExecutable []string
	// GenerationArgs is a slice of arguments that are used to adjust timeouts/memory usage
	// when generating files.
	GenerationArgs []string
}

// fuzzers is a map of fuzzer_name -> FuzzerInfo for all registered fuzzers.  This should be a
// centralized location to add a new fuzzer, i.e. adding an entry and information here, combined
// with modifying the fuzzer-be.service should be sufficient to add a new fuzzer into the system.
var fuzzers = map[string]FuzzerInfo{
	"api_draw_functions": {
		PrettyName:          "API - CanvasDrawFunctions",
		Status:              STABLE_FUZZER,
		Groomer:             "hcm",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "DrawFunctions", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_gradient": {
		PrettyName:          "API - Gradients",
		Status:              STABLE_FUZZER,
		Groomer:             "fmalita",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "Gradients", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_image_filter": {
		PrettyName:          "API - SerializedImageFilter",
		Status:              STABLE_FUZZER,
		Groomer:             "robertphillips",
		ExtraBugLabels:      []string{"Area-ImageFilter"},
		ArgsAfterExecutable: []string{"--type", "api", "--name", "SerializedImageFilter", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_parse_path": {
		PrettyName:          "API - ParsePath",
		Status:              STABLE_FUZZER,
		Groomer:             "caryclark",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "ParsePath", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_path_measure": {
		PrettyName:          "API - PathMeasure",
		Status:              STABLE_FUZZER,
		Groomer:             "caryclark",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PathMeasure", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_pathop": {
		PrettyName:          "API - PathOp",
		Status:              STABLE_FUZZER,
		Groomer:             "caryclark",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "Pathop", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"api_polyutils": {
		PrettyName:          "API - PolyUtils",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "jvanverth",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PolyUtils", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"color_deserialize": {
		PrettyName:          "SkColorSpace - Deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      []string{"Area-ImageDecoder"},
		ArgsAfterExecutable: []string{"--type", "color_deserialize", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"image_filter_deserialize": {
		PrettyName:          "FilterFuzz Stub",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "filter_fuzz", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "200+"},
	},
	"jpeg_encoder": {
		PrettyName:          "JPEG encoder",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "JPEGEncoder", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"mock_gpu_canvas": {
		PrettyName:          "Canvas to mock GL backend",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "robertphillips",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "MockGPUCanvas", "--bytes"},
		// For most of the canvases, some of the initial test cases take a long time. If they
		// time out (1000+ms ), afl-fuzz aborts during startup. To keep things moving, we
		// tell afl-fuzz to set the timout to 500ms and ignore any test cases that take longer
		// than that (the plus sign after 500).
		GenerationArgs: []string{"-m", "5000", "-t", "500+"},
	},
	"n32_canvas": {
		PrettyName:          "Canvas to raster n32 backend",
		Status:              STABLE_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "RasterN32Canvas", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "500+"},
	},
	"null_canvas": {
		PrettyName:          "Canvas to null canvas backend",
		Status:              STABLE_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "NullCanvas", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"null_gl_canvas": {
		PrettyName:          "Canvas to null gl canvas backend",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "robertphillips",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "NullGLCanvas", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "500+"},
	},
	"path_deserialize": {
		PrettyName:          "SkPath deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "path_deserialize", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"pdf_canvas": {
		PrettyName:          "Canvas to PDF backend",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PDFCanvas", "--bytes"},
		GenerationArgs:      []string{"-m", "5000", "-t", "500+"},
	},
	"png_encoder": {
		PrettyName:          "PNG encoder",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "PNGEncoder", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"region_deserialize": {
		PrettyName:          "SkRegion deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "halcanary",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "region_deserialize", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"region_set_path": {
		PrettyName:          "SkRegion set_path",
		Status:              STABLE_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "region_set_path", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skcodec_scale": {
		PrettyName:          "SkCodec (Scaling)",
		Status:              STABLE_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      []string{"Area-ImageDecoder"},
		ArgsAfterExecutable: []string{"--type", "image_scale", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skcodec_mode": {
		PrettyName:          "SkCodec (Modes)",
		Status:              STABLE_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      []string{"Area-ImageDecoder"},
		ArgsAfterExecutable: []string{"--type", "image_mode", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"sksl2glsl": {
		PrettyName:          "SKSL Compiler (GLSL)",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "ethannicholas",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "sksl2glsl", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skottie_json": {
		PrettyName:          "Skottie from JSON",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "fmalita",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "skottie_json", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"skp": {
		PrettyName:          "SKP from ReadBuffer",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "reed",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "skp", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"textblob": {
		PrettyName:          "TextBlob deserialize",
		Status:              STABLE_FUZZER,
		Groomer:             "fmalita",
		ExtraBugLabels:      []string{},
		ArgsAfterExecutable: []string{"--type", "textblob", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
	"webp_encoder": {
		PrettyName:          "WEBP encoder",
		Status:              EXPERIMENTAL_FUZZER,
		Groomer:             "scroggo",
		ExtraBugLabels:      nil,
		ArgsAfterExecutable: []string{"--type", "api", "--name", "WEBPEncoder", "--bytes"},
		GenerationArgs:      defaultGenerationArgs,
	},
}

// FUZZ_CATEGORIES is an alphabetized list of known fuzz categories.
var FUZZ_CATEGORIES = []string{}

var ANALYSIS_TYPES = []string{"ASAN_RELEASE", "ASAN_DEBUG", "CLANG_RELEASE", "CLANG_DEBUG"}

// TODO(kjlubick): Is it necessary to have two separate lists?
var STACKTRACE_ORDER = []string{"ASAN_RELEASE", "CLANG_RELEASE", "ASAN_DEBUG", "CLANG_DEBUG"}

func init() {
	commonImpl = &defaultImpl{}
	for k := range fuzzers {
		FUZZ_CATEGORIES = append(FUZZ_CATEGORIES, k)
	}
	sort.Strings(FUZZ_CATEGORIES)
}

func PrettifyCategory(category string) string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return FUZZER_NOT_FOUND
	}
	return f.PrettyName
}

func ExtraBugLabels(category string) []string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return nil
	}
	return f.ExtraBugLabels
}

// ReplicationArgs returns a space separated list of the args needed to replicate the crash
// of a fuzz of a given category.
func ReplicationArgs(category string) string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return FUZZER_NOT_FOUND
	}
	return strings.Join(f.ArgsAfterExecutable, " ")
}

// CategoryReminder returns a short string to remind a human what fuzz category
// a fuzz belongs to.  For example "skcodec_scale" -> "image_scale" and
// "pdf_canvas" -> "api-PDFCanvas"
func CategoryReminder(category string) string {
	f, found := fuzzers[category]
	if !found {
		sklog.Errorf("Unknown category %s", category)
		return FUZZER_NOT_FOUND
	}
	// assume ['--type', type, {'--name', api_name}]
	cr := f.ArgsAfterExecutable[1]
	if cr == "api" {
		cr += "-" + f.ArgsAfterExecutable[3]
	}
	return cr
}

// HasCategory returns if a given string corresponds to a known fuzzer category.
func HasCategory(c string) bool {
	_, found := fuzzers[c]
	return found
}

// Status returns the status of a fuzz category (i.e. stable, experimental, etc)
func Status(c string) string {
	f, found := fuzzers[c]
	if !found {
		sklog.Errorf("Unknown category %s", c)
		return FUZZER_NOT_FOUND
	}
	return f.Status
}

// Groomer returns the groomer of a fuzz category
func Groomer(c string) string {
	f, found := fuzzers[c]
	if !found {
		sklog.Errorf("Unknown category %s", c)
		return FUZZER_NOT_FOUND
	}
	return f.Groomer
}

// Returns if fuzzer knows about a given architecture
func HasArchitecture(a string) bool {
	for _, ar := range ARCHITECTURES {
		if a == ar {
			return true
		}
	}
	return false
}

func SetMockCommon(c CommonImpl) {
	commonImpl = c
}

// Returns the Hostname of this machine. Clients should use this instead of
// os.Hostname because this function can be mocked out.
func Hostname() string {
	return commonImpl.Hostname()
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://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.
 */
// [START classroom_quickstart]
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/classroom/v1"
)

// Retrieve a token, saves the token, then returns the generated client.
func getClient(config *oauth2.Config) *http.Client {
	tokFile := "token.json"
	tok, err := tokenFromFile(tokFile)
	if err != nil {
		tok = getTokenFromWeb(config)
		saveToken(tokFile, tok)
	}
	return config.Client(context.Background(), tok)
}

// Request a token from the web, then returns the retrieved token.
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
	fmt.Printf("Go to the following link in your browser then type the "+
		"authorization code: \n%v\n", authURL)

	var authCode string
	if _, err := fmt.Scan(&authCode); err != nil {
		log.Fatalf("Unable to read authorization code: %v", err)
	}

	tok, err := config.Exchange(context.TODO(), authCode)
	if err != nil {
		log.Fatalf("Unable to retrieve token from web: %v", err)
	}
	return tok
}

// Retrieves a token from a local file.
func tokenFromFile(file string) (*oauth2.Token, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	tok := &oauth2.Token{}
	err = json.NewDecoder(f).Decode(tok)
	return tok, err
}

// Saves a token to a file path.
func saveToken(path string, token *oauth2.Token) {
	fmt.Printf("Saving credential file to: %s\n", path)
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		log.Fatalf("Unable to cache oauth token: %v", err)
	}
	defer f.Close()
	json.NewEncoder(f).Encode(token)
}

func main() {
	b, err := ioutil.ReadFile("credentials.json")
	if err != nil {
		log.Fatalf("Unable to read client secret file: %v", err)
	}

	// If modifying these scopes, delete your previously saved token.json.
	config, err := google.ConfigFromJSON(b, classroom.ClassroomCoursesReadonlyScope)
	if err != nil {
		log.Fatalf("Unable to parse client secret file to config: %v", err)
	}
	client := getClient(config)

	srv, err := classroom.New(client)
	if err != nil {
		log.Fatalf("Unable to create classroom Client %v", err)
	}

	r, err := srv.Courses.List().PageSize(10).Do()
	if err != nil {
		log.Fatalf("Unable to retrieve courses. %v", err)
	}
	if len(r.Courses) > 0 {
		fmt.Print("Courses:\n")
		for _, c := range r.Courses {
			fmt.Printf("%s (%s)\n", c.Name, c.Id)
		}
	} else {
		fmt.Print("No courses found.")
	}
}

// [END classroom_quickstart]
<commit_msg>updated log from client secret to credentials<commit_after>/**
 * @license
 * Copyright Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://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.
 */
// [START classroom_quickstart]
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/classroom/v1"
)

// Retrieve a token, saves the token, then returns the generated client.
func getClient(config *oauth2.Config) *http.Client {
	tokFile := "token.json"
	tok, err := tokenFromFile(tokFile)
	if err != nil {
		tok = getTokenFromWeb(config)
		saveToken(tokFile, tok)
	}
	return config.Client(context.Background(), tok)
}

// Request a token from the web, then returns the retrieved token.
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
	fmt.Printf("Go to the following link in your browser then type the "+
		"authorization code: \n%v\n", authURL)

	var authCode string
	if _, err := fmt.Scan(&authCode); err != nil {
		log.Fatalf("Unable to read authorization code: %v", err)
	}

	tok, err := config.Exchange(context.TODO(), authCode)
	if err != nil {
		log.Fatalf("Unable to retrieve token from web: %v", err)
	}
	return tok
}

// Retrieves a token from a local file.
func tokenFromFile(file string) (*oauth2.Token, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	tok := &oauth2.Token{}
	err = json.NewDecoder(f).Decode(tok)
	return tok, err
}

// Saves a token to a file path.
func saveToken(path string, token *oauth2.Token) {
	fmt.Printf("Saving credential file to: %s\n", path)
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		log.Fatalf("Unable to cache oauth token: %v", err)
	}
	defer f.Close()
	json.NewEncoder(f).Encode(token)
}

func main() {
	b, err := ioutil.ReadFile("credentials.json")
	if err != nil {
		log.Fatalf("Unable to read credentials file: %v", err)
	}

	// If modifying these scopes, delete your previously saved token.json.
	config, err := google.ConfigFromJSON(b, classroom.ClassroomCoursesReadonlyScope)
	if err != nil {
		log.Fatalf("Unable to parse client secret file to config: %v", err)
	}
	client := getClient(config)

	srv, err := classroom.New(client)
	if err != nil {
		log.Fatalf("Unable to create classroom Client %v", err)
	}

	r, err := srv.Courses.List().PageSize(10).Do()
	if err != nil {
		log.Fatalf("Unable to retrieve courses. %v", err)
	}
	if len(r.Courses) > 0 {
		fmt.Print("Courses:\n")
		for _, c := range r.Courses {
			fmt.Printf("%s (%s)\n", c.Name, c.Id)
		}
	} else {
		fmt.Print("No courses found.")
	}
}

// [END classroom_quickstart]
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

// Package prometheus creates and registers prometheus metrics with
// rest clients. To use this package, you just have to import it.
package prometheus

import (
	"net/url"
	"time"

	"k8s.io/kubernetes/pkg/client/metrics"

	"github.com/prometheus/client_golang/prometheus"
)

const restClientSubsystem = "rest_client"

var (
	// requestLatency is a Prometheus Summary metric type partitioned by
	// "verb" and "url" labels. It is used for the rest client latency metrics.
	requestLatency = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Subsystem: restClientSubsystem,
			Name:      "request_latency_microseconds",
			Help:      "Request latency in microseconds. Broken down by verb and URL",
			MaxAge:    time.Hour,
		},
		[]string{"verb", "url"},
	)

	requestResult = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Subsystem: restClientSubsystem,
			Name:      "request_status_codes",
			Help:      "Number of http requests, partitioned by metadata",
		},
		[]string{"code", "method", "host"},
	)
)

func init() {
	prometheus.MustRegister(requestLatency)
	prometheus.MustRegister(requestResult)
	metrics.Register(&latencyAdapter{requestLatency}, &resultAdapter{requestResult})
}

type latencyAdapter struct {
	m *prometheus.SummaryVec
}

func (l *latencyAdapter) Observe(verb string, u url.URL, latency time.Duration) {
	microseconds := float64(latency) / float64(time.Microsecond)
	l.m.WithLabelValues(verb, u.String()).Observe(microseconds)
}

type resultAdapter struct {
	m *prometheus.CounterVec
}

func (r *resultAdapter) Increment(code, method, host string) {
	r.m.WithLabelValues(code, method, host).Inc()
}
<commit_msg>Use Prometheus instrumentation conventions<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

// Package prometheus creates and registers prometheus metrics with
// rest clients. To use this package, you just have to import it.
package prometheus

import (
	"net/url"
	"time"

	"k8s.io/kubernetes/pkg/client/metrics"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	// requestLatency is a Prometheus Summary metric type partitioned by
	// "verb" and "url" labels. It is used for the rest client latency metrics.
	requestLatency = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name:   "rest_client_request_latency_seconds",
			Help:   "Request latency in seconds. Broken down by verb and URL.",
			MaxAge: time.Hour,
		},
		[]string{"verb", "url"},
	)

	requestResult = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "rest_client_requests_total",
			Help: "Number of HTTP requests, partitioned by status code, method, and host.",
		},
		[]string{"code", "method", "host"},
	)
)

func init() {
	prometheus.MustRegister(requestLatency)
	prometheus.MustRegister(requestResult)
	metrics.Register(&latencyAdapter{requestLatency}, &resultAdapter{requestResult})
}

type latencyAdapter struct {
	m *prometheus.SummaryVec
}

func (l *latencyAdapter) Observe(verb string, u url.URL, latency time.Duration) {
	l.m.WithLabelValues(verb, u.String()).Observe(latency.Seconds())
}

type resultAdapter struct {
	m *prometheus.CounterVec
}

func (r *resultAdapter) Increment(code, method, host string) {
	r.m.WithLabelValues(code, method, host).Inc()
}
<|endoftext|>


<commit_before>// Package ratelimiter implements a rate limiting and traffic shaping middleware with a set of token buckets.
package ratelimiter

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"github.com/containous/traefik/v2/pkg/config/dynamic"
	"github.com/containous/traefik/v2/pkg/log"
	"github.com/containous/traefik/v2/pkg/middlewares"
	"github.com/containous/traefik/v2/pkg/tracing"
	"github.com/mailgun/ttlmap"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/vulcand/oxy/utils"
	"golang.org/x/time/rate"
)

const (
	typeName   = "RateLimiterType"
	maxSources = 65536
)

// rateLimiter implements rate limiting and traffic shaping with a set of token buckets;
// one for each traffic source. The same parameters are applied to all the buckets.
type rateLimiter struct {
	name  string
	rate  rate.Limit // reqs/s
	burst int64
	// maxDelay is the maximum duration we're willing to wait for a bucket reservation to become effective, in nanoseconds.
	// For now it is somewhat arbitrarily set to 1/rate.
	maxDelay      time.Duration
	sourceMatcher utils.SourceExtractor
	next          http.Handler

	bucketsMu sync.Mutex
	buckets   *ttlmap.TtlMap // actual buckets, keyed by source.
}

// New returns a rate limiter middleware.
func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name string) (http.Handler, error) {
	ctxLog := log.With(ctx, log.Str(log.MiddlewareName, name), log.Str(log.MiddlewareType, typeName))
	log.FromContext(ctxLog).Debug("Creating middleware")

	if config.SourceCriterion == nil ||
		config.SourceCriterion.IPStrategy == nil &&
			config.SourceCriterion.RequestHeaderName == "" && !config.SourceCriterion.RequestHost {
		config.SourceCriterion = &dynamic.SourceCriterion{
			IPStrategy: &dynamic.IPStrategy{},
		}
	}

	sourceMatcher, err := middlewares.GetSourceExtractor(ctxLog, config.SourceCriterion)
	if err != nil {
		return nil, err
	}

	buckets, err := ttlmap.NewMap(maxSources)
	if err != nil {
		return nil, err
	}

	burst := config.Burst
	if burst <= 0 {
		burst = 1
	}

	// Logically, we should set maxDelay to ~infinity when config.Average == 0 (because it means to rate limiting),
	// but since the reservation will give us a delay = 0 anyway in this case, we're good even with any maxDelay >= 0.
	var maxDelay time.Duration
	if config.Average != 0 {
		maxDelay = time.Second / time.Duration(config.Average*2)
	}

	return &rateLimiter{
		name:          name,
		rate:          rate.Limit(config.Average),
		burst:         burst,
		maxDelay:      maxDelay,
		next:          next,
		sourceMatcher: sourceMatcher,
		buckets:       buckets,
	}, nil
}

func (rl *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
	return rl.name, tracing.SpanKindNoneEnum
}

func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := middlewares.GetLoggerCtx(r.Context(), rl.name, typeName)
	logger := log.FromContext(ctx)

	source, amount, err := rl.sourceMatcher.Extract(r)
	if err != nil {
		logger.Errorf("could not extract source of request: %v", err)
		http.Error(w, "could not extract source of request", http.StatusInternalServerError)
		return
	}

	if amount != 1 {
		logger.Infof("ignoring token bucket amount > 1: %d", amount)
	}

	rl.bucketsMu.Lock()
	defer rl.bucketsMu.Unlock()

	var bucket *rate.Limiter
	if rlSource, exists := rl.buckets.Get(source); exists {
		bucket = rlSource.(*rate.Limiter)
	} else {
		bucket = rate.NewLimiter(rl.rate, int(rl.burst))
		if err := rl.buckets.Set(source, bucket, int(rl.maxDelay)*10+1); err != nil {
			logger.Errorf("could not insert bucket: %v", err)
			http.Error(w, "could not insert bucket", http.StatusInternalServerError)
			return
		}
	}

	res := bucket.Reserve()
	if !res.OK() {
		http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests)
		return
	}

	delay := res.Delay()
	if delay > rl.maxDelay {
		res.Cancel()
		rl.serveDelayError(ctx, w, r, delay)
		return
	}

	time.Sleep(delay)
	rl.next.ServeHTTP(w, r)
}

func (rl *rateLimiter) serveDelayError(ctx context.Context, w http.ResponseWriter, r *http.Request, delay time.Duration) {
	w.Header().Set("Retry-After", fmt.Sprintf("%.0f", delay.Seconds()))
	w.Header().Set("X-Retry-In", delay.String())
	w.WriteHeader(http.StatusTooManyRequests)

	if _, err := w.Write([]byte(http.StatusText(http.StatusTooManyRequests))); err != nil {
		log.FromContext(ctx).Errorf("could not serve 429: %v", err)
	}
}
<commit_msg>Fix rate limiting and SSE<commit_after>// Package ratelimiter implements a rate limiting and traffic shaping middleware with a set of token buckets.
package ratelimiter

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/containous/traefik/v2/pkg/config/dynamic"
	"github.com/containous/traefik/v2/pkg/log"
	"github.com/containous/traefik/v2/pkg/middlewares"
	"github.com/containous/traefik/v2/pkg/tracing"
	"github.com/mailgun/ttlmap"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/vulcand/oxy/utils"
	"golang.org/x/time/rate"
)

const (
	typeName   = "RateLimiterType"
	maxSources = 65536
)

// rateLimiter implements rate limiting and traffic shaping with a set of token buckets;
// one for each traffic source. The same parameters are applied to all the buckets.
type rateLimiter struct {
	name  string
	rate  rate.Limit // reqs/s
	burst int64
	// maxDelay is the maximum duration we're willing to wait for a bucket reservation to become effective, in nanoseconds.
	// For now it is somewhat arbitrarily set to 1/rate.
	maxDelay      time.Duration
	sourceMatcher utils.SourceExtractor
	next          http.Handler

	buckets *ttlmap.TtlMap // actual buckets, keyed by source.
}

// New returns a rate limiter middleware.
func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name string) (http.Handler, error) {
	ctxLog := log.With(ctx, log.Str(log.MiddlewareName, name), log.Str(log.MiddlewareType, typeName))
	log.FromContext(ctxLog).Debug("Creating middleware")

	if config.SourceCriterion == nil ||
		config.SourceCriterion.IPStrategy == nil &&
			config.SourceCriterion.RequestHeaderName == "" && !config.SourceCriterion.RequestHost {
		config.SourceCriterion = &dynamic.SourceCriterion{
			IPStrategy: &dynamic.IPStrategy{},
		}
	}

	sourceMatcher, err := middlewares.GetSourceExtractor(ctxLog, config.SourceCriterion)
	if err != nil {
		return nil, err
	}

	buckets, err := ttlmap.NewConcurrent(maxSources)
	if err != nil {
		return nil, err
	}

	burst := config.Burst
	if burst <= 0 {
		burst = 1
	}

	// Logically, we should set maxDelay to ~infinity when config.Average == 0 (because it means to rate limiting),
	// but since the reservation will give us a delay = 0 anyway in this case, we're good even with any maxDelay >= 0.
	var maxDelay time.Duration
	if config.Average != 0 {
		maxDelay = time.Second / time.Duration(config.Average*2)
	}

	return &rateLimiter{
		name:          name,
		rate:          rate.Limit(config.Average),
		burst:         burst,
		maxDelay:      maxDelay,
		next:          next,
		sourceMatcher: sourceMatcher,
		buckets:       buckets,
	}, nil
}

func (rl *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
	return rl.name, tracing.SpanKindNoneEnum
}

func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := middlewares.GetLoggerCtx(r.Context(), rl.name, typeName)
	logger := log.FromContext(ctx)

	source, amount, err := rl.sourceMatcher.Extract(r)
	if err != nil {
		logger.Errorf("could not extract source of request: %v", err)
		http.Error(w, "could not extract source of request", http.StatusInternalServerError)
		return
	}

	if amount != 1 {
		logger.Infof("ignoring token bucket amount > 1: %d", amount)
	}

	var bucket *rate.Limiter
	if rlSource, exists := rl.buckets.Get(source); exists {
		bucket = rlSource.(*rate.Limiter)
	} else {
		bucket = rate.NewLimiter(rl.rate, int(rl.burst))
		if err := rl.buckets.Set(source, bucket, int(rl.maxDelay)*10+1); err != nil {
			logger.Errorf("could not insert bucket: %v", err)
			http.Error(w, "could not insert bucket", http.StatusInternalServerError)
			return
		}
	}

	res := bucket.Reserve()
	if !res.OK() {
		http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests)
		return
	}

	delay := res.Delay()
	if delay > rl.maxDelay {
		res.Cancel()
		rl.serveDelayError(ctx, w, r, delay)
		return
	}

	time.Sleep(delay)
	rl.next.ServeHTTP(w, r)
}

func (rl *rateLimiter) serveDelayError(ctx context.Context, w http.ResponseWriter, r *http.Request, delay time.Duration) {
	w.Header().Set("Retry-After", fmt.Sprintf("%.0f", delay.Seconds()))
	w.Header().Set("X-Retry-In", delay.String())
	w.WriteHeader(http.StatusTooManyRequests)

	if _, err := w.Write([]byte(http.StatusText(http.StatusTooManyRequests))); err != nil {
		log.FromContext(ctx).Errorf("could not serve 429: %v", err)
	}
}
<|endoftext|>


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

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

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

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

// Package bsutil will eventually be renamed to kubeadm package after getting rid of older one
package bsutil

import (
	"bytes"
	"fmt"
	"path"

	"github.com/blang/semver"
	"github.com/golang/glog"
	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/minikube/bootstrapper/bsutil/ktmpl"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/cruntime"
	"k8s.io/minikube/pkg/minikube/vmpath"
	"k8s.io/minikube/pkg/util"
)

// Container runtimes
const remoteContainerRuntime = "remote"

// GenerateKubeadmYAML generates the kubeadm.yaml file
func GenerateKubeadmYAML(cc config.ClusterConfig, n config.Node, r cruntime.Manager) ([]byte, error) {
	k8s := cc.KubernetesConfig
	version, err := util.ParseKubernetesVersion(k8s.KubernetesVersion)
	if err != nil {
		return nil, errors.Wrap(err, "parsing kubernetes version")
	}

	// parses a map of the feature gates for kubeadm and component
	kubeadmFeatureArgs, componentFeatureArgs, err := parseFeatureArgs(k8s.FeatureGates)
	if err != nil {
		return nil, errors.Wrap(err, "parses feature gate config for kubeadm and component")
	}

	// In case of no port assigned, use default
	cp, err := config.PrimaryControlPlane(&cc)
	if err != nil {
		return nil, errors.Wrap(err, "getting control plane")
	}
	nodePort := cp.Port
	if nodePort <= 0 {
		nodePort = constants.APIServerPort
	}

	componentOpts, err := createExtraComponentConfig(k8s.ExtraOptions, version, componentFeatureArgs, cp)
	if err != nil {
		return nil, errors.Wrap(err, "generating extra component config for kubeadm")
	}

	opts := struct {
		CertDir             string
		ServiceCIDR         string
		PodSubnet           string
		AdvertiseAddress    string
		APIServerPort       int
		KubernetesVersion   string
		EtcdDataDir         string
		ClusterName         string
		NodeName            string
		DNSDomain           string
		CRISocket           string
		ImageRepository     string
		ComponentOptions    []componentOptions
		FeatureArgs         map[string]bool
		NoTaintMaster       bool
		NodeIP              string
		ControlPlaneAddress string
		KubeProxyMode       string
		// KubeProxyOptions    []componentOptions
	}{
		CertDir:           vmpath.GuestKubernetesCertsDir,
		ServiceCIDR:       constants.DefaultServiceCIDR,
		PodSubnet:         k8s.ExtraOptions.Get("pod-network-cidr", Kubeadm),
		AdvertiseAddress:  n.IP,
		APIServerPort:     nodePort,
		KubernetesVersion: k8s.KubernetesVersion,
		EtcdDataDir:       EtcdDataDir(),
		ClusterName:       cc.Name,
		//kubeadm uses NodeName as the --hostname-override parameter, so this needs to be the name of the machine
		NodeName:            KubeNodeName(cc, n),
		CRISocket:           r.SocketPath(),
		ImageRepository:     k8s.ImageRepository,
		ComponentOptions:    componentOpts,
		FeatureArgs:         kubeadmFeatureArgs,
		NoTaintMaster:       false, // That does not work with k8s 1.12+
		DNSDomain:           k8s.DNSDomain,
		NodeIP:              n.IP,
		ControlPlaneAddress: cp.IP,
		KubeProxyMode:       k8s.ExtraOptions.Get("mode", Kubeproxy),
	}

	if k8s.ServiceCIDR != "" {
		opts.ServiceCIDR = k8s.ServiceCIDR
	}

	opts.NoTaintMaster = true
	b := bytes.Buffer{}
	configTmpl := ktmpl.V1Alpha1
	if version.GTE(semver.MustParse("1.12.0")) {
		configTmpl = ktmpl.V1Alpha3
	}
	// v1beta1 works in v1.13, but isn't required until v1.14.
	if version.GTE(semver.MustParse("1.14.0-alpha.0")) {
		configTmpl = ktmpl.V1Beta1
	}
	// v1beta2 isn't required until v1.17.
	if version.GTE(semver.MustParse("1.17.0")) {
		configTmpl = ktmpl.V1Beta2
	}
	glog.Infof("kubeadm options: %+v", opts)
	if err := configTmpl.Execute(&b, opts); err != nil {
		return nil, err
	}
	glog.Infof("kubeadm config:\n%s\n", b.String())
	return b.Bytes(), nil
}

// These are the components that can be configured
// through the "extra-config"
const (
	Kubelet           = "kubelet"
	Kubeadm           = "kubeadm"
	Apiserver         = "apiserver"
	Scheduler         = "scheduler"
	ControllerManager = "controller-manager"
	Kubeproxy         = "kube-proxy"
)

// InvokeKubeadm returns the invocation command for Kubeadm
func InvokeKubeadm(version string) string {
	return fmt.Sprintf("sudo env PATH=%s:$PATH kubeadm", binRoot(version))
}

// EtcdDataDir is where etcd data is stored.
func EtcdDataDir() string {
	return path.Join(vmpath.GuestPersistentDir, "etcd")
}
<commit_msg>make KubeProxyOptions part of kubeadm.yaml<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

// Package bsutil will eventually be renamed to kubeadm package after getting rid of older one
package bsutil

import (
	"bytes"
	"fmt"
	"path"

	"github.com/blang/semver"
	"github.com/golang/glog"
	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/minikube/bootstrapper/bsutil/ktmpl"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/cruntime"
	"k8s.io/minikube/pkg/minikube/vmpath"
	"k8s.io/minikube/pkg/util"
)

// Container runtimes
const remoteContainerRuntime = "remote"

// GenerateKubeadmYAML generates the kubeadm.yaml file
func GenerateKubeadmYAML(cc config.ClusterConfig, n config.Node, r cruntime.Manager) ([]byte, error) {
	k8s := cc.KubernetesConfig
	version, err := util.ParseKubernetesVersion(k8s.KubernetesVersion)
	if err != nil {
		return nil, errors.Wrap(err, "parsing kubernetes version")
	}

	// parses a map of the feature gates for kubeadm and component
	kubeadmFeatureArgs, componentFeatureArgs, err := parseFeatureArgs(k8s.FeatureGates)
	if err != nil {
		return nil, errors.Wrap(err, "parses feature gate config for kubeadm and component")
	}

	// In case of no port assigned, use default
	cp, err := config.PrimaryControlPlane(&cc)
	if err != nil {
		return nil, errors.Wrap(err, "getting control plane")
	}
	nodePort := cp.Port
	if nodePort <= 0 {
		nodePort = constants.APIServerPort
	}

	componentOpts, err := createExtraComponentConfig(k8s.ExtraOptions, version, componentFeatureArgs, cp)
	if err != nil {
		return nil, errors.Wrap(err, "generating extra component config for kubeadm")
	}

	kubeProxyOpts, err := createKubeProxyOptions(k8s.ExtraOptions, version, componentFeatureArgs, cp)
	if err != nil {
		return nil, errors.Wrap(err, "generating extra component config for kube-proxy")
	}

	opts := struct {
		CertDir             string
		ServiceCIDR         string
		PodSubnet           string
		AdvertiseAddress    string
		APIServerPort       int
		KubernetesVersion   string
		EtcdDataDir         string
		ClusterName         string
		NodeName            string
		DNSDomain           string
		CRISocket           string
		ImageRepository     string
		ComponentOptions    []componentOptions
		FeatureArgs         map[string]bool
		NoTaintMaster       bool
		NodeIP              string
		ControlPlaneAddress string
		KubeProxyOptions    map[string]string
	}{
		CertDir:           vmpath.GuestKubernetesCertsDir,
		ServiceCIDR:       constants.DefaultServiceCIDR,
		PodSubnet:         k8s.ExtraOptions.Get("pod-network-cidr", Kubeadm),
		AdvertiseAddress:  n.IP,
		APIServerPort:     nodePort,
		KubernetesVersion: k8s.KubernetesVersion,
		EtcdDataDir:       EtcdDataDir(),
		ClusterName:       cc.Name,
		//kubeadm uses NodeName as the --hostname-override parameter, so this needs to be the name of the machine
		NodeName:            KubeNodeName(cc, n),
		CRISocket:           r.SocketPath(),
		ImageRepository:     k8s.ImageRepository,
		ComponentOptions:    componentOpts,
		FeatureArgs:         kubeadmFeatureArgs,
		NoTaintMaster:       false, // That does not work with k8s 1.12+
		DNSDomain:           k8s.DNSDomain,
		NodeIP:              n.IP,
		ControlPlaneAddress: cp.IP,
		KubeProxyOptions:    kubeProxyOpts,
	}

	if k8s.ServiceCIDR != "" {
		opts.ServiceCIDR = k8s.ServiceCIDR
	}

	opts.NoTaintMaster = true
	b := bytes.Buffer{}
	configTmpl := ktmpl.V1Alpha1
	if version.GTE(semver.MustParse("1.12.0")) {
		configTmpl = ktmpl.V1Alpha3
	}
	// v1beta1 works in v1.13, but isn't required until v1.14.
	if version.GTE(semver.MustParse("1.14.0-alpha.0")) {
		configTmpl = ktmpl.V1Beta1
	}
	// v1beta2 isn't required until v1.17.
	if version.GTE(semver.MustParse("1.17.0")) {
		configTmpl = ktmpl.V1Beta2
	}
	glog.Infof("kubeadm options: %+v", opts)
	if err := configTmpl.Execute(&b, opts); err != nil {
		return nil, err
	}
	glog.Infof("kubeadm config:\n%s\n", b.String())
	return b.Bytes(), nil
}

// These are the components that can be configured
// through the "extra-config"
const (
	Kubelet           = "kubelet"
	Kubeadm           = "kubeadm"
	Apiserver         = "apiserver"
	Scheduler         = "scheduler"
	ControllerManager = "controller-manager"
	Kubeproxy         = "kube-proxy"
)

// InvokeKubeadm returns the invocation command for Kubeadm
func InvokeKubeadm(version string) string {
	return fmt.Sprintf("sudo env PATH=%s:$PATH kubeadm", binRoot(version))
}

// EtcdDataDir is where etcd data is stored.
func EtcdDataDir() string {
	return path.Join(vmpath.GuestPersistentDir, "etcd")
}
<|endoftext|>