<commit_before>// Copyright © 2016 Aaron Longwell
//
// Use of this source code is governed by an MIT licese.
// Details in the LICENSE file.

package trello

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
)

type Card struct {
	client *Client

	// Key metadata
	ID               string     `json:"id"`
	IDShort          int        `json:"idShort"`
	Name             string     `json:"name"`
	Pos              float64    `json:"pos"`
	Email            string     `json:"email"`
	ShortLink        string     `json:"shortLink"`
	ShortUrl         string     `json:"shortUrl"`
	Url              string     `json:"url"`
	Desc             string     `json:"desc"`
	Due              *time.Time `json:"due"`
	DueComplete      bool       `json:"dueComplete"`
	Closed           bool       `json:"closed"`
	Subscribed       bool       `json:"subscribed"`
	DateLastActivity *time.Time `json:"dateLastActivity"`

	// Board
	Board   *Board
	IDBoard string `json:"idBoard"`

	// List
	List   *List
	IDList string `json:"idList"`

	// Badges
	Badges struct {
		Votes              int        `json:"votes"`
		ViewingMemberVoted bool       `json:"viewingMemberVoted"`
		Subscribed         bool       `json:"subscribed"`
		Fogbugz            string     `json:"fogbugz,omitempty"`
		CheckItems         int        `json:"checkItems"`
		CheckItemsChecked  int        `json:"checkItemsChecked"`
		Comments           int        `json:"comments"`
		Attachments        int        `json:"attachments"`
		Description        bool       `json:"description"`
		Due                *time.Time `json:"due,omitempty"`
	} `json:"badges"`

	// Actions
	Actions ActionCollection `json:"actions,omitempty"`

	// Checklists
	IDCheckLists    []string          `json:"idCheckLists"`
	Checklists      []*Checklist      `json:"checklists,omitempty"`
	CheckItemStates []*CheckItemState `json:"checkItemStates,omitempty"`

	// Members
	IDMembers      []string  `json:"idMembers,omitempty"`
	IDMembersVoted []string  `json:"idMembersVoted,omitempty"`
	Members        []*Member `json:"members,omitempty"`

	// Attachments
	IDAttachmentCover     string        `json:"idAttachmentCover"`
	ManualCoverAttachment bool          `json:"manualCoverAttachment"`
	Attachments           []*Attachment `json:"attachments,omitempty"`

	// Labels
	Labels []*Label `json:"labels,omitempty"`
}

func (c *Card) CreatedAt() time.Time {
	t, _ := IDToTime(c.ID)
	return t
}

func (c *Card) MoveToList(listID string, args Arguments) error {
	path := fmt.Sprintf("cards/%s", c.ID)
	args["idList"] = listID
	return c.client.Put(path, args, &c)
}

func (c *Card) SetPos(newPos float64) error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, Arguments{"pos": fmt.Sprintf("%f", newPos)}, c)
}

func (c *Card) MoveToTopOfList() error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, Arguments{"pos": "top"}, c)
}

func (c *Card) MoveToBottomOfList() error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, Arguments{"pos": "bottom"}, c)
}

func (c *Card) Update(args Arguments) error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, args, c)
}

func (c *Client) CreateCard(card *Card, extraArgs Arguments) error {
	path := "cards"
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"pos":       strconv.FormatFloat(card.Pos, 'g', -1, 64),
		"idList":    card.IDList,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overriding the creation position with 'top' or 'botttom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := c.Post(path, args, &card)
	if err == nil {
		card.client = c
	}
	return err
}

func (l *List) AddCard(card *Card, extraArgs Arguments) error {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overwriting the creation position with 'top' or 'bottom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := l.client.Post(path, args, &card)
	if err == nil {
		card.client = l.client
	} else {
		err = errors.Wrapf(err, "Error adding card to list %s", l.ID)
	}
	return err
}

// Try these Arguments
//
// 	Arguments["keepFromSource"] = "all"
//  Arguments["keepFromSource"] = "none"
// 	Arguments["keepFromSource"] = "attachments,checklists,comments"
//
func (c *Card) CopyToList(listID string, args Arguments) (*Card, error) {
	path := "cards"
	args["idList"] = listID
	args["idCardSource"] = c.ID
	newCard := Card{}
	err := c.client.Post(path, args, &newCard)
	if err == nil {
		newCard.client = c.client
	} else {
		err = errors.Wrapf(err, "Error copying card '%s' to list '%s'.", c.ID, listID)
	}
	return &newCard, err
}

func (c *Card) AddComment(comment string, args Arguments) (*Action, error) {
	path := fmt.Sprintf("cards/%s/actions/comments", c.ID)
	args["text"] = comment
	action := Action{}
	err := c.client.Post(path, args, &action)
	if err != nil {
		err = errors.Wrapf(err, "Error commenting on card %s", c.ID)
	}
	return &action, err
}

// If this Card was created from a copy of another Card, this func retrieves
// the originating Card. Returns an error only when a low-level failure occurred.
// If this Card has no parent, a nil card and nil error are returned. In other words, the
// non-existence of a parent is not treated as an error.
//
func (c *Card) GetParentCard(args Arguments) (*Card, error) {

	// Hopefully the card came pre-loaded with Actions including the card creation
	action := c.Actions.FirstCardCreateAction()

	if action == nil {
		// No luck. Go get copyCard actions for this card.
		c.client.log("Creation action wasn't supplied before GetParentCard() on '%s'. Getting copyCard actions.", c.ID)
		actions, err := c.GetActions(Arguments{"filter": "copyCard"})
		if err != nil {
			err = errors.Wrapf(err, "GetParentCard() failed to GetActions() for card '%s'", c.ID)
			return nil, err
		}
		action = actions.FirstCardCreateAction()
	}

	if action != nil && action.Data != nil && action.Data.CardSource != nil {
		card, err := c.client.GetCard(action.Data.CardSource.ID, args)
		return card, err
	}

	return nil, nil
}

func (c *Card) GetAncestorCards(args Arguments) (ancestors []*Card, err error) {

	// Get the first parent
	parent, err := c.GetParentCard(args)
	if IsNotFound(err) || IsPermissionDenied(err) {
		c.client.log("[trello] Can't get details about the parent of card '%s' due to lack of permissions or card deleted.", c.ID)
		return ancestors, nil
	}

	for parent != nil {
		ancestors = append(ancestors, parent)
		parent, err = parent.GetParentCard(args)
		if IsNotFound(err) || IsPermissionDenied(err) {
			c.client.log("[trello] Can't get details about the parent of card '%s' due to lack of permissions or card deleted.", c.ID)
			return ancestors, nil
		} else if err != nil {
			return ancestors, err
		}
	}

	return ancestors, err
}

func (c *Card) GetOriginatingCard(args Arguments) (*Card, error) {
	ancestors, err := c.GetAncestorCards(args)
	if err != nil {
		return c, err
	}
	if len(ancestors) > 0 {
		return ancestors[len(ancestors)-1], nil
	} else {
		return c, nil
	}
}

func (c *Card) CreatorMember() (*Member, error) {
	var actions ActionCollection
	var err error

	if len(c.Actions) == 0 {
		c.Actions, err = c.GetActions(Arguments{"filter": "all", "limit": "1000", "memberCreator_fields": "all"})
		if err != nil {
			err = errors.Wrapf(err, "GetActions() call failed.")
			return nil, err
		}
	}
	actions = c.Actions.FilterToCardCreationActions()

	if len(actions) > 0 {
		return actions[0].MemberCreator, nil
	}
	return nil, errors.Errorf("No card creation actions on Card %s with a .MemberCreator", c.ID)
}

func (c *Card) CreatorMemberID() (string, error) {

	var actions ActionCollection
	var err error

	if len(c.Actions) == 0 {
		c.client.log("[trello] CreatorMemberID() called on card '%s' without any Card.Actions. Fetching fresh.", c.ID)
		c.Actions, err = c.GetActions(Defaults())
		if err != nil {
			err = errors.Wrapf(err, "GetActions() call failed.")
		}
	}
	actions = c.Actions.FilterToCardCreationActions()

	if len(actions) > 0 {
		if actions[0].IDMemberCreator != "" {
			return actions[0].IDMemberCreator, err
		}
	}

	return "", errors.Wrapf(err, "No Actions on card '%s' could be used to find its creator.", c.ID)
}

func (b *Board) ContainsCopyOfCard(cardID string, args Arguments) (bool, error) {
	args["filter"] = "copyCard"
	actions, err := b.GetActions(args)
	if err != nil {
		err := errors.Wrapf(err, "GetCards() failed inside ContainsCopyOf() for board '%s' and card '%s'.", b.ID, cardID)
		return false, err
	}
	for _, action := range actions {
		if action.Data != nil && action.Data.CardSource != nil && action.Data.CardSource.ID == cardID {
			return true, nil
		}
	}
	return false, nil
}

func (c *Client) GetCard(cardID string, args Arguments) (card *Card, err error) {
	path := fmt.Sprintf("cards/%s", cardID)
	err = c.Get(path, args, &card)
	if card != nil {
		card.client = c
	}
	return card, err
}

/**
 * Retrieves all Cards on a Board
 *
 * If before
 */
func (b *Board) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("boards/%s/cards", b.ID)

	err = b.client.Get(path, args, &cards)

	// Naive implementation would return here. To make sure we get all
	// cards, we begin
	if len(cards) > 0 {
		moreCards := true
		for moreCards == true {
			nextCardBatch := make([]*Card, 0)
			args["before"] = EarliestCardID(cards)
			err = b.client.Get(path, args, &nextCardBatch)
			if len(nextCardBatch) > 0 {
				cards = append(cards, nextCardBatch...)
			} else {
				moreCards = false
			}
		}
	}

	for i := range cards {
		cards[i].client = b.client
	}

	return
}

/**
 * Retrieves all Cards in a List
 */
func (l *List) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	err = l.client.Get(path, args, &cards)
	for i := range cards {
		cards[i].client = l.client
	}
	return
}

func EarliestCardID(cards []*Card) string {
	if len(cards) == 0 {
		return ""
	}
	earliest := cards[0].ID
	for _, card := range cards {
		if card.ID < earliest {
			earliest = card.ID
		}
	}
	return earliest
}
<commit_msg>Adds Client.RemoveMember().<commit_after>// Copyright © 2016 Aaron Longwell
//
// Use of this source code is governed by an MIT licese.
// Details in the LICENSE file.

package trello

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
)

type Card struct {
	client *Client

	// Key metadata
	ID               string     `json:"id"`
	IDShort          int        `json:"idShort"`
	Name             string     `json:"name"`
	Pos              float64    `json:"pos"`
	Email            string     `json:"email"`
	ShortLink        string     `json:"shortLink"`
	ShortUrl         string     `json:"shortUrl"`
	Url              string     `json:"url"`
	Desc             string     `json:"desc"`
	Due              *time.Time `json:"due"`
	DueComplete      bool       `json:"dueComplete"`
	Closed           bool       `json:"closed"`
	Subscribed       bool       `json:"subscribed"`
	DateLastActivity *time.Time `json:"dateLastActivity"`

	// Board
	Board   *Board
	IDBoard string `json:"idBoard"`

	// List
	List   *List
	IDList string `json:"idList"`

	// Badges
	Badges struct {
		Votes              int        `json:"votes"`
		ViewingMemberVoted bool       `json:"viewingMemberVoted"`
		Subscribed         bool       `json:"subscribed"`
		Fogbugz            string     `json:"fogbugz,omitempty"`
		CheckItems         int        `json:"checkItems"`
		CheckItemsChecked  int        `json:"checkItemsChecked"`
		Comments           int        `json:"comments"`
		Attachments        int        `json:"attachments"`
		Description        bool       `json:"description"`
		Due                *time.Time `json:"due,omitempty"`
	} `json:"badges"`

	// Actions
	Actions ActionCollection `json:"actions,omitempty"`

	// Checklists
	IDCheckLists    []string          `json:"idCheckLists"`
	Checklists      []*Checklist      `json:"checklists,omitempty"`
	CheckItemStates []*CheckItemState `json:"checkItemStates,omitempty"`

	// Members
	IDMembers      []string  `json:"idMembers,omitempty"`
	IDMembersVoted []string  `json:"idMembersVoted,omitempty"`
	Members        []*Member `json:"members,omitempty"`

	// Attachments
	IDAttachmentCover     string        `json:"idAttachmentCover"`
	ManualCoverAttachment bool          `json:"manualCoverAttachment"`
	Attachments           []*Attachment `json:"attachments,omitempty"`

	// Labels
	Labels []*Label `json:"labels,omitempty"`
}

func (c *Card) CreatedAt() time.Time {
	t, _ := IDToTime(c.ID)
	return t
}

func (c *Card) MoveToList(listID string, args Arguments) error {
	path := fmt.Sprintf("cards/%s", c.ID)
	args["idList"] = listID
	return c.client.Put(path, args, &c)
}

func (c *Card) SetPos(newPos float64) error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, Arguments{"pos": fmt.Sprintf("%f", newPos)}, c)
}

func (c *Card) RemoveMember(memberID string) error {
	path := fmt.Sprintf("cards/%s/idMembers/%s", c.ID, memberID)
	return c.client.Delete(path, Defaults(), nil)
}

func (c *Card) MoveToTopOfList() error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, Arguments{"pos": "top"}, c)
}

func (c *Card) MoveToBottomOfList() error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, Arguments{"pos": "bottom"}, c)
}

func (c *Card) Update(args Arguments) error {
	path := fmt.Sprintf("cards/%s", c.ID)
	return c.client.Put(path, args, c)
}

func (c *Client) CreateCard(card *Card, extraArgs Arguments) error {
	path := "cards"
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"pos":       strconv.FormatFloat(card.Pos, 'g', -1, 64),
		"idList":    card.IDList,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overriding the creation position with 'top' or 'botttom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := c.Post(path, args, &card)
	if err == nil {
		card.client = c
	}
	return err
}

func (l *List) AddCard(card *Card, extraArgs Arguments) error {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overwriting the creation position with 'top' or 'bottom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := l.client.Post(path, args, &card)
	if err == nil {
		card.client = l.client
	} else {
		err = errors.Wrapf(err, "Error adding card to list %s", l.ID)
	}
	return err
}

// Try these Arguments
//
// 	Arguments["keepFromSource"] = "all"
//  Arguments["keepFromSource"] = "none"
// 	Arguments["keepFromSource"] = "attachments,checklists,comments"
//
func (c *Card) CopyToList(listID string, args Arguments) (*Card, error) {
	path := "cards"
	args["idList"] = listID
	args["idCardSource"] = c.ID
	newCard := Card{}
	err := c.client.Post(path, args, &newCard)
	if err == nil {
		newCard.client = c.client
	} else {
		err = errors.Wrapf(err, "Error copying card '%s' to list '%s'.", c.ID, listID)
	}
	return &newCard, err
}

func (c *Card) AddComment(comment string, args Arguments) (*Action, error) {
	path := fmt.Sprintf("cards/%s/actions/comments", c.ID)
	args["text"] = comment
	action := Action{}
	err := c.client.Post(path, args, &action)
	if err != nil {
		err = errors.Wrapf(err, "Error commenting on card %s", c.ID)
	}
	return &action, err
}

// If this Card was created from a copy of another Card, this func retrieves
// the originating Card. Returns an error only when a low-level failure occurred.
// If this Card has no parent, a nil card and nil error are returned. In other words, the
// non-existence of a parent is not treated as an error.
//
func (c *Card) GetParentCard(args Arguments) (*Card, error) {

	// Hopefully the card came pre-loaded with Actions including the card creation
	action := c.Actions.FirstCardCreateAction()

	if action == nil {
		// No luck. Go get copyCard actions for this card.
		c.client.log("Creation action wasn't supplied before GetParentCard() on '%s'. Getting copyCard actions.", c.ID)
		actions, err := c.GetActions(Arguments{"filter": "copyCard"})
		if err != nil {
			err = errors.Wrapf(err, "GetParentCard() failed to GetActions() for card '%s'", c.ID)
			return nil, err
		}
		action = actions.FirstCardCreateAction()
	}

	if action != nil && action.Data != nil && action.Data.CardSource != nil {
		card, err := c.client.GetCard(action.Data.CardSource.ID, args)
		return card, err
	}

	return nil, nil
}

func (c *Card) GetAncestorCards(args Arguments) (ancestors []*Card, err error) {

	// Get the first parent
	parent, err := c.GetParentCard(args)
	if IsNotFound(err) || IsPermissionDenied(err) {
		c.client.log("[trello] Can't get details about the parent of card '%s' due to lack of permissions or card deleted.", c.ID)
		return ancestors, nil
	}

	for parent != nil {
		ancestors = append(ancestors, parent)
		parent, err = parent.GetParentCard(args)
		if IsNotFound(err) || IsPermissionDenied(err) {
			c.client.log("[trello] Can't get details about the parent of card '%s' due to lack of permissions or card deleted.", c.ID)
			return ancestors, nil
		} else if err != nil {
			return ancestors, err
		}
	}

	return ancestors, err
}

func (c *Card) GetOriginatingCard(args Arguments) (*Card, error) {
	ancestors, err := c.GetAncestorCards(args)
	if err != nil {
		return c, err
	}
	if len(ancestors) > 0 {
		return ancestors[len(ancestors)-1], nil
	} else {
		return c, nil
	}
}

func (c *Card) CreatorMember() (*Member, error) {
	var actions ActionCollection
	var err error

	if len(c.Actions) == 0 {
		c.Actions, err = c.GetActions(Arguments{"filter": "all", "limit": "1000", "memberCreator_fields": "all"})
		if err != nil {
			err = errors.Wrapf(err, "GetActions() call failed.")
			return nil, err
		}
	}
	actions = c.Actions.FilterToCardCreationActions()

	if len(actions) > 0 {
		return actions[0].MemberCreator, nil
	}
	return nil, errors.Errorf("No card creation actions on Card %s with a .MemberCreator", c.ID)
}

func (c *Card) CreatorMemberID() (string, error) {

	var actions ActionCollection
	var err error

	if len(c.Actions) == 0 {
		c.client.log("[trello] CreatorMemberID() called on card '%s' without any Card.Actions. Fetching fresh.", c.ID)
		c.Actions, err = c.GetActions(Defaults())
		if err != nil {
			err = errors.Wrapf(err, "GetActions() call failed.")
		}
	}
	actions = c.Actions.FilterToCardCreationActions()

	if len(actions) > 0 {
		if actions[0].IDMemberCreator != "" {
			return actions[0].IDMemberCreator, err
		}
	}

	return "", errors.Wrapf(err, "No Actions on card '%s' could be used to find its creator.", c.ID)
}

func (b *Board) ContainsCopyOfCard(cardID string, args Arguments) (bool, error) {
	args["filter"] = "copyCard"
	actions, err := b.GetActions(args)
	if err != nil {
		err := errors.Wrapf(err, "GetCards() failed inside ContainsCopyOf() for board '%s' and card '%s'.", b.ID, cardID)
		return false, err
	}
	for _, action := range actions {
		if action.Data != nil && action.Data.CardSource != nil && action.Data.CardSource.ID == cardID {
			return true, nil
		}
	}
	return false, nil
}

func (c *Client) GetCard(cardID string, args Arguments) (card *Card, err error) {
	path := fmt.Sprintf("cards/%s", cardID)
	err = c.Get(path, args, &card)
	if card != nil {
		card.client = c
	}
	return card, err
}

/**
 * Retrieves all Cards on a Board
 *
 * If before
 */
func (b *Board) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("boards/%s/cards", b.ID)

	err = b.client.Get(path, args, &cards)

	// Naive implementation would return here. To make sure we get all
	// cards, we begin
	if len(cards) > 0 {
		moreCards := true
		for moreCards == true {
			nextCardBatch := make([]*Card, 0)
			args["before"] = EarliestCardID(cards)
			err = b.client.Get(path, args, &nextCardBatch)
			if len(nextCardBatch) > 0 {
				cards = append(cards, nextCardBatch...)
			} else {
				moreCards = false
			}
		}
	}

	for i := range cards {
		cards[i].client = b.client
	}

	return
}

/**
 * Retrieves all Cards in a List
 */
func (l *List) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	err = l.client.Get(path, args, &cards)
	for i := range cards {
		cards[i].client = l.client
	}
	return
}

func EarliestCardID(cards []*Card) string {
	if len(cards) == 0 {
		return ""
	}
	earliest := cards[0].ID
	for _, card := range cards {
		if card.ID < earliest {
			earliest = card.ID
		}
	}
	return earliest
}
<|endoftext|>


<commit_before>package handler

import (
	"time"

	"github.com/gsdocker/gslogger"
	"github.com/gsrpc/gorpc"
)

type _HeartbeatHandler struct {
	gslogger.Log               // Mixin log APIs
	timeout      time.Duration // timeout
	context      gorpc.Context // handler context
	exitflag     chan bool     // exit flag
	flag         uint32        // flag
	timestamp    time.Time     // last received heartbeat timestamp
}

// NewHeartbeatHandler create new heartbeat handler
func NewHeartbeatHandler(timeout time.Duration) gorpc.Handler {
	return &_HeartbeatHandler{
		Log:       gslogger.Get("heartbeat"),
		timeout:   timeout,
		timestamp: time.Now(),
	}
}

func (handler *_HeartbeatHandler) Register(context gorpc.Context) error {
	return nil
}

func (handler *_HeartbeatHandler) Active(context gorpc.Context) error {

	if handler.exitflag == nil {

		handler.context = context

		handler.exitflag = make(chan bool)

		handler.timestamp = time.Now()

		if handler.timeout != 0 {
			go handler.timeoutLoop(context, handler.exitflag)
		}
	}

	return nil
}

func (handler *_HeartbeatHandler) Unregister(context gorpc.Context) {
}

func (handler *_HeartbeatHandler) Inactive(context gorpc.Context) {

	if handler.exitflag != nil {

		close(handler.exitflag)

		handler.exitflag = nil
	}

}

func (handler *_HeartbeatHandler) timeoutLoop(context gorpc.Context, exitflag chan bool) {

	wheel := context.Pipeline().EventLoop().TimeWheel()

	ticker := wheel.NewTicker(handler.timeout)

	defer ticker.Stop()

	for {

		select {
		case <-ticker.C:

			if time.Now().Sub(handler.timestamp) > handler.timeout*2 {
				handler.context.Close()
				handler.W("heartbeat timeout(%s), close current pipeline(%s)", handler.timeout*2, handler.context.Pipeline())
				return
			}

			message := gorpc.NewMessage()

			message.Code = gorpc.CodeHeartbeat

			handler.context.Send(message)

			handler.V("%s send heartbeat message", handler.context.Pipeline())

		case <-exitflag:
			handler.I("exit heartbeat loop .....................")
			return
		}
	}
}

func (handler *_HeartbeatHandler) MessageReceived(context gorpc.Context, message *gorpc.Message) (*gorpc.Message, error) {

	if message.Code == gorpc.CodeHeartbeat {

		handler.V("%s recv heartbeat message", handler.context.Pipeline())

		if handler.timeout != 0 {
			handler.timestamp = time.Now()
		}

		return nil, nil
	}

	return message, nil
}
func (handler *_HeartbeatHandler) MessageSending(context gorpc.Context, message *gorpc.Message) (*gorpc.Message, error) {
	return message, nil
}

func (handler *_HeartbeatHandler) Panic(context gorpc.Context, err error) {

}
<commit_msg>fix bug<commit_after>package handler

import (
	"time"

	"github.com/gsdocker/gslogger"
	"github.com/gsrpc/gorpc"
)

type _HeartbeatHandler struct {
	gslogger.Log               // Mixin log APIs
	timeout      time.Duration // timeout
	context      gorpc.Context // handler context
	exitflag     chan bool     // exit flag
	flag         uint32        // flag
	timestamp    time.Time     // last received heartbeat timestamp
}

// NewHeartbeatHandler create new heartbeat handler
func NewHeartbeatHandler(timeout time.Duration) gorpc.Handler {
	return &_HeartbeatHandler{
		Log:       gslogger.Get("heartbeat"),
		timeout:   timeout,
		timestamp: time.Now(),
	}
}

func (handler *_HeartbeatHandler) Register(context gorpc.Context) error {
	return nil
}

func (handler *_HeartbeatHandler) Active(context gorpc.Context) error {

	if handler.exitflag == nil {

		handler.context = context

		handler.exitflag = make(chan bool)

		handler.timestamp = time.Now()

		if handler.timeout != 0 {
			go handler.timeoutLoop(context, handler.exitflag)
		}
	}

	return nil
}

func (handler *_HeartbeatHandler) Unregister(context gorpc.Context) {
}

func (handler *_HeartbeatHandler) Inactive(context gorpc.Context) {

	if handler.exitflag != nil {

		close(handler.exitflag)

		handler.exitflag = nil
	}

}

func (handler *_HeartbeatHandler) timeoutLoop(context gorpc.Context, exitflag chan bool) {

	wheel := context.Pipeline().EventLoop().TimeWheel()

	ticker := wheel.NewTicker(handler.timeout)

	defer ticker.Stop()

	for {

		select {
		case <-ticker.C:

			if time.Now().Sub(handler.timestamp) > handler.timeout*2 {
				handler.context.Close()
				handler.W("heartbeat timeout(%s), close current pipeline(%s)", handler.timeout*2, handler.context.Pipeline())
				return
			}

			message := gorpc.NewMessage()

			message.Code = gorpc.CodeHeartbeat

			handler.context.Send(message)

			handler.V("%s send heartbeat message", handler.context.Pipeline())

		case <-exitflag:
			handler.I("exit heartbeat loop .....................")
			return
		}
	}
}

func (handler *_HeartbeatHandler) MessageReceived(context gorpc.Context, message *gorpc.Message) (*gorpc.Message, error) {

	if message.Code == gorpc.CodeHeartbeat {

		handler.V("%s recv heartbeat message", context.Pipeline())

		if handler.timeout != 0 {
			handler.timestamp = time.Now()
		}

		return nil, nil
	}

	return message, nil
}
func (handler *_HeartbeatHandler) MessageSending(context gorpc.Context, message *gorpc.Message) (*gorpc.Message, error) {
	return message, nil
}

func (handler *_HeartbeatHandler) Panic(context gorpc.Context, err error) {

}
<|endoftext|>


<commit_before>package gcs_test

import (
	"bytes"
	"io"
	"log"
	"reflect"
	"testing"

	"github.com/nyaxt/otaru/flags"
	authtu "github.com/nyaxt/otaru/gcloud/auth/testutils"
	"github.com/nyaxt/otaru/gcloud/gcs"
	tu "github.com/nyaxt/otaru/testutils"
)

func testGCSBlobStore() *gcs.GCSBlobStore {
	bs, err := gcs.NewGCSBlobStore(
		authtu.TestConfig().ProjectName,
		authtu.TestBucketName(),
		authtu.TestTokenSource(),
		flags.O_RDWR,
	)
	if err != nil {
		log.Fatalf("Failed to create GCSBlobStore: %v", err)
	}
	return bs
}

func TestGCSBlobStore_WriteReadDelete(t *testing.T) {
	bs := testGCSBlobStore()

	// Write
	{
		w, err := bs.OpenWriter("hoge")
		if err != nil {
			t.Errorf("Failed to open writer: %v", err)
			return
		}

		n, err := w.Write(tu.HelloWorld)
		if err != nil {
			t.Errorf("Write failed: %v", err)
			return
		}
		if n != len(tu.HelloWorld) {
			t.Errorf("Write returned unexpected len: %d", n)
			return
		}
		if err := w.Close(); err != nil {
			t.Errorf("Failed to close writer: %v", err)
			return
		}
	}

	// Read
	{
		r, err := bs.OpenReader("hoge")
		if err != nil {
			t.Errorf("Failed to open reader: %v", err)
			return
		}

		buf := make([]byte, len(tu.HelloWorld))
		if _, err = io.ReadFull(r, buf); err != nil {
			t.Errorf("ReadFull failed: %v", err)
			return
		}
		if !bytes.Equal(tu.HelloWorld, buf) {
			t.Errorf("Read content != Write content")
		}

		if err := r.Close(); err != nil {
			t.Errorf("Failed to close reader: %v", err)
			return
		}
	}

	// ListBlobs
	{
		bpaths, err := bs.ListBlobs()
		if err != nil {
			t.Errorf("Failed to ListBlobs(): %v", err)
			return
		}

		if !reflect.DeepEqual([]string{"hoge"}, bpaths) {
			t.Errorf("Unexpected BlobList: %v", bpaths)
		}
	}

	// Delete
	if err := bs.RemoveBlob("hoge"); err != nil {
		t.Errorf("Failed to remove blob: %v", err)
	}
}
<commit_msg>TestGCSBlobStore_ReadOnly<commit_after>package gcs_test

import (
	"bytes"
	"io"
	"log"
	"reflect"
	"sort"
	"testing"

	"github.com/nyaxt/otaru/flags"
	authtu "github.com/nyaxt/otaru/gcloud/auth/testutils"
	"github.com/nyaxt/otaru/gcloud/gcs"
	tu "github.com/nyaxt/otaru/testutils"
	"github.com/nyaxt/otaru/util"
)

func testGCSBlobStore(f int) *gcs.GCSBlobStore {
	bs, err := gcs.NewGCSBlobStore(
		authtu.TestConfig().ProjectName,
		authtu.TestBucketName(),
		authtu.TestTokenSource(),
		f,
	)
	if err != nil {
		log.Fatalf("Failed to create GCSBlobStore: %v", err)
	}
	return bs
}

func TestGCSBlobStore_WriteReadDelete(t *testing.T) {
	bs := testGCSBlobStore(flags.O_RDWR)

	// Write
	{
		w, err := bs.OpenWriter("hoge")
		if err != nil {
			t.Errorf("Failed to open writer: %v", err)
			return
		}

		n, err := w.Write(tu.HelloWorld)
		if err != nil {
			t.Errorf("Write failed: %v", err)
			return
		}
		if n != len(tu.HelloWorld) {
			t.Errorf("Write returned unexpected len: %d", n)
			return
		}
		if err := w.Close(); err != nil {
			t.Errorf("Failed to close writer: %v", err)
			return
		}
	}

	// Read
	{
		r, err := bs.OpenReader("hoge")
		if err != nil {
			t.Errorf("Failed to open reader: %v", err)
			return
		}

		buf := make([]byte, len(tu.HelloWorld))
		if _, err = io.ReadFull(r, buf); err != nil {
			t.Errorf("ReadFull failed: %v", err)
			return
		}
		if !bytes.Equal(tu.HelloWorld, buf) {
			t.Errorf("Read content != Write content")
		}

		if err := r.Close(); err != nil {
			t.Errorf("Failed to close reader: %v", err)
			return
		}
	}

	// ListBlobs
	{
		bpaths, err := bs.ListBlobs()
		if err != nil {
			t.Errorf("Failed to ListBlobs(): %v", err)
			return
		}

		if !reflect.DeepEqual([]string{"hoge"}, bpaths) {
			t.Errorf("Unexpected BlobList: %v", bpaths)
		}
	}

	// Delete
	if err := bs.RemoveBlob("hoge"); err != nil {
		t.Errorf("Failed to remove blob: %v", err)
	}
}

func TestGCSBlobStore_ReadOnly(t *testing.T) {
	wbs := testGCSBlobStore(flags.O_RDWR)
	defer func() {
		_ = wbs.RemoveBlob("hoge")
		_ = wbs.RemoveBlob("fuga")
	}()
	if err := tu.WriteVersionedBlob(wbs, "hoge", 42); err != nil {
		t.Errorf("%v", err)
		return
	}
	if err := tu.WriteVersionedBlob(wbs, "fuga", 123); err != nil {
		t.Errorf("%v", err)
		return
	}

	// Read should work just fine
	rbs := testGCSBlobStore(flags.O_RDONLY)
	if err := tu.AssertBlobVersion(rbs, "hoge", 42); err != nil {
		t.Errorf("assert hoge 42. err: %v", err)
	}
	if err := tu.AssertBlobVersion(rbs, "fuga", 123); err != nil {
		t.Errorf("assert fuga 123. err: %v", err)
	}

	// Delete should fail
	err := rbs.RemoveBlob("hoge")
	if err == nil {
		t.Errorf("Unexpected RemoveBlob success.")
		return
	}
	if err != util.EPERM {
		t.Errorf("Expected EPERM. got %v", err)
		return
	}

	// Write should fail
	_, err = rbs.OpenWriter("new")
	if err == nil {
		t.Errorf("Unexpected OpenWriter success.")
		return
	}
	if err != util.EPERM {
		t.Errorf("Expected EPERM. got %v", err)
		return
	}

	// ListBlobs should work
	{
		bpaths, err := rbs.ListBlobs()
		if err != nil {
			t.Errorf("Failed to ListBlobs(): %v", err)
			return
		}
		sort.Strings(bpaths)
		if !reflect.DeepEqual([]string{"fuga", "hoge"}, bpaths) {
			t.Errorf("Unexpected BlobList: %v", bpaths)
		}
	}
}
<|endoftext|>


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

// Tool to get xDS configs from pilot. This tool simulate envoy sidecar gRPC call to get config,
// so it will work even when sidecar haswhen sidecar hasn't connected (e.g in the case of pilot running on local machine))
//
// Usage:
//
// First, you can either manually expose pilot gRPC port or rely on this tool to port-forward pilot by omitting -pilot_url flag:
//
// * By port-forward existing pilot:
// ```bash
// kubectl port-forward $(kubectl get pod -l app=istiod -o jsonpath='{.items[0].metadata.name}' -n istio-system) -n istio-system 15010
// ```
// * Or run local pilot using the same k8s config.
// ```bash
// pilot-discovery discovery --kubeconfig=${HOME}/.kube/config
// ```
//
// To get LDS or CDS, use -type lds or -type cds, and provide the pod id or app label. For example:
// ```bash
// go run pilot_cli.go --type lds --proxytag httpbin-5766dd474b-2hlnx  # --res will be ignored
// go run pilot_cli.go --type lds --proxytag httpbin
// ```
// Note If more than one pod match with the app label, one will be picked arbitrarily.
//
// For EDS/RDS, provide comma-separated-list of corresponding clusters or routes name. For example:
// ```bash
// go run ./pilot/tools/debug/pilot_cli.go --type eds --proxytag httpbin \
// --res "inbound|http||sleep.default.svc.cluster.local,outbound|http||httpbin.default.svc.cluster.local"
// ```
//
// Script requires kube config in order to connect to k8s registry to get pod information (for LDS and CDS type). The default
// value for kubeconfig path is .kube/config in home folder (works for Linux only). It can be changed via -kubeconfig flag.
// ```bash
// go run ./pilot/debug/pilot_cli.go --type lds --proxytag httpbin --kubeconfig path/to/kube/config
// ```

package main

import (
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	"google.golang.org/grpc"
	v1 "k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"k8s.io/client-go/tools/clientcmd"

	v3 "istio.io/istio/pilot/pkg/xds/v3"
	"istio.io/istio/pkg/util/gogoprotomarshal"
	"istio.io/pkg/env"
	"istio.io/pkg/log"
)

const (
	LocalPortStart = 50000
	LocalPortEnd   = 60000
)

// PodInfo holds information to identify pod.
type PodInfo struct {
	Name      string
	Namespace string
	IP        string
	ProxyType string
}

func getAllPods(kubeconfig string) (*v1.PodList, error) {
	cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, err
	}
	return clientset.CoreV1().Pods(meta_v1.NamespaceAll).List(context.TODO(), meta_v1.ListOptions{})
}

func NewPodInfo(nameOrAppLabel string, kubeconfig string, proxyType string) *PodInfo {
	log.Infof("Using kube config at %s", kubeconfig)
	pods, err := getAllPods(kubeconfig)
	if err != nil {
		log.Errorf(err.Error())
		return nil
	}

	for _, pod := range pods.Items {
		log.Infof("pod %q", pod.Name)
		if pod.Name == nameOrAppLabel {
			log.Infof("Found pod %s.%s~%s matching name %q", pod.Name, pod.Namespace, pod.Status.PodIP, nameOrAppLabel)
			return &PodInfo{
				Name:      pod.Name,
				Namespace: pod.Namespace,
				IP:        pod.Status.PodIP,
				ProxyType: proxyType,
			}
		}
		if app, ok := pod.ObjectMeta.Labels["app"]; ok && app == nameOrAppLabel {
			log.Infof("Found pod %s.%s~%s matching app label %q", pod.Name, pod.Namespace, pod.Status.PodIP, nameOrAppLabel)
			return &PodInfo{
				Name:      pod.Name,
				Namespace: pod.Namespace,
				IP:        pod.Status.PodIP,
				ProxyType: proxyType,
			}
		}
		if istio, ok := pod.ObjectMeta.Labels["istio"]; ok && istio == nameOrAppLabel {
			log.Infof("Found pod %s.%s~%s matching app label %q", pod.Name, pod.Namespace, pod.Status.PodIP, nameOrAppLabel)
			return &PodInfo{
				Name:      pod.Name,
				Namespace: pod.Namespace,
				IP:        pod.Status.PodIP,
			}
		}
	}
	log.Warnf("Cannot find pod with name or app label matching %q in registry.", nameOrAppLabel)
	return nil
}

func (p PodInfo) makeNodeID() string {
	if p.ProxyType != "" {
		return fmt.Sprintf("%s~%s~%s.%s~%s.svc.cluster.local", p.ProxyType, p.IP, p.Name, p.Namespace, p.Namespace)
	}
	if strings.HasPrefix(p.Name, "istio-ingressgateway") || strings.HasPrefix(p.Name, "istio-egressgateway") {
		return fmt.Sprintf("router~%s~%s.%s~%s.svc.cluster.local", p.IP, p.Name, p.Namespace, p.Namespace)
	}
	if strings.HasPrefix(p.Name, "istio-ingress") {
		return fmt.Sprintf("ingress~%s~%s.%s~%s.svc.cluster.local", p.IP, p.Name, p.Namespace, p.Namespace)
	}
	return fmt.Sprintf("sidecar~%s~%s.%s~%s.svc.cluster.local", p.IP, p.Name, p.Namespace, p.Namespace)
}

func configTypeToTypeURL(configType string) string {
	switch configType {
	case "lds":
		return v3.ListenerType
	case "cds":
		return v3.ClusterType
	case "rds":
		return v3.RouteType
	case "eds":
		return v3.EndpointType
	default:
		panic(fmt.Sprintf("Unknown type %s", configType))
	}
}

func (p PodInfo) makeRequest(configType string) *discovery.DiscoveryRequest {
	return &discovery.DiscoveryRequest{
		Node: &core.Node{
			Id: p.makeNodeID(),
		},
		TypeUrl: configTypeToTypeURL(configType),
	}
}

func (p PodInfo) appendResources(req *discovery.DiscoveryRequest, resources []string) *discovery.DiscoveryRequest {
	req.ResourceNames = resources
	return req
}

func (p PodInfo) getXdsResponse(pilotURL string, req *discovery.DiscoveryRequest) (*discovery.DiscoveryResponse, error) {
	conn, err := grpc.Dial(pilotURL, grpc.WithInsecure())
	if err != nil {
		panic(err.Error())
	}
	defer func() { _ = conn.Close() }()

	adsClient := discovery.NewAggregatedDiscoveryServiceClient(conn)
	stream, err := adsClient.StreamAggregatedResources(context.Background())
	if err != nil {
		panic(err.Error())
	}
	err = stream.Send(req)
	if err != nil {
		panic(err.Error())
	}
	res, err := stream.Recv()
	if err != nil {
		panic(err.Error())
	}
	return res, err
}

var homeVar = env.RegisterStringVar("HOME", "", "")

func resolveKubeConfigPath(kubeConfig string) string {
	path := strings.Replace(kubeConfig, "~", homeVar.Get(), 1)
	ret, err := filepath.Abs(path)
	if err != nil {
		panic(err.Error())
	}
	return ret
}

// nolint: golint
func portForwardPilot(kubeConfig, pilotURL string) (*os.Process, string, error) {
	if pilotURL != "" {
		// No need to port-forward, url is already provided.
		return nil, pilotURL, nil
	}
	log.Info("Pilot url is not provided, try to port-forward pilot pod.")

	podName := ""
	pods, err := getAllPods(kubeConfig)
	if err != nil {
		return nil, "", err
	}
	for _, pod := range pods.Items {
		if app, ok := pod.ObjectMeta.Labels["istio"]; ok && app == "istiod" {
			podName = pod.Name
		}
	}
	if podName == "" {
		return nil, "", fmt.Errorf("cannot find istio-pilot pod")
	}

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	localPort := r.Intn(LocalPortEnd-LocalPortStart) + LocalPortStart
	cmd := fmt.Sprintf("kubectl port-forward %s -n istio-system %d:15010", podName, localPort)
	parts := strings.Split(cmd, " ")
	c := exec.Command(parts[0], parts[1:]...)
	err = c.Start()
	if err != nil {
		return nil, "", err
	}
	// Make sure istio-pilot is reachable.
	reachable := false
	url := fmt.Sprintf("localhost:%d", localPort)
	for i := 0; i < 10 && !reachable; i++ {
		conn, err := net.Dial("tcp", url)
		if err == nil {
			_ = conn.Close()
			reachable = true
		}
		time.Sleep(1 * time.Second)
	}
	if !reachable {
		return nil, "", fmt.Errorf("cannot reach local pilot url: %s", url)
	}
	return c.Process, fmt.Sprintf("localhost:%d", localPort), nil
}

func main() {
	kubeConfig := flag.String("kubeconfig", "~/.kube/config", "path to the kubeconfig file. Default is ~/.kube/config")
	pilotURL := flag.String("pilot", "", "pilot address. Will try port forward if not provided.")
	configType := flag.String("type", "lds", "lds, cds, or eds. Default lds.")
	proxyType := flag.String("proxytype", "", "sidecar, ingress, router.")
	proxyTag := flag.String("proxytag", "", "Pod name or app label or istio label to identify the proxy.")
	resources := flag.String("res", "", "Resource(s) to get config for. LDS/CDS should leave it empty.")
	outputFile := flag.String("out", "", "output file. Leave blank to go to stdout")
	flag.Parse()

	process, pilot, err := portForwardPilot(resolveKubeConfigPath(*kubeConfig), *pilotURL)
	if err != nil {
		log.Errorf("pilot port forward failed: %v", err)
		return
	}
	defer func() {
		if process != nil {
			err := process.Kill()
			if err != nil {
				log.Errorf("Failed to kill port-forward process, pid: %d", process.Pid)
			}
		}
	}()
	pod := NewPodInfo(*proxyTag, resolveKubeConfigPath(*kubeConfig), *proxyType)

	var resp *discovery.DiscoveryResponse
	switch *configType {
	case "lds", "cds":
		resp, err = pod.getXdsResponse(pilot, pod.makeRequest(*configType))
	case "rds", "eds":
		resp, err = pod.getXdsResponse(pilot, pod.appendResources(pod.makeRequest(*configType), strings.Split(*resources, ",")))
	default:
		log.Errorf("Unknown config type: %q", *configType)
		os.Exit(1)
	}

	if err != nil {
		log.Errorf("Failed to get Xds response for %v. Error: %v", *resources, err)
		return
	}
	strResponse, _ := gogoprotomarshal.ToJSONWithIndent(resp, " ")
	if outputFile == nil || *outputFile == "" {
		fmt.Printf("%v\n", strResponse)
	} else if err := ioutil.WriteFile(*outputFile, []byte(strResponse), 0644); err != nil {
		log.Errorf("Cannot write output to file %q", *outputFile)
	}
}
<commit_msg>fix labels (#31543)<commit_after>// Copyright Istio 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.

// Tool to get xDS configs from pilot. This tool simulate envoy sidecar gRPC call to get config,
// so it will work even when sidecar haswhen sidecar hasn't connected (e.g in the case of pilot running on local machine))
//
// Usage:
//
// First, you can either manually expose pilot gRPC port or rely on this tool to port-forward pilot by omitting -pilot_url flag:
//
// * By port-forward existing pilot:
// ```bash
// kubectl port-forward $(kubectl get pod -l app=istiod -o jsonpath='{.items[0].metadata.name}' -n istio-system) -n istio-system 15010
// ```
// * Or run local pilot using the same k8s config.
// ```bash
// pilot-discovery discovery --kubeconfig=${HOME}/.kube/config
// ```
//
// To get LDS or CDS, use -type lds or -type cds, and provide the pod id or app label. For example:
// ```bash
// go run pilot_cli.go --type lds --proxytag httpbin-5766dd474b-2hlnx  # --res will be ignored
// go run pilot_cli.go --type lds --proxytag httpbin
// ```
// Note If more than one pod match with the app label, one will be picked arbitrarily.
//
// For EDS/RDS, provide comma-separated-list of corresponding clusters or routes name. For example:
// ```bash
// go run ./pilot/tools/debug/pilot_cli.go --type eds --proxytag httpbin \
// --res "inbound|http||sleep.default.svc.cluster.local,outbound|http||httpbin.default.svc.cluster.local"
// ```
//
// Script requires kube config in order to connect to k8s registry to get pod information (for LDS and CDS type). The default
// value for kubeconfig path is .kube/config in home folder (works for Linux only). It can be changed via -kubeconfig flag.
// ```bash
// go run ./pilot/debug/pilot_cli.go --type lds --proxytag httpbin --kubeconfig path/to/kube/config
// ```

package main

import (
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	"google.golang.org/grpc"
	v1 "k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"k8s.io/client-go/tools/clientcmd"

	v3 "istio.io/istio/pilot/pkg/xds/v3"
	"istio.io/istio/pkg/util/gogoprotomarshal"
	"istio.io/pkg/env"
	"istio.io/pkg/log"
)

const (
	LocalPortStart = 50000
	LocalPortEnd   = 60000
)

// PodInfo holds information to identify pod.
type PodInfo struct {
	Name      string
	Namespace string
	IP        string
	ProxyType string
}

func getAllPods(kubeconfig string) (*v1.PodList, error) {
	cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, err
	}
	return clientset.CoreV1().Pods(meta_v1.NamespaceAll).List(context.TODO(), meta_v1.ListOptions{})
}

func NewPodInfo(nameOrAppLabel string, kubeconfig string, proxyType string) *PodInfo {
	log.Infof("Using kube config at %s", kubeconfig)
	pods, err := getAllPods(kubeconfig)
	if err != nil {
		log.Errorf(err.Error())
		return nil
	}

	for _, pod := range pods.Items {
		log.Infof("pod %q", pod.Name)
		if pod.Name == nameOrAppLabel {
			log.Infof("Found pod %s.%s~%s matching name %q", pod.Name, pod.Namespace, pod.Status.PodIP, nameOrAppLabel)
			return &PodInfo{
				Name:      pod.Name,
				Namespace: pod.Namespace,
				IP:        pod.Status.PodIP,
				ProxyType: proxyType,
			}
		}
		if app, ok := pod.ObjectMeta.Labels["app"]; ok && app == nameOrAppLabel {
			log.Infof("Found pod %s.%s~%s matching app label %q", pod.Name, pod.Namespace, pod.Status.PodIP, nameOrAppLabel)
			return &PodInfo{
				Name:      pod.Name,
				Namespace: pod.Namespace,
				IP:        pod.Status.PodIP,
				ProxyType: proxyType,
			}
		}
		if istio, ok := pod.ObjectMeta.Labels["istio"]; ok && istio == nameOrAppLabel {
			log.Infof("Found pod %s.%s~%s matching app label %q", pod.Name, pod.Namespace, pod.Status.PodIP, nameOrAppLabel)
			return &PodInfo{
				Name:      pod.Name,
				Namespace: pod.Namespace,
				IP:        pod.Status.PodIP,
			}
		}
	}
	log.Warnf("Cannot find pod with name or app label matching %q in registry.", nameOrAppLabel)
	return nil
}

func (p PodInfo) makeNodeID() string {
	if p.ProxyType != "" {
		return fmt.Sprintf("%s~%s~%s.%s~%s.svc.cluster.local", p.ProxyType, p.IP, p.Name, p.Namespace, p.Namespace)
	}
	if strings.HasPrefix(p.Name, "istio-ingressgateway") || strings.HasPrefix(p.Name, "istio-egressgateway") {
		return fmt.Sprintf("router~%s~%s.%s~%s.svc.cluster.local", p.IP, p.Name, p.Namespace, p.Namespace)
	}
	if strings.HasPrefix(p.Name, "istio-ingress") {
		return fmt.Sprintf("ingress~%s~%s.%s~%s.svc.cluster.local", p.IP, p.Name, p.Namespace, p.Namespace)
	}
	return fmt.Sprintf("sidecar~%s~%s.%s~%s.svc.cluster.local", p.IP, p.Name, p.Namespace, p.Namespace)
}

func configTypeToTypeURL(configType string) string {
	switch configType {
	case "lds":
		return v3.ListenerType
	case "cds":
		return v3.ClusterType
	case "rds":
		return v3.RouteType
	case "eds":
		return v3.EndpointType
	default:
		panic(fmt.Sprintf("Unknown type %s", configType))
	}
}

func (p PodInfo) makeRequest(configType string) *discovery.DiscoveryRequest {
	return &discovery.DiscoveryRequest{
		Node: &core.Node{
			Id: p.makeNodeID(),
		},
		TypeUrl: configTypeToTypeURL(configType),
	}
}

func (p PodInfo) appendResources(req *discovery.DiscoveryRequest, resources []string) *discovery.DiscoveryRequest {
	req.ResourceNames = resources
	return req
}

func (p PodInfo) getXdsResponse(pilotURL string, req *discovery.DiscoveryRequest) (*discovery.DiscoveryResponse, error) {
	conn, err := grpc.Dial(pilotURL, grpc.WithInsecure())
	if err != nil {
		panic(err.Error())
	}
	defer func() { _ = conn.Close() }()

	adsClient := discovery.NewAggregatedDiscoveryServiceClient(conn)
	stream, err := adsClient.StreamAggregatedResources(context.Background())
	if err != nil {
		panic(err.Error())
	}
	err = stream.Send(req)
	if err != nil {
		panic(err.Error())
	}
	res, err := stream.Recv()
	if err != nil {
		panic(err.Error())
	}
	return res, err
}

var homeVar = env.RegisterStringVar("HOME", "", "")

func resolveKubeConfigPath(kubeConfig string) string {
	path := strings.Replace(kubeConfig, "~", homeVar.Get(), 1)
	ret, err := filepath.Abs(path)
	if err != nil {
		panic(err.Error())
	}
	return ret
}

// nolint: golint
func portForwardPilot(kubeConfig, pilotURL string) (*os.Process, string, error) {
	if pilotURL != "" {
		// No need to port-forward, url is already provided.
		return nil, pilotURL, nil
	}
	log.Info("Pilot url is not provided, try to port-forward pilot pod.")

	podName := ""
	pods, err := getAllPods(kubeConfig)
	if err != nil {
		return nil, "", err
	}
	for _, pod := range pods.Items {
		if app, ok := pod.ObjectMeta.Labels["app"]; ok && app == "istiod" {
			podName = pod.Name
		}
	}
	if podName == "" {
		return nil, "", fmt.Errorf("cannot find istio-pilot pod")
	}

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	localPort := r.Intn(LocalPortEnd-LocalPortStart) + LocalPortStart
	cmd := fmt.Sprintf("kubectl port-forward %s -n istio-system %d:15010", podName, localPort)
	parts := strings.Split(cmd, " ")
	c := exec.Command(parts[0], parts[1:]...)
	err = c.Start()
	if err != nil {
		return nil, "", err
	}
	// Make sure istio-pilot is reachable.
	reachable := false
	url := fmt.Sprintf("localhost:%d", localPort)
	for i := 0; i < 10 && !reachable; i++ {
		conn, err := net.Dial("tcp", url)
		if err == nil {
			_ = conn.Close()
			reachable = true
		}
		time.Sleep(1 * time.Second)
	}
	if !reachable {
		return nil, "", fmt.Errorf("cannot reach local pilot url: %s", url)
	}
	return c.Process, fmt.Sprintf("localhost:%d", localPort), nil
}

func main() {
	kubeConfig := flag.String("kubeconfig", "~/.kube/config", "path to the kubeconfig file. Default is ~/.kube/config")
	pilotURL := flag.String("pilot", "", "pilot address. Will try port forward if not provided.")
	configType := flag.String("type", "lds", "lds, cds, or eds. Default lds.")
	proxyType := flag.String("proxytype", "", "sidecar, ingress, router.")
	proxyTag := flag.String("proxytag", "", "Pod name or app label or istio label to identify the proxy.")
	resources := flag.String("res", "", "Resource(s) to get config for. LDS/CDS should leave it empty.")
	outputFile := flag.String("out", "", "output file. Leave blank to go to stdout")
	flag.Parse()

	process, pilot, err := portForwardPilot(resolveKubeConfigPath(*kubeConfig), *pilotURL)
	if err != nil {
		log.Errorf("pilot port forward failed: %v", err)
		return
	}
	defer func() {
		if process != nil {
			err := process.Kill()
			if err != nil {
				log.Errorf("Failed to kill port-forward process, pid: %d", process.Pid)
			}
		}
	}()
	pod := NewPodInfo(*proxyTag, resolveKubeConfigPath(*kubeConfig), *proxyType)

	var resp *discovery.DiscoveryResponse
	switch *configType {
	case "lds", "cds":
		resp, err = pod.getXdsResponse(pilot, pod.makeRequest(*configType))
	case "rds", "eds":
		resp, err = pod.getXdsResponse(pilot, pod.appendResources(pod.makeRequest(*configType), strings.Split(*resources, ",")))
	default:
		log.Errorf("Unknown config type: %q", *configType)
		os.Exit(1)
	}

	if err != nil {
		log.Errorf("Failed to get Xds response for %v. Error: %v", *resources, err)
		return
	}
	strResponse, _ := gogoprotomarshal.ToJSONWithIndent(resp, " ")
	if outputFile == nil || *outputFile == "" {
		fmt.Printf("%v\n", strResponse)
	} else if err := ioutil.WriteFile(*outputFile, []byte(strResponse), 0644); err != nil {
		log.Errorf("Cannot write output to file %q", *outputFile)
	}
}
<|endoftext|>


<commit_before>package handlers

import (
	"github.com/danryan/hal"
	"github.com/davecgh/go-spew/spew"
)

const okStr = "OK"

// GetHandler pulls a key out of the store, when addressed like so:
// foobar?
// bawt> get: foobar=baz
var GetHandler = hal.Hear(`^([:word]{1})\?$`, func(res *hal.Response) error {
	key := res.Match[0][1]
	val, err := res.Robot.Store.Get(key)
	if err != nil {
		res.Send(err.Error())
		return err
	}
	return res.Send(string(val))
})

// SetHandler does a simple match/set into the Store
var SetHandler = hal.Hear(`^([:word]{1}) is ([:word]+)$`, func(res *hal.Response) error {
	key := res.Match[0][1]
	val := res.Match[0][2]
	err := res.Robot.Store.Set(key, []byte(val))
	if err != nil {
		res.Send(err.Error())
		return err
	}
	return res.Send(okStr)
})

// DeleteHandler nukes keys in the Store
var DeleteHandler = hal.Hear(`^forget ([:word]{1})$`, func(res *hal.Response) error {
	key := res.Match[0][1]

	if err := res.Robot.Store.Delete(key); err != nil {
		res.Send(err.Error())
		return err
	}
	return res.Send(okStr)
})

// UsersHandler provides insite into the Robots user storage
var UsersHandler = hal.Hear(`^!show users$`, func(res *hal.Response) error {
	lines := []string{}
	for _, user := range res.Robot.Users.All() {
		lines = append(lines, spew.Sdump(user))
	}
	return res.Send(lines...)
})

// UserHandler is the singular form of UsersHandler
var UserHandler = hal.Hear(`^!show user (.+)$`, func(res *hal.Response) error {
	id := res.Match[0][1]
	user, _ := res.Robot.Users.Get(id)
	line := spew.Sdump(user)
	return res.Send(line)
})
<commit_msg>ascii word typos<commit_after>package handlers

import (
	"github.com/danryan/hal"
	"github.com/davecgh/go-spew/spew"
)

const okStr = "OK"

// GetHandler pulls a key out of the store, when addressed like so:
// foobar?
// bawt> get: foobar=baz
var GetHandler = hal.Hear(`^([:word:]{1})\?$`, func(res *hal.Response) error {
	key := res.Match[0][1]
	val, err := res.Robot.Store.Get(key)
	if err != nil {
		res.Send(err.Error())
		return err
	}
	return res.Send(string(val))
})

// SetHandler does a simple match/set into the Store
var SetHandler = hal.Hear(`^([:word:]{1}) is ([:word]+)$`, func(res *hal.Response) error {
	key := res.Match[0][1]
	val := res.Match[0][2]
	err := res.Robot.Store.Set(key, []byte(val))
	if err != nil {
		res.Send(err.Error())
		return err
	}
	return res.Send(okStr)
})

// DeleteHandler nukes keys in the Store
var DeleteHandler = hal.Hear(`^forget ([:word:]{1})$`, func(res *hal.Response) error {
	key := res.Match[0][1]

	if err := res.Robot.Store.Delete(key); err != nil {
		res.Send(err.Error())
		return err
	}
	return res.Send(okStr)
})

// UsersHandler provides insite into the Robots user storage
var UsersHandler = hal.Hear(`^!show users$`, func(res *hal.Response) error {
	lines := []string{}
	for _, user := range res.Robot.Users.All() {
		lines = append(lines, spew.Sdump(user))
	}
	return res.Send(lines...)
})

// UserHandler is the singular form of UsersHandler
var UserHandler = hal.Hear(`^!show user (.+)$`, func(res *hal.Response) error {
	id := res.Match[0][1]
	user, _ := res.Robot.Users.Get(id)
	line := spew.Sdump(user)
	return res.Send(line)
})
<|endoftext|>


<commit_before>/*
Copyright 2014 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 v1

import (
	"fmt"
	"strconv"
	"strings"

	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/conversion"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"
)

func AddConversionFuncs(scheme *runtime.Scheme) error {
	return scheme.AddConversionFuncs(
		Convert_v1_TypeMeta_To_v1_TypeMeta,

		Convert_unversioned_ListMeta_To_unversioned_ListMeta,

		Convert_intstr_IntOrString_To_intstr_IntOrString,

		Convert_unversioned_Time_To_unversioned_Time,

		Convert_Slice_string_To_unversioned_Time,

		Convert_resource_Quantity_To_resource_Quantity,

		Convert_string_To_labels_Selector,
		Convert_labels_Selector_To_string,

		Convert_string_To_fields_Selector,
		Convert_fields_Selector_To_string,

		Convert_Pointer_bool_To_bool,
		Convert_bool_To_Pointer_bool,

		Convert_Pointer_string_To_string,
		Convert_string_To_Pointer_string,

		Convert_Pointer_int64_To_int,
		Convert_int_To_Pointer_int64,

		Convert_Pointer_int32_To_int32,
		Convert_int32_To_Pointer_int32,

		Convert_Pointer_float64_To_float64,
		Convert_float64_To_Pointer_float64,

		Convert_map_to_unversioned_LabelSelector,
		Convert_unversioned_LabelSelector_to_map,

		Convert_Slice_string_To_Slice_int32,
	)
}

func Convert_Pointer_float64_To_float64(in **float64, out *float64, s conversion.Scope) error {
	if *in == nil {
		*out = 0
		return nil
	}
	*out = float64(**in)
	return nil
}

func Convert_float64_To_Pointer_float64(in *float64, out **float64, s conversion.Scope) error {
	temp := float64(*in)
	*out = &temp
	return nil
}

func Convert_Pointer_int32_To_int32(in **int32, out *int32, s conversion.Scope) error {
	if *in == nil {
		*out = 0
		return nil
	}
	*out = int32(**in)
	return nil
}

func Convert_int32_To_Pointer_int32(in *int32, out **int32, s conversion.Scope) error {
	temp := int32(*in)
	*out = &temp
	return nil
}

func Convert_Pointer_int64_To_int(in **int64, out *int, s conversion.Scope) error {
	if *in == nil {
		*out = 0
		return nil
	}
	*out = int(**in)
	return nil
}

func Convert_int_To_Pointer_int64(in *int, out **int64, s conversion.Scope) error {
	temp := int64(*in)
	*out = &temp
	return nil
}

func Convert_Pointer_string_To_string(in **string, out *string, s conversion.Scope) error {
	if *in == nil {
		*out = ""
		return nil
	}
	*out = **in
	return nil
}

func Convert_string_To_Pointer_string(in *string, out **string, s conversion.Scope) error {
	if in == nil {
		stringVar := ""
		*out = &stringVar
		return nil
	}
	*out = in
	return nil
}

func Convert_Pointer_bool_To_bool(in **bool, out *bool, s conversion.Scope) error {
	if *in == nil {
		*out = false
		return nil
	}
	*out = **in
	return nil
}

func Convert_bool_To_Pointer_bool(in *bool, out **bool, s conversion.Scope) error {
	if in == nil {
		boolVar := false
		*out = &boolVar
		return nil
	}
	*out = in
	return nil
}

// +k8s:conversion-fn=drop
func Convert_v1_TypeMeta_To_v1_TypeMeta(in, out *TypeMeta, s conversion.Scope) error {
	// These values are explicitly not copied
	//out.APIVersion = in.APIVersion
	//out.Kind = in.Kind
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_unversioned_ListMeta_To_unversioned_ListMeta(in, out *ListMeta, s conversion.Scope) error {
	*out = *in
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_intstr_IntOrString_To_intstr_IntOrString(in, out *intstr.IntOrString, s conversion.Scope) error {
	*out = *in
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_unversioned_Time_To_unversioned_Time(in *Time, out *Time, s conversion.Scope) error {
	// Cannot deep copy these, because time.Time has unexported fields.
	*out = *in
	return nil
}

// Convert_Slice_string_To_unversioned_Time allows converting a URL query parameter value
func Convert_Slice_string_To_unversioned_Time(input *[]string, out *Time, s conversion.Scope) error {
	str := ""
	if len(*input) > 0 {
		str = (*input)[0]
	}
	return out.UnmarshalQueryParameter(str)
}

func Convert_string_To_labels_Selector(in *string, out *labels.Selector, s conversion.Scope) error {
	selector, err := labels.Parse(*in)
	if err != nil {
		return err
	}
	*out = selector
	return nil
}

func Convert_string_To_fields_Selector(in *string, out *fields.Selector, s conversion.Scope) error {
	selector, err := fields.ParseSelector(*in)
	if err != nil {
		return err
	}
	*out = selector
	return nil
}

func Convert_labels_Selector_To_string(in *labels.Selector, out *string, s conversion.Scope) error {
	if *in == nil {
		return nil
	}
	*out = (*in).String()
	return nil
}

func Convert_fields_Selector_To_string(in *fields.Selector, out *string, s conversion.Scope) error {
	if *in == nil {
		return nil
	}
	*out = (*in).String()
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_resource_Quantity_To_resource_Quantity(in *resource.Quantity, out *resource.Quantity, s conversion.Scope) error {
	*out = *in
	return nil
}

func Convert_map_to_unversioned_LabelSelector(in *map[string]string, out *LabelSelector, s conversion.Scope) error {
	if in == nil {
		return nil
	}
	out = new(LabelSelector)
	for labelKey, labelValue := range *in {
		AddLabelToSelector(out, labelKey, labelValue)
	}
	return nil
}

func Convert_unversioned_LabelSelector_to_map(in *LabelSelector, out *map[string]string, s conversion.Scope) error {
	var err error
	*out, err = LabelSelectorAsMap(in)
	return err
}

// Convert_Slice_string_To_Slice_int32 converts multiple query parameters or
// a single query parameter with a comma delimited value to multiple int32.
// This is used for port forwarding which needs the ports as int32.
func Convert_Slice_string_To_Slice_int32(in *[]string, out *[]int32, s conversion.Scope) error {
	for _, s := range *in {
		for _, v := range strings.Split(s, ",") {
			x, err := strconv.ParseUint(v, 10, 16)
			if err != nil {
				return fmt.Errorf("cannot convert to []int32: %v", err)
			}
			*out = append(*out, int32(x))
		}
	}
	return nil
}
<commit_msg>Dynamic Kubelet Configuration<commit_after>/*
Copyright 2014 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 v1

import (
	"fmt"
	"strconv"
	"strings"

	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/conversion"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"
)

func AddConversionFuncs(scheme *runtime.Scheme) error {
	return scheme.AddConversionFuncs(
		Convert_v1_TypeMeta_To_v1_TypeMeta,

		Convert_unversioned_ListMeta_To_unversioned_ListMeta,

		Convert_intstr_IntOrString_To_intstr_IntOrString,

		Convert_unversioned_Time_To_unversioned_Time,

		Convert_Pointer_v1_Duration_To_v1_Duration,
		Convert_v1_Duration_To_Pointer_v1_Duration,

		Convert_Slice_string_To_unversioned_Time,

		Convert_resource_Quantity_To_resource_Quantity,

		Convert_string_To_labels_Selector,
		Convert_labels_Selector_To_string,

		Convert_string_To_fields_Selector,
		Convert_fields_Selector_To_string,

		Convert_Pointer_bool_To_bool,
		Convert_bool_To_Pointer_bool,

		Convert_Pointer_string_To_string,
		Convert_string_To_Pointer_string,

		Convert_Pointer_int64_To_int,
		Convert_int_To_Pointer_int64,

		Convert_Pointer_int32_To_int32,
		Convert_int32_To_Pointer_int32,

		Convert_Pointer_float64_To_float64,
		Convert_float64_To_Pointer_float64,

		Convert_map_to_unversioned_LabelSelector,
		Convert_unversioned_LabelSelector_to_map,

		Convert_Slice_string_To_Slice_int32,
	)
}

func Convert_Pointer_float64_To_float64(in **float64, out *float64, s conversion.Scope) error {
	if *in == nil {
		*out = 0
		return nil
	}
	*out = float64(**in)
	return nil
}

func Convert_float64_To_Pointer_float64(in *float64, out **float64, s conversion.Scope) error {
	temp := float64(*in)
	*out = &temp
	return nil
}

func Convert_Pointer_int32_To_int32(in **int32, out *int32, s conversion.Scope) error {
	if *in == nil {
		*out = 0
		return nil
	}
	*out = int32(**in)
	return nil
}

func Convert_int32_To_Pointer_int32(in *int32, out **int32, s conversion.Scope) error {
	temp := int32(*in)
	*out = &temp
	return nil
}

func Convert_Pointer_int64_To_int(in **int64, out *int, s conversion.Scope) error {
	if *in == nil {
		*out = 0
		return nil
	}
	*out = int(**in)
	return nil
}

func Convert_int_To_Pointer_int64(in *int, out **int64, s conversion.Scope) error {
	temp := int64(*in)
	*out = &temp
	return nil
}

func Convert_Pointer_string_To_string(in **string, out *string, s conversion.Scope) error {
	if *in == nil {
		*out = ""
		return nil
	}
	*out = **in
	return nil
}

func Convert_string_To_Pointer_string(in *string, out **string, s conversion.Scope) error {
	if in == nil {
		stringVar := ""
		*out = &stringVar
		return nil
	}
	*out = in
	return nil
}

func Convert_Pointer_bool_To_bool(in **bool, out *bool, s conversion.Scope) error {
	if *in == nil {
		*out = false
		return nil
	}
	*out = **in
	return nil
}

func Convert_bool_To_Pointer_bool(in *bool, out **bool, s conversion.Scope) error {
	if in == nil {
		boolVar := false
		*out = &boolVar
		return nil
	}
	*out = in
	return nil
}

// +k8s:conversion-fn=drop
func Convert_v1_TypeMeta_To_v1_TypeMeta(in, out *TypeMeta, s conversion.Scope) error {
	// These values are explicitly not copied
	//out.APIVersion = in.APIVersion
	//out.Kind = in.Kind
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_unversioned_ListMeta_To_unversioned_ListMeta(in, out *ListMeta, s conversion.Scope) error {
	*out = *in
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_intstr_IntOrString_To_intstr_IntOrString(in, out *intstr.IntOrString, s conversion.Scope) error {
	*out = *in
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_unversioned_Time_To_unversioned_Time(in *Time, out *Time, s conversion.Scope) error {
	// Cannot deep copy these, because time.Time has unexported fields.
	*out = *in
	return nil
}

func Convert_Pointer_v1_Duration_To_v1_Duration(in **Duration, out *Duration, s conversion.Scope) error {
	if *in == nil {
		*out = Duration{} // zero duration
		return nil
	}
	*out = **in // copy
	return nil
}

func Convert_v1_Duration_To_Pointer_v1_Duration(in *Duration, out **Duration, s conversion.Scope) error {
	temp := *in //copy
	*out = &temp
	return nil
}

// Convert_Slice_string_To_unversioned_Time allows converting a URL query parameter value
func Convert_Slice_string_To_unversioned_Time(input *[]string, out *Time, s conversion.Scope) error {
	str := ""
	if len(*input) > 0 {
		str = (*input)[0]
	}
	return out.UnmarshalQueryParameter(str)
}

func Convert_string_To_labels_Selector(in *string, out *labels.Selector, s conversion.Scope) error {
	selector, err := labels.Parse(*in)
	if err != nil {
		return err
	}
	*out = selector
	return nil
}

func Convert_string_To_fields_Selector(in *string, out *fields.Selector, s conversion.Scope) error {
	selector, err := fields.ParseSelector(*in)
	if err != nil {
		return err
	}
	*out = selector
	return nil
}

func Convert_labels_Selector_To_string(in *labels.Selector, out *string, s conversion.Scope) error {
	if *in == nil {
		return nil
	}
	*out = (*in).String()
	return nil
}

func Convert_fields_Selector_To_string(in *fields.Selector, out *string, s conversion.Scope) error {
	if *in == nil {
		return nil
	}
	*out = (*in).String()
	return nil
}

// +k8s:conversion-fn=copy-only
func Convert_resource_Quantity_To_resource_Quantity(in *resource.Quantity, out *resource.Quantity, s conversion.Scope) error {
	*out = *in
	return nil
}

func Convert_map_to_unversioned_LabelSelector(in *map[string]string, out *LabelSelector, s conversion.Scope) error {
	if in == nil {
		return nil
	}
	out = new(LabelSelector)
	for labelKey, labelValue := range *in {
		AddLabelToSelector(out, labelKey, labelValue)
	}
	return nil
}

func Convert_unversioned_LabelSelector_to_map(in *LabelSelector, out *map[string]string, s conversion.Scope) error {
	var err error
	*out, err = LabelSelectorAsMap(in)
	return err
}

// Convert_Slice_string_To_Slice_int32 converts multiple query parameters or
// a single query parameter with a comma delimited value to multiple int32.
// This is used for port forwarding which needs the ports as int32.
func Convert_Slice_string_To_Slice_int32(in *[]string, out *[]int32, s conversion.Scope) error {
	for _, s := range *in {
		for _, v := range strings.Split(s, ",") {
			x, err := strconv.ParseUint(v, 10, 16)
			if err != nil {
				return fmt.Errorf("cannot convert to []int32: %v", err)
			}
			*out = append(*out, int32(x))
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package rollrus

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/stvp/roll"
)

func TestIntConversion(t *testing.T) {
	i := make(logrus.Fields)
	i["test"] = 5

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != "5" {
		t.Fatal("Expected value to equal 5, but instead it is: ", v)
	}
}

func TestErrConversion(t *testing.T) {
	i := make(logrus.Fields)
	i["test"] = fmt.Errorf("This is an error")

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != "This is an error" {
		t.Fatal("Expected value to be a string of the error but instead it is: ", v)
	}
}

func TestStringConversion(t *testing.T) {
	i := make(logrus.Fields)
	i["test"] = "This is a string"

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != "This is a string" {
		t.Fatal("Expected value to equal a certain string, but instead it is: ", v)
	}
}

func TestTimeConversion(t *testing.T) {
	now := time.Now()
	i := make(logrus.Fields)
	i["test"] = now

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != now.Format(time.RFC3339) {
		t.Fatal("Expected value to equal, but instead it is: ", v)
	}
}

func TestExtractError(t *testing.T) {
	entry := logrus.NewEntry(nil)
	entry.Data["err"] = fmt.Errorf("foo bar baz")

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "foo bar baz" {
		t.Fatalf("Expected error as string to be 'foo bar baz', but was instead: %q", cause)
	}
}

type CauserError struct {
	fakeCause string
	error
}

func (ce CauserError) Cause() error {
	return fmt.Errorf(ce.fakeCause)
}

func TestExtractCauserErrorWithCause(t *testing.T) {

	entry := logrus.NewEntry(nil)

	entry.Data["err"] = CauserError{fakeCause: "fbb cause", error: fmt.Errorf("foo bar baz")}

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "fbb cause" {
		t.Fatalf("Expected error as string to be 'fbb cause', but was instead: %q", cause)
	}
}

type NilCauserError struct {
	error
}

func (ce NilCauserError) Cause() error {
	return nil
}

func TestExtractCauserErrorWithNilCause(t *testing.T) {

	entry := logrus.NewEntry(nil)

	entry.Data["err"] = NilCauserError{error: fmt.Errorf("foo bar baz")}

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "foo bar baz" {
		t.Fatalf("Expected error as string to be 'foo bar baz', but was instead: %q", cause)
	}
}

func TestExtractErrorDefault(t *testing.T) {
	entry := logrus.NewEntry(nil)
	entry.Data["no-err"] = fmt.Errorf("foo bar baz")
	entry.Message = "message error"

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "message error" {
		t.Fatalf("Expected error as string to be 'message error', but was instead: %q", cause)
	}
}

func TestExtractErrorFromStackTracer(t *testing.T) {
	entry := logrus.NewEntry(nil)
	entry.Data["err"] = errors.Errorf("foo bar baz")

	trace, cause := extractError(entry)
	if len(trace) != 3 {
		t.Fatal("Expected length of trace to be == 3, but instead is: ", len(trace))
	}

	if cause.Error() != "foo bar baz" {
		t.Fatalf("Expected error as string to be 'foo bar baz', but was instead: %q", cause.Error())
	}
}

func TestTriggerLevels(t *testing.T) {
	client := roll.New("", "testing")
	underTest := &Hook{Client: client}
	if !reflect.DeepEqual(underTest.Levels(), defaultTriggerLevels) {
		t.Fatal("Expected Levels() to return defaultTriggerLevels")
	}

	newLevels := []logrus.Level{logrus.InfoLevel}
	underTest.triggers = newLevels
	if !reflect.DeepEqual(underTest.Levels(), newLevels) {
		t.Fatal("Expected Levels() to return newLevels")
	}
}

func TestWithMinLevelInfo(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.InfoLevel))
	expectedLevels := []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
		logrus.ErrorLevel,
		logrus.WarnLevel,
		logrus.InfoLevel,
	}
	if !reflect.DeepEqual(h.Levels(), expectedLevels) {
		t.Fatal("Expected Levels() to return all levels above Info")
	}
}

func TestWithMinLevelFatal(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.FatalLevel))
	expectedLevels := []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
	}
	if !reflect.DeepEqual(h.Levels(), expectedLevels) {
		t.Fatal("Expected Levels() to return all levels above Fatal")
	}
}

func TestLoggingBelowTheMinimumLevelDoesNotFire(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.FatalLevel))
	l := logrus.New()
	l.AddHook(h)

	l.Error("This is a test")

	if h.reported {
		t.Fatal("expected no report to have happened")
	}
}
func TestLoggingAboveTheMinimumLevelDoesFire(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.WarnLevel))
	l := logrus.New()
	l.AddHook(h)

	l.Warn("This is a test")

	if !h.reported {
		t.Fatal("expected report to have happened")
	}
}

func TestWithIgnoredErrors(t *testing.T) {
	h := NewHook("", "testing", WithIgnoredErrors(io.EOF))
	entry := logrus.NewEntry(nil)
	entry.Message = "This is a test"

	// Exact error is skipped.
	entry.Data["err"] = io.EOF
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	// Wrapped error is also skipped.
	entry.Data["err"] = errors.Wrap(io.EOF, "hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	// Non blacklisted errors get reported.
	entry.Data["err"] = errors.New("hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}

	// no err gets reported.
	delete(entry.Data, "err")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}
}

type isTemporary interface {
	Temporary() bool
}

// https://github.com/go-pg/pg/blob/2ebb4d1d9b890619de2dc9e1dc0085b86f93fc91/internal/error.go#L22
type PGError struct {
	m map[byte]string
}

func (err PGError) Error() string {
	return "error"
}

// https://github.com/heroku/rollrus/issues/26
func TestWithErrorHandlesUnhashableErrors(t *testing.T) {
	_ = NewHook("", "", WithIgnoredErrors(PGError{m: make(map[byte]string)}))
	entry := logrus.NewEntry(nil)
	entry.Message = "This is a test"
	entry.Data["err"] = PGError{m: make(map[byte]string)}

	h := NewHook("", "testing")
	// actually panics
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
}

func TestWithIgnoreErrorFunc(t *testing.T) {
	h := NewHook("", "testing", WithIgnoreErrorFunc(func(err error) bool {
		if err == io.EOF {
			return true
		}

		if e, ok := err.(isTemporary); ok && e.Temporary() {
			return true
		}

		return false
	}))

	entry := logrus.NewEntry(nil)
	entry.Message = "This is a test"

	// Exact error is skipped.
	entry.Data["err"] = io.EOF
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	// Wrapped error is also skipped.
	entry.Data["err"] = errors.Wrap(io.EOF, "hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	srv := httptest.NewServer(nil)
	srv.Close()

	// Temporary error skipped
	_, err := http.Get(srv.URL)

	entry.Data["err"] = err
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}

	// Non blacklisted errors get reported.
	entry.Data["err"] = errors.New("hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}

	// no err gets reported.
	delete(entry.Data, "err")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}
}

func TestWithIgnoreFunc(t *testing.T) {
	cases := []struct {
		name       string
		fields     logrus.Fields
		skipReport bool
	}{
		{
			name:       "extract error is skipped",
			fields:     map[string]interface{}{"err": io.EOF},
			skipReport: true,
		},
		{
			name:       "wrapped error is skipped",
			fields:     map[string]interface{}{"err": errors.Wrap(io.EOF, "hello")},
			skipReport: true,
		},
		{
			name:       "ignored field is skipped",
			fields:     map[string]interface{}{"ignore": "true"},
			skipReport: true,
		},
		{
			name:       "error is not skipped",
			fields:     map[string]interface{}{},
			skipReport: false,
		},
	}

	for _, c := range cases {
		c := c // capture local var for parallel tests

		t.Run(c.name, func(t *testing.T) {
			t.Parallel()

			h := NewHook("", "testing", WithIgnoreFunc(func(err error, m map[string]string) bool {
				if err == io.EOF {
					return true
				}

				if m["ignore"] == "true" {
					return true
				}

				return false
			}))

			entry := logrus.NewEntry(nil)
			entry.Message = "This is a test"
			entry.Data = c.fields

			if err := h.Fire(entry); err != nil {
				t.Errorf("unexpected error %s", err)
			}

			if c.skipReport && h.reported {
				t.Errorf("expected report to be skipped")
			}

			if !c.skipReport && !h.reported {
				t.Errorf("expected report to be fired")
			}
		})
	}
}
<commit_msg>test nil cause<commit_after>package rollrus

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/stvp/roll"
)

func TestIntConversion(t *testing.T) {
	i := make(logrus.Fields)
	i["test"] = 5

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != "5" {
		t.Fatal("Expected value to equal 5, but instead it is: ", v)
	}
}

func TestErrConversion(t *testing.T) {
	i := make(logrus.Fields)
	i["test"] = fmt.Errorf("This is an error")

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != "This is an error" {
		t.Fatal("Expected value to be a string of the error but instead it is: ", v)
	}
}

func TestStringConversion(t *testing.T) {
	i := make(logrus.Fields)
	i["test"] = "This is a string"

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != "This is a string" {
		t.Fatal("Expected value to equal a certain string, but instead it is: ", v)
	}
}

func TestTimeConversion(t *testing.T) {
	now := time.Now()
	i := make(logrus.Fields)
	i["test"] = now

	r := convertFields(i)

	v, ok := r["test"]
	if !ok {
		t.Fatal("Expected test key, but did not find it")
	}

	if v != now.Format(time.RFC3339) {
		t.Fatal("Expected value to equal, but instead it is: ", v)
	}
}

func TestExtractError(t *testing.T) {
	entry := logrus.NewEntry(nil)
	entry.Data["err"] = fmt.Errorf("foo bar baz")

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "foo bar baz" {
		t.Fatalf("Expected error as string to be 'foo bar baz', but was instead: %q", cause)
	}
}

type CauserError struct {
	fakeCause string
	error
}

func (ce CauserError) Cause() error {
	return fmt.Errorf(ce.fakeCause)
}

func TestExtractCauserErrorWithCause(t *testing.T) {

	entry := logrus.NewEntry(nil)

	entry.Data["err"] = CauserError{fakeCause: "fbb cause", error: fmt.Errorf("foo bar baz")}

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "fbb cause" {
		t.Fatalf("Expected error as string to be 'fbb cause', but was instead: %q", cause)
	}
}

type NilCauserError struct {
	error
}

func (ce NilCauserError) Cause() error {
	return nil
}

func TestExtractCauserErrorWithNilCause(t *testing.T) {

	entry := logrus.NewEntry(nil)

	entry.Data["err"] = NilCauserError{error: fmt.Errorf("foo bar baz")}

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "foo bar baz" {
		t.Fatalf("Expected error as string to be 'foo bar baz', but was instead: %q", cause)
	}
}

func TestExtractErrorDefault(t *testing.T) {
	entry := logrus.NewEntry(nil)
	entry.Data["no-err"] = fmt.Errorf("foo bar baz")
	entry.Message = "message error"

	trace, cause := extractError(entry)
	if len(trace) != 0 {
		t.Fatal("Expected length of trace to be equal to 0, but instead is: ", len(trace))
	}

	if cause.Error() != "message error" {
		t.Fatalf("Expected error as string to be 'message error', but was instead: %q", cause)
	}
}

func TestExtractErrorFromStackTracer(t *testing.T) {
	entry := logrus.NewEntry(nil)
	entry.Data["err"] = errors.Errorf("foo bar baz")

	trace, cause := extractError(entry)
	if len(trace) != 3 {
		t.Fatal("Expected length of trace to be == 3, but instead is: ", len(trace))
	}

	if cause.Error() != "foo bar baz" {
		t.Fatalf("Expected error as string to be 'foo bar baz', but was instead: %q", cause.Error())
	}
}

type merr []error

func (merr) Error() string { return "boom?" }
func (e merr) Cause() error {
	if len(e) == 0 {
		return nil
	}
	return e[0]
}
func TestExtractErrorWithNilCause(t *testing.T) {
	entry := logrus.NewEntry(nil)
	var err error = merr{}
	entry.Data["err"] = err

	// len(err) == 0, so Cause(err) will return nil
	_, cause := extractError(entry)
	if cause == nil {
		t.Fatalf("unexpected nil cause")
	}

	if cause.Error() != "boom?" {
		t.Fatalf("Expected error as string to be 'boom?', but was instead: %q", cause.Error())
	}
}

func TestTriggerLevels(t *testing.T) {
	client := roll.New("", "testing")
	underTest := &Hook{Client: client}
	if !reflect.DeepEqual(underTest.Levels(), defaultTriggerLevels) {
		t.Fatal("Expected Levels() to return defaultTriggerLevels")
	}

	newLevels := []logrus.Level{logrus.InfoLevel}
	underTest.triggers = newLevels
	if !reflect.DeepEqual(underTest.Levels(), newLevels) {
		t.Fatal("Expected Levels() to return newLevels")
	}
}

func TestWithMinLevelInfo(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.InfoLevel))
	expectedLevels := []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
		logrus.ErrorLevel,
		logrus.WarnLevel,
		logrus.InfoLevel,
	}
	if !reflect.DeepEqual(h.Levels(), expectedLevels) {
		t.Fatal("Expected Levels() to return all levels above Info")
	}
}

func TestWithMinLevelFatal(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.FatalLevel))
	expectedLevels := []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
	}
	if !reflect.DeepEqual(h.Levels(), expectedLevels) {
		t.Fatal("Expected Levels() to return all levels above Fatal")
	}
}

func TestLoggingBelowTheMinimumLevelDoesNotFire(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.FatalLevel))
	l := logrus.New()
	l.AddHook(h)

	l.Error("This is a test")

	if h.reported {
		t.Fatal("expected no report to have happened")
	}
}
func TestLoggingAboveTheMinimumLevelDoesFire(t *testing.T) {
	h := NewHook("", "testing", WithMinLevel(logrus.WarnLevel))
	l := logrus.New()
	l.AddHook(h)

	l.Warn("This is a test")

	if !h.reported {
		t.Fatal("expected report to have happened")
	}
}

func TestWithIgnoredErrors(t *testing.T) {
	h := NewHook("", "testing", WithIgnoredErrors(io.EOF))
	entry := logrus.NewEntry(nil)
	entry.Message = "This is a test"

	// Exact error is skipped.
	entry.Data["err"] = io.EOF
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	// Wrapped error is also skipped.
	entry.Data["err"] = errors.Wrap(io.EOF, "hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	// Non blacklisted errors get reported.
	entry.Data["err"] = errors.New("hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}

	// no err gets reported.
	delete(entry.Data, "err")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}
}

type isTemporary interface {
	Temporary() bool
}

// https://github.com/go-pg/pg/blob/2ebb4d1d9b890619de2dc9e1dc0085b86f93fc91/internal/error.go#L22
type PGError struct {
	m map[byte]string
}

func (err PGError) Error() string {
	return "error"
}

// https://github.com/heroku/rollrus/issues/26
func TestWithErrorHandlesUnhashableErrors(t *testing.T) {
	_ = NewHook("", "", WithIgnoredErrors(PGError{m: make(map[byte]string)}))
	entry := logrus.NewEntry(nil)
	entry.Message = "This is a test"
	entry.Data["err"] = PGError{m: make(map[byte]string)}

	h := NewHook("", "testing")
	// actually panics
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
}

func TestWithIgnoreErrorFunc(t *testing.T) {
	h := NewHook("", "testing", WithIgnoreErrorFunc(func(err error) bool {
		if err == io.EOF {
			return true
		}

		if e, ok := err.(isTemporary); ok && e.Temporary() {
			return true
		}

		return false
	}))

	entry := logrus.NewEntry(nil)
	entry.Message = "This is a test"

	// Exact error is skipped.
	entry.Data["err"] = io.EOF
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	// Wrapped error is also skipped.
	entry.Data["err"] = errors.Wrap(io.EOF, "hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if h.reported {
		t.Fatal("expected no report to have happened")
	}

	srv := httptest.NewServer(nil)
	srv.Close()

	// Temporary error skipped
	_, err := http.Get(srv.URL)

	entry.Data["err"] = err
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}

	// Non blacklisted errors get reported.
	entry.Data["err"] = errors.New("hello")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}

	// no err gets reported.
	delete(entry.Data, "err")
	if err := h.Fire(entry); err != nil {
		t.Fatal("unexpected error ", err)
	}
	if !h.reported {
		t.Fatal("expected a report to have happened")
	}
}

func TestWithIgnoreFunc(t *testing.T) {
	cases := []struct {
		name       string
		fields     logrus.Fields
		skipReport bool
	}{
		{
			name:       "extract error is skipped",
			fields:     map[string]interface{}{"err": io.EOF},
			skipReport: true,
		},
		{
			name:       "wrapped error is skipped",
			fields:     map[string]interface{}{"err": errors.Wrap(io.EOF, "hello")},
			skipReport: true,
		},
		{
			name:       "ignored field is skipped",
			fields:     map[string]interface{}{"ignore": "true"},
			skipReport: true,
		},
		{
			name:       "error is not skipped",
			fields:     map[string]interface{}{},
			skipReport: false,
		},
	}

	for _, c := range cases {
		c := c // capture local var for parallel tests

		t.Run(c.name, func(t *testing.T) {
			t.Parallel()

			h := NewHook("", "testing", WithIgnoreFunc(func(err error, m map[string]string) bool {
				if err == io.EOF {
					return true
				}

				if m["ignore"] == "true" {
					return true
				}

				return false
			}))

			entry := logrus.NewEntry(nil)
			entry.Message = "This is a test"
			entry.Data = c.fields

			if err := h.Fire(entry); err != nil {
				t.Errorf("unexpected error %s", err)
			}

			if c.skipReport && h.reported {
				t.Errorf("expected report to be skipped")
			}

			if !c.skipReport && !h.reported {
				t.Errorf("expected report to be fired")
			}
		})
	}
}
<|endoftext|>