<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package action_test

import (
	"regexp"
	"testing"

	"github.com/juju/cmd"
	jujutesting "github.com/juju/testing"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v4"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/cmd/juju/action"
	coretesting "github.com/juju/juju/testing"
)

const (
	validActionTagString   = "action-f47ac10b-58cc-4372-a567-0e02b2c3d479"
	invalidActionTagString = "action-f47ac10b-58cc-4372-a567-0e02b2c3d47"
	validActionId          = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
	invalidActionId        = "f47ac10b-58cc-4372-a567-0e02b2c3d47"
	validUnitId            = "mysql/0"
	invalidUnitId          = "something-strange-"
	validServiceId         = "mysql"
	invalidServiceId       = "something-strange-"
)

var someCharmActions = &charm.Actions{
	ActionSpecs: map[string]charm.ActionSpec{
		"snapshot": charm.ActionSpec{
			Description: "Take a snapshot of the database.",
			Params: map[string]interface{}{
				"foo": map[string]interface{}{
					"bar": "baz",
				},
				"baz": "bar",
			},
		},
		"kill": charm.ActionSpec{
			Description: "Kill the database.",
			Params: map[string]interface{}{
				"bar": map[string]interface{}{
					"baz": "foo",
				},
				"foo": "baz",
			},
		},
		"no-description": charm.ActionSpec{
			Params: map[string]interface{}{
				"bar": map[string]interface{}{
					"baz": "foo",
				},
				"foo": "baz",
			},
		},
		"no-params": charm.ActionSpec{
			Description: "An action with no parameters.",
		},
	},
}

func TestPackage(t *testing.T) {
	gc.TestingT(t)
}

type BaseActionSuite struct {
	jujutesting.IsolationSuite
	command cmd.Command
}

func (s *BaseActionSuite) SetUpTest(c *gc.C) {
	s.command = action.NewSuperCommand()
}

func (s *BaseActionSuite) checkHelp(c *gc.C, subcmd envcmd.EnvironCommand) {
	ctx, err := coretesting.RunCommand(c, s.command, subcmd.Info().Name, "--help")
	c.Assert(err, gc.IsNil)

	expected := "(?sm).*^usage: juju action " +
		regexp.QuoteMeta(subcmd.Info().Name) +
		` \[options\] ` + regexp.QuoteMeta(subcmd.Info().Args) + ".+"
	c.Check(coretesting.Stdout(ctx), gc.Matches, expected)

	expected = "(?sm).*^purpose: " + regexp.QuoteMeta(subcmd.Info().Purpose) + "$.*"
	c.Check(coretesting.Stdout(ctx), gc.Matches, expected)

	expected = "(?sm).*^" + regexp.QuoteMeta(subcmd.Info().Doc) + "$.*"
	c.Check(coretesting.Stdout(ctx), gc.Matches, expected)
}

type fakeAPIClient struct {
	actionResults      []params.ActionResult
	actionsByReceivers []params.ActionsByReceiver
	actionTagMatches   map[string][]params.Entity
	charmActions       *charm.Actions
	apiErr             error
}

func (c *fakeAPIClient) Close() error {
	return nil
}

func (c *fakeAPIClient) Enqueue(params.Actions) (params.ActionResults, error) {
	return params.ActionResults{
		Results: c.actionResults,
	}, c.apiErr
}

func (c *fakeAPIClient) ListAll(args params.Entities) (params.ActionsByReceivers, error) {
	return params.ActionsByReceivers{
		Actions: c.actionsByReceivers,
	}, c.apiErr
}

func (c *fakeAPIClient) ListPending(args params.Entities) (params.ActionsByReceivers, error) {
	return params.ActionsByReceivers{
		Actions: c.actionsByReceivers,
	}, c.apiErr
}

func (c *fakeAPIClient) ListCompleted(args params.Entities) (params.ActionsByReceivers, error) {
	return params.ActionsByReceivers{
		Actions: c.actionsByReceivers,
	}, c.apiErr
}

func (c *fakeAPIClient) Cancel(args params.Actions) (params.ActionResults, error) {
	return params.ActionResults{
		Results: c.actionResults,
	}, c.apiErr
}

func (c *fakeAPIClient) ServiceCharmActions(params.Entity) (*charm.Actions, error) {
	return c.charmActions, c.apiErr
}

func (c *fakeAPIClient) Actions(args params.Entities) (params.ActionResults, error) {
	return params.ActionResults{
		Results: c.actionResults,
	}, c.apiErr
}

func (c *fakeAPIClient) FindActionTagsByPrefix(arg params.FindTags) (params.FindTagsResults, error) {
	return params.FindTagsResults{
		Matches: c.actionTagMatches,
	}, c.apiErr
}
<commit_msg>add patchAPIClient now that we need it<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package action_test

import (
	"regexp"
	"testing"

	"github.com/juju/cmd"
	jujutesting "github.com/juju/testing"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v4"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/cmd/juju/action"
	coretesting "github.com/juju/juju/testing"
)

const (
	validActionTagString   = "action-f47ac10b-58cc-4372-a567-0e02b2c3d479"
	invalidActionTagString = "action-f47ac10b-58cc-4372-a567-0e02b2c3d47"
	validActionId          = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
	invalidActionId        = "f47ac10b-58cc-4372-a567-0e02b2c3d47"
	validUnitId            = "mysql/0"
	invalidUnitId          = "something-strange-"
	validServiceId         = "mysql"
	invalidServiceId       = "something-strange-"
)

var someCharmActions = &charm.Actions{
	ActionSpecs: map[string]charm.ActionSpec{
		"snapshot": charm.ActionSpec{
			Description: "Take a snapshot of the database.",
			Params: map[string]interface{}{
				"foo": map[string]interface{}{
					"bar": "baz",
				},
				"baz": "bar",
			},
		},
		"kill": charm.ActionSpec{
			Description: "Kill the database.",
			Params: map[string]interface{}{
				"bar": map[string]interface{}{
					"baz": "foo",
				},
				"foo": "baz",
			},
		},
		"no-description": charm.ActionSpec{
			Params: map[string]interface{}{
				"bar": map[string]interface{}{
					"baz": "foo",
				},
				"foo": "baz",
			},
		},
		"no-params": charm.ActionSpec{
			Description: "An action with no parameters.",
		},
	},
}

func TestPackage(t *testing.T) {
	gc.TestingT(t)
}

type BaseActionSuite struct {
	jujutesting.IsolationSuite
	command cmd.Command
}

func (s *BaseActionSuite) SetUpTest(c *gc.C) {
	s.command = action.NewSuperCommand()
}

func (s *BaseActionSuite) patchAPIClient(client *fakeAPIClient) func() {
	return jujutesting.PatchValue(action.NewActionAPIClient,
		func(c *action.ActionCommandBase) (action.APIClient, error) {
			return client, nil
		},
	)
}

func (s *BaseActionSuite) checkHelp(c *gc.C, subcmd envcmd.EnvironCommand) {
	ctx, err := coretesting.RunCommand(c, s.command, subcmd.Info().Name, "--help")
	c.Assert(err, gc.IsNil)

	expected := "(?sm).*^usage: juju action " +
		regexp.QuoteMeta(subcmd.Info().Name) +
		` \[options\] ` + regexp.QuoteMeta(subcmd.Info().Args) + ".+"
	c.Check(coretesting.Stdout(ctx), gc.Matches, expected)

	expected = "(?sm).*^purpose: " + regexp.QuoteMeta(subcmd.Info().Purpose) + "$.*"
	c.Check(coretesting.Stdout(ctx), gc.Matches, expected)

	expected = "(?sm).*^" + regexp.QuoteMeta(subcmd.Info().Doc) + "$.*"
	c.Check(coretesting.Stdout(ctx), gc.Matches, expected)
}

type fakeAPIClient struct {
	actionResults      []params.ActionResult
	actionsByReceivers []params.ActionsByReceiver
	actionTagMatches   map[string][]params.Entity
	charmActions       *charm.Actions
	apiErr             error
}

func (c *fakeAPIClient) Close() error {
	return nil
}

func (c *fakeAPIClient) Enqueue(params.Actions) (params.ActionResults, error) {
	return params.ActionResults{
		Results: c.actionResults,
	}, c.apiErr
}

func (c *fakeAPIClient) ListAll(args params.Entities) (params.ActionsByReceivers, error) {
	return params.ActionsByReceivers{
		Actions: c.actionsByReceivers,
	}, c.apiErr
}

func (c *fakeAPIClient) ListPending(args params.Entities) (params.ActionsByReceivers, error) {
	return params.ActionsByReceivers{
		Actions: c.actionsByReceivers,
	}, c.apiErr
}

func (c *fakeAPIClient) ListCompleted(args params.Entities) (params.ActionsByReceivers, error) {
	return params.ActionsByReceivers{
		Actions: c.actionsByReceivers,
	}, c.apiErr
}

func (c *fakeAPIClient) Cancel(args params.Actions) (params.ActionResults, error) {
	return params.ActionResults{
		Results: c.actionResults,
	}, c.apiErr
}

func (c *fakeAPIClient) ServiceCharmActions(params.Entity) (*charm.Actions, error) {
	return c.charmActions, c.apiErr
}

func (c *fakeAPIClient) Actions(args params.Entities) (params.ActionResults, error) {
	return params.ActionResults{
		Results: c.actionResults,
	}, c.apiErr
}

func (c *fakeAPIClient) FindActionTagsByPrefix(arg params.FindTags) (params.FindTagsResults, error) {
	return params.FindTagsResults{
		Matches: c.actionTagMatches,
	}, c.apiErr
}
<|endoftext|>


<commit_before>package statsd

import (
	"bytes"
	"fmt"
	"sync"
	"time"
)

// BufferedSender provides a buffered statsd udp, sending multiple
// metrics, where possible.
type BufferedSender struct {
	flushBytes    int
	flushInterval time.Duration
	sender        Sender
	buffer        *bytes.Buffer
	reqs          chan *bytes.Buffer
	shutdown      chan chan error
	running       bool
	mx            sync.RWMutex
	pool          *bufferPool
}

// Send bytes.
func (s *BufferedSender) Send(data []byte) (int, error) {
	s.mx.RLock()
	defer s.mx.RUnlock()
	if !s.running {
		return 0, fmt.Errorf("BufferedSender is not running")
	}

	// copy bytes, because the caller may mutate the slice (and the underlying
	// array) after we return, since we may not end up sending right away.
	b := s.pool.Get()
	dlen, err := b.Write(data)
	if err != nil {
		return dlen, err
	}

	s.reqs <- b
	return dlen, nil
}

// Close Buffered Sender
func (s *BufferedSender) Close() error {
	// since we are running, write lock during cleanup
	s.mx.Lock()
	defer s.mx.Unlock()
	if !s.running {
		return nil
	}

	errChan := make(chan error)
	s.running = false
	s.shutdown <- errChan
	return <-errChan
}

// Start Buffered Sender
// Begins ticker and read loop
func (s *BufferedSender) Start() {
	// write lock to start running
	s.mx.Lock()
	defer s.mx.Unlock()
	if s.running {
		return
	}

	s.running = true
	s.reqs = make(chan *bytes.Buffer, 8)
	go s.run()
}

func (s *BufferedSender) run() {
	ticker := time.NewTicker(s.flushInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if s.buffer.Len() > 0 {
				s.flush()
			}
		case req := <-s.reqs:
			// StatsD supports receiving multiple metrics in a single packet by
			// separating them with a newline.
			if s.buffer.Len()+req.Len()+1 > s.flushBytes {
				s.flush()
			}
			req.WriteTo(s.buffer)
			s.pool.Put(req)
			s.buffer.WriteByte('\n')

			// if we happen to fill up the buffer, just flush right away
			// instead of waiting for next input.
			if s.buffer.Len() >= s.flushBytes {
				s.flush()
			}
		case errChan := <-s.shutdown:
			close(s.reqs)
			for req := range s.reqs {
				if s.buffer.Len()+req.Len()+1 > s.flushBytes {
					s.flush()
				}
				req.WriteTo(s.buffer)
				s.pool.Put(req)
				s.buffer.WriteByte('\n')
			}

			if s.buffer.Len() > 0 {
				s.flush()
			}
			errChan <- s.sender.Close()
			return
		}
	}

}

// flush the buffer/send to remove endpoint.
func (s *BufferedSender) flush() (int, error) {
	n, err := s.sender.Send(bytes.TrimSuffix(s.buffer.Bytes(), []byte("\n")))
	s.buffer.Reset() // clear the buffer
	return n, err
}

// Returns a new BufferedSender
//
// addr is a string of the format "hostname:port", and must be parsable by
// net.ResolveUDPAddr.
//
// flushInterval is a time.Duration, and specifies the maximum interval for
// packet sending. Note that if you send lots of metrics, you will send more
// often. This is just a maximal threshold.
//
// flushBytes specifies the maximum udp packet size you wish to send. If adding
// a metric would result in a larger packet than flushBytes, the packet will
// first be send, then the new data will be added to the next packet.
func NewBufferedSender(addr string, flushInterval time.Duration, flushBytes int) (Sender, error) {
	simpleSender, err := NewSimpleSender(addr)
	if err != nil {
		return nil, err
	}

	sender := &BufferedSender{
		flushBytes:    flushBytes,
		flushInterval: flushInterval,
		sender:        simpleSender,
		buffer:        bytes.NewBuffer(make([]byte, 0, flushBytes)),
		shutdown:      make(chan chan error),
		pool:          newBufferPool(),
	}

	sender.Start()
	return sender, nil
}
<commit_msg>refactor for nice speed improvement with buffered clients (almost 2x)<commit_after>package statsd

import (
	"bytes"
	"fmt"
	"sync"
	"time"
)

// BufferedSender provides a buffered statsd udp, sending multiple
// metrics, where possible.
type BufferedSender struct {
	flushBytes    int
	flushInterval time.Duration
	sender        Sender
	// lifecycle
	shutdown chan chan error
	running  bool
	runmx    sync.RWMutex
	// buffers
	bufmx  sync.Mutex
	buffer *bytes.Buffer
	bufs   chan *bytes.Buffer
	pool   *bufferPool
}

// Send bytes.
func (s *BufferedSender) Send(data []byte) (int, error) {
	s.runmx.RLock()
	defer s.runmx.RUnlock()
	if !s.running {
		return 0, fmt.Errorf("BufferedSender is not running")
	}

	s.withBufferLock(func() {
		blen := s.buffer.Len()
		if blen > 0 && blen+len(data)+1 >= s.flushBytes {
			s.swapnqueue()
		}

		s.buffer.Write(data)
		s.buffer.WriteByte('\n')

		if s.buffer.Len() >= s.flushBytes {
			s.swapnqueue()
		}
	})
	return len(data), nil
}

// Close Buffered Sender
func (s *BufferedSender) Close() error {
	// since we are running, write lock during cleanup
	s.runmx.Lock()
	defer s.runmx.Unlock()
	if !s.running {
		return nil
	}

	errChan := make(chan error)
	s.running = false
	s.shutdown <- errChan
	return <-errChan
}

// Start Buffered Sender
// Begins ticker and read loop
func (s *BufferedSender) Start() {
	// write lock to start running
	s.runmx.Lock()
	defer s.runmx.Unlock()
	if s.running {
		return
	}

	s.running = true
	s.bufs = make(chan *bytes.Buffer, 32)
	go s.run()
}

func (s *BufferedSender) withBufferLock(fn func()) {
	s.bufmx.Lock()
	defer s.bufmx.Unlock()
	fn()
}

func (s *BufferedSender) swapnqueue() {
	if s.buffer.Len() == 0 {
		return
	}
	ob := s.buffer
	s.buffer = s.pool.Get()
	s.bufs <- ob
}

func (s *BufferedSender) run() {
	ticker := time.NewTicker(s.flushInterval)
	defer ticker.Stop()

	doneChan := make(chan bool)
	go func() {
		for buf := range s.bufs {
			s.flush(buf)
			s.pool.Put(buf)
		}
		doneChan <- true
	}()

	for {
		select {
		case <-ticker.C:
			s.withBufferLock(func() {
				s.swapnqueue()
			})
		case errChan := <-s.shutdown:
			s.withBufferLock(func() {
				s.swapnqueue()
			})
			close(s.bufs)
			<-doneChan
			errChan <- s.sender.Close()
			return
		}
	}
}

// send to remove endpoint and truncate buffer
func (s *BufferedSender) flush(b *bytes.Buffer) (int, error) {
	n, err := s.sender.Send(bytes.TrimSuffix(b.Bytes(), []byte("\n")))
	b.Truncate(0) // clear the buffer
	return n, err
}

// Returns a new BufferedSender
//
// addr is a string of the format "hostname:port", and must be parsable by
// net.ResolveUDPAddr.
//
// flushInterval is a time.Duration, and specifies the maximum interval for
// packet sending. Note that if you send lots of metrics, you will send more
// often. This is just a maximal threshold.
//
// flushBytes specifies the maximum udp packet size you wish to send. If adding
// a metric would result in a larger packet than flushBytes, the packet will
// first be send, then the new data will be added to the next packet.
func NewBufferedSender(addr string, flushInterval time.Duration, flushBytes int) (Sender, error) {
	simpleSender, err := NewSimpleSender(addr)
	if err != nil {
		return nil, err
	}

	sender := &BufferedSender{
		flushBytes:    flushBytes,
		flushInterval: flushInterval,
		sender:        simpleSender,
		buffer:        bytes.NewBuffer(make([]byte, 0, flushBytes)),
		shutdown:      make(chan chan error),
		pool:          newBufferPool(),
	}

	sender.Start()
	return sender, nil
}
<|endoftext|>


<commit_before>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp = regexp.MustCompile("oItem =\\s(.+?});")
	apiCallURL = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch       = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive = errors.New("unable to accept a non-active trade")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint16 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            uint64      `json:"time_created"`
	Updated            uint64      `json:"time_updated"`
	Expires            uint64      `json:"expiration_time"`
	EscrowEndDate      uint64      `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (community *Community) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffer/v1/?key=%s&tradeofferid=%d", apiCallURL, community.apiKey, id))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (community *Community) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	values := "key=" + community.apiKey
	if testBit(filter, TradeFilterSentOffers) {
		values += "&get_sent_offers=1"
	}

	if testBit(filter, TradeFilterRecvOffers) {
		values += "&get_received_offers=1"
	}

	if testBit(filter, TradeFilterActiveOnly) {
		values += "&active_only=1"
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		values += "&historical_only=1&time_historical_cutoff=" + strconv.FormatInt(timeCutOff.Unix(), 10)
	}

	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffers/v1/?%s", apiCallURL, values))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (community *Community) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	body := url.Values{
		"sessionid":                 {community.sessionID},
		"serverid":                  {"1"},
		"partner":                   {sid.ToString()},
		"tradeoffermessage":         {offer.Message},
		"json_tradeoffer":           {string(contentJSON)},
		"trade_offer_create_params": {string(params)},
	}

	req, err := http.NewRequest(http.MethodPost, "https://steamcommunity.com/tradeoffer/new/send", strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", fmt.Sprintf("https://steamcommunity.com/tradeoffer/new/?partner=%d&token=%s", sid.GetAccountID(), token))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (community *Community) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := community.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (community *Community) DeclineTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (community *Community) CancelTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (community *Community) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	body := url.Values{
		"sessionid":    {community.sessionID},
		"serverid":     {"1"},
		"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
	}

	url := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", url)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Accept(community *Community) error {
	return community.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(community *Community) error {
	if offer.IsOurOffer {
		return community.CancelTradeOffer(offer.ID)
	}

	return community.DeclineTradeOffer(offer.ID)
}
<commit_msg>tradeoffer: add GetEscrowDuration<commit_after>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp    = regexp.MustCompile("oItem =\\s(.+?});")
	myEscrowExp   = regexp.MustCompile("var g_daysMyEscrow = (\\d+);")
	themEscrowExp = regexp.MustCompile("var g_daysTheirEscrow = (\\d+);")
	apiCallURL    = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch       = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive = errors.New("unable to accept a non-active trade")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint16 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            uint64      `json:"time_created"`
	Updated            uint64      `json:"time_updated"`
	Expires            uint64      `json:"expiration_time"`
	EscrowEndDate      uint64      `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (community *Community) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffer/v1/?key=%s&tradeofferid=%d", apiCallURL, community.apiKey, id))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (community *Community) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	values := "key=" + community.apiKey
	if testBit(filter, TradeFilterSentOffers) {
		values += "&get_sent_offers=1"
	}

	if testBit(filter, TradeFilterRecvOffers) {
		values += "&get_received_offers=1"
	}

	if testBit(filter, TradeFilterActiveOnly) {
		values += "&active_only=1"
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		values += "&historical_only=1&time_historical_cutoff=" + strconv.FormatInt(timeCutOff.Unix(), 10)
	}

	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffers/v1/?%s", apiCallURL, values))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (community *Community) GetEscrowDuration(sid SteamID, token string) (int64, int64, error) {
	req, err := http.NewRequest(
		http.MethodGet,
		fmt.Sprintf("https://steamcommunity.com/tradeoffer/new/?partner=%d&token=%s", sid.GetAccountID(), token),
		nil,
	)
	if err != nil {
		return 0, 0, err
	}

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return 0, 0, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, 0, err
	}

	my := int64(0)
	m := myEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		my, _ = strconv.ParseInt(m[1], 10, 32)
	}

	them := int64(0)
	m = themEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		them, _ = strconv.ParseInt(m[1], 10, 32)
	}

	return my, them, nil
}

func (community *Community) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	body := url.Values{
		"sessionid":                 {community.sessionID},
		"serverid":                  {"1"},
		"partner":                   {sid.ToString()},
		"tradeoffermessage":         {offer.Message},
		"json_tradeoffer":           {string(contentJSON)},
		"trade_offer_create_params": {string(params)},
	}

	req, err := http.NewRequest(http.MethodPost, "https://steamcommunity.com/tradeoffer/new/send", strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", fmt.Sprintf("https://steamcommunity.com/tradeoffer/new/?partner=%d&token=%s", sid.GetAccountID(), token))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (community *Community) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := community.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (community *Community) DeclineTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (community *Community) CancelTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (community *Community) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	body := url.Values{
		"sessionid":    {community.sessionID},
		"serverid":     {"1"},
		"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
	}

	url := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", url)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Accept(community *Community) error {
	return community.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(community *Community) error {
	if offer.IsOurOffer {
		return community.CancelTradeOffer(offer.ID)
	}

	return community.DeclineTradeOffer(offer.ID)
}
<|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 main

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

	"github.com/golang/glog"

	"k8s.io/publishing-bot/cmd/publishing-bot/config"
)

// PublisherMunger publishes content from one repository to another one.
type PublisherMunger struct {
	reposRules config.RepositoryRules
	config     *config.Config
	// plog duplicates the logs at glog and a file
	plog *plog
	// absolute path to the repos.
	baseRepoPath string
}

// New will create a new munger.
func New(cfg *config.Config, baseRepoPath string) *PublisherMunger {
	// create munger
	return &PublisherMunger{
		baseRepoPath: baseRepoPath,
		config:       cfg,
	}
}

// update the local checkout of the source repository
func (p *PublisherMunger) updateSourceRepo() (string, error) {
	repoDir := filepath.Join(p.baseRepoPath, p.config.SourceRepo)

	cmd := exec.Command("git", "fetch", "origin")
	cmd.Dir = repoDir
	if err := p.plog.Run(cmd); err != nil {
		return "", err
	}

	cmd = exec.Command("git", "rev-parse", "HEAD")
	cmd.Dir = repoDir
	hash, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("failed running %v on %q repo: %v", strings.Join(cmd.Args, " "), p.config.SourceRepo, err)
	}

	rules, err := config.LoadRules(p.config.RulesFile)
	if err != nil {
		return "", err
	}
	p.reposRules = *rules
	glog.Infof("Loaded %d repository rules from %s", len(p.reposRules.Rules), p.config.RulesFile)

	// update source repo branches that are needed by other repos.
	for _, repoRule := range p.reposRules.Rules {
		for _, branchRule := range repoRule.Branches {
			if p.skippedBranch(branchRule.Source.Branch) {
				continue
			}

			src := branchRule.Source
			// we assume src.repo is always kubernetes
			cmd := exec.Command("git", "branch", "-f", src.Branch, fmt.Sprintf("origin/%s", src.Branch))
			cmd.Dir = repoDir
			if err := p.plog.Run(cmd); err == nil {
				continue
			}
			// probably the error is because we cannot do `git branch -f` while
			// current branch is src.branch, so try `git reset --hard` instead.
			cmd = exec.Command("git", "reset", "--hard", fmt.Sprintf("origin/%s", src.Branch))
			cmd.Dir = repoDir
			if err := p.plog.Run(cmd); err != nil {
				return "", err
			}
		}
	}
	return strings.Trim(string(hash), " \t\n"), nil
}

func (p *PublisherMunger) skippedBranch(b string) bool {
	for _, skipped := range p.reposRules.SkippedSourceBranches {
		if b == skipped {
			return true
		}
	}
	return false
}

// git clone dstURL to dst if dst doesn't exist yet.
func (p *PublisherMunger) ensureCloned(dst string, dstURL string) error {
	if _, err := os.Stat(dst); err == nil {
		return nil
	}

	cmd := exec.Command("mkdir", "-p", dst)
	if err := p.plog.Run(cmd); err != nil {
		return err
	}
	cmd = exec.Command("git", "clone", dstURL, dst)
	if err := p.plog.Run(cmd); err != nil {
		return err
	}
	cmd = exec.Command("/bin/bash", "-c", "git tag -l | xargs git tag -d")
	cmd.Dir = dst
	return p.plog.Run(cmd)
}

// constructs all the repos, but does not push the changes to remotes.
func (p *PublisherMunger) construct() error {
	sourceRemote := filepath.Join(p.baseRepoPath, p.config.SourceRepo, ".git")
	for _, repoRule := range p.reposRules.Rules {
		if repoRule.Skip {
			continue
		}

		// clone the destination repo
		dstDir := filepath.Join(p.baseRepoPath, repoRule.DestinationRepository, "")
		dstURL := fmt.Sprintf("https://%s/%s/%s.git", p.config.GithubHost, p.config.TargetOrg, repoRule.DestinationRepository)
		if err := p.ensureCloned(dstDir, dstURL); err != nil {
			p.plog.Errorf("%v", err)
			return err
		}
		p.plog.Infof("Successfully ensured %s exists", dstDir)
		if err := os.Chdir(dstDir); err != nil {
			return err
		}

		// delete tags
		cmd := exec.Command("/bin/bash", "-c", "git tag | xargs git tag -d >/dev/null")
		if err := p.plog.Run(cmd); err != nil {
			return err
		}

		formatDeps := func(deps []config.Dependency) string {
			var depStrings []string
			for _, dep := range deps {
				depStrings = append(depStrings, fmt.Sprintf("%s:%s", dep.Repository, dep.Branch))
			}
			return strings.Join(depStrings, ",")
		}

		// construct branches
		for _, branchRule := range repoRule.Branches {
			if p.skippedBranch(branchRule.Source.Branch) {
				continue
			}
			if len(branchRule.Source.Dir) == 0 {
				branchRule.Source.Dir = "."
				p.plog.Infof("%v: 'dir' cannot be empty, defaulting to '.'", branchRule)
			}

			// get old HEAD. Ignore errors as the branch might be non-existent
			oldHead, _ := exec.Command("git", "rev-parse", fmt.Sprintf("origin/%s", branchRule.Name)).Output()

			goPath := os.Getenv("GOPATH")
			branchEnv := append([]string(nil), os.Environ()...) // make mutable
			if branchRule.GoVersion != "" {
				goRoot := filepath.Join(goPath, "go-"+branchRule.GoVersion)
				branchEnv = append(branchEnv, "GOROOT="+goRoot)
				goBin := filepath.Join(goRoot, "bin")
				branchEnv = updateEnv(branchEnv, "PATH", prependPath(goBin), goBin)
			}

			skipTags := ""
			if p.reposRules.SkipTags {
				skipTags = "true"
				p.plog.Infof("synchronizing tags is disabled")
			}

			// TODO: Refactor this to use environment variables instead
			repoPublishScriptPath := filepath.Join(p.config.BasePublishScriptPath, "construct.sh")
			cmd := exec.Command(repoPublishScriptPath,
				repoRule.DestinationRepository,
				branchRule.Source.Branch,
				branchRule.Name,
				formatDeps(branchRule.Dependencies),
				strings.Join(branchRule.RequiredPackages, ":"),
				sourceRemote,
				branchRule.Source.Dir,
				p.config.SourceRepo,
				p.config.SourceRepo,
				p.config.BasePackage,
				fmt.Sprintf("%v", repoRule.Library),
				strings.Join(p.reposRules.RecursiveDeletePatterns, " "),
				skipTags,
			)
			cmd.Env = append([]string(nil), branchEnv...) // make mutable
			if p.reposRules.SkipGodeps {
				cmd.Env = append(cmd.Env, "PUBLISHER_BOT_SKIP_GODEPS=true")
			}
			if err := p.plog.Run(cmd); err != nil {
				return err
			}

			newHead, _ := exec.Command("git", "rev-parse", fmt.Sprintf("HEAD", branchRule.Name)).Output()
			if len(repoRule.SmokeTest) > 0 && string(oldHead) != string(newHead) {
				p.plog.Infof("Running smoke tests for branch %s", branchRule.Name)
				cmd := exec.Command("/bin/bash", "-xec", repoRule.SmokeTest)
				cmd.Env = append([]string(nil), branchEnv...) // make mutable
				if err := p.plog.Run(cmd); err != nil {
					// do not clean up to allow debugging with kubectl-exec.
					return err
				}
				exec.Command("git", "reset", "--hard").Run()
				exec.Command("git", "clean", "-f", "-f", "-d").Run()
			}

			p.plog.Infof("Successfully constructed %s", branchRule.Name)
		}
	}
	return nil
}

func updateEnv(env []string, key string, change func(string) string, val string) []string {
	for i := range env {
		if strings.HasPrefix(env[i], key+"=") {
			ss := strings.SplitN(env[i], "=", 2)
			env[i] = fmt.Sprintf("%s=%s", key, change(ss[1]))
			return env
		}
	}
	return append(env, fmt.Sprintf("%s=%s", key, val))
}

func prependPath(p string) func(string) string {
	return func(s string) string {
		if s == "" {
			return p
		}
		return p + ":" + s
	}
}

// publish to remotes.
func (p *PublisherMunger) publish() error {
	if p.config.DryRun {
		p.plog.Infof("Skipping push in dry-run mode")
		return nil
	}

	if p.config.TokenFile == "" {
		return fmt.Errorf("token cannot be empty in non-dry-run mode")
	}

	// NOTE: because some repos depend on each other, e.g., client-go depends on
	// apimachinery, they should be published atomically, but it's not supported
	// by github.
	for _, repoRules := range p.reposRules.Rules {
		if repoRules.Skip {
			continue
		}

		dstDir := filepath.Join(p.baseRepoPath, repoRules.DestinationRepository, "")
		if err := os.Chdir(dstDir); err != nil {
			return err
		}
		for _, branchRule := range repoRules.Branches {
			if p.skippedBranch(branchRule.Source.Branch) {
				continue
			}

			cmd := exec.Command(p.config.BasePublishScriptPath+"/push.sh", p.config.TokenFile, branchRule.Name)
			if err := p.plog.Run(cmd); err != nil {
				return err
			}
		}
	}
	return nil
}

// Run constructs the repos and pushes them.
func (p *PublisherMunger) Run() (string, string, error) {
	buf := bytes.NewBuffer(nil)
	var err error
	if p.plog, err = NewPublisherLog(buf, path.Join(p.baseRepoPath, "run.log")); err != nil {
		return "", "", err
	}

	hash, err := p.updateSourceRepo()
	if err != nil {
		p.plog.Errorf("%v", err)
		p.plog.Flush()
		return p.plog.Logs(), hash, err
	}
	if err := p.construct(); err != nil {
		p.plog.Errorf("%v", err)
		p.plog.Flush()
		return p.plog.Logs(), hash, err
	}
	if err := p.publish(); err != nil {
		p.plog.Errorf("%v", err)
		p.plog.Flush()
		return p.plog.Logs(), hash, err
	}
	return p.plog.Logs(), hash, nil
}
<commit_msg>Fix smoketest branch comparison<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 main

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

	"github.com/golang/glog"

	"k8s.io/publishing-bot/cmd/publishing-bot/config"
)

// PublisherMunger publishes content from one repository to another one.
type PublisherMunger struct {
	reposRules config.RepositoryRules
	config     *config.Config
	// plog duplicates the logs at glog and a file
	plog *plog
	// absolute path to the repos.
	baseRepoPath string
}

// New will create a new munger.
func New(cfg *config.Config, baseRepoPath string) *PublisherMunger {
	// create munger
	return &PublisherMunger{
		baseRepoPath: baseRepoPath,
		config:       cfg,
	}
}

// update the local checkout of the source repository
func (p *PublisherMunger) updateSourceRepo() (string, error) {
	repoDir := filepath.Join(p.baseRepoPath, p.config.SourceRepo)

	cmd := exec.Command("git", "fetch", "origin")
	cmd.Dir = repoDir
	if err := p.plog.Run(cmd); err != nil {
		return "", err
	}

	cmd = exec.Command("git", "rev-parse", "HEAD")
	cmd.Dir = repoDir
	hash, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("failed running %v on %q repo: %v", strings.Join(cmd.Args, " "), p.config.SourceRepo, err)
	}

	rules, err := config.LoadRules(p.config.RulesFile)
	if err != nil {
		return "", err
	}
	p.reposRules = *rules
	glog.Infof("Loaded %d repository rules from %s", len(p.reposRules.Rules), p.config.RulesFile)

	// update source repo branches that are needed by other repos.
	for _, repoRule := range p.reposRules.Rules {
		for _, branchRule := range repoRule.Branches {
			if p.skippedBranch(branchRule.Source.Branch) {
				continue
			}

			src := branchRule.Source
			// we assume src.repo is always kubernetes
			cmd := exec.Command("git", "branch", "-f", src.Branch, fmt.Sprintf("origin/%s", src.Branch))
			cmd.Dir = repoDir
			if err := p.plog.Run(cmd); err == nil {
				continue
			}
			// probably the error is because we cannot do `git branch -f` while
			// current branch is src.branch, so try `git reset --hard` instead.
			cmd = exec.Command("git", "reset", "--hard", fmt.Sprintf("origin/%s", src.Branch))
			cmd.Dir = repoDir
			if err := p.plog.Run(cmd); err != nil {
				return "", err
			}
		}
	}
	return strings.Trim(string(hash), " \t\n"), nil
}

func (p *PublisherMunger) skippedBranch(b string) bool {
	for _, skipped := range p.reposRules.SkippedSourceBranches {
		if b == skipped {
			return true
		}
	}
	return false
}

// git clone dstURL to dst if dst doesn't exist yet.
func (p *PublisherMunger) ensureCloned(dst string, dstURL string) error {
	if _, err := os.Stat(dst); err == nil {
		return nil
	}

	cmd := exec.Command("mkdir", "-p", dst)
	if err := p.plog.Run(cmd); err != nil {
		return err
	}
	cmd = exec.Command("git", "clone", dstURL, dst)
	if err := p.plog.Run(cmd); err != nil {
		return err
	}
	cmd = exec.Command("/bin/bash", "-c", "git tag -l | xargs git tag -d")
	cmd.Dir = dst
	return p.plog.Run(cmd)
}

// constructs all the repos, but does not push the changes to remotes.
func (p *PublisherMunger) construct() error {
	sourceRemote := filepath.Join(p.baseRepoPath, p.config.SourceRepo, ".git")
	for _, repoRule := range p.reposRules.Rules {
		if repoRule.Skip {
			continue
		}

		// clone the destination repo
		dstDir := filepath.Join(p.baseRepoPath, repoRule.DestinationRepository, "")
		dstURL := fmt.Sprintf("https://%s/%s/%s.git", p.config.GithubHost, p.config.TargetOrg, repoRule.DestinationRepository)
		if err := p.ensureCloned(dstDir, dstURL); err != nil {
			p.plog.Errorf("%v", err)
			return err
		}
		p.plog.Infof("Successfully ensured %s exists", dstDir)
		if err := os.Chdir(dstDir); err != nil {
			return err
		}

		// delete tags
		cmd := exec.Command("/bin/bash", "-c", "git tag | xargs git tag -d >/dev/null")
		if err := p.plog.Run(cmd); err != nil {
			return err
		}

		formatDeps := func(deps []config.Dependency) string {
			var depStrings []string
			for _, dep := range deps {
				depStrings = append(depStrings, fmt.Sprintf("%s:%s", dep.Repository, dep.Branch))
			}
			return strings.Join(depStrings, ",")
		}

		// construct branches
		for _, branchRule := range repoRule.Branches {
			if p.skippedBranch(branchRule.Source.Branch) {
				continue
			}
			if len(branchRule.Source.Dir) == 0 {
				branchRule.Source.Dir = "."
				p.plog.Infof("%v: 'dir' cannot be empty, defaulting to '.'", branchRule)
			}

			// get old HEAD. Ignore errors as the branch might be non-existent
			oldHead, _ := exec.Command("git", "rev-parse", fmt.Sprintf("origin/%s", branchRule.Name)).Output()

			goPath := os.Getenv("GOPATH")
			branchEnv := append([]string(nil), os.Environ()...) // make mutable
			if branchRule.GoVersion != "" {
				goRoot := filepath.Join(goPath, "go-"+branchRule.GoVersion)
				branchEnv = append(branchEnv, "GOROOT="+goRoot)
				goBin := filepath.Join(goRoot, "bin")
				branchEnv = updateEnv(branchEnv, "PATH", prependPath(goBin), goBin)
			}

			skipTags := ""
			if p.reposRules.SkipTags {
				skipTags = "true"
				p.plog.Infof("synchronizing tags is disabled")
			}

			// TODO: Refactor this to use environment variables instead
			repoPublishScriptPath := filepath.Join(p.config.BasePublishScriptPath, "construct.sh")
			cmd := exec.Command(repoPublishScriptPath,
				repoRule.DestinationRepository,
				branchRule.Source.Branch,
				branchRule.Name,
				formatDeps(branchRule.Dependencies),
				strings.Join(branchRule.RequiredPackages, ":"),
				sourceRemote,
				branchRule.Source.Dir,
				p.config.SourceRepo,
				p.config.SourceRepo,
				p.config.BasePackage,
				fmt.Sprintf("%v", repoRule.Library),
				strings.Join(p.reposRules.RecursiveDeletePatterns, " "),
				skipTags,
			)
			cmd.Env = append([]string(nil), branchEnv...) // make mutable
			if p.reposRules.SkipGodeps {
				cmd.Env = append(cmd.Env, "PUBLISHER_BOT_SKIP_GODEPS=true")
			}
			if err := p.plog.Run(cmd); err != nil {
				return err
			}

			newHead, _ := exec.Command("git", "rev-parse", fmt.Sprintf("origin/%s", branchRule.Name)).Output()
			if len(repoRule.SmokeTest) > 0 && string(oldHead) != string(newHead) {
				p.plog.Infof("Running smoke tests for branch %s", branchRule.Name)
				cmd := exec.Command("/bin/bash", "-xec", repoRule.SmokeTest)
				cmd.Env = append([]string(nil), branchEnv...) // make mutable
				if err := p.plog.Run(cmd); err != nil {
					// do not clean up to allow debugging with kubectl-exec.
					return err
				}
				exec.Command("git", "reset", "--hard").Run()
				exec.Command("git", "clean", "-f", "-f", "-d").Run()
			}

			p.plog.Infof("Successfully constructed %s", branchRule.Name)
		}
	}
	return nil
}

func updateEnv(env []string, key string, change func(string) string, val string) []string {
	for i := range env {
		if strings.HasPrefix(env[i], key+"=") {
			ss := strings.SplitN(env[i], "=", 2)
			env[i] = fmt.Sprintf("%s=%s", key, change(ss[1]))
			return env
		}
	}
	return append(env, fmt.Sprintf("%s=%s", key, val))
}

func prependPath(p string) func(string) string {
	return func(s string) string {
		if s == "" {
			return p
		}
		return p + ":" + s
	}
}

// publish to remotes.
func (p *PublisherMunger) publish() error {
	if p.config.DryRun {
		p.plog.Infof("Skipping push in dry-run mode")
		return nil
	}

	if p.config.TokenFile == "" {
		return fmt.Errorf("token cannot be empty in non-dry-run mode")
	}

	// NOTE: because some repos depend on each other, e.g., client-go depends on
	// apimachinery, they should be published atomically, but it's not supported
	// by github.
	for _, repoRules := range p.reposRules.Rules {
		if repoRules.Skip {
			continue
		}

		dstDir := filepath.Join(p.baseRepoPath, repoRules.DestinationRepository, "")
		if err := os.Chdir(dstDir); err != nil {
			return err
		}
		for _, branchRule := range repoRules.Branches {
			if p.skippedBranch(branchRule.Source.Branch) {
				continue
			}

			cmd := exec.Command(p.config.BasePublishScriptPath+"/push.sh", p.config.TokenFile, branchRule.Name)
			if err := p.plog.Run(cmd); err != nil {
				return err
			}
		}
	}
	return nil
}

// Run constructs the repos and pushes them.
func (p *PublisherMunger) Run() (string, string, error) {
	buf := bytes.NewBuffer(nil)
	var err error
	if p.plog, err = NewPublisherLog(buf, path.Join(p.baseRepoPath, "run.log")); err != nil {
		return "", "", err
	}

	hash, err := p.updateSourceRepo()
	if err != nil {
		p.plog.Errorf("%v", err)
		p.plog.Flush()
		return p.plog.Logs(), hash, err
	}
	if err := p.construct(); err != nil {
		p.plog.Errorf("%v", err)
		p.plog.Flush()
		return p.plog.Logs(), hash, err
	}
	if err := p.publish(); err != nil {
		p.plog.Errorf("%v", err)
		p.plog.Flush()
		return p.plog.Logs(), hash, err
	}
	return p.plog.Logs(), hash, nil
}
<|endoftext|>


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

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

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

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

package volumeclaimbinder

import (
	"fmt"
	"sort"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/client/cache"
)

const (
	// A PV created specifically for one claim must contain this annotation in order to bind to the claim.
	// The value must be the name of the claim being bound to.
	// This is an experimental feature and likely to change in the future.
	createdForKey = "persistent-volume-provisioning.experimental.kubernetes.io/created-for"
)

// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes indexed by AccessModes and ordered by storage capacity.
type persistentVolumeOrderedIndex struct {
	cache.Indexer
}

var _ cache.Store = &persistentVolumeOrderedIndex{} // persistentVolumeOrderedIndex is a Store

func NewPersistentVolumeOrderedIndex() *persistentVolumeOrderedIndex {
	return &persistentVolumeOrderedIndex{
		cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"accessmodes": accessModesIndexFunc}),
	}
}

// accessModesIndexFunc is an indexing function that returns a persistent volume's AccessModes as a string
func accessModesIndexFunc(obj interface{}) ([]string, error) {
	if pv, ok := obj.(*api.PersistentVolume); ok {
		modes := api.GetAccessModesAsString(pv.Spec.AccessModes)
		return []string{modes}, nil
	}
	return []string{""}, fmt.Errorf("object is not a persistent volume: %v", obj)
}

// ListByAccessModes returns all volumes with the given set of AccessModeTypes *in order* of their storage capacity (low to high)
func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.PersistentVolumeAccessMode) ([]*api.PersistentVolume, error) {
	pv := &api.PersistentVolume{
		Spec: api.PersistentVolumeSpec{
			AccessModes: modes,
		},
	}

	objs, err := pvIndex.Index("accessmodes", pv)
	if err != nil {
		return nil, err
	}

	volumes := make([]*api.PersistentVolume, len(objs))
	for i, obj := range objs {
		volumes[i] = obj.(*api.PersistentVolume)
	}

	sort.Sort(byCapacity{volumes})
	return volumes, nil
}

// matchPredicate is a function that indicates that a persistent volume matches another
type matchPredicate func(compareThis, toThis *api.PersistentVolume) bool

// Find returns the nearest PV from the ordered list or nil if a match is not found
func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume, matchPredicate matchPredicate) (*api.PersistentVolume, error) {
	// the 'searchPV' argument is a synthetic PV with capacity and accessmodes set according to the user's PersistentVolumeClaim.
	// the synthetic pv arg is, therefore, a request for a storage resource.
	//
	// PVs are indexed by their access modes to allow easier searching.  Each index is the string representation of a set of access modes.
	// There is a finite number of possible sets and PVs will only be indexed in one of them (whichever index matches the PV's modes).
	//
	// A request for resources will always specify its desired access modes.  Any matching PV must have at least that number
	// of access modes, but it can have more.  For example, a user asks for ReadWriteOnce but a GCEPD is available, which is ReadWriteOnce+ReadOnlyMany.
	//
	// Searches are performed against a set of access modes, so we can attempt not only the exact matching modes but also
	// potential matches (the GCEPD example above).
	allPossibleModes := pvIndex.allPossibleMatchingAccessModes(searchPV.Spec.AccessModes)

	for _, modes := range allPossibleModes {
		volumes, err := pvIndex.ListByAccessModes(modes)
		if err != nil {
			return nil, err
		}

		// volumes are sorted by size but some may be bound or earmarked for a specific claim.
		// filter those volumes for easy binary search by size
		// return the exact pre-binding match, if found
		unboundVolumes := []*api.PersistentVolume{}
		for _, volume := range volumes {
			// check for current binding
			if volume.Spec.ClaimRef != nil {
				continue
			}

			// check for pre-bind where the volume is intended for one specific claim
			if createdFor, ok := volume.Annotations[createdForKey]; ok {
				if createdFor != searchPV.Annotations[createdForKey] {
					// the volume is pre-bound and does not match the search criteria.
					continue
				} else {
					// exact annotation match! No search required.
					return volume, nil
				}
			}

			// volume isn't currently bound or pre-bound.
			unboundVolumes = append(unboundVolumes, volume)
		}

		i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(searchPV, unboundVolumes[i]) })
		if i < len(unboundVolumes) {
			return unboundVolumes[i], nil
		}
	}
	return nil, nil
}

// findByAccessModesAndStorageCapacity is a convenience method that calls Find w/ requisite matchPredicate for storage
func (pvIndex *persistentVolumeOrderedIndex) findByAccessModesAndStorageCapacity(prebindKey string, modes []api.PersistentVolumeAccessMode, qty resource.Quantity) (*api.PersistentVolume, error) {
	pv := &api.PersistentVolume{
		ObjectMeta: api.ObjectMeta{
			Annotations: map[string]string{
				createdForKey: prebindKey,
			},
		},
		Spec: api.PersistentVolumeSpec{
			AccessModes: modes,
			Capacity: api.ResourceList{
				api.ResourceName(api.ResourceStorage): qty,
			},
		},
	}
	return pvIndex.Find(pv, matchStorageCapacity)
}

// FindBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
func (pvIndex *persistentVolumeOrderedIndex) FindBestMatchForClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolume, error) {
	return pvIndex.findByAccessModesAndStorageCapacity(fmt.Sprintf("%s/%s", claim.Namespace, claim.Name), claim.Spec.AccessModes, claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)])
}

// byCapacity is used to order volumes by ascending storage size
type byCapacity struct {
	volumes []*api.PersistentVolume
}

func (c byCapacity) Less(i, j int) bool {
	return matchStorageCapacity(c.volumes[i], c.volumes[j])
}

func (c byCapacity) Swap(i, j int) {
	c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i]
}

func (c byCapacity) Len() int {
	return len(c.volumes)
}

// matchStorageCapacity is a matchPredicate used to sort and find volumes
func matchStorageCapacity(pvA, pvB *api.PersistentVolume) bool {
	aQty := pvA.Spec.Capacity[api.ResourceStorage]
	bQty := pvB.Spec.Capacity[api.ResourceStorage]
	aSize := aQty.Value()
	bSize := bQty.Value()
	return aSize <= bSize
}

// allPossibleMatchingAccessModes returns an array of AccessMode arrays that can satisfy a user's requested modes.
//
// see comments in the Find func above regarding indexing.
//
// allPossibleMatchingAccessModes gets all stringified accessmodes from the index and returns all those that
// contain at least all of the requested mode.
//
// For example, assume the index contains 2 types of PVs where the stringified accessmodes are:
//
// "RWO,ROX" -- some number of GCEPDs
// "RWO,ROX,RWX" -- some number of NFS volumes
//
// A request for RWO could be satisfied by both sets of indexed volumes, so allPossibleMatchingAccessModes returns:
//
// [][]api.PersistentVolumeAccessMode {
//      []api.PersistentVolumeAccessMode {
//			api.ReadWriteOnce, api.ReadOnlyMany,
//		},
//      []api.PersistentVolumeAccessMode {
//			api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany,
//		},
// }
//
// A request for RWX can be satisfied by only one set of indexed volumes, so the return is:
//
// [][]api.PersistentVolumeAccessMode {
//      []api.PersistentVolumeAccessMode {
//			api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany,
//		},
// }
//
// This func returns modes with ascending levels of modes to give the user what is closest to what they actually asked for.
//
func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requestedModes []api.PersistentVolumeAccessMode) [][]api.PersistentVolumeAccessMode {
	matchedModes := [][]api.PersistentVolumeAccessMode{}
	keys := pvIndex.Indexer.ListIndexFuncValues("accessmodes")
	for _, key := range keys {
		indexedModes := api.GetAccessModesFromString(key)
		if containedInAll(indexedModes, requestedModes) {
			matchedModes = append(matchedModes, indexedModes)
		}
	}

	// sort by the number of modes in each array with the fewest number of modes coming first.
	// this allows searching for volumes by the minimum number of modes required of the possible matches.
	sort.Sort(byAccessModes{matchedModes})
	return matchedModes
}

func contains(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool {
	for _, m := range modes {
		if m == mode {
			return true
		}
	}
	return false
}

func containedInAll(indexedModes []api.PersistentVolumeAccessMode, requestedModes []api.PersistentVolumeAccessMode) bool {
	for _, mode := range requestedModes {
		if !contains(indexedModes, mode) {
			return false
		}
	}
	return true
}

// byAccessModes is used to order access modes by size, with the fewest modes first
type byAccessModes struct {
	modes [][]api.PersistentVolumeAccessMode
}

func (c byAccessModes) Less(i, j int) bool {
	return len(c.modes[i]) < len(c.modes[j])
}

func (c byAccessModes) Swap(i, j int) {
	c.modes[i], c.modes[j] = c.modes[j], c.modes[i]
}

func (c byAccessModes) Len() int {
	return len(c.modes)
}
<commit_msg>shortened annotation key name, improved comments<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package volumeclaimbinder

import (
	"fmt"
	"sort"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/client/cache"
)

const (
	// A PV created specifically for one claim must contain this annotation in order to bind to the claim.
	// The value must be the namespace and name of the claim being bound to (i.e, claim.Namespace/claim.Name)
	// This is an experimental feature and likely to change in the future.
	createdForKey = "volume.experimental.kubernetes.io/provisioned-for"
)

// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes indexed by AccessModes and ordered by storage capacity.
type persistentVolumeOrderedIndex struct {
	cache.Indexer
}

var _ cache.Store = &persistentVolumeOrderedIndex{} // persistentVolumeOrderedIndex is a Store

func NewPersistentVolumeOrderedIndex() *persistentVolumeOrderedIndex {
	return &persistentVolumeOrderedIndex{
		cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"accessmodes": accessModesIndexFunc}),
	}
}

// accessModesIndexFunc is an indexing function that returns a persistent volume's AccessModes as a string
func accessModesIndexFunc(obj interface{}) ([]string, error) {
	if pv, ok := obj.(*api.PersistentVolume); ok {
		modes := api.GetAccessModesAsString(pv.Spec.AccessModes)
		return []string{modes}, nil
	}
	return []string{""}, fmt.Errorf("object is not a persistent volume: %v", obj)
}

// ListByAccessModes returns all volumes with the given set of AccessModeTypes *in order* of their storage capacity (low to high)
func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.PersistentVolumeAccessMode) ([]*api.PersistentVolume, error) {
	pv := &api.PersistentVolume{
		Spec: api.PersistentVolumeSpec{
			AccessModes: modes,
		},
	}

	objs, err := pvIndex.Index("accessmodes", pv)
	if err != nil {
		return nil, err
	}

	volumes := make([]*api.PersistentVolume, len(objs))
	for i, obj := range objs {
		volumes[i] = obj.(*api.PersistentVolume)
	}

	sort.Sort(byCapacity{volumes})
	return volumes, nil
}

// matchPredicate is a function that indicates that a persistent volume matches another
type matchPredicate func(compareThis, toThis *api.PersistentVolume) bool

// Find returns the nearest PV from the ordered list or nil if a match is not found
func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume, matchPredicate matchPredicate) (*api.PersistentVolume, error) {
	// the 'searchPV' argument is a synthetic PV with capacity and accessmodes set according to the user's PersistentVolumeClaim.
	// the synthetic pv arg is, therefore, a request for a storage resource.
	//
	// PVs are indexed by their access modes to allow easier searching.  Each index is the string representation of a set of access modes.
	// There is a finite number of possible sets and PVs will only be indexed in one of them (whichever index matches the PV's modes).
	//
	// A request for resources will always specify its desired access modes.  Any matching PV must have at least that number
	// of access modes, but it can have more.  For example, a user asks for ReadWriteOnce but a GCEPD is available, which is ReadWriteOnce+ReadOnlyMany.
	//
	// Searches are performed against a set of access modes, so we can attempt not only the exact matching modes but also
	// potential matches (the GCEPD example above).
	allPossibleModes := pvIndex.allPossibleMatchingAccessModes(searchPV.Spec.AccessModes)

	for _, modes := range allPossibleModes {
		volumes, err := pvIndex.ListByAccessModes(modes)
		if err != nil {
			return nil, err
		}

		// volumes are sorted by size but some may be bound or earmarked for a specific claim.
		// filter those volumes for easy binary search by size
		// return the exact pre-binding match, if found
		unboundVolumes := []*api.PersistentVolume{}
		for _, volume := range volumes {
			// check for current binding
			if volume.Spec.ClaimRef != nil {
				continue
			}

			// check for pre-bind where the volume is intended for one specific claim
			if createdFor, ok := volume.Annotations[createdForKey]; ok {
				if createdFor != searchPV.Annotations[createdForKey] {
					// the volume is pre-bound and does not match the search criteria.
					continue
				}
				// exact annotation match! No search required.
				return volume, nil
			}

			// volume isn't currently bound or pre-bound.
			unboundVolumes = append(unboundVolumes, volume)
		}

		i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(searchPV, unboundVolumes[i]) })
		if i < len(unboundVolumes) {
			return unboundVolumes[i], nil
		}
	}
	return nil, nil
}

// findByAccessModesAndStorageCapacity is a convenience method that calls Find w/ requisite matchPredicate for storage
func (pvIndex *persistentVolumeOrderedIndex) findByAccessModesAndStorageCapacity(prebindKey string, modes []api.PersistentVolumeAccessMode, qty resource.Quantity) (*api.PersistentVolume, error) {
	pv := &api.PersistentVolume{
		ObjectMeta: api.ObjectMeta{
			Annotations: map[string]string{
				createdForKey: prebindKey,
			},
		},
		Spec: api.PersistentVolumeSpec{
			AccessModes: modes,
			Capacity: api.ResourceList{
				api.ResourceName(api.ResourceStorage): qty,
			},
		},
	}
	return pvIndex.Find(pv, matchStorageCapacity)
}

// FindBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
func (pvIndex *persistentVolumeOrderedIndex) FindBestMatchForClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolume, error) {
	return pvIndex.findByAccessModesAndStorageCapacity(fmt.Sprintf("%s/%s", claim.Namespace, claim.Name), claim.Spec.AccessModes, claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)])
}

// byCapacity is used to order volumes by ascending storage size
type byCapacity struct {
	volumes []*api.PersistentVolume
}

func (c byCapacity) Less(i, j int) bool {
	return matchStorageCapacity(c.volumes[i], c.volumes[j])
}

func (c byCapacity) Swap(i, j int) {
	c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i]
}

func (c byCapacity) Len() int {
	return len(c.volumes)
}

// matchStorageCapacity is a matchPredicate used to sort and find volumes
func matchStorageCapacity(pvA, pvB *api.PersistentVolume) bool {
	aQty := pvA.Spec.Capacity[api.ResourceStorage]
	bQty := pvB.Spec.Capacity[api.ResourceStorage]
	aSize := aQty.Value()
	bSize := bQty.Value()
	return aSize <= bSize
}

// allPossibleMatchingAccessModes returns an array of AccessMode arrays that can satisfy a user's requested modes.
//
// see comments in the Find func above regarding indexing.
//
// allPossibleMatchingAccessModes gets all stringified accessmodes from the index and returns all those that
// contain at least all of the requested mode.
//
// For example, assume the index contains 2 types of PVs where the stringified accessmodes are:
//
// "RWO,ROX" -- some number of GCEPDs
// "RWO,ROX,RWX" -- some number of NFS volumes
//
// A request for RWO could be satisfied by both sets of indexed volumes, so allPossibleMatchingAccessModes returns:
//
// [][]api.PersistentVolumeAccessMode {
//      []api.PersistentVolumeAccessMode {
//			api.ReadWriteOnce, api.ReadOnlyMany,
//		},
//      []api.PersistentVolumeAccessMode {
//			api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany,
//		},
// }
//
// A request for RWX can be satisfied by only one set of indexed volumes, so the return is:
//
// [][]api.PersistentVolumeAccessMode {
//      []api.PersistentVolumeAccessMode {
//			api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany,
//		},
// }
//
// This func returns modes with ascending levels of modes to give the user what is closest to what they actually asked for.
//
func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requestedModes []api.PersistentVolumeAccessMode) [][]api.PersistentVolumeAccessMode {
	matchedModes := [][]api.PersistentVolumeAccessMode{}
	keys := pvIndex.Indexer.ListIndexFuncValues("accessmodes")
	for _, key := range keys {
		indexedModes := api.GetAccessModesFromString(key)
		if containedInAll(indexedModes, requestedModes) {
			matchedModes = append(matchedModes, indexedModes)
		}
	}

	// sort by the number of modes in each array with the fewest number of modes coming first.
	// this allows searching for volumes by the minimum number of modes required of the possible matches.
	sort.Sort(byAccessModes{matchedModes})
	return matchedModes
}

func contains(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool {
	for _, m := range modes {
		if m == mode {
			return true
		}
	}
	return false
}

func containedInAll(indexedModes []api.PersistentVolumeAccessMode, requestedModes []api.PersistentVolumeAccessMode) bool {
	for _, mode := range requestedModes {
		if !contains(indexedModes, mode) {
			return false
		}
	}
	return true
}

// byAccessModes is used to order access modes by size, with the fewest modes first
type byAccessModes struct {
	modes [][]api.PersistentVolumeAccessMode
}

func (c byAccessModes) Less(i, j int) bool {
	return len(c.modes[i]) < len(c.modes[j])
}

func (c byAccessModes) Swap(i, j int) {
	c.modes[i], c.modes[j] = c.modes[j], c.modes[i]
}

func (c byAccessModes) Len() int {
	return len(c.modes)
}
<|endoftext|>


<commit_before>// Copyright 2016 e-Xpert Solutions SA. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package diff implements diff functions to compare objects.
package diff

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// A Diff represents the changes between two structures.
type Diff map[string]interface{}

// HasChange report whether the Diff contains some changes.
func (d Diff) HasChange() bool {
	return len(d) > 0
}

// PrettyJSON serializes the Diff into JSON in a indented (hence human readable)
// way.
func (d Diff) PrettyJSON() []byte {
	bs, err := json.MarshalIndent(d, "", "   ")
	if err != nil {
		return []byte{}
	}
	return bs
}

// JSON serializes the Diff into JSON in a compact format (no indentation).
func (d Diff) JSON() []byte {
	bs, err := json.Marshal(d)
	if err != nil {
		return []byte{}
	}
	return bs
}

type ChangeType string

// Possible values for a ChangeType.
const (
	AddType ChangeType = "ADD" // addition
	DelType ChangeType = "DEL" // deletion
	ModType ChangeType = "MOD" // modification
)

type Change struct {
	OldVal interface{} `json:"old_value,omitempty"`
	NewVal interface{} `json:"new_value,omitempty"`
	Val    interface{} `json:"value,omitempty"`
	Type   ChangeType  `json:"type,omitempty"`
}

func Compute(x, y interface{}) (Diff, error) {
	return Engine{}.Compute(x, y)
}

type Engine struct {
	ExcludeFieldList []string
	Recursive        bool
}

func (e Engine) IsIgnored(field string) bool {
	for _, f := range e.ExcludeFieldList {
		if f == field {
			return true
		}
	}
	return false
}

func (e Engine) Compute(x, y interface{}) (Diff, error) {
	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
	tx, ty := vx.Type(), vy.Type()

	if !tx.AssignableTo(ty) {
		return nil, errors.New("input objects do not share the same type")
	}

	// since x and y share the same type, there is no need to check them both
	if vx.Kind() != reflect.Struct {
		return nil, errors.New("input values are not struct")
	}

	xNumFields := vx.NumField()

	delta := make(Diff)

	for i := 0; i < xNumFields; i++ {
		fx := vx.Field(i)
		typ := tx.Field(i)

		// skip non-exported fields and the ones that are excluded
		if !isExported(typ.Name) || e.IsIgnored(typ.Name) {
			continue
		}

		fy := vy.FieldByName(typ.Name)

		if d := e.handleValue(fx, fy); d != nil {
			delta[typ.Name] = d
		}
	}

	return delta, nil
}

func (e Engine) handleValue(fx, fy reflect.Value) interface{} {
	switch fx.Kind() {

	case reflect.Struct:
		return e.handleStruct(fx, fy)
	case reflect.Array, reflect.Slice:
		return e.handleSlice(fx, fy)
	case reflect.Map:
		return nil

	case reflect.Ptr:
		if fx.IsNil() {
			if fy.IsNil() {
				return nil
			}
			return Change{OldVal: nil, NewVal: fy.Elem().Interface()}
		} else if fy.IsNil() {
			return Change{OldVal: fx.Elem().Interface(), NewVal: nil, Type: ModType}
		}
		return e.handleValue(fx.Elem(), fy.Elem())

	case reflect.Interface, reflect.Func, reflect.Chan, reflect.Invalid, reflect.UnsafePointer, reflect.Complex64, reflect.Complex128:
		// TODO(gilliek): support complex numbers
		return nil

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		ix, iy := fx.Int(), fy.Int()
		if ix != iy {
			return Change{OldVal: ix, NewVal: iy, Type: ModType}
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		uix, uiy := fx.Uint(), fy.Uint()
		if uix != uiy {
			return Change{OldVal: uix, NewVal: uiy, Type: ModType}
		}
	case reflect.Float32, reflect.Float64:
		flx, fly := fx.Float(), fy.Float()
		if flx-fly < 0.000001 {
			return Change{OldVal: flx, NewVal: fly, Type: ModType}
		}
	case reflect.String:
		sx, sy := fx.String(), fy.String()
		if sx != sy {
			return Change{OldVal: sx, NewVal: sy, Type: ModType}
		}
	}

	return nil
}

func (e Engine) handleStruct(fx, fy reflect.Value) interface{} {
	if isFullyNonExportedStruct(fx) {
		if !isEqual(fx, fy) {
			return Change{OldVal: fx.Interface(), NewVal: fy.Interface()}
		}
		return nil
	}

	delta := make(Diff)
	numFields := fx.NumField()
	for i := 0; i < numFields; i++ {
		newFx := fx.Field(i)
		typ := fx.Type().Field(i)

		// skip non-exported fields and the ones that are excluded
		if !isExported(typ.Name) || e.IsIgnored(typ.Name) {
			continue
		}

		newFy := fy.FieldByName(typ.Name)

		if d := e.handleValue(newFx, newFy); d != nil {
			delta[typ.Name] = d
		}
	}
	if len(delta) > 0 {
		return delta
	}
	return nil
}

func (e Engine) handleSlice(fx, fy reflect.Value) interface{} {
	xLen, yLen := fx.Len(), fy.Len()

	changes := make(map[string]Change)
	if xLen == 0 {
		if yLen == 0 {
			return nil
		}
		for i := 0; i < yLen; i++ {
			changes[strconv.Itoa(i)] = Change{NewVal: fy.Index(i).Interface(), Type: AddType}
		}
	} else if yLen == 0 {
		for i := 0; i < xLen; i++ {
			changes[strconv.Itoa(i)] = Change{OldVal: fx.Index(i).Interface(), Type: DelType}
		}
	} else {
		var maxLen int
		if xLen > yLen {
			maxLen = yLen
			for i := yLen; i < xLen; i++ {
				changes[strconv.Itoa(i)] = Change{OldVal: fx.Index(i).Interface(), Type: DelType}
			}
		} else if xLen < yLen {
			maxLen = xLen
			for i := xLen; i < yLen; i++ {
				changes[strconv.Itoa(i)] = Change{NewVal: fy.Index(i).Interface(), Type: AddType}
			}
		} else {
			maxLen = xLen
		}
		for i := 0; i < maxLen; i++ {
			if d := e.handleValue(fx.Index(i), fy.Index(i)); d != nil {
				changes[strconv.Itoa(i)] = Change{Val: d, Type: ModType}
			}
		}
	}
	if len(changes) > 0 {
		return changes
	}
	return nil
}

func isExported(fieldName string) bool {
	if fieldName == "" {
		return false
	}
	firstLetter := string(fieldName[0])
	return firstLetter != strings.ToLower(firstLetter)
}

func isFullyNonExportedStruct(s reflect.Value) bool {
	if s.Kind() != reflect.Struct {
		return false
	}
	numFields := s.NumField()
	for i := 0; i < numFields; i++ {
		typ := s.Type().Field(i)
		if isExported(typ.Name) {
			return false
		}
	}
	return true
}

func isEqual(x, y reflect.Value) bool {
	return fmt.Sprint(x.Interface()) == fmt.Sprint(y.Interface())
}
<commit_msg>Add action type to all comparison<commit_after>// Copyright 2016 e-Xpert Solutions SA. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package diff implements diff functions to compare objects.
package diff

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// A Diff represents the changes between two structures.
type Diff map[string]interface{}

// HasChange report whether the Diff contains some changes.
func (d Diff) HasChange() bool {
	return len(d) > 0
}

// PrettyJSON serializes the Diff into JSON in a indented (hence human readable)
// way.
func (d Diff) PrettyJSON() []byte {
	bs, err := json.MarshalIndent(d, "", "   ")
	if err != nil {
		return []byte{}
	}
	return bs
}

// JSON serializes the Diff into JSON in a compact format (no indentation).
func (d Diff) JSON() []byte {
	bs, err := json.Marshal(d)
	if err != nil {
		return []byte{}
	}
	return bs
}

type ChangeType string

// Possible values for a ChangeType.
const (
	AddType ChangeType = "ADD" // addition
	DelType ChangeType = "DEL" // deletion
	ModType ChangeType = "MOD" // modification
)

type Change struct {
	OldVal interface{} `json:"old_value,omitempty"`
	NewVal interface{} `json:"new_value,omitempty"`
	Val    interface{} `json:"value,omitempty"`
	Type   ChangeType  `json:"type,omitempty"`
}

func Compute(x, y interface{}) (Diff, error) {
	return Engine{}.Compute(x, y)
}

type Engine struct {
	ExcludeFieldList []string
	Recursive        bool
}

func (e Engine) IsIgnored(field string) bool {
	for _, f := range e.ExcludeFieldList {
		if f == field {
			return true
		}
	}
	return false
}

func (e Engine) Compute(x, y interface{}) (Diff, error) {
	vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
	tx, ty := vx.Type(), vy.Type()

	if !tx.AssignableTo(ty) {
		return nil, errors.New("input objects do not share the same type")
	}

	// since x and y share the same type, there is no need to check them both
	if vx.Kind() != reflect.Struct {
		return nil, errors.New("input values are not struct")
	}

	xNumFields := vx.NumField()

	delta := make(Diff)

	for i := 0; i < xNumFields; i++ {
		fx := vx.Field(i)
		typ := tx.Field(i)

		// skip non-exported fields and the ones that are excluded
		if !isExported(typ.Name) || e.IsIgnored(typ.Name) {
			continue
		}

		fy := vy.FieldByName(typ.Name)

		if d := e.handleValue(fx, fy); d != nil {
			delta[typ.Name] = d
		}
	}

	return delta, nil
}

func (e Engine) handleValue(fx, fy reflect.Value) interface{} {
	switch fx.Kind() {

	case reflect.Struct:
		return e.handleStruct(fx, fy)
	case reflect.Array, reflect.Slice:
		return e.handleSlice(fx, fy)
	case reflect.Map:
		return nil

	case reflect.Ptr:
		if fx.IsNil() {
			if fy.IsNil() {
				return nil
			}
			return Change{OldVal: nil, NewVal: fy.Elem().Interface(), Type: ModType}
		} else if fy.IsNil() {
			return Change{OldVal: fx.Elem().Interface(), NewVal: nil, Type: ModType}
		}
		return e.handleValue(fx.Elem(), fy.Elem())

	case reflect.Interface, reflect.Func, reflect.Chan, reflect.Invalid, reflect.UnsafePointer, reflect.Complex64, reflect.Complex128:
		// TODO(gilliek): support complex numbers
		return nil

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		ix, iy := fx.Int(), fy.Int()
		if ix != iy {
			return Change{OldVal: ix, NewVal: iy, Type: ModType}
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		uix, uiy := fx.Uint(), fy.Uint()
		if uix != uiy {
			return Change{OldVal: uix, NewVal: uiy, Type: ModType}
		}
	case reflect.Float32, reflect.Float64:
		flx, fly := fx.Float(), fy.Float()
		if flx-fly < 0.000001 {
			return Change{OldVal: flx, NewVal: fly, Type: ModType}
		}
	case reflect.String:
		sx, sy := fx.String(), fy.String()
		if sx != sy {
			return Change{OldVal: sx, NewVal: sy, Type: ModType}
		}
	}

	return nil
}

func (e Engine) handleStruct(fx, fy reflect.Value) interface{} {
	if isFullyNonExportedStruct(fx) {
		if !isEqual(fx, fy) {
			return Change{OldVal: fx.Interface(), NewVal: fy.Interface(), Type: ModType}
		}
		return nil
	}

	delta := make(Diff)
	numFields := fx.NumField()
	for i := 0; i < numFields; i++ {
		newFx := fx.Field(i)
		typ := fx.Type().Field(i)

		// skip non-exported fields and the ones that are excluded
		if !isExported(typ.Name) || e.IsIgnored(typ.Name) {
			continue
		}

		newFy := fy.FieldByName(typ.Name)

		if d := e.handleValue(newFx, newFy); d != nil {
			delta[typ.Name] = d
		}
	}
	if len(delta) > 0 {
		return delta
	}
	return nil
}

func (e Engine) handleSlice(fx, fy reflect.Value) interface{} {
	xLen, yLen := fx.Len(), fy.Len()

	changes := make(map[string]Change)
	if xLen == 0 {
		if yLen == 0 {
			return nil
		}
		for i := 0; i < yLen; i++ {
			changes[strconv.Itoa(i)] = Change{NewVal: fy.Index(i).Interface(), Type: AddType}
		}
	} else if yLen == 0 {
		for i := 0; i < xLen; i++ {
			changes[strconv.Itoa(i)] = Change{OldVal: fx.Index(i).Interface(), Type: DelType}
		}
	} else {
		var maxLen int
		if xLen > yLen {
			maxLen = yLen
			for i := yLen; i < xLen; i++ {
				changes[strconv.Itoa(i)] = Change{OldVal: fx.Index(i).Interface(), Type: DelType}
			}
		} else if xLen < yLen {
			maxLen = xLen
			for i := xLen; i < yLen; i++ {
				changes[strconv.Itoa(i)] = Change{NewVal: fy.Index(i).Interface(), Type: AddType}
			}
		} else {
			maxLen = xLen
		}
		for i := 0; i < maxLen; i++ {
			if d := e.handleValue(fx.Index(i), fy.Index(i)); d != nil {
				changes[strconv.Itoa(i)] = Change{Val: d, Type: ModType}
			}
		}
	}
	if len(changes) > 0 {
		return changes
	}
	return nil
}

func isExported(fieldName string) bool {
	if fieldName == "" {
		return false
	}
	firstLetter := string(fieldName[0])
	return firstLetter != strings.ToLower(firstLetter)
}

func isFullyNonExportedStruct(s reflect.Value) bool {
	if s.Kind() != reflect.Struct {
		return false
	}
	numFields := s.NumField()
	for i := 0; i < numFields; i++ {
		typ := s.Type().Field(i)
		if isExported(typ.Name) {
			return false
		}
	}
	return true
}

func isEqual(x, y reflect.Value) bool {
	return fmt.Sprint(x.Interface()) == fmt.Sprint(y.Interface())
}
<|endoftext|>


<commit_before>package rest

import (
	"net/http"

	"github.com/bborbe/server/handler_finder/method"
	"github.com/bborbe/server/handler_finder/part"
)

type RestHandlerFinder interface {
	RegisterListHandler(handler http.Handler)
	RegisterGetHandler(handler http.Handler)
	RegisterCreateHandler(handler http.Handler)
	RegisterDeleteHandler(handler http.Handler)
	RegisterUpdateHandler(handler http.Handler)
	RegisterPatchHandler(handler http.Handler)
}

type restHandlerFinder struct {
	prefix              string
	methodHandlerFinder method.MethodHandlerFinder
	getHandlerFinder    part.PartHandlerFinder
	postHandlerFinder   part.PartHandlerFinder
	putHandlerFinder    part.PartHandlerFinder
	patchHandlerFinder  part.PartHandlerFinder
	deleteHandlerFinder part.PartHandlerFinder
}

func New(prefix string) *restHandlerFinder {
	methodHandlerFinder := method.New()

	getHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("GET", getHandlerFinder)
	methodHandlerFinder.RegisterHandlerFinder("", getHandlerFinder)

	postHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("POST", postHandlerFinder)

	putHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("PUT", putHandlerFinder)

	patchHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("PATCH", patchHandlerFinder)

	deleteHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("DELETE", deleteHandlerFinder)

	h := new(restHandlerFinder)
	h.prefix = prefix
	h.methodHandlerFinder = methodHandlerFinder
	h.getHandlerFinder = getHandlerFinder
	h.postHandlerFinder = postHandlerFinder
	h.putHandlerFinder = putHandlerFinder
	h.patchHandlerFinder = patchHandlerFinder
	h.deleteHandlerFinder = deleteHandlerFinder

	return h
}

func (h *restHandlerFinder) FindHandler(request *http.Request) http.Handler {
	return h.methodHandlerFinder.FindHandler(request)
}

func (h *restHandlerFinder) RegisterListHandler(handler http.Handler) {
	h.getHandlerFinder.RegisterHandler("", handler)
}

func (h *restHandlerFinder) RegisterGetHandler(handler http.Handler) {
	h.getHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterCreateHandler(handler http.Handler) {
	h.postHandlerFinder.RegisterHandler("", handler)
}

func (h *restHandlerFinder) RegisterDeleteHandler(handler http.Handler) {
	h.deleteHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterUpdateHandler(handler http.Handler) {
	h.putHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterPatchHandler(handler http.Handler) {
	h.patchHandlerFinder.RegisterHandler("/", handler)
}
<commit_msg>fix put<commit_after>package rest

import (
	"net/http"

	"github.com/bborbe/server/handler_finder/method"
	"github.com/bborbe/server/handler_finder/part"
)

type RestHandlerFinder interface {
	RegisterListHandler(handler http.Handler)
	RegisterGetHandler(handler http.Handler)
	RegisterCreateHandler(handler http.Handler)
	RegisterDeleteHandler(handler http.Handler)
	RegisterUpdateHandler(handler http.Handler)
	RegisterPatchHandler(handler http.Handler)
}

type restHandlerFinder struct {
	prefix              string
	methodHandlerFinder method.MethodHandlerFinder
	getHandlerFinder    part.PartHandlerFinder
	postHandlerFinder   part.PartHandlerFinder
	putHandlerFinder    part.PartHandlerFinder
	patchHandlerFinder  part.PartHandlerFinder
	deleteHandlerFinder part.PartHandlerFinder
}

func New(prefix string) *restHandlerFinder {
	methodHandlerFinder := method.New()

	getHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("GET", getHandlerFinder)
	methodHandlerFinder.RegisterHandlerFinder("", getHandlerFinder)

	postHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("POST", postHandlerFinder)

	putHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("PUT", putHandlerFinder)

	patchHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("PATCH", patchHandlerFinder)

	deleteHandlerFinder := part.New(prefix)
	methodHandlerFinder.RegisterHandlerFinder("DELETE", deleteHandlerFinder)

	h := new(restHandlerFinder)
	h.prefix = prefix
	h.methodHandlerFinder = methodHandlerFinder
	h.getHandlerFinder = getHandlerFinder
	h.postHandlerFinder = postHandlerFinder
	h.putHandlerFinder = putHandlerFinder
	h.patchHandlerFinder = patchHandlerFinder
	h.deleteHandlerFinder = deleteHandlerFinder

	return h
}

func (h *restHandlerFinder) FindHandler(request *http.Request) http.Handler {
	return h.methodHandlerFinder.FindHandler(request)
}

func (h *restHandlerFinder) RegisterListHandler(handler http.Handler) {
	h.getHandlerFinder.RegisterHandler("", handler)
}

func (h *restHandlerFinder) RegisterGetHandler(handler http.Handler) {
	h.getHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterCreateHandler(handler http.Handler) {
	h.postHandlerFinder.RegisterHandler("", handler)
	h.postHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterDeleteHandler(handler http.Handler) {
	h.deleteHandlerFinder.RegisterHandler("", handler)
	h.deleteHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterUpdateHandler(handler http.Handler) {
	h.putHandlerFinder.RegisterHandler("", handler)
	h.putHandlerFinder.RegisterHandler("/", handler)
}

func (h *restHandlerFinder) RegisterPatchHandler(handler http.Handler) {
	h.patchHandlerFinder.RegisterHandler("", handler)
	h.patchHandlerFinder.RegisterHandler("/", handler)
}
<|endoftext|>