<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"container/heap"
	"io"
	"reflect"
	"sync"

	"golang.org/x/net/context"
)

const (
	defaultBlockRetrievalWorkerQueueSize int = 100
	defaultOnDemandRequestPriority       int = 100
)

// blockRetrievalRequest represents one consumer's request for a block.
type blockRetrievalRequest struct {
	ctx    context.Context
	block  Block
	doneCh chan error
}

// blockRetrieval contains the metadata for a given block retrieval. May
// represent many requests, all of which will be handled at once.
type blockRetrieval struct {
	//// Retrieval Metadata
	// the block pointer to retrieve
	blockPtr BlockPointer
	// the key metadata for the request
	kmd KeyMetadata
	// the individual requests for this block pointer: they must be notified
	// once the block is returned
	requests []*blockRetrievalRequest

	//// Queueing Metadata
	// the index of the retrieval in the heap
	index int
	// the priority of the retrieval: larger priorities are processed first
	priority int
	// state of global request counter when this retrieval was created;
	// maintains FIFO
	insertionOrder uint64
}

// blockRetrievalQueue manages block retrieval requests. Higher priority
// requests are executed first. Requests are executed in FIFO order within a
// given priority level.
type blockRetrievalQueue struct {
	// protects everything in this struct except workerQueue
	mtx sync.RWMutex
	// queued or in progress retrievals
	ptrs map[BlockPointer]*blockRetrieval
	// global counter of insertions to queue
	// capacity: ~584 years at 1 billion requests/sec
	insertionCount uint64

	heap *blockRetrievalHeap
	// This is a channel of channels to maximize the time that each request is
	// in the heap, allowing preemption as long as possible. This way, a
	// request only exits the heap once a worker is ready.
	workerQueue chan chan *blockRetrieval
	// Channel to represent whether we're done accepting requests
	doneCh chan struct{}
}

// newBlockRetrievalQueue creates a new block retrieval queue. The numWorkers
// parameter determines how many workers can concurrently call WorkOnRequest
// (more than numWorkers will block).
func newBlockRetrievalQueue(numWorkers int) *blockRetrievalQueue {
	return &blockRetrievalQueue{
		ptrs:        make(map[BlockPointer]*blockRetrieval),
		heap:        &blockRetrievalHeap{},
		workerQueue: make(chan chan *blockRetrieval, numWorkers),
	}
}

// notifyWorker notifies workers that there is a new request for processing.
func (brq *blockRetrievalQueue) notifyWorker() {
	go func() {
		select {
		case <-brq.doneCh:
			// Prevent interference with the heap while we're retrieving from it
			brq.mtx.Lock()
			defer brq.mtx.Unlock()
			if brq.heap.Len() > 0 {
				retrieval := heap.Pop(brq.heap).(*blockRetrieval)
				brq.FinalizeRequest(retrieval, nil, io.EOF)
			}
		default:
			// Get the next queued worker
			ch := <-brq.workerQueue
			// Prevent interference with the heap while we're retrieving from it
			brq.mtx.Lock()
			defer brq.mtx.Unlock()
			// Pop from the heap
			ch <- heap.Pop(brq.heap).(*blockRetrieval)
		}
	}()
}

// Request submits a block request to the queue.
func (brq *blockRetrievalQueue) Request(ctx context.Context, priority int, kmd KeyMetadata, ptr BlockPointer, block Block) <-chan error {
	// Only continue if we haven't been shut down
	select {
	case <-brq.doneCh:
		ch := make(chan error, 1)
		ch <- io.EOF
		return ch
	default:
	}

	brq.mtx.Lock()
	defer brq.mtx.Unlock()
	br, exists := brq.ptrs[ptr]
	if !exists {
		// Add to the heap
		br = &blockRetrieval{
			blockPtr:       ptr,
			kmd:            kmd,
			index:          -1,
			priority:       priority,
			insertionOrder: brq.insertionCount,
		}
		brq.insertionCount++
		brq.ptrs[ptr] = br
		heap.Push(brq.heap, br)
		defer brq.notifyWorker()
	}
	ch := make(chan error, 1)
	br.requests = append(br.requests, &blockRetrievalRequest{ctx, block, ch})
	// If the new request priority is higher, elevate the retrieval in the
	// queue.  Skip this if the request is no longer in the queue (which means
	// it's actively being processed).
	if br.index != -1 && priority > br.priority {
		br.priority = priority
		heap.Fix(brq.heap, br.index)
	}
	return ch
}

// WorkOnRequest returns a new channel for a worker to obtain a blockRetrieval.
func (brq *blockRetrievalQueue) WorkOnRequest() <-chan *blockRetrieval {
	ch := make(chan *blockRetrieval, 1)
	brq.workerQueue <- ch

	return ch
}

// FinalizeRequest s the last step of a retrieval request once a block has been
// obtained. It removes the request from the blockRetrievalQueue, preventing
// more requests mutating the retrieval, then calls notifyBlockRequestor for
// all subscribed requests.
func (brq *blockRetrievalQueue) FinalizeRequest(retrieval *blockRetrieval, block Block, err error) {
	brq.mtx.Lock()
	delete(brq.ptrs, retrieval.blockPtr)
	brq.mtx.Unlock()

	if block == nil {
		for _, r := range retrieval.requests {
			req := r
			go func() {
				req.doneCh <- err
			}()
		}
		return
	}
	source := reflect.ValueOf(block).Elem()
	for _, r := range retrieval.requests {
		req := r
		go func() {
			// Copy the decrypted block to the caller
			dest := reflect.ValueOf(req.block).Elem()
			dest.Set(source)
			req.doneCh <- err
		}()
	}
}

// Shutdown is called when we are no longer accepting requests
func (brq *blockRetrievalQueue) Shutdown() {
	select {
	case <-brq.doneCh:
	default:
		close(brq.doneCh)
	}
}
<commit_msg>block_retrieval_queue: refactored FinalizeRequest for clarity<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"container/heap"
	"io"
	"reflect"
	"sync"

	"golang.org/x/net/context"
)

const (
	defaultBlockRetrievalWorkerQueueSize int = 20
	defaultOnDemandRequestPriority       int = 100
)

// blockRetrievalRequest represents one consumer's request for a block.
type blockRetrievalRequest struct {
	ctx    context.Context
	block  Block
	doneCh chan error
}

// blockRetrieval contains the metadata for a given block retrieval. May
// represent many requests, all of which will be handled at once.
type blockRetrieval struct {
	//// Retrieval Metadata
	// the block pointer to retrieve
	blockPtr BlockPointer
	// the key metadata for the request
	kmd KeyMetadata
	// the individual requests for this block pointer: they must be notified
	// once the block is returned
	requests []*blockRetrievalRequest

	//// Queueing Metadata
	// the index of the retrieval in the heap
	index int
	// the priority of the retrieval: larger priorities are processed first
	priority int
	// state of global request counter when this retrieval was created;
	// maintains FIFO
	insertionOrder uint64
}

// blockRetrievalQueue manages block retrieval requests. Higher priority
// requests are executed first. Requests are executed in FIFO order within a
// given priority level.
type blockRetrievalQueue struct {
	// protects everything in this struct except workerQueue
	mtx sync.RWMutex
	// queued or in progress retrievals
	ptrs map[BlockPointer]*blockRetrieval
	// global counter of insertions to queue
	// capacity: ~584 years at 1 billion requests/sec
	insertionCount uint64

	heap *blockRetrievalHeap
	// This is a channel of channels to maximize the time that each request is
	// in the heap, allowing preemption as long as possible. This way, a
	// request only exits the heap once a worker is ready.
	workerQueue chan chan *blockRetrieval
	// Channel to represent whether we're done accepting requests
	doneCh chan struct{}
}

// newBlockRetrievalQueue creates a new block retrieval queue. The numWorkers
// parameter determines how many workers can concurrently call WorkOnRequest
// (more than numWorkers will block).
func newBlockRetrievalQueue(numWorkers int) *blockRetrievalQueue {
	return &blockRetrievalQueue{
		ptrs:        make(map[BlockPointer]*blockRetrieval),
		heap:        &blockRetrievalHeap{},
		workerQueue: make(chan chan *blockRetrieval, numWorkers),
	}
}

// notifyWorker notifies workers that there is a new request for processing.
func (brq *blockRetrievalQueue) notifyWorker() {
	go func() {
		select {
		case <-brq.doneCh:
			// Prevent interference with the heap while we're retrieving from it
			brq.mtx.Lock()
			defer brq.mtx.Unlock()
			if brq.heap.Len() > 0 {
				retrieval := heap.Pop(brq.heap).(*blockRetrieval)
				brq.FinalizeRequest(retrieval, nil, io.EOF)
			}
		default:
			// Get the next queued worker
			ch := <-brq.workerQueue
			// Prevent interference with the heap while we're retrieving from it
			brq.mtx.Lock()
			defer brq.mtx.Unlock()
			// Pop from the heap
			ch <- heap.Pop(brq.heap).(*blockRetrieval)
		}
	}()
}

// Request submits a block request to the queue.
func (brq *blockRetrievalQueue) Request(ctx context.Context, priority int, kmd KeyMetadata, ptr BlockPointer, block Block) <-chan error {
	// Only continue if we haven't been shut down
	select {
	case <-brq.doneCh:
		ch := make(chan error, 1)
		ch <- io.EOF
		return ch
	default:
	}

	brq.mtx.Lock()
	defer brq.mtx.Unlock()
	br, exists := brq.ptrs[ptr]
	if !exists {
		// Add to the heap
		br = &blockRetrieval{
			blockPtr:       ptr,
			kmd:            kmd,
			index:          -1,
			priority:       priority,
			insertionOrder: brq.insertionCount,
		}
		brq.insertionCount++
		brq.ptrs[ptr] = br
		heap.Push(brq.heap, br)
		defer brq.notifyWorker()
	}
	ch := make(chan error, 1)
	br.requests = append(br.requests, &blockRetrievalRequest{ctx, block, ch})
	// If the new request priority is higher, elevate the retrieval in the
	// queue.  Skip this if the request is no longer in the queue (which means
	// it's actively being processed).
	if br.index != -1 && priority > br.priority {
		br.priority = priority
		heap.Fix(brq.heap, br.index)
	}
	return ch
}

// WorkOnRequest returns a new channel for a worker to obtain a blockRetrieval.
func (brq *blockRetrievalQueue) WorkOnRequest() <-chan *blockRetrieval {
	ch := make(chan *blockRetrieval, 1)
	brq.workerQueue <- ch

	return ch
}

// FinalizeRequest s the last step of a retrieval request once a block has been
// obtained. It removes the request from the blockRetrievalQueue, preventing
// more requests mutating the retrieval, then notifies all subscribed
// requests.
func (brq *blockRetrievalQueue) FinalizeRequest(retrieval *blockRetrieval, block Block, err error) {
	brq.mtx.Lock()
	delete(brq.ptrs, retrieval.blockPtr)
	brq.mtx.Unlock()

	source := reflect.ValueOf(block)
	for _, r := range retrieval.requests {
		req := r
		go func() {
			if source.Kind() == reflect.Ptr {
				// Copy the decrypted block to the caller
				dest := reflect.ValueOf(req.block).Elem()
				dest.Set(source.Elem())
			}
			req.doneCh <- err
		}()
	}
}

// Shutdown is called when we are no longer accepting requests
func (brq *blockRetrievalQueue) Shutdown() {
	select {
	case <-brq.doneCh:
	default:
		close(brq.doneCh)
	}
}
<|endoftext|>


<commit_before>package openid

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/RangelReale/osincli"
	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/util/sets"

	authapi "github.com/openshift/origin/pkg/auth/api"
	"github.com/openshift/origin/pkg/auth/oauth/external"
)

const (
	// Standard claims (http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)
	SubjectClaim           = "sub"
	PreferredUsernameClaim = "preferred_username"
	EmailClaim             = "email"
	NameClaim              = "name"
)

type TokenValidator func(map[string]interface{}) error

type Config struct {
	ClientID     string
	ClientSecret string

	Scopes []string

	ExtraAuthorizeParameters map[string]string

	AuthorizeURL string
	TokenURL     string
	UserInfoURL  string

	IDClaims                []string
	PreferredUsernameClaims []string
	EmailClaims             []string
	NameClaims              []string

	IDTokenValidator TokenValidator
}

type provider struct {
	providerName string
	transport    http.RoundTripper
	Config
}

// NewProvider returns an implementation of an OpenID Connect Authorization Code Flow
// See http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
// ID Token decryption is not supported
// UserInfo decryption is not supported
func NewProvider(providerName string, transport http.RoundTripper, config Config) (external.Provider, error) {
	// TODO: Add support for discovery documents
	// see http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
	// e.g. https://accounts.google.com/.well-known/openid-configuration

	// Validate client id/secret
	if len(config.ClientID) == 0 {
		return nil, errors.New("ClientID is required")
	}
	if len(config.ClientSecret) == 0 {
		return nil, errors.New("ClientSecret is required")
	}

	// Validate url presence
	if len(config.AuthorizeURL) == 0 {
		return nil, errors.New("Authorize URL is required")
	} else if u, err := url.Parse(config.AuthorizeURL); err != nil {
		return nil, errors.New("Authorize URL is invalid")
	} else if u.Scheme != "https" {
		return nil, errors.New("Authorize URL must use https scheme")
	}

	if len(config.TokenURL) == 0 {
		return nil, errors.New("Token URL is required")
	} else if u, err := url.Parse(config.TokenURL); err != nil {
		return nil, errors.New("Token URL is invalid")
	} else if u.Scheme != "https" {
		return nil, errors.New("Token URL must use https scheme")
	}

	if len(config.UserInfoURL) > 0 {
		if u, err := url.Parse(config.UserInfoURL); err != nil {
			return nil, errors.New("UserInfo URL is invalid")
		} else if u.Scheme != "https" {
			return nil, errors.New("UserInfo URL must use https scheme")
		}
	}

	if !sets.NewString(config.Scopes...).Has("openid") {
		return nil, errors.New("Scopes must include openid")
	}

	if len(config.IDClaims) == 0 {
		return nil, errors.New("IDClaims must specify at least one claim")
	}

	return provider{providerName, transport, config}, nil
}

// NewConfig implements external/interfaces/Provider.NewConfig
func (p provider) NewConfig() (*osincli.ClientConfig, error) {
	config := &osincli.ClientConfig{
		ClientId:                 p.ClientID,
		ClientSecret:             p.ClientSecret,
		ErrorsInStatusCode:       true,
		SendClientSecretInParams: true,
		AuthorizeUrl:             p.AuthorizeURL,
		TokenUrl:                 p.TokenURL,
		Scope:                    strings.Join(p.Scopes, " "),
	}
	return config, nil
}

func (p provider) GetTransport() (http.RoundTripper, error) {
	return p.transport, nil
}

// AddCustomParameters implements external/interfaces/Provider.AddCustomParameters
func (p provider) AddCustomParameters(req *osincli.AuthorizeRequest) {
	for k, v := range p.ExtraAuthorizeParameters {
		req.CustomParameters[k] = v
	}
}

// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity
func (p provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) {
	// Token response MUST include id_token
	// http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
	idToken, ok := data.ResponseData["id_token"].(string)
	if !ok {
		return nil, false, fmt.Errorf("No id_token returned in %v", data.ResponseData)
	}

	// id_token MUST be a valid JWT
	idTokenClaims, err := decodeJWT(idToken)
	if err != nil {
		return nil, false, err
	}

	if p.IDTokenValidator != nil {
		if err := p.IDTokenValidator(idTokenClaims); err != nil {
			return nil, false, err
		}
	}

	// TODO: validate JWT
	// http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation

	// id_token MUST contain a sub claim as the subject identifier
	// http://openid.net/specs/openid-connect-core-1_0.html#IDToken
	idTokenSubject, ok := idTokenClaims[SubjectClaim].(string)
	if !ok {
		return nil, false, fmt.Errorf("id_token did not contain a 'sub' claim: %#v", idTokenClaims)
	}

	// Use id_token claims by default
	claims := idTokenClaims

	// If we have a userinfo URL, use it to get more detailed claims
	if len(p.UserInfoURL) != 0 {
		userInfoClaims, err := fetchUserInfo(p.UserInfoURL, data.AccessToken, p.transport)
		if err != nil {
			return nil, false, err
		}

		// The sub (subject) Claim MUST always be returned in the UserInfo Response.
		// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
		userInfoSubject, ok := userInfoClaims[SubjectClaim].(string)
		if !ok {
			return nil, false, fmt.Errorf("userinfo response did not contain a 'sub' claim: %#v", userInfoClaims)
		}

		// The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
		// if they do not match, the UserInfo Response values MUST NOT be used.
		// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
		if userInfoSubject != idTokenSubject {
			return nil, false, fmt.Errorf("userinfo 'sub' claim (%s) did not match id_token 'sub' claim (%s)", userInfoSubject, idTokenSubject)
		}

		// Merge in userinfo claims in case id_token claims contained some that userinfo did not
		for k, v := range userInfoClaims {
			claims[k] = v
		}
	}

	id, _ := getClaimValue(claims, p.IDClaims)
	if id == "" {
		return nil, false, fmt.Errorf("Could not retrieve id claim for %#v", p.IDClaims)
	}
	identity := authapi.NewDefaultUserIdentityInfo(p.providerName, id)

	if preferredUsername, _ := getClaimValue(claims, p.PreferredUsernameClaims); len(preferredUsername) != 0 {
		identity.Extra[authapi.IdentityPreferredUsernameKey] = preferredUsername
	}

	if email, _ := getClaimValue(claims, p.EmailClaims); len(email) != 0 {
		identity.Extra[authapi.IdentityEmailKey] = email
	}

	if name, _ := getClaimValue(claims, p.NameClaims); len(name) != 0 {
		identity.Extra[authapi.IdentityDisplayNameKey] = name
	}

	glog.V(4).Infof("identity=%v", identity)

	return identity, true, nil
}

func getClaimValue(data map[string]interface{}, claims []string) (string, error) {
	for _, claim := range claims {
		value, ok := data[claim]
		if !ok {
			continue
		}
		stringValue, ok := value.(string)
		if !ok {
			return "", fmt.Errorf("Claim %s was not a string type", claim)
		}
		if len(stringValue) > 0 {
			return stringValue, nil
		}
	}
	return "", errors.New("No value found")
}

// fetch and decode JSON from the given UserInfo URL
func fetchUserInfo(url, accessToken string, transport http.RoundTripper) (map[string]interface{}, error) {
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))

	client := &http.Client{Transport: transport}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Non-200 response from UserInfo: %d, WWW-Authenticate=%s", resp.StatusCode, resp.Header.Get("WWW-Authenticate"))
	}

	// The UserInfo Claims MUST be returned as the members of a JSON object
	// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	decoded := map[string]interface{}{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		return nil, err
	}

	return decoded, nil
}

// Decode JWT
// http://openid.net/specs/draft-jones-json-web-token-07.html
func decodeJWT(jwt string) (map[string]interface{}, error) {
	jwtParts := strings.Split(jwt, ".")
	if len(jwtParts) != 3 {
		return nil, fmt.Errorf("Invalid JSON Web Token: expected 3 parts, got %d", len(jwtParts))
	}

	// Re-pad, if needed
	encodedPayload := jwtParts[1]
	if l := len(encodedPayload) % 4; l != 0 {
		encodedPayload += strings.Repeat("=", 4-l)
	}

	// Decode base-64
	decodedPayload, err := base64.StdEncoding.DecodeString(encodedPayload)
	if err != nil {
		return nil, fmt.Errorf("Error decoding payload: %v", err)
	}

	// Parse JSON
	var data map[string]interface{}
	err = json.Unmarshal([]byte(decodedPayload), &data)
	if err != nil {
		return nil, fmt.Errorf("Error parsing token: %v", err)
	}

	return data, nil
}
<commit_msg>Add logging to assist in determining openid claims<commit_after>package openid

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/RangelReale/osincli"
	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/util/sets"

	authapi "github.com/openshift/origin/pkg/auth/api"
	"github.com/openshift/origin/pkg/auth/oauth/external"
)

const (
	// Standard claims (http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)
	SubjectClaim           = "sub"
	PreferredUsernameClaim = "preferred_username"
	EmailClaim             = "email"
	NameClaim              = "name"
)

type TokenValidator func(map[string]interface{}) error

type Config struct {
	ClientID     string
	ClientSecret string

	Scopes []string

	ExtraAuthorizeParameters map[string]string

	AuthorizeURL string
	TokenURL     string
	UserInfoURL  string

	IDClaims                []string
	PreferredUsernameClaims []string
	EmailClaims             []string
	NameClaims              []string

	IDTokenValidator TokenValidator
}

type provider struct {
	providerName string
	transport    http.RoundTripper
	Config
}

// NewProvider returns an implementation of an OpenID Connect Authorization Code Flow
// See http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
// ID Token decryption is not supported
// UserInfo decryption is not supported
func NewProvider(providerName string, transport http.RoundTripper, config Config) (external.Provider, error) {
	// TODO: Add support for discovery documents
	// see http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
	// e.g. https://accounts.google.com/.well-known/openid-configuration

	// Validate client id/secret
	if len(config.ClientID) == 0 {
		return nil, errors.New("ClientID is required")
	}
	if len(config.ClientSecret) == 0 {
		return nil, errors.New("ClientSecret is required")
	}

	// Validate url presence
	if len(config.AuthorizeURL) == 0 {
		return nil, errors.New("Authorize URL is required")
	} else if u, err := url.Parse(config.AuthorizeURL); err != nil {
		return nil, errors.New("Authorize URL is invalid")
	} else if u.Scheme != "https" {
		return nil, errors.New("Authorize URL must use https scheme")
	}

	if len(config.TokenURL) == 0 {
		return nil, errors.New("Token URL is required")
	} else if u, err := url.Parse(config.TokenURL); err != nil {
		return nil, errors.New("Token URL is invalid")
	} else if u.Scheme != "https" {
		return nil, errors.New("Token URL must use https scheme")
	}

	if len(config.UserInfoURL) > 0 {
		if u, err := url.Parse(config.UserInfoURL); err != nil {
			return nil, errors.New("UserInfo URL is invalid")
		} else if u.Scheme != "https" {
			return nil, errors.New("UserInfo URL must use https scheme")
		}
	}

	if !sets.NewString(config.Scopes...).Has("openid") {
		return nil, errors.New("Scopes must include openid")
	}

	if len(config.IDClaims) == 0 {
		return nil, errors.New("IDClaims must specify at least one claim")
	}

	return provider{providerName, transport, config}, nil
}

// NewConfig implements external/interfaces/Provider.NewConfig
func (p provider) NewConfig() (*osincli.ClientConfig, error) {
	config := &osincli.ClientConfig{
		ClientId:                 p.ClientID,
		ClientSecret:             p.ClientSecret,
		ErrorsInStatusCode:       true,
		SendClientSecretInParams: true,
		AuthorizeUrl:             p.AuthorizeURL,
		TokenUrl:                 p.TokenURL,
		Scope:                    strings.Join(p.Scopes, " "),
	}
	return config, nil
}

func (p provider) GetTransport() (http.RoundTripper, error) {
	return p.transport, nil
}

// AddCustomParameters implements external/interfaces/Provider.AddCustomParameters
func (p provider) AddCustomParameters(req *osincli.AuthorizeRequest) {
	for k, v := range p.ExtraAuthorizeParameters {
		req.CustomParameters[k] = v
	}
}

// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity
func (p provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) {
	// Token response MUST include id_token
	// http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
	idToken, ok := data.ResponseData["id_token"].(string)
	if !ok {
		return nil, false, fmt.Errorf("No id_token returned in %v", data.ResponseData)
	}

	// id_token MUST be a valid JWT
	idTokenClaims, err := decodeJWT(idToken)
	if err != nil {
		return nil, false, err
	}

	if p.IDTokenValidator != nil {
		if err := p.IDTokenValidator(idTokenClaims); err != nil {
			return nil, false, err
		}
	}

	// TODO: validate JWT
	// http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation

	// id_token MUST contain a sub claim as the subject identifier
	// http://openid.net/specs/openid-connect-core-1_0.html#IDToken
	idTokenSubject, ok := idTokenClaims[SubjectClaim].(string)
	if !ok {
		return nil, false, fmt.Errorf("id_token did not contain a 'sub' claim: %#v", idTokenClaims)
	}

	// Use id_token claims by default
	claims := idTokenClaims

	// If we have a userinfo URL, use it to get more detailed claims
	if len(p.UserInfoURL) != 0 {
		userInfoClaims, err := fetchUserInfo(p.UserInfoURL, data.AccessToken, p.transport)
		if err != nil {
			return nil, false, err
		}

		// The sub (subject) Claim MUST always be returned in the UserInfo Response.
		// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
		userInfoSubject, ok := userInfoClaims[SubjectClaim].(string)
		if !ok {
			return nil, false, fmt.Errorf("userinfo response did not contain a 'sub' claim: %#v", userInfoClaims)
		}

		// The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
		// if they do not match, the UserInfo Response values MUST NOT be used.
		// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
		if userInfoSubject != idTokenSubject {
			return nil, false, fmt.Errorf("userinfo 'sub' claim (%s) did not match id_token 'sub' claim (%s)", userInfoSubject, idTokenSubject)
		}

		// Merge in userinfo claims in case id_token claims contained some that userinfo did not
		for k, v := range userInfoClaims {
			claims[k] = v
		}
	}

	glog.V(5).Infof("openid claims: %#v", claims)

	id, _ := getClaimValue(claims, p.IDClaims)
	if id == "" {
		return nil, false, fmt.Errorf("Could not retrieve id claim for %#v from %#v", p.IDClaims, claims)
	}
	identity := authapi.NewDefaultUserIdentityInfo(p.providerName, id)

	if preferredUsername, _ := getClaimValue(claims, p.PreferredUsernameClaims); len(preferredUsername) != 0 {
		identity.Extra[authapi.IdentityPreferredUsernameKey] = preferredUsername
	}

	if email, _ := getClaimValue(claims, p.EmailClaims); len(email) != 0 {
		identity.Extra[authapi.IdentityEmailKey] = email
	}

	if name, _ := getClaimValue(claims, p.NameClaims); len(name) != 0 {
		identity.Extra[authapi.IdentityDisplayNameKey] = name
	}

	glog.V(4).Infof("identity=%v", identity)

	return identity, true, nil
}

func getClaimValue(data map[string]interface{}, claims []string) (string, error) {
	for _, claim := range claims {
		value, ok := data[claim]
		if !ok {
			continue
		}
		stringValue, ok := value.(string)
		if !ok {
			return "", fmt.Errorf("Claim %s was not a string type", claim)
		}
		if len(stringValue) > 0 {
			return stringValue, nil
		}
	}
	return "", errors.New("No value found")
}

// fetch and decode JSON from the given UserInfo URL
func fetchUserInfo(url, accessToken string, transport http.RoundTripper) (map[string]interface{}, error) {
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))

	client := &http.Client{Transport: transport}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Non-200 response from UserInfo: %d, WWW-Authenticate=%s", resp.StatusCode, resp.Header.Get("WWW-Authenticate"))
	}

	// The UserInfo Claims MUST be returned as the members of a JSON object
	// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	decoded := map[string]interface{}{}
	if err := json.Unmarshal(data, &decoded); err != nil {
		return nil, err
	}

	return decoded, nil
}

// Decode JWT
// http://openid.net/specs/draft-jones-json-web-token-07.html
func decodeJWT(jwt string) (map[string]interface{}, error) {
	jwtParts := strings.Split(jwt, ".")
	if len(jwtParts) != 3 {
		return nil, fmt.Errorf("Invalid JSON Web Token: expected 3 parts, got %d", len(jwtParts))
	}

	// Re-pad, if needed
	encodedPayload := jwtParts[1]
	if l := len(encodedPayload) % 4; l != 0 {
		encodedPayload += strings.Repeat("=", 4-l)
	}

	// Decode base-64
	decodedPayload, err := base64.StdEncoding.DecodeString(encodedPayload)
	if err != nil {
		return nil, fmt.Errorf("Error decoding payload: %v", err)
	}

	// Parse JSON
	var data map[string]interface{}
	err = json.Unmarshal([]byte(decodedPayload), &data)
	if err != nil {
		return nil, fmt.Errorf("Error parsing token: %v", err)
	}

	return data, nil
}
<|endoftext|>


<commit_before>package shellwords

import (
	"go/build"
	"os"
	"path"
	"reflect"
	"testing"
)

var testcases = []struct {
	line     string
	expected []string
}{
	{`var --bar=baz`, []string{`var`, `--bar=baz`}},
	{`var --bar="baz"`, []string{`var`, `--bar=baz`}},
	{`var "--bar=baz"`, []string{`var`, `--bar=baz`}},
	{`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}},
	{"var --bar=`baz`", []string{`var`, "--bar=`baz`"}},
	{`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}},
	{`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}},
	{`var --bar='\'`, []string{`var`, `--bar=\`}},
	{`var "--bar baz"`, []string{`var`, `--bar baz`}},
	{`var --"bar baz"`, []string{`var`, `--bar baz`}},
	{`var  --"bar baz"`, []string{`var`, `--bar baz`}},
	{`a "b"`, []string{`a`, `b`}},
	{`a " b "`, []string{`a`, ` b `}},
	{`a "   "`, []string{`a`, `   `}}, // FAILS !
	{`a 'b'`, []string{`a`, `b`}},
	{`a ' b '`, []string{`a`, ` b `}},
	{`a '   '`, []string{`a`, `   `}}, // FAILS !
}

func TestSimple(t *testing.T) {
	for _, testcase := range testcases {
		args, err := Parse(testcase.line)
		if err != nil {
			t.Fatal(err)
		}
		if !reflect.DeepEqual(args, testcase.expected) {
			t.Fatalf("Expected %#v for %q, but %#v:", testcase.expected, testcase.line, args)
		}
	}
}

func TestError(t *testing.T) {
	_, err := Parse("foo '")
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = Parse(`foo "`)
	if err == nil {
		t.Fatal("Should be an error")
	}

	_, err = Parse("foo `")
	if err == nil {
		t.Fatal("Should be an error")
	}
}

func TestLastSpace(t *testing.T) {
	args, err := Parse("foo bar\\  ")
	if err != nil {
		t.Fatal(err)
	}
	if len(args) != 2 {
		t.Fatal("Should have two elements")
	}
	if args[0] != "foo" {
		t.Fatal("1st element should be `foo`")
	}
	if args[1] != "bar " {
		t.Fatal("1st element should be `bar `")
	}
}

func TestEmptyArgs(t *testing.T) {
	args, err := Parse(`foo "" bar ''`)
	if err != nil {
		t.Fatal(err)
	}
	if len(args) != 4 {
		t.Fatal("Should have three elements")
	}
	if args[0] != "foo" {
		t.Fatal("1st element should be `foo`")
	}
	if args[1] != "" {
		t.Fatal("2nd element should be empty")
	}
	if args[2] != "bar" {
		t.Fatal("3rd element should be `bar`")
	}
	if args[3] != "" {
		t.Fatal("4th element should be empty")
	}
}

func TestShellRun(t *testing.T) {
	dir, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	pwd, err := shellRun("pwd", "")
	if err != nil {
		t.Fatal(err)
	}

	pwd2, err := shellRun("pwd", path.Join(dir, "/_example"))
	if err != nil {
		t.Fatal(err)
	}

	if pwd == pwd2 {
		t.Fatal("`pwd` should be changed")
	}
}

func TestBacktick(t *testing.T) {
	goversion, err := shellRun("go version", "")
	if err != nil {
		t.Fatal(err)
	}

	parser := NewParser()
	parser.ParseBacktick = true
	args, err := parser.Parse("echo `go version`")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", goversion}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	args, err = parser.Parse(`echo $(echo foo)`)
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "foo"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	args, err = parser.Parse(`echo bar=$(echo 200)cm`)
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "bar=200cm"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	parser.ParseBacktick = false
	args, err = parser.Parse(`echo $(echo "foo")`)
	expected = []string{"echo", `$(echo "foo")`}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
	args, err = parser.Parse("echo $(`echo1)")
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "$(`echo1)"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestBacktickMulti(t *testing.T) {
	parser := NewParser()
	parser.ParseBacktick = true
	args, err := parser.Parse(`echo $(go env GOPATH && go env GOROOT)`)
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", build.Default.GOPATH + "\n" + build.Default.GOROOT}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestBacktickError(t *testing.T) {
	parser := NewParser()
	parser.ParseBacktick = true
	_, err := parser.Parse("echo `go Version`")
	if err == nil {
		t.Fatal("Should be an error")
	}
	expected := "exit status 2:go Version: unknown command\nRun 'go help' for usage.\n"
	if expected != err.Error() {
		t.Fatalf("Expected %q, but %q", expected, err.Error())
	}
	_, err = parser.Parse(`echo $(echo1)`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo FOO=$(echo1)`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo $(echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo $ (echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo (echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo )echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
}

func TestEnv(t *testing.T) {
	os.Setenv("FOO", "bar")

	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $FOO")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "bar"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestCustomEnv(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true
	parser.Getenv = func(k string) string { return map[string]string{"FOO": "baz"}[k] }
	args, err := parser.Parse("echo $FOO")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "baz"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestNoEnv(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $BAR")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", ""}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestDupEnv(t *testing.T) {
	os.Setenv("FOO", "bar")
	os.Setenv("FOO_BAR", "baz")

	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $$FOO$")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "$bar$"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	args, err = parser.Parse("echo $${FOO_BAR}$")
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "$baz$"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestHaveMore(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true

	line := "echo foo; seq 1 10"
	args, err := parser.Parse(line)
	if err != nil {
		t.Fatalf(err.Error())
	}
	expected := []string{"echo", "foo"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	if parser.Position == 0 {
		t.Fatalf("Commands should be remaining")
	}

	line = string([]rune(line)[parser.Position+1:])
	args, err = parser.Parse(line)
	if err != nil {
		t.Fatalf(err.Error())
	}
	expected = []string{"seq", "1", "10"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	if parser.Position > 0 {
		t.Fatalf("Commands should not be remaining")
	}
}

func TestHaveRedirect(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true

	line := "ls -la 2>foo"
	args, err := parser.Parse(line)
	if err != nil {
		t.Fatalf(err.Error())
	}
	expected := []string{"ls", "-la"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	if parser.Position == 0 {
		t.Fatalf("Commands should be remaining")
	}
}

func TestBackquoteInFlag(t *testing.T) {
	parser := NewParser()
	parser.ParseBacktick = true
	args, err := parser.Parse("cmd -flag=`echo val1` -flag=val2")
	if err != nil {
		panic(err)
	}
	expected := []string{"cmd", "-flag=val1", "-flag=val2"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}
<commit_msg>Add test<commit_after>package shellwords

import (
	"go/build"
	"os"
	"path"
	"reflect"
	"testing"
)

var testcases = []struct {
	line     string
	expected []string
}{
	{`var --bar=baz`, []string{`var`, `--bar=baz`}},
	{`var --bar="baz"`, []string{`var`, `--bar=baz`}},
	{`var "--bar=baz"`, []string{`var`, `--bar=baz`}},
	{`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}},
	{"var --bar=`baz`", []string{`var`, "--bar=`baz`"}},
	{`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}},
	{`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}},
	{`var --bar='\'`, []string{`var`, `--bar=\`}},
	{`var "--bar baz"`, []string{`var`, `--bar baz`}},
	{`var --"bar baz"`, []string{`var`, `--bar baz`}},
	{`var  --"bar baz"`, []string{`var`, `--bar baz`}},
	{`a "b"`, []string{`a`, `b`}},
	{`a " b "`, []string{`a`, ` b `}},
	{`a "   "`, []string{`a`, `   `}}, // FAILS !
	{`a 'b'`, []string{`a`, `b`}},
	{`a ' b '`, []string{`a`, ` b `}},
	{`a '   '`, []string{`a`, `   `}}, // FAILS !
}

func TestSimple(t *testing.T) {
	for _, testcase := range testcases {
		args, err := Parse(testcase.line)
		if err != nil {
			t.Fatal(err)
		}
		if !reflect.DeepEqual(args, testcase.expected) {
			t.Fatalf("Expected %#v for %q, but %#v:", testcase.expected, testcase.line, args)
		}
	}
}

func TestError(t *testing.T) {
	_, err := Parse("foo '")
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = Parse(`foo "`)
	if err == nil {
		t.Fatal("Should be an error")
	}

	_, err = Parse("foo `")
	if err == nil {
		t.Fatal("Should be an error")
	}
}

func TestLastSpace(t *testing.T) {
	args, err := Parse("foo bar\\  ")
	if err != nil {
		t.Fatal(err)
	}
	if len(args) != 2 {
		t.Fatal("Should have two elements")
	}
	if args[0] != "foo" {
		t.Fatal("1st element should be `foo`")
	}
	if args[1] != "bar " {
		t.Fatal("1st element should be `bar `")
	}
}

func TestEmptyArgs(t *testing.T) {
	args, err := Parse(`foo "" bar ''`)
	if err != nil {
		t.Fatal(err)
	}
	if len(args) != 4 {
		t.Fatal("Should have three elements")
	}
	if args[0] != "foo" {
		t.Fatal("1st element should be `foo`")
	}
	if args[1] != "" {
		t.Fatal("2nd element should be empty")
	}
	if args[2] != "bar" {
		t.Fatal("3rd element should be `bar`")
	}
	if args[3] != "" {
		t.Fatal("4th element should be empty")
	}
}

func TestShellRun(t *testing.T) {
	dir, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	pwd, err := shellRun("pwd", "")
	if err != nil {
		t.Fatal(err)
	}

	pwd2, err := shellRun("pwd", path.Join(dir, "/_example"))
	if err != nil {
		t.Fatal(err)
	}

	if pwd == pwd2 {
		t.Fatal("`pwd` should be changed")
	}
}

func TestBacktick(t *testing.T) {
	goversion, err := shellRun("go version", "")
	if err != nil {
		t.Fatal(err)
	}

	parser := NewParser()
	parser.ParseBacktick = true
	args, err := parser.Parse("echo `go version`")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", goversion}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	args, err = parser.Parse(`echo $(echo foo)`)
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "foo"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	args, err = parser.Parse(`echo bar=$(echo 200)cm`)
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "bar=200cm"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	parser.ParseBacktick = false
	args, err = parser.Parse(`echo $(echo "foo")`)
	expected = []string{"echo", `$(echo "foo")`}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
	args, err = parser.Parse("echo $(`echo1)")
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "$(`echo1)"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestBacktickMulti(t *testing.T) {
	parser := NewParser()
	parser.ParseBacktick = true
	args, err := parser.Parse(`echo $(go env GOPATH && go env GOROOT)`)
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", build.Default.GOPATH + "\n" + build.Default.GOROOT}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestBacktickError(t *testing.T) {
	parser := NewParser()
	parser.ParseBacktick = true
	_, err := parser.Parse("echo `go Version`")
	if err == nil {
		t.Fatal("Should be an error")
	}
	expected := "exit status 2:go Version: unknown command\nRun 'go help' for usage.\n"
	if expected != err.Error() {
		t.Fatalf("Expected %q, but %q", expected, err.Error())
	}
	_, err = parser.Parse(`echo $(echo1)`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo FOO=$(echo1)`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo $(echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo $ (echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo (echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
	_, err = parser.Parse(`echo )echo1`)
	if err == nil {
		t.Fatal("Should be an error")
	}
}

func TestEnv(t *testing.T) {
	os.Setenv("FOO", "bar")

	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $FOO")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "bar"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestCustomEnv(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true
	parser.Getenv = func(k string) string { return map[string]string{"FOO": "baz"}[k] }
	args, err := parser.Parse("echo $FOO")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "baz"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestNoEnv(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $BAR")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestEnvArguments(t *testing.T) {
	os.Setenv("FOO", "bar baz")

	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $FOO")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "bar", "baz"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestDupEnv(t *testing.T) {
	os.Setenv("FOO", "bar")
	os.Setenv("FOO_BAR", "baz")

	parser := NewParser()
	parser.ParseEnv = true
	args, err := parser.Parse("echo $$FOO$")
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"echo", "$bar$"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	args, err = parser.Parse("echo $${FOO_BAR}$")
	if err != nil {
		t.Fatal(err)
	}
	expected = []string{"echo", "$baz$"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}

func TestHaveMore(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true

	line := "echo foo; seq 1 10"
	args, err := parser.Parse(line)
	if err != nil {
		t.Fatalf(err.Error())
	}
	expected := []string{"echo", "foo"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	if parser.Position == 0 {
		t.Fatalf("Commands should be remaining")
	}

	line = string([]rune(line)[parser.Position+1:])
	args, err = parser.Parse(line)
	if err != nil {
		t.Fatalf(err.Error())
	}
	expected = []string{"seq", "1", "10"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	if parser.Position > 0 {
		t.Fatalf("Commands should not be remaining")
	}
}

func TestHaveRedirect(t *testing.T) {
	parser := NewParser()
	parser.ParseEnv = true

	line := "ls -la 2>foo"
	args, err := parser.Parse(line)
	if err != nil {
		t.Fatalf(err.Error())
	}
	expected := []string{"ls", "-la"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}

	if parser.Position == 0 {
		t.Fatalf("Commands should be remaining")
	}
}

func TestBackquoteInFlag(t *testing.T) {
	parser := NewParser()
	parser.ParseBacktick = true
	args, err := parser.Parse("cmd -flag=`echo val1` -flag=val2")
	if err != nil {
		panic(err)
	}
	expected := []string{"cmd", "-flag=val1", "-flag=val2"}
	if !reflect.DeepEqual(args, expected) {
		t.Fatalf("Expected %#v, but %#v:", expected, args)
	}
}
<|endoftext|>


<commit_before>package catalog

import (
	"context"
	"fmt"

	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/cache"

	"github.com/operator-framework/api/pkg/operators/v1alpha1"
	crdlib "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/crd"
	index "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/index"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
	errorwrap "github.com/pkg/errors"
	logger "github.com/sirupsen/logrus"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
	apiextensionsv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"

	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Stepper manages cluster interactions based on the step.
type Stepper interface {
	Status() (v1alpha1.StepStatus, error)
}

// StepperFunc fulfills the Stepper interface.
type StepperFunc func() (v1alpha1.StepStatus, error)

func (s StepperFunc) Status() (v1alpha1.StepStatus, error) {
	return s()
}

// Builder holds clients and data structures required for the StepBuilder to work
// Builder attributes are not to meant to be accessed outside the StepBuilder method
type builder struct {
	opclient          operatorclient.ClientInterface
	dynamicClient     dynamic.Interface
	manifestResolver  ManifestResolver
	csvToProvidedAPIs map[string]cache.Indexer
	logger            logger.FieldLogger
}

func newBuilder(opclient operatorclient.ClientInterface, dynamicClient dynamic.Interface, csvToProvidedAPIs map[string]cache.Indexer,
	manifestResolver ManifestResolver, logger logger.FieldLogger) *builder {
	return &builder{
		opclient:          opclient,
		dynamicClient:     dynamicClient,
		manifestResolver:  manifestResolver,
		csvToProvidedAPIs: csvToProvidedAPIs,
		logger:            logger,
	}
}

type notSupportedStepperErr struct {
	message string
}

func (n notSupportedStepperErr) Error() string {
	return n.message
}

// step is a factory that creates StepperFuncs based on the install plan step Kind.
func (b *builder) create(step v1alpha1.Step) (Stepper, error) {
	manifest, err := b.manifestResolver.ManifestForStep(&step)
	if err != nil {
		return nil, err
	}

	switch step.Resource.Kind {
	case crdKind:
		version, err := crdlib.Version(&manifest)
		if err != nil {
			return nil, err
		}
		switch version {
		case crdlib.V1Version:
			return b.NewCRDV1Step(b.opclient.ApiextensionsInterface().ApiextensionsV1(), &step, manifest), nil
		case crdlib.V1Beta1Version:
			return b.NewCRDV1Beta1Step(b.opclient.ApiextensionsInterface().ApiextensionsV1beta1(), &step, manifest), nil
		}
	}
	return nil, notSupportedStepperErr{fmt.Sprintf("stepper interface does not support %s", step.Resource.Kind)}
}

func (b *builder) NewCRDV1Step(client apiextensionsv1client.ApiextensionsV1Interface, step *v1alpha1.Step, manifest string) StepperFunc {
	return func() (v1alpha1.StepStatus, error) {
		switch step.Status {
		case v1alpha1.StepStatusPresent:
			return v1alpha1.StepStatusPresent, nil
		case v1alpha1.StepStatusCreated:
			return v1alpha1.StepStatusCreated, nil
		case v1alpha1.StepStatusWaitingForAPI:
			crd, err := client.CustomResourceDefinitions().Get(context.TODO(), step.Resource.Name, metav1.GetOptions{})
			if err != nil {
				if k8serrors.IsNotFound(err) {
					return v1alpha1.StepStatusNotPresent, nil
				} else {
					return v1alpha1.StepStatusNotPresent, errorwrap.Wrapf(err, "error finding the %s CRD", crd.Name)
				}
			}
			established, namesAccepted := false, false
			for _, cdt := range crd.Status.Conditions {
				switch cdt.Type {
				case apiextensionsv1.Established:
					if cdt.Status == apiextensionsv1.ConditionTrue {
						established = true
					}
				case apiextensionsv1.NamesAccepted:
					if cdt.Status == apiextensionsv1.ConditionTrue {
						namesAccepted = true
					}
				}
			}
			if established && namesAccepted {
				return v1alpha1.StepStatusCreated, nil
			}
		case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent:
			crd, err := crdlib.UnmarshalV1(manifest)
			if err != nil {
				return v1alpha1.StepStatusUnknown, err
			}

			_, createError := client.CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
			if k8serrors.IsAlreadyExists(createError) {
				currentCRD, _ := client.CustomResourceDefinitions().Get(context.TODO(), crd.GetName(), metav1.GetOptions{})
				// Verify CRD ownership, only attempt to update if
				// CRD has only one owner
				// Example: provided=database.coreos.com/v1alpha1/EtcdCluster
				matchedCSV, err := index.V1CRDProviderNames(b.csvToProvidedAPIs, crd)
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error find matched CSV: %s", step.Resource.Name)
				}
				crd.SetResourceVersion(currentCRD.GetResourceVersion())
				if len(matchedCSV) == 1 {
					logger.Debugf("Found one owner for CRD %v", crd)
				} else if len(matchedCSV) > 1 {
					logger.Debugf("Found multiple owners for CRD %v", crd)

					if err = validateV1CRDCompatibility(b.dynamicClient, currentCRD, crd); err != nil {
						return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error validating existing CRs against new CRD's schema: %s", step.Resource.Name)
					}
				}

				// check to see if stored versions changed and whether the upgrade could cause potential data loss
				safe, err := crdlib.SafeStorageVersionUpgrade(currentCRD, crd)
				if !safe {
					b.logger.Errorf("risk of data loss updating %s: %s", step.Resource.Name, err)
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "risk of data loss updating %s", step.Resource.Name)
				}
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "checking CRD for potential data loss updating %s", step.Resource.Name)
				}

				// Update CRD to new version
				_, err = client.CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
				}
				// If it already existed, mark the step as Present.
				// they were equal - mark CRD as present
				return v1alpha1.StepStatusPresent, nil
			} else if createError != nil {
				// Unexpected error creating the CRD.
				return v1alpha1.StepStatusUnknown, createError
			}
			// If no error occured, make sure to wait for the API to become available.
			return v1alpha1.StepStatusWaitingForAPI, nil
		}
		return v1alpha1.StepStatusUnknown, nil
	}
}

func (b *builder) NewCRDV1Beta1Step(client apiextensionsv1beta1client.ApiextensionsV1beta1Interface, step *v1alpha1.Step, manifest string) StepperFunc {
	return func() (v1alpha1.StepStatus, error) {
		switch step.Status {
		case v1alpha1.StepStatusPresent:
			return v1alpha1.StepStatusPresent, nil
		case v1alpha1.StepStatusCreated:
			return v1alpha1.StepStatusCreated, nil
		case v1alpha1.StepStatusWaitingForAPI:
			crd, err := client.CustomResourceDefinitions().Get(context.TODO(), step.Resource.Name, metav1.GetOptions{})
			if err != nil {
				if k8serrors.IsNotFound(err) {
					return v1alpha1.StepStatusNotPresent, nil
				} else {
					return v1alpha1.StepStatusNotPresent, errorwrap.Wrapf(err, "error finding the %s CRD", crd.Name)
				}
			}
			established, namesAccepted := false, false
			for _, cdt := range crd.Status.Conditions {
				switch cdt.Type {
				case apiextensionsv1beta1.Established:
					if cdt.Status == apiextensionsv1beta1.ConditionTrue {
						established = true
					}
				case apiextensionsv1beta1.NamesAccepted:
					if cdt.Status == apiextensionsv1beta1.ConditionTrue {
						namesAccepted = true
					}
				}
			}
			if established && namesAccepted {
				return v1alpha1.StepStatusCreated, nil
			}
		case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent:
			crd, err := crdlib.UnmarshalV1Beta1(manifest)
			if err != nil {
				return v1alpha1.StepStatusUnknown, err
			}

			_, createError := client.CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
			if k8serrors.IsAlreadyExists(createError) {
				currentCRD, _ := client.CustomResourceDefinitions().Get(context.TODO(), crd.GetName(), metav1.GetOptions{})
				// Verify CRD ownership, only attempt to update if
				// CRD has only one owner
				// Example: provided=database.coreos.com/v1alpha1/EtcdCluster
				matchedCSV, err := index.V1Beta1CRDProviderNames(b.csvToProvidedAPIs, crd)
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error find matched CSV: %s", step.Resource.Name)
				}
				crd.SetResourceVersion(currentCRD.GetResourceVersion())
				if len(matchedCSV) == 1 {
					logger.Debugf("Found one owner for CRD %v", crd)
				} else if len(matchedCSV) > 1 {
					logger.Debugf("Found multiple owners for CRD %v", crd)

					if err = validateV1Beta1CRDCompatibility(b.dynamicClient, currentCRD, crd); err != nil {
						return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error validating existing CRs against new CRD's schema: %s", step.Resource.Name)
					}
				}

				// check to see if stored versions changed and whether the upgrade could cause potential data loss
				safe, err := crdlib.SafeStorageVersionUpgrade(currentCRD, crd)
				if !safe {
					b.logger.Errorf("risk of data loss updating %s: %s", step.Resource.Name, err)
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "risk of data loss updating %s", step.Resource.Name)
				}
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "checking CRD for potential data loss updating %s", step.Resource.Name)
				}

				// Update CRD to new version
				_, err = client.CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
				}
				// If it already existed, mark the step as Present.
				// they were equal - mark CRD as present
				return v1alpha1.StepStatusPresent, nil
			} else if createError != nil {
				// Unexpected error creating the CRD.
				return v1alpha1.StepStatusUnknown, createError
			}
			// If no error occured, make sure to wait for the API to become available.
			return v1alpha1.StepStatusWaitingForAPI, nil
		}
		return v1alpha1.StepStatusUnknown, nil
	}
}
<commit_msg>Always validate CR upgrade compatibility.<commit_after>package catalog

import (
	"context"
	"fmt"

	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/cache"

	"github.com/operator-framework/api/pkg/operators/v1alpha1"
	crdlib "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/crd"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
	errorwrap "github.com/pkg/errors"
	logger "github.com/sirupsen/logrus"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
	apiextensionsv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"

	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Stepper manages cluster interactions based on the step.
type Stepper interface {
	Status() (v1alpha1.StepStatus, error)
}

// StepperFunc fulfills the Stepper interface.
type StepperFunc func() (v1alpha1.StepStatus, error)

func (s StepperFunc) Status() (v1alpha1.StepStatus, error) {
	return s()
}

// Builder holds clients and data structures required for the StepBuilder to work
// Builder attributes are not to meant to be accessed outside the StepBuilder method
type builder struct {
	opclient          operatorclient.ClientInterface
	dynamicClient     dynamic.Interface
	manifestResolver  ManifestResolver
	csvToProvidedAPIs map[string]cache.Indexer
	logger            logger.FieldLogger
}

func newBuilder(opclient operatorclient.ClientInterface, dynamicClient dynamic.Interface, csvToProvidedAPIs map[string]cache.Indexer,
	manifestResolver ManifestResolver, logger logger.FieldLogger) *builder {
	return &builder{
		opclient:          opclient,
		dynamicClient:     dynamicClient,
		manifestResolver:  manifestResolver,
		csvToProvidedAPIs: csvToProvidedAPIs,
		logger:            logger,
	}
}

type notSupportedStepperErr struct {
	message string
}

func (n notSupportedStepperErr) Error() string {
	return n.message
}

// step is a factory that creates StepperFuncs based on the install plan step Kind.
func (b *builder) create(step v1alpha1.Step) (Stepper, error) {
	manifest, err := b.manifestResolver.ManifestForStep(&step)
	if err != nil {
		return nil, err
	}

	switch step.Resource.Kind {
	case crdKind:
		version, err := crdlib.Version(&manifest)
		if err != nil {
			return nil, err
		}
		switch version {
		case crdlib.V1Version:
			return b.NewCRDV1Step(b.opclient.ApiextensionsInterface().ApiextensionsV1(), &step, manifest), nil
		case crdlib.V1Beta1Version:
			return b.NewCRDV1Beta1Step(b.opclient.ApiextensionsInterface().ApiextensionsV1beta1(), &step, manifest), nil
		}
	}
	return nil, notSupportedStepperErr{fmt.Sprintf("stepper interface does not support %s", step.Resource.Kind)}
}

func (b *builder) NewCRDV1Step(client apiextensionsv1client.ApiextensionsV1Interface, step *v1alpha1.Step, manifest string) StepperFunc {
	return func() (v1alpha1.StepStatus, error) {
		switch step.Status {
		case v1alpha1.StepStatusPresent:
			return v1alpha1.StepStatusPresent, nil
		case v1alpha1.StepStatusCreated:
			return v1alpha1.StepStatusCreated, nil
		case v1alpha1.StepStatusWaitingForAPI:
			crd, err := client.CustomResourceDefinitions().Get(context.TODO(), step.Resource.Name, metav1.GetOptions{})
			if err != nil {
				if k8serrors.IsNotFound(err) {
					return v1alpha1.StepStatusNotPresent, nil
				} else {
					return v1alpha1.StepStatusNotPresent, errorwrap.Wrapf(err, "error finding the %s CRD", crd.Name)
				}
			}
			established, namesAccepted := false, false
			for _, cdt := range crd.Status.Conditions {
				switch cdt.Type {
				case apiextensionsv1.Established:
					if cdt.Status == apiextensionsv1.ConditionTrue {
						established = true
					}
				case apiextensionsv1.NamesAccepted:
					if cdt.Status == apiextensionsv1.ConditionTrue {
						namesAccepted = true
					}
				}
			}
			if established && namesAccepted {
				return v1alpha1.StepStatusCreated, nil
			}
		case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent:
			crd, err := crdlib.UnmarshalV1(manifest)
			if err != nil {
				return v1alpha1.StepStatusUnknown, err
			}

			_, createError := client.CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
			if k8serrors.IsAlreadyExists(createError) {
				currentCRD, _ := client.CustomResourceDefinitions().Get(context.TODO(), crd.GetName(), metav1.GetOptions{})
				crd.SetResourceVersion(currentCRD.GetResourceVersion())
				if err = validateV1CRDCompatibility(b.dynamicClient, currentCRD, crd); err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error validating existing CRs against new CRD's schema: %s", step.Resource.Name)
				}

				// check to see if stored versions changed and whether the upgrade could cause potential data loss
				safe, err := crdlib.SafeStorageVersionUpgrade(currentCRD, crd)
				if !safe {
					b.logger.Errorf("risk of data loss updating %s: %s", step.Resource.Name, err)
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "risk of data loss updating %s", step.Resource.Name)
				}
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "checking CRD for potential data loss updating %s", step.Resource.Name)
				}

				// Update CRD to new version
				_, err = client.CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
				}
				// If it already existed, mark the step as Present.
				// they were equal - mark CRD as present
				return v1alpha1.StepStatusPresent, nil
			} else if createError != nil {
				// Unexpected error creating the CRD.
				return v1alpha1.StepStatusUnknown, createError
			}
			// If no error occured, make sure to wait for the API to become available.
			return v1alpha1.StepStatusWaitingForAPI, nil
		}
		return v1alpha1.StepStatusUnknown, nil
	}
}

func (b *builder) NewCRDV1Beta1Step(client apiextensionsv1beta1client.ApiextensionsV1beta1Interface, step *v1alpha1.Step, manifest string) StepperFunc {
	return func() (v1alpha1.StepStatus, error) {
		switch step.Status {
		case v1alpha1.StepStatusPresent:
			return v1alpha1.StepStatusPresent, nil
		case v1alpha1.StepStatusCreated:
			return v1alpha1.StepStatusCreated, nil
		case v1alpha1.StepStatusWaitingForAPI:
			crd, err := client.CustomResourceDefinitions().Get(context.TODO(), step.Resource.Name, metav1.GetOptions{})
			if err != nil {
				if k8serrors.IsNotFound(err) {
					return v1alpha1.StepStatusNotPresent, nil
				} else {
					return v1alpha1.StepStatusNotPresent, errorwrap.Wrapf(err, "error finding the %s CRD", crd.Name)
				}
			}
			established, namesAccepted := false, false
			for _, cdt := range crd.Status.Conditions {
				switch cdt.Type {
				case apiextensionsv1beta1.Established:
					if cdt.Status == apiextensionsv1beta1.ConditionTrue {
						established = true
					}
				case apiextensionsv1beta1.NamesAccepted:
					if cdt.Status == apiextensionsv1beta1.ConditionTrue {
						namesAccepted = true
					}
				}
			}
			if established && namesAccepted {
				return v1alpha1.StepStatusCreated, nil
			}
		case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent:
			crd, err := crdlib.UnmarshalV1Beta1(manifest)
			if err != nil {
				return v1alpha1.StepStatusUnknown, err
			}

			_, createError := client.CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
			if k8serrors.IsAlreadyExists(createError) {
				currentCRD, _ := client.CustomResourceDefinitions().Get(context.TODO(), crd.GetName(), metav1.GetOptions{})
				crd.SetResourceVersion(currentCRD.GetResourceVersion())

				if err = validateV1Beta1CRDCompatibility(b.dynamicClient, currentCRD, crd); err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error validating existing CRs against new CRD's schema: %s", step.Resource.Name)
				}

				// check to see if stored versions changed and whether the upgrade could cause potential data loss
				safe, err := crdlib.SafeStorageVersionUpgrade(currentCRD, crd)
				if !safe {
					b.logger.Errorf("risk of data loss updating %s: %s", step.Resource.Name, err)
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "risk of data loss updating %s", step.Resource.Name)
				}
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "checking CRD for potential data loss updating %s", step.Resource.Name)
				}

				// Update CRD to new version
				_, err = client.CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
				if err != nil {
					return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
				}
				// If it already existed, mark the step as Present.
				// they were equal - mark CRD as present
				return v1alpha1.StepStatusPresent, nil
			} else if createError != nil {
				// Unexpected error creating the CRD.
				return v1alpha1.StepStatusUnknown, createError
			}
			// If no error occured, make sure to wait for the API to become available.
			return v1alpha1.StepStatusWaitingForAPI, nil
		}
		return v1alpha1.StepStatusUnknown, nil
	}
}
<|endoftext|>


<commit_before>package noderemove

import (
	"context"

	"k8s.io/apimachinery/pkg/runtime"

	nodehelper "github.com/rancher/rancher/pkg/node"
	v1 "github.com/rancher/types/apis/core/v1"
	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
)

type nodeRemove struct {
	userNodes v1.NodeInterface
}

func Register(ctx context.Context, userContext *config.UserContext) {
	nsh := &nodeRemove{
		userNodes: userContext.Core.Nodes(""),
	}
	userContext.Management.Management.Nodes(userContext.ClusterName).AddClusterScopedLifecycle(ctx, "user-node-remove", userContext.ClusterName, nsh)
}

func (n *nodeRemove) Create(obj *v3.Node) (runtime.Object, error) {
	return obj, nil
}

func (n *nodeRemove) Remove(obj *v3.Node) (runtime.Object, error) {
	if nodehelper.IgnoreNode(obj.Status.NodeName, obj.Labels) {
		logrus.Debugf("Skipping v1.node removal for [%v] node", obj.Status.NodeName)
		return obj, nil
	}

	if obj.Status.NodeName != "" {
		n.userNodes.Delete(obj.Status.NodeName, nil)
	}
	return obj, nil
}

func (n *nodeRemove) Updated(obj *v3.Node) (runtime.Object, error) {
	return obj, nil
}
<commit_msg>Respect ignore label on node removal<commit_after>package noderemove

import (
	"context"

	"k8s.io/apimachinery/pkg/runtime"

	nodehelper "github.com/rancher/rancher/pkg/node"
	v1 "github.com/rancher/types/apis/core/v1"
	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/config"
	"github.com/sirupsen/logrus"
)

type nodeRemove struct {
	userNodes v1.NodeInterface
}

func Register(ctx context.Context, userContext *config.UserContext) {
	nsh := &nodeRemove{
		userNodes: userContext.Core.Nodes(""),
	}
	userContext.Management.Management.Nodes(userContext.ClusterName).AddClusterScopedLifecycle(ctx, "user-node-remove", userContext.ClusterName, nsh)
}

func (n *nodeRemove) Create(obj *v3.Node) (runtime.Object, error) {
	return obj, nil
}

func (n *nodeRemove) Remove(obj *v3.Node) (runtime.Object, error) {
	if nodehelper.IgnoreNode(obj.Status.NodeName, obj.Status.NodeLabels) {
		logrus.Debugf("Skipping v1.node removal for [%v] node", obj.Status.NodeName)
		return obj, nil
	}

	if obj.Status.NodeName != "" {
		n.userNodes.Delete(obj.Status.NodeName, nil)
	}
	return obj, nil
}

func (n *nodeRemove) Updated(obj *v3.Node) (runtime.Object, error) {
	return obj, nil
}
<|endoftext|>


<commit_before>// Copyright Istio 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 pkg

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"

	"istio.io/release-builder/pkg/model"
	"istio.io/release-builder/pkg/util"

	"istio.io/pkg/log"
)

// Sources will copy all dependencies require, pulling from Github if required, and set up the working tree.
// This includes locally tagging all git repos with the version being built, so that the right version is present in binaries.
func Sources(manifest model.Manifest) error {
	// Clone istio first, as it is needed to determine which other dependencies to use
	if err := cloneRepo(manifest, "istio", manifest.Dependencies.Istio); err != nil {
		return err
	}

	for repo, dependency := range manifest.Dependencies.Get() {
		if repo == "istio" {
			continue
		}
		if dependency == nil {
			log.Warnf("skipping clone of missing dependency: %v", repo)
			continue
		}
		if err := cloneRepo(manifest, repo, dependency); err != nil {
			return err
		}
	}
	return nil
}

func cloneRepo(manifest model.Manifest, repo string, dependency *model.Dependency) error {
	src := path.Join(manifest.SourceDir(), repo)
	// Fetch the dependency
	if err := util.Clone(repo, *dependency, src); err != nil {
		return fmt.Errorf("failed to resolve %+v: %v", dependency, err)
	}
	log.Infof("Resolved %v", repo)
	// Also copy it to the working directory
	if err := util.CopyDir(src, manifest.RepoDir(repo)); err != nil {
		return fmt.Errorf("failed to copy dependency %v to working directory: %v", repo, err)
	}
	// Tag the repo. This allows the build process to look at the git tag for version information
	if err := TagRepo(manifest, manifest.RepoDir(repo)); err != nil {
		return fmt.Errorf("failed to tag repo %v: %v", repo, err)
	}
	return nil
}

// The release expects a working directory with:
// * sources/ contains all of the sources to build from. These should not be modified
// * work/ initially contains all the sources, but may be modified during the build
// * out/ contains all final artifacts
func SetupWorkDir(dir string) error {
	if err := os.Mkdir(path.Join(dir, "sources"), 0750); err != nil {
		return fmt.Errorf("failed to set up working directory: %v", err)
	}
	if err := os.Mkdir(path.Join(dir, "work"), 0750); err != nil {
		return fmt.Errorf("failed to set up working directory: %v", err)
	}
	if err := os.Mkdir(path.Join(dir, "out"), 0750); err != nil {
		return fmt.Errorf("failed to set up working directory: %v", err)
	}
	return nil
}

// TagRepo tags a given git repo with the version from the manifest.
func TagRepo(manifest model.Manifest, repo string) error {
	headSha, err := GetSha(repo, "HEAD")
	if err != nil {
		return fmt.Errorf("failed to get HEAD SHA: %v", err)
	}
	currentTagSha, _ := GetSha(repo, manifest.Version)
	if currentTagSha != "" {
		if currentTagSha == headSha {
			log.Infof("Tag %v already exists, but points to the right place.", manifest.Version)
			return nil
		}
		return fmt.Errorf("tag %v already exists, retagging would move from %v to %v", manifest.Version, currentTagSha, headSha)
	}
	cmd := util.VerboseCommand("git", "tag", manifest.Version)
	cmd.Dir = repo
	return cmd.Run()
}

// GetSha returns the SHA for a given reference, or error if sha is not found
func GetSha(repo string, ref string) (string, error) {
	buf := bytes.Buffer{}
	cmd := exec.Command("git", "rev-parse", ref)
	cmd.Stdout = &buf
	cmd.Dir = repo
	if err := cmd.Run(); err != nil {
		return "", err
	}
	return buf.String(), nil
}

// StandardizeManifest will convert a manifest to a fixed SHA, rather than a branch
// This allows outputting the exact version used after the build is complete
func StandardizeManifest(manifest *model.Manifest) error {
	for repo, dep := range manifest.Dependencies.Get() {
		if dep == nil {
			continue
		}
		sha, err := GetSha(manifest.RepoDir(repo), "HEAD")
		if err != nil {
			return fmt.Errorf("failed to get SHA for %v: %v", repo, err)
		}
		newDep := model.Dependency{
			Sha:              strings.TrimSpace(sha),
			GoVersionEnabled: dep.GoVersionEnabled,
		}
		manifest.Dependencies.Set(repo, newDep)
	}
	return nil
}
<commit_msg>Fix the logic to identify SHA from a git tag (#193)<commit_after>// Copyright Istio 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 pkg

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"

	"istio.io/release-builder/pkg/model"
	"istio.io/release-builder/pkg/util"

	"istio.io/pkg/log"
)

// Sources will copy all dependencies require, pulling from Github if required, and set up the working tree.
// This includes locally tagging all git repos with the version being built, so that the right version is present in binaries.
func Sources(manifest model.Manifest) error {
	// Clone istio first, as it is needed to determine which other dependencies to use
	if err := cloneRepo(manifest, "istio", manifest.Dependencies.Istio); err != nil {
		return err
	}

	for repo, dependency := range manifest.Dependencies.Get() {
		if repo == "istio" {
			continue
		}
		if dependency == nil {
			log.Warnf("skipping clone of missing dependency: %v", repo)
			continue
		}
		if err := cloneRepo(manifest, repo, dependency); err != nil {
			return err
		}
	}
	return nil
}

func cloneRepo(manifest model.Manifest, repo string, dependency *model.Dependency) error {
	src := path.Join(manifest.SourceDir(), repo)
	// Fetch the dependency
	if err := util.Clone(repo, *dependency, src); err != nil {
		return fmt.Errorf("failed to resolve %+v: %v", dependency, err)
	}
	log.Infof("Resolved %v", repo)
	// Also copy it to the working directory
	if err := util.CopyDir(src, manifest.RepoDir(repo)); err != nil {
		return fmt.Errorf("failed to copy dependency %v to working directory: %v", repo, err)
	}
	// Tag the repo. This allows the build process to look at the git tag for version information
	if err := TagRepo(manifest, manifest.RepoDir(repo)); err != nil {
		return fmt.Errorf("failed to tag repo %v: %v", repo, err)
	}
	return nil
}

// The release expects a working directory with:
// * sources/ contains all of the sources to build from. These should not be modified
// * work/ initially contains all the sources, but may be modified during the build
// * out/ contains all final artifacts
func SetupWorkDir(dir string) error {
	if err := os.Mkdir(path.Join(dir, "sources"), 0750); err != nil {
		return fmt.Errorf("failed to set up working directory: %v", err)
	}
	if err := os.Mkdir(path.Join(dir, "work"), 0750); err != nil {
		return fmt.Errorf("failed to set up working directory: %v", err)
	}
	if err := os.Mkdir(path.Join(dir, "out"), 0750); err != nil {
		return fmt.Errorf("failed to set up working directory: %v", err)
	}
	return nil
}

// TagRepo tags a given git repo with the version from the manifest.
func TagRepo(manifest model.Manifest, repo string) error {
	headSha, err := GetSha(repo, "HEAD")
	if err != nil {
		return fmt.Errorf("failed to get HEAD SHA: %v", err)
	}
	currentTagSha, _ := GetSha(repo, manifest.Version)
	if currentTagSha != "" {
		if currentTagSha == headSha {
			log.Infof("Tag %v already exists, but points to the right place.", manifest.Version)
			return nil
		}
		return fmt.Errorf("tag %v already exists, retagging would move from %v to %v", manifest.Version, currentTagSha, headSha)
	}
	cmd := util.VerboseCommand("git", "tag", manifest.Version)
	cmd.Dir = repo
	return cmd.Run()
}

// GetSha returns the SHA for a given reference, or error if sha is not found
func GetSha(repo string, ref string) (string, error) {
	buf := bytes.Buffer{}
	cmd := exec.Command("git", "rev-list", "-n", "1", ref)
	cmd.Stdout = &buf
	cmd.Dir = repo
	if err := cmd.Run(); err != nil {
		return "", err
	}
	return buf.String(), nil
}

// StandardizeManifest will convert a manifest to a fixed SHA, rather than a branch
// This allows outputting the exact version used after the build is complete
func StandardizeManifest(manifest *model.Manifest) error {
	for repo, dep := range manifest.Dependencies.Get() {
		if dep == nil {
			continue
		}
		sha, err := GetSha(manifest.RepoDir(repo), "HEAD")
		if err != nil {
			return fmt.Errorf("failed to get SHA for %v: %v", repo, err)
		}
		newDep := model.Dependency{
			Sha:              strings.TrimSpace(sha),
			GoVersionEnabled: dep.GoVersionEnabled,
		}
		manifest.Dependencies.Set(repo, newDep)
	}
	return nil
}
<|endoftext|>


<commit_before>package gameboy

import (
	"fmt"
	"io"
	"sdl"
	"os"
	"os/signal"
)

var saveDir = "." 		// TODO configure

func Start(quit chan int, path string) (err interface{}) {
	var rom romImage
	rom, err = loadROM(path)
	if err != nil { return }

	fmt.Printf("Loaded ROM image '%s'\n", rom.title())
	fmt.Printf("Logo match: %t\n", rom.checkLogo())
	fmt.Printf("Header checksum: %t\n", rom.doHeaderChecksum())
	fmt.Printf("Global checksum: %t\n", rom.doGlobalChecksum())

	if mbc, e := rom.mbcType(); e == nil {
		fmt.Printf("MBC: %d\n", mbc)
	}

	if sdl.Init(sdl.INIT_VIDEO) != 0 {
		err = sdl.GetError()
		return
	}

	var mem *memory
	mem, err = newMemory(rom)
	if err != nil { return }

	if e := mem.load(saveDir); e != nil {
		fmt.Fprintf(os.Stderr, "load failed: %v\n", e)
	}

	sys := newCPU(mem)
	lcd := newDisplay(mem)

	go func() {
		run(sys, lcd)
		if e := mem.save(saveDir); e != nil {
			fmt.Fprintf(os.Stderr, "save failed: %v\n", e)
		}
		quit <- 1
	}()
	return
}

func run(sys *cpu, lcd *display) {
	defer sdl.Quit()
	defer func() {
		if e := recover(); e != nil {
			fmt.Fprintf(os.Stderr, "panic: %v\n\n", e)
			sys.dump(os.Stderr)
			fmt.Fprint(os.Stderr, "RUNTIME TRACE\n\n")
			panic(e)
		}
	}()

	t := 0
	for {
		if sig, ok := <-signal.Incoming; ok {
			fmt.Printf("\nReceived %v, cleaning up\n", sig)
			break
		}
		var s int
		for s = 0; s < 10; s += sys.step() {}
		lcd.step(s)
		t += s
	}
	fmt.Printf("total ticks: %d\n", t)
	fmt.Printf("%v\n", sys)
}

func (sys *cpu) dump(w io.Writer) {
	fmt.Fprintf(w,
		"LAST INSTRUCTION\n" +
		"%04X\t%s\n\n" +
		"CPU STATE\n" +
		"%v\n\n",
		sys.mar, sys.disasm(sys.mar), sys)
	sys.dumpStack(w)
	sys.memory.dump(w)
}
<commit_msg>Put save files in ~/.gogb/sav<commit_after>package gameboy

import (
	"fmt"
	"io"
	"path"
	"sdl"
	"os"
	"os/signal"
)

var dotCmdName = ".gogb"
var saveDir = "."

func init() {
	home := os.Getenv("HOME")
	if len(home) > 0 {
		save := path.Join(home, dotCmdName, "sav")
		if err := os.MkdirAll(save, 0755); err == nil {
			saveDir = save
		}
	}
}

func Start(quit chan int, path string) (err interface{}) {
	var rom romImage
	rom, err = loadROM(path)
	if err != nil { return }

	fmt.Printf("Loaded ROM image '%s'\n", rom.title())
	fmt.Printf("Logo match: %t\n", rom.checkLogo())
	fmt.Printf("Header checksum: %t\n", rom.doHeaderChecksum())
	fmt.Printf("Global checksum: %t\n", rom.doGlobalChecksum())

	if mbc, e := rom.mbcType(); e == nil {
		fmt.Printf("MBC: %d\n", mbc)
	}

	if sdl.Init(sdl.INIT_VIDEO) != 0 {
		err = sdl.GetError()
		return
	}

	var mem *memory
	mem, err = newMemory(rom)
	if err != nil { return }

	if e := mem.load(saveDir); e != nil {
		fmt.Fprintf(os.Stderr, "load failed: %v\n", e)
	}

	sys := newCPU(mem)
	lcd := newDisplay(mem)

	go func() {
		run(sys, lcd)
		if e := mem.save(saveDir); e != nil {
			fmt.Fprintf(os.Stderr, "save failed: %v\n", e)
		}
		quit <- 1
	}()
	return
}

func run(sys *cpu, lcd *display) {
	defer sdl.Quit()
	defer func() {
		if e := recover(); e != nil {
			fmt.Fprintf(os.Stderr, "panic: %v\n\n", e)
			sys.dump(os.Stderr)
			fmt.Fprint(os.Stderr, "RUNTIME TRACE\n\n")
			panic(e)
		}
	}()

	t := 0
	for {
		if sig, ok := <-signal.Incoming; ok {
			fmt.Printf("\nReceived %v, cleaning up\n", sig)
			break
		}
		var s int
		for s = 0; s < 10; s += sys.step() {}
		lcd.step(s)
		t += s
	}
	fmt.Printf("total ticks: %d\n", t)
	fmt.Printf("%v\n", sys)
}

func (sys *cpu) dump(w io.Writer) {
	fmt.Fprintf(w,
		"LAST INSTRUCTION\n" +
		"%04X\t%s\n\n" +
		"CPU STATE\n" +
		"%v\n\n",
		sys.mar, sys.disasm(sys.mar), sys)
	sys.dumpStack(w)
	sys.memory.dump(w)
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package player

import (
	"encoding/json"
	"fmt"
)

// RepFn is a type which represents a command function which will be called
// with a standardised command structure when Player methods are used.
type RepFn func(interface{})

// RepAction is a representation of a player action as it would be transmitted.
type RepAction struct {
	Action string      `json:"action"`
	Value  interface{} `json:",omitempty"`
}

// Apply applies the action in RepAction to the Player.
func (r RepAction) Apply(p Player) (err error) {
	a := Action(r.Action)
	switch a {
	case ActionPlay, ActionPause, ActionNext, ActionPrev, ActionTogglePlayPause, ActionToggleMute, ActionToggleRepeat:
		err = p.Do(a)

	case ActionSetVolume, ActionSetMute, ActionSetTime, ActionSetRepeat:
		if r.Value == nil {
			err = InvalidValueError("value required")
			break
		}

		switch a {
		case ActionSetVolume:
			f, ok := r.Value.(float64)
			if !ok {
				err = InvalidValueError("invalid volume value: expected float")
				break
			}
			err = p.SetVolume(f)

		case ActionSetMute:
			b, ok := r.Value.(bool)
			if !ok {
				err = InvalidValueError("invalid mute value: expected boolean")
				break
			}
			err = p.SetMute(b)

		case ActionSetRepeat:
			b, ok := r.Value.(bool)
			if !ok {
				err = InvalidValueError("invalid repeat value: expected boolean")
				break
			}
			err = p.SetRepeat(b)

		case ActionSetTime:
			f, ok := r.Value.(float64)
			if !ok {
				err = InvalidValueError("invalid time value: expected float")
				break
			}
			err = p.SetTime(f)
		}

	default:
		err = InvalidActionError(a)
	}
	return
}

type rep struct {
	key string
	fn  RepFn
}

// NewRep creates a Player p which will call fn with a standardised representation for each
// call to methods of p (eee RepActions for the Action mapping).
// FIXME: this is possibly a unhelpful and overly generic mess.
func NewRep(key string, fn RepFn) Player {
	return &rep{
		key: key,
		fn:  fn,
	}
}

func (r rep) sendAction(action string) error {
	r.fn(RepAction{
		Action: action,
	})
	return nil
}

func (r rep) sendActionValue(action string, value interface{}) error {
	r.fn(RepAction{
		Action: action,
		Value:  value,
	})
	return nil
}

func (r rep) Key() string { return r.key }

var RepActions = map[Action]string{
	ActionPlay:            "PLAY",
	ActionPause:           "PAUSE",
	ActionNext:            "NEXT",
	ActionPrev:            "PREV",
	ActionTogglePlayPause: "TOGGLE_PLAY_PAUSE",
	ActionToggleRepeat:    "TOGGLE_REPEAT",
	ActionToggleMute:      "TOGGLE_MUTE",

	ActionSetVolume: "SET_VOLUME",
	ActionSetMute:   "SET_MUTE",
	ActionSetRepeat: "SET_REPEAT",
	ActionSetTime:   "SET_TIME",
}

func RepActionToAction(a string) (Action, bool) {
	for k, v := range RepActions {
		if v == a {
			return k, true
		}
	}
	return Action(""), false
}

type InvalidActionError string

func (i InvalidActionError) Error() string {
	return fmt.Sprintf("invalid player action: '%s'", string(i))
}

func (r rep) Do(a Action) error {
	s, ok := RepActions[a]
	if !ok {
		return InvalidActionError(a)
	}
	return r.sendAction(s)
}

func (r rep) SetMute(b bool) error      { return r.sendActionValue("mute", b) }
func (r rep) SetRepeat(b bool) error    { return r.sendActionValue("repeat", b) }
func (r rep) SetVolume(f float64) error { return r.sendActionValue("volume", f) }
func (r rep) SetTime(f float64) error   { return r.sendActionValue("time", f) }

func (r rep) MarshalJSON() ([]byte, error) {
	rep := struct {
		Key string `json:"key"`
	}{
		Key: r.key,
	}
	return json.Marshal(rep)
}
<commit_msg>Added more Player docs.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package player

import (
	"encoding/json"
	"fmt"
)

// RepFn is a type which represents a command function which will be called
// with a standardised command structure when Player methods are used.
type RepFn func(interface{})

// RepAction is a representation of a player action as it would be transmitted.
type RepAction struct {
	Action string      `json:"action"`
	Value  interface{} `json:",omitempty"`
}

// Apply applies the action in RepAction to the Player.
func (r RepAction) Apply(p Player) (err error) {
	a := Action(r.Action)
	switch a {
	case ActionPlay, ActionPause, ActionNext, ActionPrev, ActionTogglePlayPause, ActionToggleMute, ActionToggleRepeat:
		err = p.Do(a)

	case ActionSetVolume, ActionSetMute, ActionSetTime, ActionSetRepeat:
		if r.Value == nil {
			err = InvalidValueError("value required")
			break
		}

		switch a {
		case ActionSetVolume:
			f, ok := r.Value.(float64)
			if !ok {
				err = InvalidValueError("invalid volume value: expected float")
				break
			}
			err = p.SetVolume(f)

		case ActionSetMute:
			b, ok := r.Value.(bool)
			if !ok {
				err = InvalidValueError("invalid mute value: expected boolean")
				break
			}
			err = p.SetMute(b)

		case ActionSetRepeat:
			b, ok := r.Value.(bool)
			if !ok {
				err = InvalidValueError("invalid repeat value: expected boolean")
				break
			}
			err = p.SetRepeat(b)

		case ActionSetTime:
			f, ok := r.Value.(float64)
			if !ok {
				err = InvalidValueError("invalid time value: expected float")
				break
			}
			err = p.SetTime(f)
		}

	default:
		err = InvalidActionError(a)
	}
	return
}

type rep struct {
	key string
	fn  RepFn
}

// NewRep creates a Player p which will call fn with a standardised representation for each
// call to methods of p (eee RepActions for the Action mapping).
// FIXME: this is possibly a unhelpful and overly generic mess.
func NewRep(key string, fn RepFn) Player {
	return &rep{
		key: key,
		fn:  fn,
	}
}

func (r rep) sendAction(action string) error {
	r.fn(RepAction{
		Action: action,
	})
	return nil
}

func (r rep) sendActionValue(action string, value interface{}) error {
	r.fn(RepAction{
		Action: action,
		Value:  value,
	})
	return nil
}

func (r rep) Key() string { return r.key }

var RepActions = map[Action]string{
	ActionPlay:            "PLAY",
	ActionPause:           "PAUSE",
	ActionNext:            "NEXT",
	ActionPrev:            "PREV",
	ActionTogglePlayPause: "TOGGLE_PLAY_PAUSE",
	ActionToggleRepeat:    "TOGGLE_REPEAT",
	ActionToggleMute:      "TOGGLE_MUTE",

	ActionSetVolume: "SET_VOLUME",
	ActionSetMute:   "SET_MUTE",
	ActionSetRepeat: "SET_REPEAT",
	ActionSetTime:   "SET_TIME",
}

// RepActionToAction takes a string and returns an Action and true if the
// string represents a valid action, or Action("") and false otherwise.
// TODO(dhowden): This seems quite clunky??
func RepActionToAction(a string) (Action, bool) {
	for k, v := range RepActions {
		if v == a {
			return k, true
		}
	}
	return Action(""), false
}

// InvalidActionError is an error returned when an invalid action has been
// received.
type InvalidActionError string

func (i InvalidActionError) Error() string {
	return fmt.Sprintf("invalid player action: '%s'", string(i))
}

func (r rep) Do(a Action) error {
	s, ok := RepActions[a]
	if !ok {
		return InvalidActionError(a)
	}
	return r.sendAction(s)
}

func (r rep) SetMute(b bool) error      { return r.sendActionValue("mute", b) }
func (r rep) SetRepeat(b bool) error    { return r.sendActionValue("repeat", b) }
func (r rep) SetVolume(f float64) error { return r.sendActionValue("volume", f) }
func (r rep) SetTime(f float64) error   { return r.sendActionValue("time", f) }

func (r rep) MarshalJSON() ([]byte, error) {
	rep := struct {
		Key string `json:"key"`
	}{
		Key: r.key,
	}
	return json.Marshal(rep)
}
<|endoftext|>