<commit_before>package stripe

import "encoding/json"

// IssuingAuthorizationAuthorizationMethod is the list of possible values for the authorization method
// on an issuing authorization.
type IssuingAuthorizationAuthorizationMethod string

// List of values that IssuingAuthorizationAuthorizationMethod can take.
const (
	IssuingAuthorizationAuthorizationMethodChip        IssuingAuthorizationAuthorizationMethod = "chip"
	IssuingAuthorizationAuthorizationMethodContactless IssuingAuthorizationAuthorizationMethod = "contactless"
	IssuingAuthorizationAuthorizationMethodKeyedIn     IssuingAuthorizationAuthorizationMethod = "keyed_in"
	IssuingAuthorizationAuthorizationMethodOnline      IssuingAuthorizationAuthorizationMethod = "online"
	IssuingAuthorizationAuthorizationMethodSwipe       IssuingAuthorizationAuthorizationMethod = "swipe"
)

// IssuingAuthorizationRequestHistoryReason is the list of possible values for the request history
// reason on an issuing authorization.
type IssuingAuthorizationRequestHistoryReason string

// List of values that IssuingAuthorizationRequestHistoryReason can take.
const (
	IssuingAuthorizationRequestHistoryReasonAuthorizationControls IssuingAuthorizationRequestHistoryReason = "authorization_controls"
	IssuingAuthorizationRequestHistoryReasonCardActive            IssuingAuthorizationRequestHistoryReason = "card_active"
	IssuingAuthorizationRequestHistoryReasonCardInactive          IssuingAuthorizationRequestHistoryReason = "card_inactive"
	IssuingAuthorizationRequestHistoryReasonInsufficientFunds     IssuingAuthorizationRequestHistoryReason = "insufficient_funds"
	IssuingAuthorizationRequestHistoryReasonWebhookApproved       IssuingAuthorizationRequestHistoryReason = "webhook_approved"
	IssuingAuthorizationRequestHistoryReasonWebhookDeclined       IssuingAuthorizationRequestHistoryReason = "webhook_declined"
	IssuingAuthorizationRequestHistoryReasonWebhookTimeout        IssuingAuthorizationRequestHistoryReason = "webhook_timeout"
)

// IssuingAuthorizationStatus is the possible values for status for an issuing authorization.
type IssuingAuthorizationStatus string

// List of values that IssuingAuthorizationStatus can take.
const (
	IssuingAuthorizationStatusClosed   IssuingAuthorizationStatus = "closed"
	IssuingAuthorizationStatusPending  IssuingAuthorizationStatus = "pending"
	IssuingAuthorizationStatusReversed IssuingAuthorizationStatus = "reversed"
)

// IssuingAuthorizationVerificationDataCheck is the list of possible values for result of a check
// for verification data on an issuing authorization.
type IssuingAuthorizationVerificationDataCheck string

// List of values that IssuingAuthorizationVerificationDataCheck can take.
const (
	IssuingAuthorizationVerificationDataCheckMatch       IssuingAuthorizationVerificationDataCheck = "match"
	IssuingAuthorizationVerificationDataCheckMismatch    IssuingAuthorizationVerificationDataCheck = "mismatch"
	IssuingAuthorizationVerificationDataCheckNotProvided IssuingAuthorizationVerificationDataCheck = "not_provided"
)

// IssuingAuthorizationWalletProviderType is the list of possible values for the authorization's wallet provider.
type IssuingAuthorizationWalletProviderType string

// List of values that IssuingAuthorizationWalletProviderType can take.
const (
	IssuingAuthorizationWalletProviderTypeApplePay   IssuingAuthorizationWalletProviderType = "apple_pay"
	IssuingAuthorizationWalletProviderTypeGooglePay  IssuingAuthorizationWalletProviderType = "google_pay"
	IssuingAuthorizationWalletProviderTypeSamsungPay IssuingAuthorizationWalletProviderType = "samsung_pay"
)

// IssuingAuthorizationParams is the set of parameters that can be used when updating an issuing authorization.
type IssuingAuthorizationParams struct {
	Params     `form:"*"`
	HeldAmount *int64 `form:"held_amount"`
}

// IssuingAuthorizationListParams is the set of parameters that can be used when listing issuing authorizations.
type IssuingAuthorizationListParams struct {
	ListParams   `form:"*"`
	Card         *string           `form:"card"`
	Cardholder   *string           `form:"cardholder"`
	Created      *int64            `form:"created"`
	CreatedRange *RangeQueryParams `form:"created"`
	Status       *string           `form:"status"`
}

// IssuingAuthorizationAuthorizationControls is the resource representing authorization controls on an issuing authorization.
type IssuingAuthorizationAuthorizationControls struct {
	AllowedCategories []string `json:"allowed_categories"`
	BlockedCategories []string `json:"blocked_categories"`
	Currency          Currency `json:"currency"`
	MaxAmount         int64    `json:"max_amount"`
	MaxApprovals      int64    `json:"max_approvals"`
}

// IssuingAuthorizationRequestHistory is the resource representing a request history on an issuing authorization.
type IssuingAuthorizationRequestHistory struct {
	Approved           bool                                     `json:"approved"`
	AuthorizedAmount   int64                                    `json:"authorized_amount"`
	AuthorizedCurrency Currency                                 `json:"authorized_currency"`
	Created            int64                                    `json:"created"`
	HeldAmount         int64                                    `json:"held_amount"`
	HeldCurrency       Currency                                 `json:"held_currency"`
	Reason             IssuingAuthorizationRequestHistoryReason `json:"reason"`
}

// IssuingAuthorizationVerificationData is the resource representing verification data on an issuing authorization.
type IssuingAuthorizationVerificationData struct {
	AddressLine1Check IssuingAuthorizationVerificationDataCheck `json:"address_line1_check"`
	AddressZipCheck   IssuingAuthorizationVerificationDataCheck `json:"address_zip_check"`
	CVCCheck          IssuingAuthorizationVerificationDataCheck `json:"cvc_check"`
}

// IssuingAuthorization is the resource representing a Stripe issuing authorization.
type IssuingAuthorization struct {
	Approved                 bool                                    `json:"approved"`
	AuthorizationMethod      IssuingAuthorizationAuthorizationMethod `json:"authorization_method"`
	AuthorizedAmount         int64                                   `json:"authorized_amount"`
	AuthorizedCurrency       Currency                                `json:"authorized_currency"`
	BalanceTransactions      []*BalanceTransaction                   `json:"balance_transactions"`
	Card                     *IssuingCard                            `json:"card"`
	Cardholder               *IssuingCardholder                      `json:"cardholder"`
	Created                  int64                                   `json:"created"`
	HeldAmount               int64                                   `json:"held_amount"`
	HeldCurrency             Currency                                `json:"held_currency"`
	ID                       string                                  `json:"id"`
	IsHeldAmountControllable bool                                    `json:"is_held_amount_controllable"`
	Livemode                 bool                                    `json:"livemode"`
	MerchantData             *IssuingMerchantData                    `json:"merchant_data"`
	Metadata                 map[string]string                       `json:"metadata"`
	Object                   string                                  `json:"object"`
	PendingAuthorizedAmount  int64                                   `json:"pending_authorized_amount"`
	PendingHeldAmount        int64                                   `json:"pending_held_amount"`
	RequestHistory           []*IssuingAuthorizationRequestHistory   `json:"request_history"`
	Status                   IssuingAuthorizationStatus              `json:"status"`
	Transactions             []*IssuingTransaction                   `json:"transactions"`
	VerificationData         *IssuingAuthorizationVerificationData   `json:"verification_data"`
	WalletProvider           IssuingAuthorizationWalletProviderType  `json:"wallet_provider"`
}

// IssuingMerchantData is the resource representing merchant data on Issuing APIs.
type IssuingMerchantData struct {
	Category   string `json:"category"`
	City       string `json:"city"`
	Country    string `json:"country"`
	Name       string `json:"name"`
	NetworkID  string `json:"network_id"`
	PostalCode string `json:"postal_code"`
	State      string `json:"state"`
}

// IssuingAuthorizationList is a list of issuing authorizations as retrieved from a list endpoint.
type IssuingAuthorizationList struct {
	ListMeta
	Data []*IssuingAuthorization `json:"data"`
}

// UnmarshalJSON handles deserialization of an IssuingAuthorization.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (i *IssuingAuthorization) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		i.ID = id
		return nil
	}

	type issuingAuthorization IssuingAuthorization
	var v issuingAuthorization
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*i = IssuingAuthorization(v)
	return nil
}
<commit_msg>Add support for `Authentication` and `URL` on Issuing `Authorization`<commit_after>package stripe

import "encoding/json"

// IssuingAuthorizationAuthorizationMethod is the list of possible values for the authorization method
// on an issuing authorization.
type IssuingAuthorizationAuthorizationMethod string

// List of values that IssuingAuthorizationAuthorizationMethod can take.
const (
	IssuingAuthorizationAuthorizationMethodChip        IssuingAuthorizationAuthorizationMethod = "chip"
	IssuingAuthorizationAuthorizationMethodContactless IssuingAuthorizationAuthorizationMethod = "contactless"
	IssuingAuthorizationAuthorizationMethodKeyedIn     IssuingAuthorizationAuthorizationMethod = "keyed_in"
	IssuingAuthorizationAuthorizationMethodOnline      IssuingAuthorizationAuthorizationMethod = "online"
	IssuingAuthorizationAuthorizationMethodSwipe       IssuingAuthorizationAuthorizationMethod = "swipe"
)

// IssuingAuthorizationRequestHistoryReason is the list of possible values for the request history
// reason on an issuing authorization.
type IssuingAuthorizationRequestHistoryReason string

// List of values that IssuingAuthorizationRequestHistoryReason can take.
const (
	IssuingAuthorizationRequestHistoryReasonAuthorizationControls IssuingAuthorizationRequestHistoryReason = "authorization_controls"
	IssuingAuthorizationRequestHistoryReasonCardActive            IssuingAuthorizationRequestHistoryReason = "card_active"
	IssuingAuthorizationRequestHistoryReasonCardInactive          IssuingAuthorizationRequestHistoryReason = "card_inactive"
	IssuingAuthorizationRequestHistoryReasonInsufficientFunds     IssuingAuthorizationRequestHistoryReason = "insufficient_funds"
	IssuingAuthorizationRequestHistoryReasonWebhookApproved       IssuingAuthorizationRequestHistoryReason = "webhook_approved"
	IssuingAuthorizationRequestHistoryReasonWebhookDeclined       IssuingAuthorizationRequestHistoryReason = "webhook_declined"
	IssuingAuthorizationRequestHistoryReasonWebhookTimeout        IssuingAuthorizationRequestHistoryReason = "webhook_timeout"
)

// IssuingAuthorizationStatus is the possible values for status for an issuing authorization.
type IssuingAuthorizationStatus string

// List of values that IssuingAuthorizationStatus can take.
const (
	IssuingAuthorizationStatusClosed   IssuingAuthorizationStatus = "closed"
	IssuingAuthorizationStatusPending  IssuingAuthorizationStatus = "pending"
	IssuingAuthorizationStatusReversed IssuingAuthorizationStatus = "reversed"
)

// IssuingAuthorizationVerificationDataAuthentication is the list of possible values for the result
// of an authentication on an issuing authorization.
type IssuingAuthorizationVerificationDataAuthentication string

// List of values that IssuingAuthorizationVerificationDataCheck can take.
const (
	IssuingAuthorizationVerificationDataAuthenticationExempt  IssuingAuthorizationVerificationDataAuthentication = "exempt"
	IssuingAuthorizationVerificationDataAuthenticationFailure IssuingAuthorizationVerificationDataAuthentication = "failure"
	IssuingAuthorizationVerificationDataAuthenticationNone    IssuingAuthorizationVerificationDataAuthentication = "none"
	IssuingAuthorizationVerificationDataAuthenticationSuccess IssuingAuthorizationVerificationDataAuthentication = "success"
)

// IssuingAuthorizationVerificationDataCheck is the list of possible values for result of a check
// for verification data on an issuing authorization.
type IssuingAuthorizationVerificationDataCheck string

// List of values that IssuingAuthorizationVerificationDataCheck can take.
const (
	IssuingAuthorizationVerificationDataCheckMatch       IssuingAuthorizationVerificationDataCheck = "match"
	IssuingAuthorizationVerificationDataCheckMismatch    IssuingAuthorizationVerificationDataCheck = "mismatch"
	IssuingAuthorizationVerificationDataCheckNotProvided IssuingAuthorizationVerificationDataCheck = "not_provided"
)

// IssuingAuthorizationWalletProviderType is the list of possible values for the authorization's wallet provider.
type IssuingAuthorizationWalletProviderType string

// List of values that IssuingAuthorizationWalletProviderType can take.
const (
	IssuingAuthorizationWalletProviderTypeApplePay   IssuingAuthorizationWalletProviderType = "apple_pay"
	IssuingAuthorizationWalletProviderTypeGooglePay  IssuingAuthorizationWalletProviderType = "google_pay"
	IssuingAuthorizationWalletProviderTypeSamsungPay IssuingAuthorizationWalletProviderType = "samsung_pay"
)

// IssuingAuthorizationParams is the set of parameters that can be used when updating an issuing authorization.
type IssuingAuthorizationParams struct {
	Params     `form:"*"`
	HeldAmount *int64 `form:"held_amount"`
}

// IssuingAuthorizationListParams is the set of parameters that can be used when listing issuing authorizations.
type IssuingAuthorizationListParams struct {
	ListParams   `form:"*"`
	Card         *string           `form:"card"`
	Cardholder   *string           `form:"cardholder"`
	Created      *int64            `form:"created"`
	CreatedRange *RangeQueryParams `form:"created"`
	Status       *string           `form:"status"`
}

// IssuingAuthorizationAuthorizationControls is the resource representing authorization controls on an issuing authorization.
type IssuingAuthorizationAuthorizationControls struct {
	AllowedCategories []string `json:"allowed_categories"`
	BlockedCategories []string `json:"blocked_categories"`
	Currency          Currency `json:"currency"`
	MaxAmount         int64    `json:"max_amount"`
	MaxApprovals      int64    `json:"max_approvals"`
}

// IssuingAuthorizationRequestHistory is the resource representing a request history on an issuing authorization.
type IssuingAuthorizationRequestHistory struct {
	Approved           bool                                     `json:"approved"`
	AuthorizedAmount   int64                                    `json:"authorized_amount"`
	AuthorizedCurrency Currency                                 `json:"authorized_currency"`
	Created            int64                                    `json:"created"`
	HeldAmount         int64                                    `json:"held_amount"`
	HeldCurrency       Currency                                 `json:"held_currency"`
	Reason             IssuingAuthorizationRequestHistoryReason `json:"reason"`
}

// IssuingAuthorizationVerificationData is the resource representing verification data on an issuing authorization.
type IssuingAuthorizationVerificationData struct {
	AddressLine1Check IssuingAuthorizationVerificationDataCheck          `json:"address_line1_check"`
	AddressZipCheck   IssuingAuthorizationVerificationDataCheck          `json:"address_zip_check"`
	Authentication    IssuingAuthorizationVerificationDataAuthentication `json:"authentication"`
	CVCCheck          IssuingAuthorizationVerificationDataCheck          `json:"cvc_check"`
}

// IssuingAuthorization is the resource representing a Stripe issuing authorization.
type IssuingAuthorization struct {
	Approved                 bool                                    `json:"approved"`
	AuthorizationMethod      IssuingAuthorizationAuthorizationMethod `json:"authorization_method"`
	AuthorizedAmount         int64                                   `json:"authorized_amount"`
	AuthorizedCurrency       Currency                                `json:"authorized_currency"`
	BalanceTransactions      []*BalanceTransaction                   `json:"balance_transactions"`
	Card                     *IssuingCard                            `json:"card"`
	Cardholder               *IssuingCardholder                      `json:"cardholder"`
	Created                  int64                                   `json:"created"`
	HeldAmount               int64                                   `json:"held_amount"`
	HeldCurrency             Currency                                `json:"held_currency"`
	ID                       string                                  `json:"id"`
	IsHeldAmountControllable bool                                    `json:"is_held_amount_controllable"`
	Livemode                 bool                                    `json:"livemode"`
	MerchantData             *IssuingMerchantData                    `json:"merchant_data"`
	Metadata                 map[string]string                       `json:"metadata"`
	Object                   string                                  `json:"object"`
	PendingAuthorizedAmount  int64                                   `json:"pending_authorized_amount"`
	PendingHeldAmount        int64                                   `json:"pending_held_amount"`
	RequestHistory           []*IssuingAuthorizationRequestHistory   `json:"request_history"`
	Status                   IssuingAuthorizationStatus              `json:"status"`
	Transactions             []*IssuingTransaction                   `json:"transactions"`
	VerificationData         *IssuingAuthorizationVerificationData   `json:"verification_data"`
	WalletProvider           IssuingAuthorizationWalletProviderType  `json:"wallet_provider"`
}

// IssuingMerchantData is the resource representing merchant data on Issuing APIs.
type IssuingMerchantData struct {
	Category   string `json:"category"`
	City       string `json:"city"`
	Country    string `json:"country"`
	Name       string `json:"name"`
	NetworkID  string `json:"network_id"`
	PostalCode string `json:"postal_code"`
	State      string `json:"state"`
	URL        string `json:"url"`
}

// IssuingAuthorizationList is a list of issuing authorizations as retrieved from a list endpoint.
type IssuingAuthorizationList struct {
	ListMeta
	Data []*IssuingAuthorization `json:"data"`
}

// UnmarshalJSON handles deserialization of an IssuingAuthorization.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (i *IssuingAuthorization) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		i.ID = id
		return nil
	}

	type issuingAuthorization IssuingAuthorization
	var v issuingAuthorization
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*i = IssuingAuthorization(v)
	return nil
}
<|endoftext|>


<commit_before>package hummingbird

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestParseRange(t *testing.T) {
	//Setting up individual test data
	tests := []struct {
		rangeHeader string
		exResBegin  int64
		exResEnd    int64
		exError     string
	}{
		{" ", 0, 0, ""},
		{"bytes=", 0, 0, "invalid range format"},
		{"bytes=-", 0, 0, "invalid range format"},
		{"bytes=-cv", 0, 0, "invalid end with no begin"},
		{"bytes=cv-", 0, 0, "invalid begin with no end"},
		{"bytes=-0", 0, 0, "zero end with no begin"},
		{"bytes=-12346", 0, 12345, ""},
		{"bytes=-12344", 1, 12345, ""},
		{"bytes=12344-", 12344, 12345, ""},
		{"bytes=12345-cv", 0, 0, "invalid end"},
		{"bytes=cv-12345", 0, 0, "invalid begin"},
		{"bytes=12346-12", 0, 12346, ""},
		{"bytes=12346-123457", 0, 0, "Begin bigger than file"},
		{"bytes=12342-12343", 12342, 12344, ""},
		{"bytes=12342-12344", 12342, 12345, ""},
	}

	//Run tests with data from above
	for _, test := range tests {
		result, err := ParseRange(test.rangeHeader, 12345)
		if test.rangeHeader == " " {
			assert.Nil(t, result)
			assert.Nil(t, err)
			continue
		}
		if test.exError == "" {
			httpResult := httpRange{test.exResBegin, test.exResEnd}
			assert.Nil(t, err)
			assert.Contains(t, result, httpResult)
		} else {
			assert.Equal(t, err.Error(), test.exError)
		}
	}
}

func TestParseRange_NoEnd_BeginLargerThanFilesize(t *testing.T) {
	result, err := ParseRange("bytes=12346-", 12345)
	assert.Nil(t, err)
	assert.Empty(t, result)
}

func TestParseDate(t *testing.T) {
	//Setup tests with individual data
	tests := []string{
		"Mon, 02 Jan 2006 15:04:05 MST",
		"Mon, 02 Jan 2006 15:04:05 -0700",
		"Mon Jan 02 15:04:05 2006",
		"Monday, 02-Jan-06 15:04:05 MST",
		"1136214245",
		"1136214245.1234",
		"2006-01-02 15:04:05",
	}

	//Run Tests from above
	for _, timestamp := range tests {
		timeResult, err := ParseDate(timestamp)
		if err == nil {
			assert.Equal(t, timeResult.Day(), 02)
			assert.Equal(t, timeResult.Month(), 01)
			assert.Equal(t, timeResult.Year(), 2006)
			assert.Equal(t, timeResult.Hour(), 15)
			assert.Equal(t, timeResult.Minute(), 04)
			assert.Equal(t, timeResult.Second(), 05)
		} else {
			assert.Equal(t, err.Error(), "invalid time")
		}
	}

}

func TestStandardizeTimestamp(t *testing.T) {
	//Setup tests with individual data
	tests := []struct {
		timestamp      string
		expectedResult string
	}{
		{"12345.12345", "0000012345.12345"},
		{"12345.1234", "0000012345.12340"},
		{"12345.1234_123455", "0000012345.12340_0000000000123455"},
		{"12345.12343_12345a", "0000012345.12343_000000000012345a"},
	}

	//Run Tests from above
	for _, test := range tests {
		result, _ := StandardizeTimestamp(test.timestamp)
		assert.Equal(t, test.expectedResult, result)
	}

}

func TestStandardizeTimestamp_invalidTimestamp(t *testing.T) {
	//Setup test data
	tests := []struct {
		timestamp string
		errorMsg  string
	}{
		{"invalidTimestamp", "Could not parse float from 'invalidTimestamp'."},
		{"1234.1234_invalidOffset", "Could not parse int from 'invalidOffset'."},
	}
	for _, test := range tests {
		_, err := StandardizeTimestamp(test.timestamp)
		assert.Equal(t, err.Error(), test.errorMsg)
	}
}

func TestGetEpochFromTimestamp(t *testing.T) {
	//Setup tests with individual data
	tests := []struct {
		timestamp      string
		expectedResult string
	}{
		{"12345.12345", "0000012345.12345"},
		{"12345.1234", "0000012345.12340"},
		{"12345.1234_123455", "0000012345.12340"},
		{"12345.12343_12345a", "0000012345.12343"},
	}

	//Run Tests from above
	for _, test := range tests {
		result, _ := GetEpochFromTimestamp(test.timestamp)
		assert.Equal(t, test.expectedResult, result)
	}
}

func TestGetEpochFromTimestamp_invalidTimestamp(t *testing.T) {
	_, err := GetEpochFromTimestamp("invalidTimestamp")
	assert.Equal(t, err.Error(), "Could not parse float from 'invalidTimestamp'.")
}

func TestParseTimestamp(t *testing.T) {
	tests := []string{
		"2006-01-02 15:04:05",
		"Mon, 02 Jan 2006 15:04:05 MST",
	}

	for _, timestamp := range tests {
		timeResult, err := FormatTimestamp(timestamp)
		if err != nil {
			assert.Equal(t, err.Error(), "invalid time")
			assert.Empty(t, timeResult)
		} else {
			assert.Equal(t, "2006-01-02T15:04:05", timeResult)
		}
	}
}

func TestLooksTrue(t *testing.T) {
	tests := []string{
		"true ",
		"true",
		"t",
		"yes",
		"y",
		"1",
		"on",
	}

	for _, test := range tests {
		isTrue := LooksTrue(test)
		assert.True(t, isTrue)
	}
}

func TestValidTimestamp(t *testing.T) {
	assert.True(t, ValidTimestamp("12345.12345"))
	assert.False(t, ValidTimestamp("12345"))
	assert.False(t, ValidTimestamp("your.face"))
}

func TestUrlencode(t *testing.T) {
	assert.True(t, Urlencode("HELLOTHERE") == "HELLOTHERE")
	assert.True(t, Urlencode("HELLO THERE, YOU TWO//\x00\xFF") == "HELLO%20THERE%2C%20YOU%20TWO//%00%FF")
	assert.True(t, Urlencode("鐋댋") == "%E9%90%8B%EB%8C%8B")
}
<commit_msg>fix go tests<commit_after>package hummingbird

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestParseRange(t *testing.T) {
	//Setting up individual test data
	tests := []struct {
		rangeHeader string
		exResBegin  int64
		exResEnd    int64
		exError     string
	}{
		{" ", 0, 0, ""},
		{"bytes=", 0, 0, "invalid range format"},
		{"bytes=-", 0, 0, "invalid range format"},
		{"bytes=-cv", 0, 0, "invalid end with no begin"},
		{"bytes=cv-", 0, 0, "invalid begin with no end"},
		{"bytes=-0", 0, 0, "zero end with no begin"},
		{"bytes=-12346", 0, 12345, ""},
		{"bytes=-12344", 1, 12345, ""},
		{"bytes=12344-", 12344, 12345, ""},
		{"bytes=12345-cv", 0, 0, "invalid end"},
		{"bytes=cv-12345", 0, 0, "invalid begin"},
		{"bytes=12346-123457", 0, 0, "Begin bigger than file"},
		{"bytes=12342-12343", 12342, 12344, ""},
		{"bytes=12342-12344", 12342, 12345, ""},
	}

	//Run tests with data from above
	for _, test := range tests {
		result, err := ParseRange(test.rangeHeader, 12345)
		if test.rangeHeader == " " {
			assert.Nil(t, result)
			assert.Nil(t, err)
			continue
		}
		if test.exError == "" {
			httpResult := httpRange{test.exResBegin, test.exResEnd}
			assert.Nil(t, err)
			assert.Contains(t, result, httpResult)
		} else {
			assert.Equal(t, err.Error(), test.exError)
		}
	}
}

func TestParseRange_BeginAfterEnd(t *testing.T) {
	result, err := ParseRange("bytes=12346-12", 12345)
	assert.Nil(t, err)
	assert.Empty(t, result)
}

func TestParseRange_NoEnd_BeginLargerThanFilesize(t *testing.T) {
	result, err := ParseRange("bytes=12346-", 12345)
	assert.Nil(t, err)
	assert.Empty(t, result)
}

func TestParseDate(t *testing.T) {
	//Setup tests with individual data
	tests := []string{
		"Mon, 02 Jan 2006 15:04:05 MST",
		"Mon, 02 Jan 2006 15:04:05 -0700",
		"Mon Jan 02 15:04:05 2006",
		"Monday, 02-Jan-06 15:04:05 MST",
		"1136214245",
		"1136214245.1234",
		"2006-01-02 15:04:05",
	}

	//Run Tests from above
	for _, timestamp := range tests {
		timeResult, err := ParseDate(timestamp)
		if err == nil {
			assert.Equal(t, timeResult.Day(), 02)
			assert.Equal(t, timeResult.Month(), 01)
			assert.Equal(t, timeResult.Year(), 2006)
			assert.Equal(t, timeResult.Hour(), 15)
			assert.Equal(t, timeResult.Minute(), 04)
			assert.Equal(t, timeResult.Second(), 05)
		} else {
			assert.Equal(t, err.Error(), "invalid time")
		}
	}

}

func TestStandardizeTimestamp(t *testing.T) {
	//Setup tests with individual data
	tests := []struct {
		timestamp      string
		expectedResult string
	}{
		{"12345.12345", "0000012345.12345"},
		{"12345.1234", "0000012345.12340"},
		{"12345.1234_123455", "0000012345.12340_0000000000123455"},
		{"12345.12343_12345a", "0000012345.12343_000000000012345a"},
	}

	//Run Tests from above
	for _, test := range tests {
		result, _ := StandardizeTimestamp(test.timestamp)
		assert.Equal(t, test.expectedResult, result)
	}

}

func TestStandardizeTimestamp_invalidTimestamp(t *testing.T) {
	//Setup test data
	tests := []struct {
		timestamp string
		errorMsg  string
	}{
		{"invalidTimestamp", "Could not parse float from 'invalidTimestamp'."},
		{"1234.1234_invalidOffset", "Could not parse int from 'invalidOffset'."},
	}
	for _, test := range tests {
		_, err := StandardizeTimestamp(test.timestamp)
		assert.Equal(t, err.Error(), test.errorMsg)
	}
}

func TestGetEpochFromTimestamp(t *testing.T) {
	//Setup tests with individual data
	tests := []struct {
		timestamp      string
		expectedResult string
	}{
		{"12345.12345", "0000012345.12345"},
		{"12345.1234", "0000012345.12340"},
		{"12345.1234_123455", "0000012345.12340"},
		{"12345.12343_12345a", "0000012345.12343"},
	}

	//Run Tests from above
	for _, test := range tests {
		result, _ := GetEpochFromTimestamp(test.timestamp)
		assert.Equal(t, test.expectedResult, result)
	}
}

func TestGetEpochFromTimestamp_invalidTimestamp(t *testing.T) {
	_, err := GetEpochFromTimestamp("invalidTimestamp")
	assert.Equal(t, err.Error(), "Could not parse float from 'invalidTimestamp'.")
}

func TestParseTimestamp(t *testing.T) {
	tests := []string{
		"2006-01-02 15:04:05",
		"Mon, 02 Jan 2006 15:04:05 MST",
	}

	for _, timestamp := range tests {
		timeResult, err := FormatTimestamp(timestamp)
		if err != nil {
			assert.Equal(t, err.Error(), "invalid time")
			assert.Empty(t, timeResult)
		} else {
			assert.Equal(t, "2006-01-02T15:04:05", timeResult)
		}
	}
}

func TestLooksTrue(t *testing.T) {
	tests := []string{
		"true ",
		"true",
		"t",
		"yes",
		"y",
		"1",
		"on",
	}

	for _, test := range tests {
		isTrue := LooksTrue(test)
		assert.True(t, isTrue)
	}
}

func TestValidTimestamp(t *testing.T) {
	assert.True(t, ValidTimestamp("12345.12345"))
	assert.False(t, ValidTimestamp("12345"))
	assert.False(t, ValidTimestamp("your.face"))
}

func TestUrlencode(t *testing.T) {
	assert.True(t, Urlencode("HELLOTHERE") == "HELLOTHERE")
	assert.True(t, Urlencode("HELLO THERE, YOU TWO//\x00\xFF") == "HELLO%20THERE%2C%20YOU%20TWO//%00%FF")
	assert.True(t, Urlencode("鐋댋") == "%E9%90%8B%EB%8C%8B")
}
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package yamltemplate_test

import (
	// Replace text/template in your code with safetext/yamltemplate for automatic YAML injection detection

	//"text/template"
	template "github.com/google/safetext/yamltemplate"

	"bytes"
	"fmt"
	"strconv"
	"testing"
)

func TestSafetextYamltemplate(t *testing.T) {
	type testCase struct {
		tmplText     string
		replacements map[interface{}]interface{}
		err          error
	}

	testCases := []testCase{
		// Negative cases
		{
			tmplText: "{ hello: \"{{ .addressee | js }}\" }",
			replacements: map[interface{}]interface{}{
				"addressee": "world\", inject: \"oops",
			},
			err: nil,
		},

		{
			tmplText: `
---
- stream: one,
- hello: {{ .addressee }},
---
- stream: two,
- hello: {{ .addressee }},
`,
			replacements: nil,
			err:          nil,
		},

		{
			tmplText: `
data:
  HTTPS_PROXY: {{.p1}}
  NO_PROXY: {{.p2}}
`,
			replacements: map[interface{}]interface{}{
				"p1": "1",
				"p2": "localhost, 127.0.0.1",
			},
			err: nil,
		},

		{
			tmplText: `
data:
  HTTPS_PROXY: {{.p1}}
  NO_PROXY: {{.p2}}
`,
			replacements: map[interface{}]interface{}{
				"p1": "",
				"p2": "localhost, 127.0.0.1",
			},
			err: nil,
		},

		{
			tmplText: "{ {{ if not .hide }}hello: {{ .addressee }}{{end}} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"hide":      false,
			},
			err: nil,
		},

		{
			tmplText: "{ {{ if eq .addressee \"world\" }}hello: {{ .addressee }}{{end}} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
			},
			err: nil,
		},

		{
			tmplText: `{ list: "{{ range .entries }}{{.}}{{ end }}" }`,
			replacements: map[interface{}]interface{}{
				"entries": []string{"(special characters to not trigger fast path {})", "two", "three"},
			},
			err: nil,
		},

		{
			tmplText: `
list:
{{with .some_field}}
{{if eq . "x"}}
- {{.}}
{{end}}
{{end}}
`,
			replacements: map[interface{}]interface{}{
				"some_field": "x",
				"slow":       "{}",
			},
			err: nil,
		},

		{
			tmplText: "{ test: bla }",
			replacements: map[interface{}]interface{}{
				0: "(special characters to not trigger fast path {})",
			},
			err: nil,
		},

		// Verify that unused replacements in nested yaml don't cause templates to fail
		{
			tmplText: `hello:
- to: {{ .addressee }}
  next:
  - first: test
`,
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"unused":    "some-thing",
			},
			err: nil,
		},

		// Verify that valid strings with non-standard characters work in nested yaml
		{
			tmplText: `hello:
- to: {{ .addressee }}
  next:
  - first: test
`,
			replacements: map[interface{}]interface{}{
				"addressee": "whole-world",
			},
			err: nil,
		},

		// Verify that internal YAML parser rejects duplicate keys
		{
			tmplText: "{ hello: {{ .addressee }}, hello: multiple }",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: template.ErrInvalidYAMLTemplate,
		},

		// Verify that internal YAML parsers rejects map keys
		{
			tmplText: "{ {}: {{ .addressee }} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: template.ErrInvalidYAMLTemplate,
		},

		// Verify that internal YAML parsers rejects slice keys
		{
			tmplText: "{ [1, 2, 3]: {{ .addressee }} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: template.ErrInvalidYAMLTemplate,
		},

		// Verify that YAML parses still accepts "non-strict" YAML (whilst rejecting duplicate keys)
		{
			tmplText: "a: {{ .addressee }}",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: nil,
		},

		// Positive cases
		{
			tmplText: "{ hello: \"{{ .addressee }}\" }",
			replacements: map[interface{}]interface{}{
				"addressee": "world\", hello: \"oops_p",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ hello: \"{{ .addressee }}\", parent: [ 1, {{ .s }}, 3 ] }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"s":         "2, 4",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ hello: \"{{ .addressee }}\", parent: [ 1, { a: {{ .s }} }, 3 ] }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"s":         "2 , b : b",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ hello: {{ .addressee }} }",
			replacements: map[interface{}]interface{}{
				"addressee": "{}",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ {{ if eq .caddressee \"world\" }}hello: {{ .addressee }}{{end}} }",
			replacements: map[interface{}]interface{}{
				"caddressee": "world",
				"addressee":  "world, inject: true",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: `
---
- stream: one
- hello: a
---
- stream: two
- hello: {{ .addressee }}
`,
			replacements: map[interface{}]interface{}{"addressee": "world\n- inject"},
			err:          template.ErrYAMLInjection,
		},
	}

	for _, tc := range testCases {
		tmpl, _ := template.New("test").Parse(tc.tmplText)
		var buf bytes.Buffer
		err := tmpl.Execute(&buf, tc.replacements)

		if err != tc.err {
			t.Errorf("Expected %v, got %v\n", tc.err, err)

			if err == nil {
				t.Logf("template execution result was %s\n", buf.String())
			}
		}
	}
}

// Check func maps still work
func sanitize(input interface{}) string {
	return fmt.Sprintf("%q", input)
}

func TestSafetextYamltemplateNegativeFuncMap(t *testing.T) {
	var funcMap = map[string]interface{}{
		"sanitize": sanitize,
	}

	tmpl, _ := template.New("test").Funcs(template.FuncMap(funcMap)).Parse("{ a: {{ .a | sanitize }}, b: {{ .b | sanitize }} }")

	replacements := map[string]interface{}{
		"a": "world\", inject: \"oops",
		"b": "world, inject: oops",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

// Check structs, instead of maps
func TestSafetextYamltemplateNegativeStruct(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ name: {{ .Name }}, age: {{ .Age }} }")

	type person struct {
		Name string
		Age  int
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: "bla", Age: 42})
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

func TestSafetextYamltemplatePositiveStruct(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ name: {{ .Name }}, age: {{ .Age }} }")

	type person struct {
		Name string
		Age  int
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: "bla, age: 31", Age: 42})
	if err != template.ErrYAMLInjection {
		t.Errorf("Failed to detect YAML injection (%v)!", err)
	}
}

// Root node being a list instead of a map
func TestSafetextYamltemplateNegativeRootList(t *testing.T) {
	tmpl, _ := template.New("test").Parse(`
- one: a
- one: b
`)

	replacements := map[string]interface{}{
		"some_field":    "x",
		"use_slow_path": "{}",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

// Check indirect types are followed
func TestSafetextYamltemplatePositiveIndirection(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ name: {{ .Name }}, age: {{ .Age }} }")

	type person struct {
		Name **string
		Age  int
	}

	n := "bla, age 31"
	nAddr := &n

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: &nAddr, Age: 42})
	if err != template.ErrYAMLInjection {
		t.Errorf("Failed to detect YAML injection (%v)!", err)
	}
}

// Check parsing files works
func TestSafetextYamltemplateFiles(t *testing.T) {
	tmpl, _ := template.ParseFiles("testdata/list.yaml.tmpl")

	replacements := map[string]interface{}{
		"some_field":    "x",
		"use_slow_path": "{}",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

// Check methods work
type A struct {
}

func (A) GetName(n int) string { return "n is " + strconv.Itoa(n) }

func TestSafetextYamltemplateMethod(t *testing.T) {
	tmpl, _ := template.New("test").Parse(`- {{ (.a.GetName 0x41) | js }}`)

	replacements := map[string]interface{}{
		"a":             A{},
		"use_slow_path": "{}",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}

	if buf.String() != "- n is 65" {
		t.Errorf("Got %v, want %v\n", buf.String(), "- n is 65")
	}
}

func TestSafetextYamltemplateOptOut(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ Person-{{ (StructuralData .Name) }}: {{ .Age }} }")

	type person struct {
		Name string
		Age  int
		Slow string
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: "bla", Age: 42, Slow: "{}"})
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}

	if buf.String() != "{ Person-bla: 42 }" {
		t.Errorf("Got %v, want { Person-bla: 42 }", buf.String())
	}
}

func TestCustomTypeWithStringBaseYamltemplatePositiveStruct(t *testing.T) {
	yamlTemplate := `
name: {{ .Name }}
type: {{ .PDType }}
`
	tmpl, _ := template.New("test").Parse(yamlTemplate)

	type PersistentDiskType string

	type StorageClassSpec struct {
		Name   string
		PDType PersistentDiskType
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, StorageClassSpec{Name: "ssd", PDType: "pd-ssd"})
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}
<commit_msg>Update yamltemplate_test.go<commit_after>/*
 *
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package yamltemplate_test

import (
	// Replace text/template in your code with safetext/yamltemplate for automatic YAML injection detection

	//"text/template"
	template "github.com/google/safetext/yamltemplate"

	"bytes"
	"fmt"
	"strconv"
	"testing"
)

func TestSafetextYamltemplate(t *testing.T) {
	type testCase struct {
		tmplText     string
		replacements map[interface{}]interface{}
		err          error
	}

	testCases := []testCase{
		// Negative cases
		{
			tmplText: "{ hello: \"{{ .addressee | js }}\" }",
			replacements: map[interface{}]interface{}{
				"addressee": "world\", inject: \"oops",
			},
			err: nil,
		},

		{
			tmplText: `
---
- stream: one,
- hello: {{ .addressee }},
---
- stream: two,
- hello: {{ .addressee }},
`,
			replacements: nil,
			err:          nil,
		},

		{
			tmplText: `
data:
  HTTPS_PROXY: {{.p1}}
  NO_PROXY: {{.p2}}
`,
			replacements: map[interface{}]interface{}{
				"p1": "1",
				"p2": "localhost, 127.0.0.1",
			},
			err: nil,
		},

		{
			tmplText: `
data:
  HTTPS_PROXY: {{.p1}}
  NO_PROXY: {{.p2}}
`,
			replacements: map[interface{}]interface{}{
				"p1": "",
				"p2": "localhost, 127.0.0.1",
			},
			err: nil,
		},

		{
			tmplText: "{ {{ if not .hide }}hello: {{ .addressee }}{{end}} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"hide":      false,
			},
			err: nil,
		},

		{
			tmplText: "{ {{ if eq .addressee \"world\" }}hello: {{ .addressee }}{{end}} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
			},
			err: nil,
		},

		{
			tmplText: `{ list: "{{ range .entries }}{{.}}{{ end }}" }`,
			replacements: map[interface{}]interface{}{
				"entries": []string{"(special characters to not trigger fast path {})", "two", "three"},
			},
			err: nil,
		},

		{
			tmplText: `
list:
{{with .some_field}}
{{if eq . "x"}}
- {{.}}
{{end}}
{{end}}
`,
			replacements: map[interface{}]interface{}{
				"some_field": "x",
				"slow":       "{}",
			},
			err: nil,
		},

		{
			tmplText: "{ test: bla }",
			replacements: map[interface{}]interface{}{
				0: "(special characters to not trigger fast path {})",
			},
			err: nil,
		},

		// Verify that unused replacements in nested yaml don't cause templates to fail
		{
			tmplText: `hello:
- to: {{ .addressee }}
  next:
  - first: test
`,
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"unused":    "some-thing",
			},
			err: nil,
		},

		// Verify that valid strings with non-standard characters work in nested yaml
		{
			tmplText: `hello:
- to: {{ .addressee }}
  next:
  - first: test
`,
			replacements: map[interface{}]interface{}{
				"addressee": "whole-world",
			},
			err: nil,
		},

		// Verify that internal YAML parser rejects duplicate keys
		{
			tmplText: "{ hello: {{ .addressee }}, hello: multiple }",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: template.ErrInvalidYAMLTemplate,
		},

		// Verify that internal YAML parsers rejects map keys
		{
			tmplText: "{ {}: {{ .addressee }} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: template.ErrInvalidYAMLTemplate,
		},

		// Verify that internal YAML parsers rejects slice keys
		{
			tmplText: "{ [1, 2, 3]: {{ .addressee }} }",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: template.ErrInvalidYAMLTemplate,
		},

		// Verify that YAML parses still accepts "non-strict" YAML (whilst rejecting duplicate keys)
		{
			tmplText: "a: {{ .addressee }}",
			replacements: map[interface{}]interface{}{
				"addressee": "world (special characters to not trigger fast path {})",
			},
			err: nil,
		},

		// Positive cases
		{
			tmplText: "{ hello: \"{{ .addressee }}\" }",
			replacements: map[interface{}]interface{}{
				"addressee": "world\", hello: \"oops_p",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ hello: \"{{ .addressee }}\", parent: [ 1, {{ .s }}, 3 ] }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"s":         "2, 4",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ hello: \"{{ .addressee }}\", parent: [ 1, { a: {{ .s }} }, 3 ] }",
			replacements: map[interface{}]interface{}{
				"addressee": "world",
				"s":         "2 , b : b",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ hello: {{ .addressee }} }",
			replacements: map[interface{}]interface{}{
				"addressee": "{}",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: "{ {{ if eq .caddressee \"world\" }}hello: {{ .addressee }}{{end}} }",
			replacements: map[interface{}]interface{}{
				"caddressee": "world",
				"addressee":  "world, inject: true",
			},
			err: template.ErrYAMLInjection,
		},

		{
			tmplText: `
---
- stream: one
- hello: a
---
- stream: two
- hello: {{ .addressee }}
`,
			replacements: map[interface{}]interface{}{"addressee": "world\n- inject"},
			err:          template.ErrYAMLInjection,
		},

		// Accessing anchors should count as injected YAML syntax
		{
			tmplText: `{ secret: &secret_label 'test', disclosed: {{ .controlled }}  }`,
			replacements: map[interface{}]interface{}{
				"controlled": "*secret_label",
			},
			err: template.ErrYAMLInjection,
		},
	}

	for _, tc := range testCases {
		tmpl, _ := template.New("test").Parse(tc.tmplText)
		var buf bytes.Buffer
		err := tmpl.Execute(&buf, tc.replacements)

		if err != tc.err {
			t.Errorf("Expected %v, got %v\n", tc.err, err)

			if err == nil {
				t.Logf("template execution result was %s\n", buf.String())
			}
		}
	}
}

// Check func maps still work
func sanitize(input interface{}) string {
	return fmt.Sprintf("%q", input)
}

func TestSafetextYamltemplateNegativeFuncMap(t *testing.T) {
	var funcMap = map[string]interface{}{
		"sanitize": sanitize,
	}

	tmpl, _ := template.New("test").Funcs(template.FuncMap(funcMap)).Parse("{ a: {{ .a | sanitize }}, b: {{ .b | sanitize }} }")

	replacements := map[string]interface{}{
		"a": "world\", inject: \"oops",
		"b": "world, inject: oops",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

// Check structs, instead of maps
func TestSafetextYamltemplateNegativeStruct(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ name: {{ .Name }}, age: {{ .Age }} }")

	type person struct {
		Name string
		Age  int
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: "bla", Age: 42})
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

func TestSafetextYamltemplatePositiveStruct(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ name: {{ .Name }}, age: {{ .Age }} }")

	type person struct {
		Name string
		Age  int
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: "bla, age: 31", Age: 42})
	if err != template.ErrYAMLInjection {
		t.Errorf("Failed to detect YAML injection (%v)!", err)
	}
}

// Root node being a list instead of a map
func TestSafetextYamltemplateNegativeRootList(t *testing.T) {
	tmpl, _ := template.New("test").Parse(`
- one: a
- one: b
`)

	replacements := map[string]interface{}{
		"some_field":    "x",
		"use_slow_path": "{}",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

// Check indirect types are followed
func TestSafetextYamltemplatePositiveIndirection(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ name: {{ .Name }}, age: {{ .Age }} }")

	type person struct {
		Name **string
		Age  int
	}

	n := "bla, age 31"
	nAddr := &n

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: &nAddr, Age: 42})
	if err != template.ErrYAMLInjection {
		t.Errorf("Failed to detect YAML injection (%v)!", err)
	}
}

// Check parsing files works
func TestSafetextYamltemplateFiles(t *testing.T) {
	tmpl, _ := template.ParseFiles("testdata/list.yaml.tmpl")

	replacements := map[string]interface{}{
		"some_field":    "x",
		"use_slow_path": "{}",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}

// Check methods work
type A struct {
}

func (A) GetName(n int) string { return "n is " + strconv.Itoa(n) }

func TestSafetextYamltemplateMethod(t *testing.T) {
	tmpl, _ := template.New("test").Parse(`- {{ (.a.GetName 0x41) | js }}`)

	replacements := map[string]interface{}{
		"a":             A{},
		"use_slow_path": "{}",
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, replacements)
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}

	if buf.String() != "- n is 65" {
		t.Errorf("Got %v, want %v\n", buf.String(), "- n is 65")
	}
}

func TestSafetextYamltemplateOptOut(t *testing.T) {
	tmpl, _ := template.New("test").Parse("{ Person-{{ (StructuralData .Name) }}: {{ .Age }} }")

	type person struct {
		Name string
		Age  int
		Slow string
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, person{Name: "bla", Age: 42, Slow: "{}"})
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}

	if buf.String() != "{ Person-bla: 42 }" {
		t.Errorf("Got %v, want { Person-bla: 42 }", buf.String())
	}
}

func TestCustomTypeWithStringBaseYamltemplatePositiveStruct(t *testing.T) {
	yamlTemplate := `
name: {{ .Name }}
type: {{ .PDType }}
`
	tmpl, _ := template.New("test").Parse(yamlTemplate)

	type PersistentDiskType string

	type StorageClassSpec struct {
		Name   string
		PDType PersistentDiskType
	}

	var buf bytes.Buffer
	err := tmpl.Execute(&buf, StorageClassSpec{Name: "ssd", PDType: "pd-ssd"})
	if err != nil {
		t.Errorf("tmpl.Execute() error = %v", err)
	}
}
<|endoftext|>


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

package logopt

import (
	"internal/testenv"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

const srcCode = `package x
type pair struct {a,b int}
func bar(y *pair) *int {
	return &y.b
}
var a []int
func foo(w, z *pair) *int {
	if *bar(w) > 0 {
		return bar(z)
	}
	if a[1] > 0 {
		a = a[:2]
	}
	return &a[0]
}
`

func want(t *testing.T, out string, desired string) {
	if !strings.Contains(out, desired) {
		t.Errorf("did not see phrase %s in \n%s", desired, out)
	}
}

func wantN(t *testing.T, out string, desired string, n int) {
	if strings.Count(out, desired) != n {
		t.Errorf("expected exactly %d occurences of %s in \n%s", n, desired, out)
	}
}

func TestLogOpt(t *testing.T) {
	t.Parallel()

	testenv.MustHaveGoBuild(t)

	dir, err := ioutil.TempDir("", "TestLogOpt")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dir)

	dir = fixSlash(dir) // Normalize the directory name as much as possible, for Windows testing
	src := filepath.Join(dir, "file.go")
	if err := ioutil.WriteFile(src, []byte(srcCode), 0644); err != nil {
		t.Fatal(err)
	}

	outfile := filepath.Join(dir, "file.o")

	t.Run("JSON_fails", func(t *testing.T) {
		// Test malformed flag
		out, err := testLogOpt(t, "-json=foo", src, outfile)
		if err == nil {
			t.Error("-json=foo succeeded unexpectedly")
		}
		want(t, out, "option should be")
		want(t, out, "number")

		// Test a version number that is currently unsupported (and should remain unsupported for a while)
		out, err = testLogOpt(t, "-json=9,foo", src, outfile)
		if err == nil {
			t.Error("-json=0,foo succeeded unexpectedly")
		}
		want(t, out, "version must be")

	})

	// replace d (dir)  with t ("tmpdir") and convert path separators to '/'
	normalize := func(out []byte, d, t string) string {
		s := string(out)
		s = strings.ReplaceAll(s, d, t)
		s = strings.ReplaceAll(s, string(os.PathSeparator), "/")
		return s
	}

	// Ensure that <128 byte copies are not reported and that 128-byte copies are.
	// Check at both 1 and 8-byte alignments.
	t.Run("Copy", func(t *testing.T) {
		const copyCode = `package x
func s128a1(x *[128]int8) [128]int8 {
	return *x
}
func s127a1(x *[127]int8) [127]int8 {
	return *x
}
func s16a8(x *[16]int64) [16]int64 {
	return *x
}
func s15a8(x *[15]int64) [15]int64 {
	return *x
}
`
		copy := filepath.Join(dir, "copy.go")
		if err := ioutil.WriteFile(copy, []byte(copyCode), 0644); err != nil {
			t.Fatal(err)
		}
		outcopy := filepath.Join(dir, "copy.o")

		// On not-amd64, test the host architecture and os
		arches := []string{runtime.GOARCH}
		goos0 := runtime.GOOS
		if runtime.GOARCH == "amd64" { // Test many things with "linux" (wasm will get "js")
			arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "ppc64le", "s390x", "wasm"}
			goos0 = "linux"
		}

		for _, arch := range arches {
			t.Run(arch, func(t *testing.T) {
				goos := goos0
				if arch == "wasm" {
					goos = "js"
				}
				_, err := testCopy(t, dir, arch, goos, copy, outcopy)
				if err != nil {
					t.Error("-json=0,file://log/opt should have succeeded")
				}
				logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "copy.json"))
				if err != nil {
					t.Error("-json=0,file://log/opt missing expected log file")
				}
				slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
				t.Logf("%s", slogged)
				want(t, slogged, `{"range":{"start":{"line":3,"character":2},"end":{"line":3,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
				want(t, slogged, `{"range":{"start":{"line":9,"character":2},"end":{"line":9,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
				wantN(t, slogged, `"code":"copy"`, 2)
			})
		}
	})

	// Some architectures don't fault on nil dereference, so nilchecks are eliminated differently.
	// The N-way copy test also doesn't need to run N-ways N times.
	if runtime.GOARCH != "amd64" {
		return
	}

	t.Run("Success", func(t *testing.T) {
		// This test is supposed to succeed

		// Note 'file://' is the I-Know-What-I-Am-Doing way of specifying a file, also to deal with corner cases for Windows.
		_, err := testLogOptDir(t, dir, "-json=0,file://log/opt", src, outfile)
		if err != nil {
			t.Error("-json=0,file://log/opt should have succeeded")
		}
		logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "file.json"))
		if err != nil {
			t.Error("-json=0,file://log/opt missing expected log file")
		}
		// All this delicacy with uriIfy and filepath.Join is to get this test to work right on Windows.
		slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
		t.Logf("%s", slogged)
		// below shows proper inlining and nilcheck
		want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"nilcheck","source":"go compiler","message":"","relatedInformation":[{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"}]}`)
		want(t, slogged, `{"range":{"start":{"line":11,"character":6},"end":{"line":11,"character":6}},"severity":3,"code":"isInBounds","source":"go compiler","message":""}`)
		want(t, slogged, `{"range":{"start":{"line":7,"character":6},"end":{"line":7,"character":6}},"severity":3,"code":"canInlineFunction","source":"go compiler","message":"cost: 35"}`)
		want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"inlineCall","source":"go compiler","message":"x.bar"}`)
		want(t, slogged, `{"range":{"start":{"line":8,"character":9},"end":{"line":8,"character":9}},"severity":3,"code":"inlineCall","source":"go compiler","message":"x.bar"}`)
	})
}

func testLogOpt(t *testing.T, flag, src, outfile string) (string, error) {
	run := []string{testenv.GoToolPath(t), "tool", "compile", flag, "-o", outfile, src}
	t.Log(run)
	cmd := exec.Command(run[0], run[1:]...)
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)
	return string(out), err
}

func testLogOptDir(t *testing.T, dir, flag, src, outfile string) (string, error) {
	// Notice the specified import path "x"
	run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", flag, "-o", outfile, src}
	t.Log(run)
	cmd := exec.Command(run[0], run[1:]...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)
	return string(out), err
}

func testCopy(t *testing.T, dir, goarch, goos, src, outfile string) (string, error) {
	// Notice the specified import path "x"
	run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", "-json=0,file://log/opt", "-o", outfile, src}
	t.Log(run)
	cmd := exec.Command(run[0], run[1:]...)
	cmd.Dir = dir
	cmd.Env = []string{"GOARCH=" + goarch, "GOOS=" + goos}
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)
	return string(out), err
}
<commit_msg>cmd/compile: make logopt test skip if cannot create scratch directory<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package logopt

import (
	"internal/testenv"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

const srcCode = `package x
type pair struct {a,b int}
func bar(y *pair) *int {
	return &y.b
}
var a []int
func foo(w, z *pair) *int {
	if *bar(w) > 0 {
		return bar(z)
	}
	if a[1] > 0 {
		a = a[:2]
	}
	return &a[0]
}
`

func want(t *testing.T, out string, desired string) {
	if !strings.Contains(out, desired) {
		t.Errorf("did not see phrase %s in \n%s", desired, out)
	}
}

func wantN(t *testing.T, out string, desired string, n int) {
	if strings.Count(out, desired) != n {
		t.Errorf("expected exactly %d occurences of %s in \n%s", n, desired, out)
	}
}

func TestLogOpt(t *testing.T) {
	t.Parallel()

	testenv.MustHaveGoBuild(t)

	dir, err := ioutil.TempDir("", "TestLogOpt")
	if err != nil {
		t.Skipf("Could not create work directory, assuming not allowed on this platform.  Error was '%v'", err)
	}
	defer os.RemoveAll(dir)

	dir = fixSlash(dir) // Normalize the directory name as much as possible, for Windows testing
	src := filepath.Join(dir, "file.go")
	if err := ioutil.WriteFile(src, []byte(srcCode), 0644); err != nil {
		t.Fatal(err)
	}

	outfile := filepath.Join(dir, "file.o")

	t.Run("JSON_fails", func(t *testing.T) {
		// Test malformed flag
		out, err := testLogOpt(t, "-json=foo", src, outfile)
		if err == nil {
			t.Error("-json=foo succeeded unexpectedly")
		}
		want(t, out, "option should be")
		want(t, out, "number")

		// Test a version number that is currently unsupported (and should remain unsupported for a while)
		out, err = testLogOpt(t, "-json=9,foo", src, outfile)
		if err == nil {
			t.Error("-json=0,foo succeeded unexpectedly")
		}
		want(t, out, "version must be")

	})

	// replace d (dir)  with t ("tmpdir") and convert path separators to '/'
	normalize := func(out []byte, d, t string) string {
		s := string(out)
		s = strings.ReplaceAll(s, d, t)
		s = strings.ReplaceAll(s, string(os.PathSeparator), "/")
		return s
	}

	// Ensure that <128 byte copies are not reported and that 128-byte copies are.
	// Check at both 1 and 8-byte alignments.
	t.Run("Copy", func(t *testing.T) {
		const copyCode = `package x
func s128a1(x *[128]int8) [128]int8 {
	return *x
}
func s127a1(x *[127]int8) [127]int8 {
	return *x
}
func s16a8(x *[16]int64) [16]int64 {
	return *x
}
func s15a8(x *[15]int64) [15]int64 {
	return *x
}
`
		copy := filepath.Join(dir, "copy.go")
		if err := ioutil.WriteFile(copy, []byte(copyCode), 0644); err != nil {
			t.Fatal(err)
		}
		outcopy := filepath.Join(dir, "copy.o")

		// On not-amd64, test the host architecture and os
		arches := []string{runtime.GOARCH}
		goos0 := runtime.GOOS
		if runtime.GOARCH == "amd64" { // Test many things with "linux" (wasm will get "js")
			arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "ppc64le", "s390x", "wasm"}
			goos0 = "linux"
		}

		for _, arch := range arches {
			t.Run(arch, func(t *testing.T) {
				goos := goos0
				if arch == "wasm" {
					goos = "js"
				}
				_, err := testCopy(t, dir, arch, goos, copy, outcopy)
				if err != nil {
					t.Error("-json=0,file://log/opt should have succeeded")
				}
				logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "copy.json"))
				if err != nil {
					t.Error("-json=0,file://log/opt missing expected log file")
				}
				slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
				t.Logf("%s", slogged)
				want(t, slogged, `{"range":{"start":{"line":3,"character":2},"end":{"line":3,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
				want(t, slogged, `{"range":{"start":{"line":9,"character":2},"end":{"line":9,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
				wantN(t, slogged, `"code":"copy"`, 2)
			})
		}
	})

	// Some architectures don't fault on nil dereference, so nilchecks are eliminated differently.
	// The N-way copy test also doesn't need to run N-ways N times.
	if runtime.GOARCH != "amd64" {
		return
	}

	t.Run("Success", func(t *testing.T) {
		// This test is supposed to succeed

		// Note 'file://' is the I-Know-What-I-Am-Doing way of specifying a file, also to deal with corner cases for Windows.
		_, err := testLogOptDir(t, dir, "-json=0,file://log/opt", src, outfile)
		if err != nil {
			t.Error("-json=0,file://log/opt should have succeeded")
		}
		logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "file.json"))
		if err != nil {
			t.Error("-json=0,file://log/opt missing expected log file")
		}
		// All this delicacy with uriIfy and filepath.Join is to get this test to work right on Windows.
		slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
		t.Logf("%s", slogged)
		// below shows proper inlining and nilcheck
		want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"nilcheck","source":"go compiler","message":"","relatedInformation":[{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"}]}`)
		want(t, slogged, `{"range":{"start":{"line":11,"character":6},"end":{"line":11,"character":6}},"severity":3,"code":"isInBounds","source":"go compiler","message":""}`)
		want(t, slogged, `{"range":{"start":{"line":7,"character":6},"end":{"line":7,"character":6}},"severity":3,"code":"canInlineFunction","source":"go compiler","message":"cost: 35"}`)
		want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"inlineCall","source":"go compiler","message":"x.bar"}`)
		want(t, slogged, `{"range":{"start":{"line":8,"character":9},"end":{"line":8,"character":9}},"severity":3,"code":"inlineCall","source":"go compiler","message":"x.bar"}`)
	})
}

func testLogOpt(t *testing.T, flag, src, outfile string) (string, error) {
	run := []string{testenv.GoToolPath(t), "tool", "compile", flag, "-o", outfile, src}
	t.Log(run)
	cmd := exec.Command(run[0], run[1:]...)
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)
	return string(out), err
}

func testLogOptDir(t *testing.T, dir, flag, src, outfile string) (string, error) {
	// Notice the specified import path "x"
	run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", flag, "-o", outfile, src}
	t.Log(run)
	cmd := exec.Command(run[0], run[1:]...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)
	return string(out), err
}

func testCopy(t *testing.T, dir, goarch, goos, src, outfile string) (string, error) {
	// Notice the specified import path "x"
	run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", "-json=0,file://log/opt", "-o", outfile, src}
	t.Log(run)
	cmd := exec.Command(run[0], run[1:]...)
	cmd.Dir = dir
	cmd.Env = []string{"GOARCH=" + goarch, "GOOS=" + goos}
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)
	return string(out), err
}
<|endoftext|>


<commit_before>// Copyright 2016 The Prometheus 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 kubernetes

import (
	"context"
	"net"
	"strconv"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/pkg/errors"
	"github.com/prometheus/common/model"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"

	"github.com/prometheus/prometheus/discovery/targetgroup"
	"github.com/prometheus/prometheus/util/strutil"
)

const (
	NodeLegacyHostIP = "LegacyHostIP"
)

// Node discovers Kubernetes nodes.
type Node struct {
	logger   log.Logger
	informer cache.SharedInformer
	store    cache.Store
	queue    *workqueue.Type
}

// NewNode returns a new node discovery.
func NewNode(l log.Logger, inf cache.SharedInformer) *Node {
	if l == nil {
		l = log.NewNopLogger()
	}
	n := &Node{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("node")}
	n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(o interface{}) {
			eventCount.WithLabelValues("node", "add").Inc()
			n.enqueue(o)
		},
		DeleteFunc: func(o interface{}) {
			eventCount.WithLabelValues("node", "delete").Inc()
			n.enqueue(o)
		},
		UpdateFunc: func(_, o interface{}) {
			eventCount.WithLabelValues("node", "update").Inc()
			n.enqueue(o)
		},
	})
	return n
}

func (n *Node) enqueue(obj interface{}) {
	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return
	}

	n.queue.Add(key)
}

// Run implements the Discoverer interface.
func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
	defer n.queue.ShutDown()

	if !cache.WaitForCacheSync(ctx.Done(), n.informer.HasSynced) {
		level.Error(n.logger).Log("msg", "node informer unable to sync cache")
		return
	}

	go func() {
		for n.process(ctx, ch) {
		}
	}()

	// Block until the target provider is explicitly canceled.
	<-ctx.Done()
}

func (n *Node) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool {
	keyObj, quit := n.queue.Get()
	if quit {
		return false
	}
	defer n.queue.Done(keyObj)
	key := keyObj.(string)

	_, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		return true
	}

	o, exists, err := n.store.GetByKey(key)
	if err != nil {
		return true
	}
	if !exists {
		send(ctx, n.logger, RoleNode, ch, &targetgroup.Group{Source: nodeSourceFromName(name)})
		return true
	}
	node, err := convertToNode(o)
	if err != nil {
		level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err)
		return true
	}
	send(ctx, n.logger, RoleNode, ch, n.buildNode(node))
	return true
}

func convertToNode(o interface{}) (*apiv1.Node, error) {
	node, ok := o.(*apiv1.Node)
	if ok {
		return node, nil
	}

	return nil, errors.Errorf("received unexpected object: %v", o)
}

func nodeSource(n *apiv1.Node) string {
	return nodeSourceFromName(n.Name)
}

func nodeSourceFromName(name string) string {
	return "node/" + name
}

const (
	nodeNameLabel               = metaLabelPrefix + "node_name"
	nodeLabelPrefix             = metaLabelPrefix + "node_label_"
	nodeLabelPresentPrefix      = metaLabelPrefix + "node_labelpresent_"
	nodeAnnotationPrefix        = metaLabelPrefix + "node_annotation_"
	nodeAnnotationPresentPrefix = metaLabelPrefix + "node_annotationpresent_"
	nodeAddressPrefix           = metaLabelPrefix + "node_address_"
)

func nodeLabels(n *apiv1.Node) model.LabelSet {
	ls := make(model.LabelSet, len(n.Labels)+len(n.Annotations)+1)

	ls[nodeNameLabel] = lv(n.Name)

	for k, v := range n.Labels {
		ln := strutil.SanitizeLabelName(k)
		ls[model.LabelName(nodeLabelPrefix+ln)] = lv(v)
		ls[model.LabelName(nodeLabelPresentPrefix+ln)] = presentValue
	}

	for k, v := range n.Annotations {
		ln := strutil.SanitizeLabelName(k)
		ls[model.LabelName(nodeAnnotationPrefix+ln)] = lv(v)
		ls[model.LabelName(nodeAnnotationPresentPrefix+ln)] = presentValue
	}
	return ls
}

func (n *Node) buildNode(node *apiv1.Node) *targetgroup.Group {
	tg := &targetgroup.Group{
		Source: nodeSource(node),
	}
	tg.Labels = nodeLabels(node)

	addr, addrMap, err := nodeAddress(node)
	if err != nil {
		level.Warn(n.logger).Log("msg", "No node address found", "err", err)
		return nil
	}
	addr = net.JoinHostPort(addr, strconv.FormatInt(int64(node.Status.DaemonEndpoints.KubeletEndpoint.Port), 10))

	t := model.LabelSet{
		model.AddressLabel:  lv(addr),
		model.InstanceLabel: lv(node.Name),
	}

	for ty, a := range addrMap {
		ln := strutil.SanitizeLabelName(nodeAddressPrefix + string(ty))
		t[model.LabelName(ln)] = lv(a[0])
	}
	tg.Targets = append(tg.Targets, t)

	return tg
}

// nodeAddresses returns the provided node's address, based on the priority:
// 1. NodeInternalIP
// 2. NodeExternalIP
// 3. NodeLegacyHostIP
// 3. NodeHostName
//
// Derived from k8s.io/kubernetes/pkg/util/node/node.go
func nodeAddress(node *apiv1.Node) (string, map[apiv1.NodeAddressType][]string, error) {
	m := map[apiv1.NodeAddressType][]string{}
	for _, a := range node.Status.Addresses {
		m[a.Type] = append(m[a.Type], a.Address)
	}

	if addresses, ok := m[apiv1.NodeInternalIP]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeExternalIP]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeAddressType(NodeLegacyHostIP)]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeHostName]; ok {
		return addresses[0], m, nil
	}
	return "", m, errors.New("host address unknown")
}
<commit_msg>[prometheus_sd/kubernetes]add new node address types for discover (#5902)<commit_after>// Copyright 2016 The Prometheus 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 kubernetes

import (
	"context"
	"net"
	"strconv"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/pkg/errors"
	"github.com/prometheus/common/model"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"

	"github.com/prometheus/prometheus/discovery/targetgroup"
	"github.com/prometheus/prometheus/util/strutil"
)

const (
	NodeLegacyHostIP = "LegacyHostIP"
)

// Node discovers Kubernetes nodes.
type Node struct {
	logger   log.Logger
	informer cache.SharedInformer
	store    cache.Store
	queue    *workqueue.Type
}

// NewNode returns a new node discovery.
func NewNode(l log.Logger, inf cache.SharedInformer) *Node {
	if l == nil {
		l = log.NewNopLogger()
	}
	n := &Node{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("node")}
	n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(o interface{}) {
			eventCount.WithLabelValues("node", "add").Inc()
			n.enqueue(o)
		},
		DeleteFunc: func(o interface{}) {
			eventCount.WithLabelValues("node", "delete").Inc()
			n.enqueue(o)
		},
		UpdateFunc: func(_, o interface{}) {
			eventCount.WithLabelValues("node", "update").Inc()
			n.enqueue(o)
		},
	})
	return n
}

func (n *Node) enqueue(obj interface{}) {
	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return
	}

	n.queue.Add(key)
}

// Run implements the Discoverer interface.
func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
	defer n.queue.ShutDown()

	if !cache.WaitForCacheSync(ctx.Done(), n.informer.HasSynced) {
		level.Error(n.logger).Log("msg", "node informer unable to sync cache")
		return
	}

	go func() {
		for n.process(ctx, ch) {
		}
	}()

	// Block until the target provider is explicitly canceled.
	<-ctx.Done()
}

func (n *Node) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool {
	keyObj, quit := n.queue.Get()
	if quit {
		return false
	}
	defer n.queue.Done(keyObj)
	key := keyObj.(string)

	_, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		return true
	}

	o, exists, err := n.store.GetByKey(key)
	if err != nil {
		return true
	}
	if !exists {
		send(ctx, n.logger, RoleNode, ch, &targetgroup.Group{Source: nodeSourceFromName(name)})
		return true
	}
	node, err := convertToNode(o)
	if err != nil {
		level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err)
		return true
	}
	send(ctx, n.logger, RoleNode, ch, n.buildNode(node))
	return true
}

func convertToNode(o interface{}) (*apiv1.Node, error) {
	node, ok := o.(*apiv1.Node)
	if ok {
		return node, nil
	}

	return nil, errors.Errorf("received unexpected object: %v", o)
}

func nodeSource(n *apiv1.Node) string {
	return nodeSourceFromName(n.Name)
}

func nodeSourceFromName(name string) string {
	return "node/" + name
}

const (
	nodeNameLabel               = metaLabelPrefix + "node_name"
	nodeLabelPrefix             = metaLabelPrefix + "node_label_"
	nodeLabelPresentPrefix      = metaLabelPrefix + "node_labelpresent_"
	nodeAnnotationPrefix        = metaLabelPrefix + "node_annotation_"
	nodeAnnotationPresentPrefix = metaLabelPrefix + "node_annotationpresent_"
	nodeAddressPrefix           = metaLabelPrefix + "node_address_"
)

func nodeLabels(n *apiv1.Node) model.LabelSet {
	ls := make(model.LabelSet, len(n.Labels)+len(n.Annotations)+1)

	ls[nodeNameLabel] = lv(n.Name)

	for k, v := range n.Labels {
		ln := strutil.SanitizeLabelName(k)
		ls[model.LabelName(nodeLabelPrefix+ln)] = lv(v)
		ls[model.LabelName(nodeLabelPresentPrefix+ln)] = presentValue
	}

	for k, v := range n.Annotations {
		ln := strutil.SanitizeLabelName(k)
		ls[model.LabelName(nodeAnnotationPrefix+ln)] = lv(v)
		ls[model.LabelName(nodeAnnotationPresentPrefix+ln)] = presentValue
	}
	return ls
}

func (n *Node) buildNode(node *apiv1.Node) *targetgroup.Group {
	tg := &targetgroup.Group{
		Source: nodeSource(node),
	}
	tg.Labels = nodeLabels(node)

	addr, addrMap, err := nodeAddress(node)
	if err != nil {
		level.Warn(n.logger).Log("msg", "No node address found", "err", err)
		return nil
	}
	addr = net.JoinHostPort(addr, strconv.FormatInt(int64(node.Status.DaemonEndpoints.KubeletEndpoint.Port), 10))

	t := model.LabelSet{
		model.AddressLabel:  lv(addr),
		model.InstanceLabel: lv(node.Name),
	}

	for ty, a := range addrMap {
		ln := strutil.SanitizeLabelName(nodeAddressPrefix + string(ty))
		t[model.LabelName(ln)] = lv(a[0])
	}
	tg.Targets = append(tg.Targets, t)

	return tg
}

// nodeAddresses returns the provided node's address, based on the priority:
// 1. NodeInternalIP
// 2. NodeInternalDNS
// 3. NodeExternalIP
// 4. NodeExternalDNS
// 5. NodeLegacyHostIP
// 6. NodeHostName
//
// Derived from k8s.io/kubernetes/pkg/util/node/node.go
func nodeAddress(node *apiv1.Node) (string, map[apiv1.NodeAddressType][]string, error) {
	m := map[apiv1.NodeAddressType][]string{}
	for _, a := range node.Status.Addresses {
		m[a.Type] = append(m[a.Type], a.Address)
	}

	if addresses, ok := m[apiv1.NodeInternalIP]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeInternalDNS]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeExternalIP]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeExternalDNS]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeAddressType(NodeLegacyHostIP)]; ok {
		return addresses[0], m, nil
	}
	if addresses, ok := m[apiv1.NodeHostName]; ok {
		return addresses[0], m, nil
	}
	return "", m, errors.New("host address unknown")
}
<|endoftext|>


<commit_before>package machine

import (
	"fmt"
	"os/exec"
	"strings"
	"time"

	"github.com/innovate-technologies/Dispatch/dispatchd/unit/state"

	"github.com/innovate-technologies/Dispatch/dispatchd/config"
	"github.com/innovate-technologies/Dispatch/dispatchd/etcdclient"
	"github.com/innovate-technologies/Dispatch/dispatchd/unit"

	etcd "github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"github.com/coreos/go-systemd/dbus"
	"golang.org/x/net/context"
)

var (
	ctx             = context.Background()
	etcdAPI         = etcdclient.GetEtcdv3()
	machineLocation string
	// Config is a pointer need to be set to the main configuration
	Config         *config.ConfigurationInfo
	units          map[string]unit.Unit
	aliveLease     *etcd.LeaseGrantResponse
	dbusConnection *dbus.Conn
)

// RegisterMachine adds the machine to the cluster
func RegisterMachine() {
	unit.KillAllOldUnits() // Starting clean

	unit.Config = Config           // pass through the config
	units = map[string]unit.Unit{} // initialize map

	machineLocation = fmt.Sprintf("/dispatch/%s/machines/%s", Config.Zone, Config.MachineName)

	etcdAPI.Put(ctx, machineLocation+"/arch", Config.Arch)
	etcdAPI.Put(ctx, machineLocation+"/ip", Config.PublicIP)

	var err error
	aliveLease, err = etcdAPI.Lease.Grant(ctx, 10)
	if err != nil {
		panic(err)
	}

	etcdAPI.Put(ctx, machineLocation+"/alive", "1", etcd.WithLease(aliveLease.ID))

	if dbusConnection == nil {
		var err error
		dbusConnection, err = dbus.NewSystemdConnection()
		if err != nil {
			panic(err)
		}
	}

	go renewAlive()
	go updateLoad()
	go startUnits()
	go checkUnits()
	go watchLocalUnitState()
}

func renewAlive() {
	for {
		etcdAPI.Lease.KeepAliveOnce(ctx, aliveLease.ID)
		time.Sleep(1 * time.Second)
	}
}

func updateLoad() {
	for {
		out, err := exec.Command("uptime").Output()
		if err == nil {
			uptimeString := fmt.Sprintf("%s", out)
			var textAfterLoadAverage string
			if strings.Index(uptimeString, "load averages") >= 0 {
				textAfterLoadAverage = strings.Split(uptimeString, "load averages: ")[1]
			} else {
				textAfterLoadAverage = strings.Split(uptimeString, "load average: ")[1]
			}
			load := strings.Split(textAfterLoadAverage, ",")[0] //to do: divide #CPU
			etcdAPI.Put(ctx, machineLocation+"/load", load)
		}
		time.Sleep(1 * time.Second)
	}
}

func setTags(tags map[string]string) {

}

func startUnits() {
	result, err := etcdAPI.Get(ctx, machineLocation+"/units")
	if err == nil {
		for _, kv := range result.Kvs {
			unitName := string(kv.Value)
			u := unit.NewFromEtcd(unitName)
			go u.LoadAndWatch()
			units[unitName] = u
		}
	}
	go watchUnits()
}

func watchUnits() {
	chans := etcdAPI.Watch(context.Background(), machineLocation+"/units", etcd.WithPrefix())
	for resp := range chans {
		for _, ev := range resp.Events {
			if ev.IsCreate() || ev.IsModify() {
				unitName := string(ev.Kv.Value)
				fmt.Println("Found new unit", unitName)
				u := unit.NewFromEtcd(unitName)
				go u.LoadAndWatch()
				units[unitName] = u
			}
			if ev.Type == mvccpb.DELETE {
				if ev.PrevKv != nil {
					unitName := string(ev.PrevKv.Value)
					fmt.Println("Delete unit", unitName)
					if unit, exists := units[unitName]; exists {
						delete(units, unitName)
						unit.Destroy()
					}
				}
			}
		}
	}
}

func checkUnits() {
	for {
		time.Sleep(10 * time.Second)
		result, err := etcdAPI.Get(ctx, machineLocation+"/units", etcd.WithPrefix())
		if err == nil {
			unitsOnCluster := map[string]bool{}

			for _, kv := range result.Kvs {
				unitName := string(kv.Value)
				unitsOnCluster[unitName] = true
				if _, ok := units[unitName]; !ok {
					fmt.Println("Found new unit via check", unitName)
					u := unit.NewFromEtcd(unitName)
					go u.LoadAndWatch()
					units[unitName] = u
				}
			}

			// Check if not running too much
			for unitName, unit := range units {
				if _, ok := unitsOnCluster[unitName]; unit.Global == "" && !ok {
					fmt.Println("Found non deleted unit via check", unitName)
					delete(units, unitName)
					unit.Destroy()
				}
			}
		}
	}
}

func watchLocalUnitState() {
	statusChan, errorChan := dbusConnection.SubscribeUnits(5 * time.Second)
	for {
		select {
		case status := <-statusChan:
			for _, status := range status {
				if unit, exists := units[status.Name]; exists {
					if status.ActiveState == "active" {
						unit.SetState(state.Active)
					} else {
						unit.SetState(state.Dead)
					}
				}
			}
			break
		case err := <-errorChan:
			fmt.Println(err) // not sure when this will be the case yet
			break
		}
	}
}
<commit_msg>initialize units on start<commit_after>package machine

import (
	"fmt"
	"os/exec"
	"strings"
	"time"

	"github.com/innovate-technologies/Dispatch/dispatchd/unit/state"

	"github.com/innovate-technologies/Dispatch/dispatchd/config"
	"github.com/innovate-technologies/Dispatch/dispatchd/etcdclient"
	"github.com/innovate-technologies/Dispatch/dispatchd/unit"

	etcd "github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"github.com/coreos/go-systemd/dbus"
	"golang.org/x/net/context"
)

var (
	ctx             = context.Background()
	etcdAPI         = etcdclient.GetEtcdv3()
	machineLocation string
	// Config is a pointer need to be set to the main configuration
	Config         *config.ConfigurationInfo
	units          = map[string]unit.Unit{}
	aliveLease     *etcd.LeaseGrantResponse
	dbusConnection *dbus.Conn
)

// RegisterMachine adds the machine to the cluster
func RegisterMachine() {
	unit.KillAllOldUnits() // Starting clean

	unit.Config = Config // pass through the config

	machineLocation = fmt.Sprintf("/dispatch/%s/machines/%s", Config.Zone, Config.MachineName)

	etcdAPI.Put(ctx, machineLocation+"/arch", Config.Arch)
	etcdAPI.Put(ctx, machineLocation+"/ip", Config.PublicIP)

	var err error
	aliveLease, err = etcdAPI.Lease.Grant(ctx, 10)
	if err != nil {
		panic(err)
	}

	etcdAPI.Put(ctx, machineLocation+"/alive", "1", etcd.WithLease(aliveLease.ID))

	if dbusConnection == nil {
		var err error
		dbusConnection, err = dbus.NewSystemdConnection()
		if err != nil {
			panic(err)
		}
	}

	go renewAlive()
	go updateLoad()
	go startUnits()
	go checkUnits()
	go watchLocalUnitState()
}

func renewAlive() {
	for {
		etcdAPI.Lease.KeepAliveOnce(ctx, aliveLease.ID)
		time.Sleep(1 * time.Second)
	}
}

func updateLoad() {
	for {
		out, err := exec.Command("uptime").Output()
		if err == nil {
			uptimeString := fmt.Sprintf("%s", out)
			var textAfterLoadAverage string
			if strings.Index(uptimeString, "load averages") >= 0 {
				textAfterLoadAverage = strings.Split(uptimeString, "load averages: ")[1]
			} else {
				textAfterLoadAverage = strings.Split(uptimeString, "load average: ")[1]
			}
			load := strings.Split(textAfterLoadAverage, ",")[0] //to do: divide #CPU
			etcdAPI.Put(ctx, machineLocation+"/load", load)
		}
		time.Sleep(1 * time.Second)
	}
}

func setTags(tags map[string]string) {

}

func startUnits() {
	result, err := etcdAPI.Get(ctx, machineLocation+"/units")
	if err == nil {
		for _, kv := range result.Kvs {
			unitName := string(kv.Value)
			u := unit.NewFromEtcd(unitName)
			go u.LoadAndWatch()
			units[unitName] = u
		}
	}
	go watchUnits()
}

func watchUnits() {
	chans := etcdAPI.Watch(context.Background(), machineLocation+"/units", etcd.WithPrefix())
	for resp := range chans {
		for _, ev := range resp.Events {
			if ev.IsCreate() || ev.IsModify() {
				unitName := string(ev.Kv.Value)
				fmt.Println("Found new unit", unitName)
				u := unit.NewFromEtcd(unitName)
				go u.LoadAndWatch()
				units[unitName] = u
			}
			if ev.Type == mvccpb.DELETE {
				if ev.PrevKv != nil {
					unitName := string(ev.PrevKv.Value)
					fmt.Println("Delete unit", unitName)
					if unit, exists := units[unitName]; exists {
						delete(units, unitName)
						unit.Destroy()
					}
				}
			}
		}
	}
}

func checkUnits() {
	for {
		time.Sleep(10 * time.Second)
		result, err := etcdAPI.Get(ctx, machineLocation+"/units", etcd.WithPrefix())
		if err == nil {
			unitsOnCluster := map[string]bool{}

			for _, kv := range result.Kvs {
				unitName := string(kv.Value)
				unitsOnCluster[unitName] = true
				if _, ok := units[unitName]; !ok {
					fmt.Println("Found new unit via check", unitName)
					u := unit.NewFromEtcd(unitName)
					go u.LoadAndWatch()
					units[unitName] = u
				}
			}

			// Check if not running too much
			for unitName, unit := range units {
				if _, ok := unitsOnCluster[unitName]; unit.Global == "" && !ok {
					fmt.Println("Found non deleted unit via check", unitName)
					delete(units, unitName)
					unit.Destroy()
				}
			}
		}
	}
}

func watchLocalUnitState() {
	statusChan, errorChan := dbusConnection.SubscribeUnits(5 * time.Second)
	for {
		select {
		case status := <-statusChan:
			for _, status := range status {
				if unit, exists := units[status.Name]; exists {
					if status.ActiveState == "active" {
						unit.SetState(state.Active)
					} else {
						unit.SetState(state.Dead)
					}
				}
			}
			break
		case err := <-errorChan:
			fmt.Println(err) // not sure when this will be the case yet
			break
		}
	}
}
<|endoftext|>


<commit_before>/* This simple example mirrors the "hello world" TLDP ncurses howto */

package main

import "code.google.com/p/goncurses"

func main() {
	stdscr, _ := goncurses.Init()
	defer goncurses.End()

	stdscr.Print("Hello, World!!!")
	stdscr.Refresh()
	stdscr.GetChar()
}
<commit_msg>Improve hello world example<commit_after>/* This simple example mirrors the "hello world" TLDP ncurses howto */

package main

import (
	"code.google.com/p/goncurses"
	"log"
)

func main() {
	// Initialize goncurses. It's essential End() is called to ensure the
	// terminal isn't altered after the program ends
	stdscr, err := goncurses.Init()
	if err != nil {
		log.Fatal("init", err)
	}
	defer goncurses.End()

	stdscr.Print("Hello, World!!!")
	stdscr.MovePrint(3, 0, "Press any key to continue")
	stdscr.Refresh()
	stdscr.GetChar()
}
<|endoftext|>