<commit_before>package nodetxlog

import (
	"encoding/json"
	"time"

	"golang.org/x/net/context"

	"chain/api/appdb"
	chainjson "chain/encoding/json"
	"chain/errors"
	"chain/fedchain/bc"
)

type NodeTx struct {
	ID       bc.Hash            `json:"id"`
	Time     time.Time          `json:"transaction_time"`
	Inputs   []nodeTxInput      `json:"inputs"`
	Outputs  []nodeTxOutput     `json:"outputs"`
	Metadata chainjson.HexBytes `json:"metadata"`
}

type nodeTxInput struct {
	Type            string             `json:"type"`
	TxID            *bc.Hash           `json:"transaction_id,omitempty"`
	TxOut           *uint32            `json:"transaction_output,omitempty"`
	AssetID         bc.AssetID         `json:"asset_id"`
	AssetLabel      string             `json:"asset_label,omitempty"`
	AssetDefinition chainjson.HexBytes `json:"asset_definition,omitempty"`
	Amount          uint64             `json:"amount"`
	Address         chainjson.HexBytes `json:"address,omitempty"`
	Script          chainjson.HexBytes `json:"script,omitempty"`
	AccountID       string             `json:"account_id,omitempty"`
	AccountLabel    string             `json:"account_label,omitempty"`
	Metadata        chainjson.HexBytes `json:"metadata"`

	mNodeID string
}

type nodeTxOutput struct {
	AssetID      bc.AssetID         `json:"asset_id"`
	AssetLabel   string             `json:"asset_label,omitempty"`
	Amount       uint64             `json:"amount"`
	Address      chainjson.HexBytes `json:"address,omitempty"`
	Script       chainjson.HexBytes `json:"script,omitempty"`
	AccountID    string             `json:"account_id,omitempty"`
	AccountLabel string             `json:"account_label,omitempty"`
	Metadata     chainjson.HexBytes `json:"metadata"`

	mNodeID string
}

// Write persists a transaction along with its metadata
// for every node (issuer, manager) associated with the transaction.
func Write(ctx context.Context, tx *bc.Tx, ts time.Time) error {
	ins, outs, err := appdb.GetActUTXOs(ctx, tx)
	if err != nil {
		return errors.Wrap(err, "fetching utxo account data")
	}

	assetMap := make(map[string]*appdb.ActAsset)
	var assetIDs []string
	for _, out := range tx.Outputs {
		assetIDs = append(assetIDs, out.AssetID.String())
	}
	assets, err := appdb.GetActAssets(ctx, assetIDs)
	if err != nil {
		return errors.Wrap(err, "getting assets")
	}
	for _, asset := range assets {
		assetMap[asset.ID] = asset
	}

	accountMap := make(map[string]*appdb.ActAccount)
	nodeAccounts := make(map[string][]string)
	var accountIDs []string
	for _, utxo := range append(ins, outs...) {
		if utxo != nil && utxo.AccountID != "" {
			accountIDs = append(accountIDs, utxo.AccountID)
		}
	}
	accounts, err := appdb.GetActAccounts(ctx, accountIDs)
	if err != nil {
		return errors.Wrap(err, "getting accounts")
	}
	for _, acc := range accounts {
		accountMap[acc.ID] = acc
		nodeAccounts[acc.ManagerNodeID] = append(nodeAccounts[acc.ManagerNodeID], acc.ID)
	}

	NodeTx, err := generateNodeTx(tx, ins, outs, assetMap, accountMap, ts)
	if err != nil {
		return errors.Wrap(err, "generating master")
	}

	if tx.IsIssuance() {
		filteredTx, err := json.Marshal(filterAccounts(NodeTx, ""))
		if err != nil {
			return errors.Wrap(err, "filtering tx")
		}
		_, err = appdb.WriteIssuerTx(ctx, tx.Hash.String(), filteredTx, assets[0].IssuerNodeID, assets[0].ID)
		if err != nil {
			return errors.Wrap(err, "writing issuer tx")
		}
	}

	for nodeID, accountIDs := range nodeAccounts {
		filteredTx, err := json.Marshal(filterAccounts(NodeTx, nodeID))
		if err != nil {
			return errors.Wrap(err, "filtering tx")
		}
		_, err = appdb.WriteManagerTx(ctx, tx.Hash.String(), filteredTx, nodeID, accountIDs)
		if err != nil {
			return errors.Wrap(err, "writing manager tx")
		}
	}

	return nil
}

func generateNodeTx(
	tx *bc.Tx,
	ins []*appdb.ActUTXO,
	outs []*appdb.ActUTXO,
	assetMap map[string]*appdb.ActAsset,
	accountMap map[string]*appdb.ActAccount,
	ts time.Time,
) (*NodeTx, error) {
	actTx := &NodeTx{
		ID:       tx.Hash,
		Metadata: tx.Metadata,
		Time:     ts,
	}
	for i, in := range tx.Inputs {
		if in.IsIssuance() {
			var (
				amt     uint64
				assetID bc.AssetID
			)
			for _, o := range tx.Outputs {
				amt += o.Amount
				assetID = o.AssetID
			}
			asset := assetMap[assetID.String()]
			var label string
			if asset != nil {
				label = asset.Label
			}
			actTx.Inputs = append(actTx.Inputs, nodeTxInput{
				Type:            "issuance",
				AssetID:         assetID,
				AssetLabel:      label,
				AssetDefinition: in.AssetDefinition,
				Amount:          amt,
				Metadata:        in.Metadata,
			})
			continue
		}

		assetID, err := bc.ParseHash(ins[i].AssetID)
		if err != nil {
			return nil, errors.Wrap(err, "parsing utxo asset id")
		}
		asset := assetMap[ins[i].AssetID]
		var assetLabel string
		if asset != nil {
			assetLabel = asset.Label
		}

		account := accountMap[ins[i].AccountID]
		var accountLabel, mNodeID string
		if account != nil {
			accountLabel = account.Label
			mNodeID = account.ManagerNodeID
		}

		actTx.Inputs = append(actTx.Inputs, nodeTxInput{
			Type:         "transfer",
			TxID:         &in.Previous.Hash,
			TxOut:        &in.Previous.Index,
			AssetID:      bc.AssetID(assetID),
			AssetLabel:   assetLabel,
			Amount:       ins[i].Amount,
			AccountID:    ins[i].AccountID,
			AccountLabel: accountLabel,
			Address:      ins[i].Script,
			Script:       ins[i].Script,
			Metadata:     in.Metadata,
			mNodeID:      mNodeID,
		})
	}

	for i, out := range tx.Outputs {
		asset := assetMap[out.AssetID.String()]
		var assetLabel string
		if asset != nil {
			assetLabel = asset.Label
		}

		account := accountMap[outs[i].AccountID]
		var accountLabel, mNodeID string
		if account != nil {
			accountLabel = account.Label
			mNodeID = account.ManagerNodeID
		}

		actTx.Outputs = append(actTx.Outputs, nodeTxOutput{
			AssetID:      out.AssetID,
			AssetLabel:   assetLabel,
			Amount:       out.Amount,
			Address:      out.Script,
			Script:       out.Script,
			AccountID:    outs[i].AccountID,
			AccountLabel: accountLabel,
			Metadata:     out.Metadata,
			mNodeID:      mNodeID,
		})
	}
	return actTx, nil
}

func filterAccounts(tx *NodeTx, keepID string) *NodeTx {
	filteredTx := new(NodeTx)
	*filteredTx = *tx
	filteredTx.Inputs = make([]nodeTxInput, len(tx.Inputs))
	copy(filteredTx.Inputs, tx.Inputs)
	filteredTx.Outputs = make([]nodeTxOutput, len(tx.Outputs))
	copy(filteredTx.Outputs, tx.Outputs)
	for i := range filteredTx.Inputs {
		in := &filteredTx.Inputs[i]
		if in.mNodeID != keepID {
			in.AccountLabel = ""
			in.AccountID = ""
		}
	}
	for i := range filteredTx.Outputs {
		out := &filteredTx.Outputs[i]
		if out.mNodeID != keepID {
			out.AccountLabel = ""
			out.AccountID = ""
		}
	}
	return filteredTx
}
<commit_msg>api/asset/nodetxlog: s/master/principal<commit_after>package nodetxlog

import (
	"encoding/json"
	"time"

	"golang.org/x/net/context"

	"chain/api/appdb"
	chainjson "chain/encoding/json"
	"chain/errors"
	"chain/fedchain/bc"
)

type NodeTx struct {
	ID       bc.Hash            `json:"id"`
	Time     time.Time          `json:"transaction_time"`
	Inputs   []nodeTxInput      `json:"inputs"`
	Outputs  []nodeTxOutput     `json:"outputs"`
	Metadata chainjson.HexBytes `json:"metadata"`
}

type nodeTxInput struct {
	Type            string             `json:"type"`
	TxID            *bc.Hash           `json:"transaction_id,omitempty"`
	TxOut           *uint32            `json:"transaction_output,omitempty"`
	AssetID         bc.AssetID         `json:"asset_id"`
	AssetLabel      string             `json:"asset_label,omitempty"`
	AssetDefinition chainjson.HexBytes `json:"asset_definition,omitempty"`
	Amount          uint64             `json:"amount"`
	Address         chainjson.HexBytes `json:"address,omitempty"`
	Script          chainjson.HexBytes `json:"script,omitempty"`
	AccountID       string             `json:"account_id,omitempty"`
	AccountLabel    string             `json:"account_label,omitempty"`
	Metadata        chainjson.HexBytes `json:"metadata"`

	mNodeID string
}

type nodeTxOutput struct {
	AssetID      bc.AssetID         `json:"asset_id"`
	AssetLabel   string             `json:"asset_label,omitempty"`
	Amount       uint64             `json:"amount"`
	Address      chainjson.HexBytes `json:"address,omitempty"`
	Script       chainjson.HexBytes `json:"script,omitempty"`
	AccountID    string             `json:"account_id,omitempty"`
	AccountLabel string             `json:"account_label,omitempty"`
	Metadata     chainjson.HexBytes `json:"metadata"`

	mNodeID string
}

// Write persists a transaction along with its metadata
// for every node (issuer, manager) associated with the transaction.
func Write(ctx context.Context, tx *bc.Tx, ts time.Time) error {
	ins, outs, err := appdb.GetActUTXOs(ctx, tx)
	if err != nil {
		return errors.Wrap(err, "fetching utxo account data")
	}

	assetMap := make(map[string]*appdb.ActAsset)
	var assetIDs []string
	for _, out := range tx.Outputs {
		assetIDs = append(assetIDs, out.AssetID.String())
	}
	assets, err := appdb.GetActAssets(ctx, assetIDs)
	if err != nil {
		return errors.Wrap(err, "getting assets")
	}
	for _, asset := range assets {
		assetMap[asset.ID] = asset
	}

	accountMap := make(map[string]*appdb.ActAccount)
	nodeAccounts := make(map[string][]string)
	var accountIDs []string
	for _, utxo := range append(ins, outs...) {
		if utxo != nil && utxo.AccountID != "" {
			accountIDs = append(accountIDs, utxo.AccountID)
		}
	}
	accounts, err := appdb.GetActAccounts(ctx, accountIDs)
	if err != nil {
		return errors.Wrap(err, "getting accounts")
	}
	for _, acc := range accounts {
		accountMap[acc.ID] = acc
		nodeAccounts[acc.ManagerNodeID] = append(nodeAccounts[acc.ManagerNodeID], acc.ID)
	}

	nodeTx, err := generateNodeTx(tx, ins, outs, assetMap, accountMap, ts)
	if err != nil {
		return errors.Wrap(err, "generating principal nodetx")
	}

	if tx.IsIssuance() {
		filteredTx, err := json.Marshal(filterAccounts(nodeTx, ""))
		if err != nil {
			return errors.Wrap(err, "filtering tx")
		}
		_, err = appdb.WriteIssuerTx(ctx, tx.Hash.String(), filteredTx, assets[0].IssuerNodeID, assets[0].ID)
		if err != nil {
			return errors.Wrap(err, "writing issuer tx")
		}
	}

	for nodeID, accountIDs := range nodeAccounts {
		filteredTx, err := json.Marshal(filterAccounts(nodeTx, nodeID))
		if err != nil {
			return errors.Wrap(err, "filtering tx")
		}
		_, err = appdb.WriteManagerTx(ctx, tx.Hash.String(), filteredTx, nodeID, accountIDs)
		if err != nil {
			return errors.Wrap(err, "writing manager tx")
		}
	}

	return nil
}

func generateNodeTx(
	tx *bc.Tx,
	ins []*appdb.ActUTXO,
	outs []*appdb.ActUTXO,
	assetMap map[string]*appdb.ActAsset,
	accountMap map[string]*appdb.ActAccount,
	ts time.Time,
) (*NodeTx, error) {
	actTx := &NodeTx{
		ID:       tx.Hash,
		Metadata: tx.Metadata,
		Time:     ts,
	}
	for i, in := range tx.Inputs {
		if in.IsIssuance() {
			var (
				amt     uint64
				assetID bc.AssetID
			)
			for _, o := range tx.Outputs {
				amt += o.Amount
				assetID = o.AssetID
			}
			asset := assetMap[assetID.String()]
			var label string
			if asset != nil {
				label = asset.Label
			}
			actTx.Inputs = append(actTx.Inputs, nodeTxInput{
				Type:            "issuance",
				AssetID:         assetID,
				AssetLabel:      label,
				AssetDefinition: in.AssetDefinition,
				Amount:          amt,
				Metadata:        in.Metadata,
			})
			continue
		}

		assetID, err := bc.ParseHash(ins[i].AssetID)
		if err != nil {
			return nil, errors.Wrap(err, "parsing utxo asset id")
		}
		asset := assetMap[ins[i].AssetID]
		var assetLabel string
		if asset != nil {
			assetLabel = asset.Label
		}

		account := accountMap[ins[i].AccountID]
		var accountLabel, mNodeID string
		if account != nil {
			accountLabel = account.Label
			mNodeID = account.ManagerNodeID
		}

		actTx.Inputs = append(actTx.Inputs, nodeTxInput{
			Type:         "transfer",
			TxID:         &in.Previous.Hash,
			TxOut:        &in.Previous.Index,
			AssetID:      bc.AssetID(assetID),
			AssetLabel:   assetLabel,
			Amount:       ins[i].Amount,
			AccountID:    ins[i].AccountID,
			AccountLabel: accountLabel,
			Address:      ins[i].Script,
			Script:       ins[i].Script,
			Metadata:     in.Metadata,
			mNodeID:      mNodeID,
		})
	}

	for i, out := range tx.Outputs {
		asset := assetMap[out.AssetID.String()]
		var assetLabel string
		if asset != nil {
			assetLabel = asset.Label
		}

		account := accountMap[outs[i].AccountID]
		var accountLabel, mNodeID string
		if account != nil {
			accountLabel = account.Label
			mNodeID = account.ManagerNodeID
		}

		actTx.Outputs = append(actTx.Outputs, nodeTxOutput{
			AssetID:      out.AssetID,
			AssetLabel:   assetLabel,
			Amount:       out.Amount,
			Address:      out.Script,
			Script:       out.Script,
			AccountID:    outs[i].AccountID,
			AccountLabel: accountLabel,
			Metadata:     out.Metadata,
			mNodeID:      mNodeID,
		})
	}
	return actTx, nil
}

func filterAccounts(tx *NodeTx, keepID string) *NodeTx {
	filteredTx := new(NodeTx)
	*filteredTx = *tx
	filteredTx.Inputs = make([]nodeTxInput, len(tx.Inputs))
	copy(filteredTx.Inputs, tx.Inputs)
	filteredTx.Outputs = make([]nodeTxOutput, len(tx.Outputs))
	copy(filteredTx.Outputs, tx.Outputs)
	for i := range filteredTx.Inputs {
		in := &filteredTx.Inputs[i]
		if in.mNodeID != keepID {
			in.AccountLabel = ""
			in.AccountID = ""
		}
	}
	for i := range filteredTx.Outputs {
		out := &filteredTx.Outputs[i]
		if out.mNodeID != keepID {
			out.AccountLabel = ""
			out.AccountID = ""
		}
	}
	return filteredTx
}
<|endoftext|>


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

package data

import (
	"encoding"
	"encoding/json"
	"fmt"
	"strings"
)

const (
	prefixToSkipObfsucation = ".kbfs_"
	tarGzSuffix             = ".tar.gz"

	// extRestoreMaxLen specifies the max length of an extension, such
	// that it's restored to the obfuscated string.  It includes the
	// leading dot.
	extRestoreMaxLen = 5
)

// PathPartString returns an obfuscated version of part of a path,
// preserving the suffixes if they're small enough or if it's a
// conflicted copy of a file.  Two `PathPartString`s for the asme
// plaintext data, created using the same `Obfuscator`, should pass
// equality checks.
type PathPartString struct {
	plaintext  string
	ext        string
	obsfucator Obfuscator
}

var _ fmt.Stringer = PathPartString{}

// NewPathPartString creates a new `PathPartString` instance, given a
// plaintext string representing one part of a path, and a
// `Obfuscator`.
func NewPathPartString(
	plaintext string, obfuscator Obfuscator) PathPartString {
	_, ext := SplitFileExtension(plaintext)
	if len(ext) > extRestoreMaxLen && ext != tarGzSuffix {
		ext = ""
	}
	conflictedIndex := strings.LastIndex(plaintext, ".conflicted (")
	if conflictedIndex > 0 {
		ext = plaintext[conflictedIndex:]
	}
	return PathPartString{plaintext, ext, obfuscator}
}

func (pps PathPartString) String() string {
	if strings.HasPrefix(pps.plaintext, prefixToSkipObfsucation) {
		return pps.plaintext
	}

	obs := pps.obsfucator.Obfuscate(pps.plaintext)
	return obs + pps.ext
}

// Plaintext returns the plaintext underlying this string part.
func (pps PathPartString) Plaintext() string {
	return pps.plaintext
}

var _ json.Marshaler = PathPartString{}

// MarshalJSON implements the json.Marshaler interface for PathPartString.
func (pps PathPartString) MarshalJSON() ([]byte, error) {
	panic("Cannot marshal PathPartString; use `Plaintext()` instead")
}

var _ encoding.TextMarshaler = PathPartString{}

// MarshalText implements the encoding.TextMarshaler interface for
// PathPartString.
func (pps PathPartString) MarshalText() ([]byte, error) {
	panic("Cannot marshal PathPartString; use `Plaintext()` instead")
}
<commit_msg>data: address PathPartString review feedback<commit_after>// Copyright 2019 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package data

import (
	"encoding"
	"encoding/json"
	"fmt"
	"strings"
)

const (
	prefixToSkipObfsucation = ".kbfs_"
	tarGzSuffix             = ".tar.gz"

	// extRestoreMaxLen specifies the max length of an extension, such
	// that it's restored to the obfuscated string.  It includes the
	// leading dot.
	extRestoreMaxLen = 5
)

// PathPartString returns an obfuscated version of part of a path,
// preserving the suffixes if they're small enough or if it's a
// conflicted copy of a file.  Two `PathPartString`s for the same
// plaintext data, created using the same `Obfuscator`, should pass
// equality checks.
type PathPartString struct {
	plaintext  string
	ext        string
	obsfucator Obfuscator
}

var _ fmt.Stringer = PathPartString{}

// NewPathPartString creates a new `PathPartString` instance, given a
// plaintext string representing one part of a path, and a
// `Obfuscator`.
func NewPathPartString(
	plaintext string, obfuscator Obfuscator) PathPartString {
	_, ext := SplitFileExtension(plaintext)
	// Treat the long tarball suffix specially, since it's already
	// parsed specially by `SplitFileExtension`.  Otherwise, strictly
	// filter for length,m so we don't accidentally expose suffixes
	// that aren't common file suffixes, but instead part of the
	// actual privately-named file (e.g., "this.file.is.secret").
	if len(ext) > extRestoreMaxLen && ext != tarGzSuffix {
		ext = ""
	}
	conflictedIndex := strings.LastIndex(plaintext, ".conflicted (")
	if conflictedIndex > 0 {
		ext = plaintext[conflictedIndex:]
	}
	return PathPartString{plaintext, ext, obfuscator}
}

func (pps PathPartString) String() string {
	if strings.HasPrefix(pps.plaintext, prefixToSkipObfsucation) {
		return pps.plaintext
	}

	obs := pps.obsfucator.Obfuscate(pps.plaintext)
	return obs + pps.ext
}

// Plaintext returns the plaintext underlying this string part.
func (pps PathPartString) Plaintext() string {
	return pps.plaintext
}

var _ json.Marshaler = PathPartString{}

// MarshalJSON implements the json.Marshaler interface for PathPartString.
func (pps PathPartString) MarshalJSON() ([]byte, error) {
	panic("Cannot marshal PathPartString; use `Plaintext()` instead")
}

var _ encoding.TextMarshaler = PathPartString{}

// MarshalText implements the encoding.TextMarshaler interface for
// PathPartString.
func (pps PathPartString) MarshalText() ([]byte, error) {
	panic("Cannot marshal PathPartString; use `Plaintext()` instead")
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strings"

	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

type RetryErrorPredicateFunc func(error) (bool, string)

/** ADD GLOBAL ERROR RETRY PREDICATES HERE **/
// Retry predicates that shoud apply to all requests should be added here.
var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
	// Common network errors (usually wrapped by URL error)
	isNetworkTemporaryError,
	isNetworkTimeoutError,
	isIoEOFError,
	isConnectionResetNetworkError,

	// Common GCP error codes
	isCommonRetryableErrorCode,

	//While this might apply only to Cloud SQL, historically,
	// we had this in our global default error retries.
	// Keeping it as a default for now.
	is409OperationInProgressError,
}

/** END GLOBAL ERROR RETRY PREDICATES HERE **/

func isNetworkTemporaryError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Temporary() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Temporary() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isNetworkTimeoutError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isIoEOFError(err error) (bool, string) {
	if err == io.ErrUnexpectedEOF {
		return true, "Got unexpected EOF"
	}

	if urlerr, urlok := err.(*url.Error); urlok {
		wrappedErr := urlerr.Unwrap()
		if wrappedErr == io.ErrUnexpectedEOF {
			return true, "Got unexpected EOF"
		}
	}
	return false, ""
}

const connectionResetByPeerErr = ": connection reset by peer"

func isConnectionResetNetworkError(err error) (bool, string) {
	if strings.HasSuffix(err.Error(), connectionResetByPeerErr) {
		return true, fmt.Sprintf("reset connection error: %v", err)
	}
	return false, ""
}

// Retry 409s because some APIs like Cloud SQL throw a 409 if concurrent calls
// are being made.
//
//The only way right now to determine it is a retryable 409 due to
// concurrent calls is to look at the contents of the error message.
// See https://github.com/hashicorp/terraform-provider-google/issues/3279
func is409OperationInProgressError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
		return true, "Operation still in progress"
	}
	return false, ""
}

// Retry on comon googleapi error codes for retryable errors.
// TODO(#5609): This may not need to be applied globally - figure out
// what retryable error codes apply to which API.
func isCommonRetryableErrorCode(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503 {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
		return true, fmt.Sprintf("Retryable error code %d", gerr.Code)
	}
	return false, ""
}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// Retry the operation if it looks like a fingerprint mismatch.
func isFingerprintError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code != 412 {
		return false, ""
	}

	for _, msg := range FINGERPRINT_FAIL_ERRORS {
		if strings.Contains(err.Error(), msg) {
			return true, "fingerprint mismatch"
		}
	}

	return false, ""
}

// If a permission necessary to provision a resource is created in the same config
// as the resource itself, the permission may not have propagated by the time terraform
// attempts to create the resource. This allows those errors to be retried until the timeout expires
func iamMemberMissing(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "permission") {
			return true, "Waiting for IAM member permissions to propagate."
		}
	}
	return false, ""
}

// Cloud PubSub returns a 400 error if a topic's parent project was recently created and an
// organization policy has not propagated.
// See https://github.com/hashicorp/terraform-provider-google/issues/4349
func pubsubTopicProjectNotReady(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "retry this operation") {
			log.Printf("[DEBUG] Dismissed error as a retryable operation: %s", err)
			return true, "Waiting for Pubsub topic's project to properly initialize with organiation policy"
		}
	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlInternalError(err error) (bool, string) {
	if gerr, ok := err.(*SqlAdminOperationError); ok {
		// SqlAdminOperationError is a non-interface type so we need to cast it through
		// a layer of interface{}.  :)
		var ierr interface{}
		ierr = gerr
		if serr, ok := ierr.(*sqladmin.OperationErrors); ok && serr.Errors[0].Code == "INTERNAL_ERROR" {
			return true, "Received an internal error, which is sometimes retryable for some SQL resources.  Optimistically retrying."
		}

	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlOperationInProgressError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 {
		if strings.Contains(gerr.Body, "you cannot reuse the name of the deleted instance until one week from the deletion date.") {
			return false, ""
		}

		return true, "Waiting for other concurrent Cloud SQL operations to finish"
	}
	return false, ""
}

// Retry if service usage decides you're activating the same service multiple
// times. This can happen if a service and a dependent service aren't batched
// together- eg container.googleapis.com in one request followed by compute.g.c
// in the next (container relies on compute and implicitly activates it)
func serviceUsageServiceBeingActivated(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "Another activation or deactivation is in progress") {
			return true, "Waiting for same service activation/deactivation to finish"
		}

		return false, ""
	}
	return false, ""
}

// Retry if Bigquery operation returns a 403 with a specific message for
// concurrent operations (which are implemented in terms of 'edit quota').
func isBigqueryIAMQuotaError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 403 && strings.Contains(strings.ToLower(gerr.Body), "exceeded rate limits") {
			return true, "Waiting for Bigquery edit quota to refresh"
		}
	}
	return false, ""
}

// Retry if Monitoring operation returns a 409 with a specific message for
// concurrent operations.
func isMonitoringConcurrentEditError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && (strings.Contains(strings.ToLower(gerr.Body), "too many concurrent edits") ||
			strings.Contains(strings.ToLower(gerr.Body), "could not fulfill the request")) {
			return true, "Waiting for other Monitoring changes to finish"
		}
	}
	return false, ""
}

// Retry if App Engine operation returns a 409 with a specific message for
// concurrent operations.
func isAppEngineRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation is already in progress") {
			return true, "Waiting for other concurrent App Engine changes to finish"
		}
	}
	return false, ""
}

// Retry if KMS CryptoKeyVersions returns a 400 for PENDING_GENERATION
func isCryptoKeyVersionsPendingGeneration(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "PENDING_GENERATION") {
			return true, "Waiting for pending key generation"
		}
	}
	return false, ""
}

// Retry if getting a resource/operation returns a 404 for specific operations.
// opType should describe the operation for which 404 can be retryable.
func isNotFoundRetryableError(opType string) RetryErrorPredicateFunc {
	return func(err error) (bool, string) {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			return true, fmt.Sprintf("Retry 404s for %s", opType)
		}
		return false, ""
	}
}

func isStoragePreconditionError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 412 {
		return true, fmt.Sprintf("Retry on storage precondition not met")
	}
	return false, ""
}

func isDataflowJobUpdateRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 404 && strings.Contains(gerr.Body, "in RUNNING OR DRAINING state") {
			return true, "Waiting for job to be in a valid state"
		}
	}
	return false, ""
}

func isPeeringOperationInProgress(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "There is a peering operation in progress") {
			return true, "Waiting peering operation to complete"
		}
	}
	return false, ""
}

func isCloudFunctionsSourceCodeError(err error) (bool, string) {
	if operr, ok := err.(*CommonOpError); ok {
		if operr.Code == 3 && operr.Message == "Failed to retrieve function source code" {
			return true, fmt.Sprintf("Retry on Function failing to pull code from GCS")
		}
	}
	return false, ""
}

func datastoreIndex409Contention(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(gerr.Body, "too much contention") {
			return true, "too much contention - waiting for less activity"
		}
	}
	return false, ""
}
<commit_msg>Add iap client 409 retry (#4204) (#577)<commit_after>package google

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strings"

	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

type RetryErrorPredicateFunc func(error) (bool, string)

/** ADD GLOBAL ERROR RETRY PREDICATES HERE **/
// Retry predicates that shoud apply to all requests should be added here.
var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
	// Common network errors (usually wrapped by URL error)
	isNetworkTemporaryError,
	isNetworkTimeoutError,
	isIoEOFError,
	isConnectionResetNetworkError,

	// Common GCP error codes
	isCommonRetryableErrorCode,

	//While this might apply only to Cloud SQL, historically,
	// we had this in our global default error retries.
	// Keeping it as a default for now.
	is409OperationInProgressError,
}

/** END GLOBAL ERROR RETRY PREDICATES HERE **/

func isNetworkTemporaryError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Temporary() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Temporary() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isNetworkTimeoutError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isIoEOFError(err error) (bool, string) {
	if err == io.ErrUnexpectedEOF {
		return true, "Got unexpected EOF"
	}

	if urlerr, urlok := err.(*url.Error); urlok {
		wrappedErr := urlerr.Unwrap()
		if wrappedErr == io.ErrUnexpectedEOF {
			return true, "Got unexpected EOF"
		}
	}
	return false, ""
}

const connectionResetByPeerErr = ": connection reset by peer"

func isConnectionResetNetworkError(err error) (bool, string) {
	if strings.HasSuffix(err.Error(), connectionResetByPeerErr) {
		return true, fmt.Sprintf("reset connection error: %v", err)
	}
	return false, ""
}

// Retry 409s because some APIs like Cloud SQL throw a 409 if concurrent calls
// are being made.
//
//The only way right now to determine it is a retryable 409 due to
// concurrent calls is to look at the contents of the error message.
// See https://github.com/hashicorp/terraform-provider-google/issues/3279
func is409OperationInProgressError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
		return true, "Operation still in progress"
	}
	return false, ""
}

// Retry on comon googleapi error codes for retryable errors.
// TODO(#5609): This may not need to be applied globally - figure out
// what retryable error codes apply to which API.
func isCommonRetryableErrorCode(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503 {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
		return true, fmt.Sprintf("Retryable error code %d", gerr.Code)
	}
	return false, ""
}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// Retry the operation if it looks like a fingerprint mismatch.
func isFingerprintError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code != 412 {
		return false, ""
	}

	for _, msg := range FINGERPRINT_FAIL_ERRORS {
		if strings.Contains(err.Error(), msg) {
			return true, "fingerprint mismatch"
		}
	}

	return false, ""
}

// If a permission necessary to provision a resource is created in the same config
// as the resource itself, the permission may not have propagated by the time terraform
// attempts to create the resource. This allows those errors to be retried until the timeout expires
func iamMemberMissing(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "permission") {
			return true, "Waiting for IAM member permissions to propagate."
		}
	}
	return false, ""
}

// Cloud PubSub returns a 400 error if a topic's parent project was recently created and an
// organization policy has not propagated.
// See https://github.com/hashicorp/terraform-provider-google/issues/4349
func pubsubTopicProjectNotReady(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "retry this operation") {
			log.Printf("[DEBUG] Dismissed error as a retryable operation: %s", err)
			return true, "Waiting for Pubsub topic's project to properly initialize with organiation policy"
		}
	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlInternalError(err error) (bool, string) {
	if gerr, ok := err.(*SqlAdminOperationError); ok {
		// SqlAdminOperationError is a non-interface type so we need to cast it through
		// a layer of interface{}.  :)
		var ierr interface{}
		ierr = gerr
		if serr, ok := ierr.(*sqladmin.OperationErrors); ok && serr.Errors[0].Code == "INTERNAL_ERROR" {
			return true, "Received an internal error, which is sometimes retryable for some SQL resources.  Optimistically retrying."
		}

	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlOperationInProgressError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 {
		if strings.Contains(gerr.Body, "you cannot reuse the name of the deleted instance until one week from the deletion date.") {
			return false, ""
		}

		return true, "Waiting for other concurrent Cloud SQL operations to finish"
	}
	return false, ""
}

// Retry if service usage decides you're activating the same service multiple
// times. This can happen if a service and a dependent service aren't batched
// together- eg container.googleapis.com in one request followed by compute.g.c
// in the next (container relies on compute and implicitly activates it)
func serviceUsageServiceBeingActivated(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "Another activation or deactivation is in progress") {
			return true, "Waiting for same service activation/deactivation to finish"
		}

		return false, ""
	}
	return false, ""
}

// Retry if Bigquery operation returns a 403 with a specific message for
// concurrent operations (which are implemented in terms of 'edit quota').
func isBigqueryIAMQuotaError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 403 && strings.Contains(strings.ToLower(gerr.Body), "exceeded rate limits") {
			return true, "Waiting for Bigquery edit quota to refresh"
		}
	}
	return false, ""
}

// Retry if Monitoring operation returns a 409 with a specific message for
// concurrent operations.
func isMonitoringConcurrentEditError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && (strings.Contains(strings.ToLower(gerr.Body), "too many concurrent edits") ||
			strings.Contains(strings.ToLower(gerr.Body), "could not fulfill the request")) {
			return true, "Waiting for other Monitoring changes to finish"
		}
	}
	return false, ""
}

// Retry if App Engine operation returns a 409 with a specific message for
// concurrent operations.
func isAppEngineRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation is already in progress") {
			return true, "Waiting for other concurrent App Engine changes to finish"
		}
	}
	return false, ""
}

// Retry if KMS CryptoKeyVersions returns a 400 for PENDING_GENERATION
func isCryptoKeyVersionsPendingGeneration(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "PENDING_GENERATION") {
			return true, "Waiting for pending key generation"
		}
	}
	return false, ""
}

// Retry if getting a resource/operation returns a 404 for specific operations.
// opType should describe the operation for which 404 can be retryable.
func isNotFoundRetryableError(opType string) RetryErrorPredicateFunc {
	return func(err error) (bool, string) {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			return true, fmt.Sprintf("Retry 404s for %s", opType)
		}
		return false, ""
	}
}

func isStoragePreconditionError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 412 {
		return true, fmt.Sprintf("Retry on storage precondition not met")
	}
	return false, ""
}

func isDataflowJobUpdateRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 404 && strings.Contains(gerr.Body, "in RUNNING OR DRAINING state") {
			return true, "Waiting for job to be in a valid state"
		}
	}
	return false, ""
}

func isPeeringOperationInProgress(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "There is a peering operation in progress") {
			return true, "Waiting peering operation to complete"
		}
	}
	return false, ""
}

func isCloudFunctionsSourceCodeError(err error) (bool, string) {
	if operr, ok := err.(*CommonOpError); ok {
		if operr.Code == 3 && operr.Message == "Failed to retrieve function source code" {
			return true, fmt.Sprintf("Retry on Function failing to pull code from GCS")
		}
	}
	return false, ""
}

func datastoreIndex409Contention(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(gerr.Body, "too much contention") {
			return true, "too much contention - waiting for less activity"
		}
	}
	return false, ""
}

func iapClient409Operation(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation was aborted") {
			return true, "operation was aborted possibly due to concurrency issue - retrying"
		}
	}
	return false, ""
}
<|endoftext|>


<commit_before>// lissajous2 solution for task 1.6
package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/gif"
	"io"
	"log"
	"math"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"time"
)

var (
	web     bool
	res     float64
	cycles  float64
	size    int
	nframes int
	delay   int
)

func init() {
	flag.BoolVar(&web, "web", false, "run web server on :8000")
	flag.Float64Var(&res, "res", 0.001, "angular resolution")
	flag.Float64Var(&cycles, "cycles", 5, "number of revolutions")
	flag.IntVar(&size, "size", 200, "image canvas covers")
	flag.IntVar(&nframes, "nframes", 64, "number of animation frames")
	flag.IntVar(&delay, "delay", 64, "delay between frames in 10ms units")

}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	flag.Parse()

	if web {
		serve()
		return
	}
	lissajous(os.Stdout, res, cycles, size, nframes, delay)
}

func handler(w http.ResponseWriter, r *http.Request) {
	// TODO: add per request context.
	query := r.URL.Query()
	if qsize := query.Get("size"); qsize != "" {
		parsed, err := strconv.Atoi(qsize)
		if err != nil {
			fmt.Fprintf(w, "error parsing size paramert: %v", err)
			return
		}
		size = parsed
	}
	lissajous(w, res, cycles, size, nframes, delay)
}

func serve() {
	http.HandleFunc("/", handler)
	log.Fatalln(http.ListenAndServe("localhost:8000", nil))

}

func lissajous(out io.Writer, res, cycles float64, size, nframes, delay int) {
	freq := rand.Float64() * 3.0
	anim := gif.GIF{LoopCount: nframes}
	phase := 0.0
	for i := 0; i < nframes; i++ {
		palette := []color.Color{color.Black}
		rect := image.Rect(0, 0, 2*size+1, 2*size+1)
		img := image.NewPaletted(rect, append(palette, rainbow()))

		for t := 0.0; t < cycles*2*math.Pi; t += res {
			x := math.Sin(t)
			y := math.Sin(t*freq + phase)
			img.SetColorIndex(
				size+int(x*float64(size)+0.5),
				size+int(y*float64(size)+0.5), 1)
		}
		phase += 0.1
		anim.Delay = append(anim.Delay, delay)
		anim.Image = append(anim.Image, img)
	}
	err := gif.EncodeAll(out, &anim)
	if err != nil {
		log.Fatalf("error encoding gif image: %v", err)
	}
}

func rainbow() color.Color {

	var vibgyor = []color.Color{
		color.RGBA{0x94, 0x00, 0xD3, 0xFF}, // violet
		color.RGBA{0x4B, 0x00, 0x82, 0xFF}, // indigo
		color.RGBA{0x00, 0x00, 0xFF, 0xFF}, // blue
		color.RGBA{0x00, 0xFF, 0x00, 0xFF}, // green
		color.RGBA{0xFF, 0xFF, 0x00, 0xFF}, // yellow
		color.RGBA{0xFF, 0x7F, 0x00, 0xFF}, // orange
		color.RGBA{0xFF, 0x00, 0x00, 0xFF}, // red
	}
	return vibgyor[rand.Intn(len(vibgyor))]
}
<commit_msg>Added more canonical error notification.<commit_after>// lissajous2 solution for task 1.6
package main

import (
	"flag"
	"image"
	"image/color"
	"image/gif"
	"io"
	"log"
	"math"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"time"
)

var (
	web     bool
	res     float64
	cycles  float64
	size    int
	nframes int
	delay   int
)

func init() {
	flag.BoolVar(&web, "web", false, "run web server on :8000")
	flag.Float64Var(&res, "res", 0.001, "angular resolution")
	flag.Float64Var(&cycles, "cycles", 5, "number of revolutions")
	flag.IntVar(&size, "size", 200, "image canvas covers")
	flag.IntVar(&nframes, "nframes", 64, "number of animation frames")
	flag.IntVar(&delay, "delay", 64, "delay between frames in 10ms units")

}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	flag.Parse()

	if web {
		serve()
		return
	}
	lissajous(os.Stdout, res, cycles, size, nframes, delay)
}

func handler(w http.ResponseWriter, r *http.Request) {
	// TODO: add per request context.
	query := r.URL.Query()
	if qsize := query.Get("size"); qsize != "" {
		parsed, err := strconv.Atoi(qsize)
		if err != nil {
			http.Error(w, "size value error", http.StatusBadRequest)
			return
		}
		size = parsed
	}
	lissajous(w, res, cycles, size, nframes, delay)
}

func serve() {
	http.HandleFunc("/", handler)
	log.Fatalln(http.ListenAndServe("localhost:8000", nil))

}

func lissajous(out io.Writer, res, cycles float64, size, nframes, delay int) {
	freq := rand.Float64() * 3.0
	anim := gif.GIF{LoopCount: nframes}
	phase := 0.0
	for i := 0; i < nframes; i++ {
		palette := []color.Color{color.Black}
		rect := image.Rect(0, 0, 2*size+1, 2*size+1)
		img := image.NewPaletted(rect, append(palette, rainbow()))

		for t := 0.0; t < cycles*2*math.Pi; t += res {
			x := math.Sin(t)
			y := math.Sin(t*freq + phase)
			img.SetColorIndex(
				size+int(x*float64(size)+0.5),
				size+int(y*float64(size)+0.5), 1)
		}
		phase += 0.1
		anim.Delay = append(anim.Delay, delay)
		anim.Image = append(anim.Image, img)
	}
	err := gif.EncodeAll(out, &anim)
	if err != nil {
		log.Fatalf("error encoding gif image: %v", err)
	}
}

func rainbow() color.Color {

	var vibgyor = []color.Color{
		color.RGBA{0x94, 0x00, 0xD3, 0xFF}, // violet
		color.RGBA{0x4B, 0x00, 0x82, 0xFF}, // indigo
		color.RGBA{0x00, 0x00, 0xFF, 0xFF}, // blue
		color.RGBA{0x00, 0xFF, 0x00, 0xFF}, // green
		color.RGBA{0xFF, 0xFF, 0x00, 0xFF}, // yellow
		color.RGBA{0xFF, 0x7F, 0x00, 0xFF}, // orange
		color.RGBA{0xFF, 0x00, 0x00, 0xFF}, // red
	}
	return vibgyor[rand.Intn(len(vibgyor))]
}
<|endoftext|>


<commit_before>// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
// go-aah/router source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package router

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"aahframework.org/ahttp.v0"
	"aahframework.org/config.v0"
	"aahframework.org/essentials.v0"
	"aahframework.org/log.v0"
)

const allowAll = "*"

// CORS errors
var (
	ErrCORSOriginIsInvalid       = errors.New("cors: invalid origin")
	ErrCORSMethodNotAllowed      = errors.New("cors: method not allowed")
	ErrCORSHeaderNotAllowed      = errors.New("cors: header not allowed")
	ErrCORSContentTypeNotAllowed = errors.New("cors: content-type not allowed")

	// Excluded simple allowed headers and adding sensiable allowed headers.
	// Refer to: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
	defaultAllowHeaders = []string{ahttp.HeaderOrigin, ahttp.HeaderAccept,
		ahttp.HeaderAcceptLanguage, ahttp.HeaderAuthorization}

	// Sensiable default allowed methods
	defaultAllowMethods = []string{ahttp.MethodGet, ahttp.MethodHead, ahttp.MethodPost}
)

// CORS struct is to hold Cross-Origin Resource Sharing (CORS) configuration
// values and verification for the route.
//
// Specification: https://www.w3.org/TR/cors/
// Friendly Read: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
type CORS struct {
	AllowOrigins     []string
	AllowMethods     []string
	AllowHeaders     []string
	ExposeHeaders    []string
	AllowCredentials bool
	MaxAge           string

	maxAgeStr       string
	allowAllOrigins bool
	allowAllMethods bool
	allowAllHeaders bool
}

// AddOrigins method adds the given origin into allow origin list.
func (c *CORS) AddOrigins(origins []string) *CORS {
	for _, o := range origins {
		if o == allowAll {
			c.allowAllOrigins = true
			c.AllowOrigins = []string{allowAll}
			break
		}
		o = strings.ToLower(o)
		if !ess.IsSliceContainsString(c.AllowOrigins, o) {
			c.AllowOrigins = append(c.AllowOrigins, o)
		}
	}
	return c
}

// AddAllowHeaders method adds the given HTTP header into allow headers list.
func (c *CORS) AddAllowHeaders(hdrs []string) *CORS {
	c.AllowHeaders = c.addHeaders(c.AllowHeaders, hdrs)
	if ess.IsSliceContainsString(c.AllowHeaders, allowAll) {
		c.allowAllHeaders = true
		c.AllowHeaders = []string{allowAll}
	}
	return c
}

// AddAllowMethods method adds the given HTTP verb into allow methods list.
func (c *CORS) AddAllowMethods(methods []string) *CORS {
	for _, m := range methods {
		if m == allowAll {
			c.allowAllMethods = true
			c.AllowMethods = []string{allowAll}
			break
		}
		m = strings.ToUpper(strings.TrimSpace(m))
		if !ess.IsStrEmpty(m) && !ess.IsSliceContainsString(c.AllowMethods, m) {
			c.AllowMethods = append(c.AllowMethods, m)
		}
	}
	return c
}

// AddExposeHeaders method adds the given HTTP header into expose headers list.
func (c *CORS) AddExposeHeaders(hdrs []string) *CORS {
	c.ExposeHeaders = c.addHeaders(c.ExposeHeaders, hdrs)
	return c
}

// SetMaxAge method parses the given duration string into seconds and adds to CORS.
// `time.ParseDuration` method time units are supported.
func (c *CORS) SetMaxAge(age string) *CORS {
	if dur, err := time.ParseDuration(age); err == nil {
		c.MaxAge = strconv.Itoa(int(dur.Seconds()))
	} else {
		log.Errorf("Unable to parse CORS 'max_age' value '%v'", age)
	}
	return c
}

// SetAllowCredentials method sets the given boolean into allow credentials.
func (c *CORS) SetAllowCredentials(b bool) *CORS {
	c.AllowCredentials = b
	return c
}

// IsOriginAllowed method check given origin is allowed or not.
func (c *CORS) IsOriginAllowed(origin string) bool {
	if ess.IsStrEmpty(origin) {
		return false
	}

	if c.allowAllOrigins {
		return true
	}

	return ess.IsSliceContainsString(c.AllowOrigins, strings.ToLower(origin))
}

// IsMethodAllowed method returns true if preflight method is allowed otherwise
// false.
func (c *CORS) IsMethodAllowed(method string) bool {
	if c.allowAllMethods {
		return true
	}
	return ess.IsSliceContainsString(c.AllowMethods, method)
}

// IsHeadersAllowed method returns true if preflight headers are allowed otherwise
// false.
func (c *CORS) IsHeadersAllowed(hdrs string) bool {
	if c.allowAllHeaders {
		return true
	}

	for _, h := range strings.Split(hdrs, ",") {
		h = http.CanonicalHeaderKey(strings.TrimSpace(h))
		if ess.IsSliceContainsString(c.AllowHeaders, h) {
			return true
		}
	}

	return false
}

// String method returns string representation of CORS configuration values.
func (c *CORS) String() string {
	var buf bytes.Buffer
	buf.WriteString("Allow Origins: ")
	buf.WriteString(strings.Join(c.AllowOrigins, ", "))
	buf.WriteString("\nAllow Headers: ")
	buf.WriteString(strings.Join(c.AllowHeaders, ", "))
	buf.WriteString("\nAllow Methods: ")
	buf.WriteString(strings.Join(c.AllowMethods, ", "))
	buf.WriteString("\nExpose Headers: ")
	buf.WriteString(strings.Join(c.ExposeHeaders, ", "))
	buf.WriteString(fmt.Sprintf("\nAllow Credentials: %v", c.AllowCredentials))
	buf.WriteString(fmt.Sprintf("\nMax Age: %v", c.maxAgeStr))
	return buf.String()
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Unexported CORS methods
//___________________________________

func (c *CORS) addHeaders(dst []string, src []string) []string {
	for _, h := range src {
		if h == allowAll {
			return []string{allowAll}
		}
		h = http.CanonicalHeaderKey(strings.TrimSpace(h))
		if !ess.IsStrEmpty(h) && !ess.IsSliceContainsString(dst, h) {
			dst = append(dst, h)
		}
	}
	return dst
}

func processBaseCORSSection(cfg *config.Config) *CORS {
	cors := &CORS{}

	// Access-Control-Allow-Origin
	if origins, found := cfg.StringList("allow_origins"); found {
		cors.AddOrigins(origins)
	} else {
		cors.AddOrigins([]string{allowAll})
	}

	// Access-Control-Allow-Headers
	if hdrs, found := cfg.StringList("allow_headers"); found {
		cors.AddAllowHeaders(hdrs)
	} else {
		cors.AddAllowHeaders(defaultAllowHeaders)
	}

	// Access-Control-Allow-Methods
	if methods, found := cfg.StringList("allow_methods"); found {
		cors.AddAllowMethods(methods)
	} else {
		cors.AddAllowMethods(defaultAllowMethods)
	}
	cors.AddAllowMethods([]string{ahttp.MethodOptions})

	// Access-Control-Allow-Credentials
	cors.SetAllowCredentials(cfg.BoolDefault("allow_credentials", false))

	// Access-Control-Expose-Headers
	if hdrs, found := cfg.StringList("expose_headers"); found {
		cors.AddExposeHeaders(hdrs)
	}

	// Access-Control-Max-Age
	cors.maxAgeStr = cfg.StringDefault("max_age", "24h")
	cors.SetMaxAge(cors.maxAgeStr)

	return cors
}

func processCORSSection(cfg *config.Config, parent *CORS) *CORS {
	cors := &CORS{}

	// Access-Control-Allow-Origin
	if origins, found := cfg.StringList("allow_origins"); found {
		cors.AddOrigins(origins)
	} else {
		cors.AddOrigins(parent.AllowOrigins)
	}

	// Access-Control-Allow-Headers
	if hdrs, found := cfg.StringList("allow_headers"); found {
		cors.AddAllowHeaders(hdrs)
	} else {
		cors.AddAllowHeaders(parent.AllowHeaders)
	}

	// Access-Control-Allow-Methods
	if methods, found := cfg.StringList("allow_methods"); found {
		cors.AddAllowMethods(methods)
	} else {
		cors.AddAllowMethods(parent.AllowMethods)
	}

	// Access-Control-Allow-Credentials
	cors.SetAllowCredentials(cfg.BoolDefault("allow_credentials", parent.AllowCredentials))

	// Access-Control-Expose-Headers
	if hdrs, found := cfg.StringList("expose_headers"); found {
		cors.AddExposeHeaders(hdrs)
	} else {
		cors.AddExposeHeaders(parent.ExposeHeaders)
	}

	// Access-Control-Max-Age
	cors.maxAgeStr = cfg.StringDefault("max_age", parent.maxAgeStr)
	cors.SetMaxAge(cors.maxAgeStr)

	return cors
}
<commit_msg>allowed header update<commit_after>// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
// go-aah/router source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package router

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"aahframework.org/ahttp.v0"
	"aahframework.org/config.v0"
	"aahframework.org/essentials.v0"
	"aahframework.org/log.v0"
)

const allowAll = "*"

// CORS errors
var (
	ErrCORSOriginIsInvalid       = errors.New("cors: invalid origin")
	ErrCORSMethodNotAllowed      = errors.New("cors: method not allowed")
	ErrCORSHeaderNotAllowed      = errors.New("cors: header not allowed")
	ErrCORSContentTypeNotAllowed = errors.New("cors: content-type not allowed")

	// Excluded simple allowed headers and adding sensiable allowed headers.
	// Refer to: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
	defaultAllowHeaders = []string{ahttp.HeaderOrigin, ahttp.HeaderAccept,
		ahttp.HeaderAcceptLanguage, ahttp.HeaderAuthorization}

	// Sensiable default allowed methods
	defaultAllowMethods = []string{ahttp.MethodGet, ahttp.MethodHead, ahttp.MethodPost}
)

// CORS struct is to hold Cross-Origin Resource Sharing (CORS) configuration
// values and verification for the route.
//
// Specification: https://www.w3.org/TR/cors/
// Friendly Read: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
type CORS struct {
	AllowOrigins     []string
	AllowMethods     []string
	AllowHeaders     []string
	ExposeHeaders    []string
	AllowCredentials bool
	MaxAge           string

	maxAgeStr       string
	allowAllOrigins bool
	allowAllMethods bool
	allowAllHeaders bool
}

// AddOrigins method adds the given origin into allow origin list.
func (c *CORS) AddOrigins(origins []string) *CORS {
	for _, o := range origins {
		if o == allowAll {
			c.allowAllOrigins = true
			c.AllowOrigins = []string{allowAll}
			break
		}
		o = strings.ToLower(o)
		if !ess.IsSliceContainsString(c.AllowOrigins, o) {
			c.AllowOrigins = append(c.AllowOrigins, o)
		}
	}
	return c
}

// AddAllowHeaders method adds the given HTTP header into allow headers list.
func (c *CORS) AddAllowHeaders(hdrs []string) *CORS {
	c.AllowHeaders = c.addHeaders(c.AllowHeaders, hdrs)
	if ess.IsSliceContainsString(c.AllowHeaders, allowAll) {
		c.allowAllHeaders = true
		c.AllowHeaders = []string{allowAll}
	}
	return c
}

// AddAllowMethods method adds the given HTTP verb into allow methods list.
func (c *CORS) AddAllowMethods(methods []string) *CORS {
	for _, m := range methods {
		if m == allowAll {
			c.allowAllMethods = true
			c.AllowMethods = []string{allowAll}
			break
		}
		m = strings.ToUpper(strings.TrimSpace(m))
		if !ess.IsStrEmpty(m) && !ess.IsSliceContainsString(c.AllowMethods, m) {
			c.AllowMethods = append(c.AllowMethods, m)
		}
	}
	return c
}

// AddExposeHeaders method adds the given HTTP header into expose headers list.
func (c *CORS) AddExposeHeaders(hdrs []string) *CORS {
	c.ExposeHeaders = c.addHeaders(c.ExposeHeaders, hdrs)
	return c
}

// SetMaxAge method parses the given duration string into seconds and adds to CORS.
// `time.ParseDuration` method time units are supported.
func (c *CORS) SetMaxAge(age string) *CORS {
	if dur, err := time.ParseDuration(age); err == nil {
		c.MaxAge = strconv.Itoa(int(dur.Seconds()))
	} else {
		log.Errorf("Unable to parse CORS 'max_age' value '%v'", age)
	}
	return c
}

// SetAllowCredentials method sets the given boolean into allow credentials.
func (c *CORS) SetAllowCredentials(b bool) *CORS {
	c.AllowCredentials = b
	return c
}

// IsOriginAllowed method check given origin is allowed or not.
func (c *CORS) IsOriginAllowed(origin string) bool {
	if ess.IsStrEmpty(origin) {
		return false
	}

	if c.allowAllOrigins {
		return true
	}

	return ess.IsSliceContainsString(c.AllowOrigins, strings.ToLower(origin))
}

// IsMethodAllowed method returns true if preflight method is allowed otherwise
// false.
func (c *CORS) IsMethodAllowed(method string) bool {
	if c.allowAllMethods {
		return true
	}
	return ess.IsSliceContainsString(c.AllowMethods, method)
}

// IsHeadersAllowed method returns true if preflight headers are allowed otherwise
// false.
func (c *CORS) IsHeadersAllowed(hdrs string) bool {
	if c.allowAllHeaders || len(hdrs) == 0 {
		return true
	}

	for _, h := range strings.Split(hdrs, ",") {
		h = http.CanonicalHeaderKey(strings.TrimSpace(h))
		allowed := false
		if ess.IsSliceContainsString(c.AllowHeaders, h) {
			allowed = true
		}

		if !allowed {
			return false
		}
	}

	return true
}

// String method returns string representation of CORS configuration values.
func (c *CORS) String() string {
	var buf bytes.Buffer
	buf.WriteString("Allow Origins: ")
	buf.WriteString(strings.Join(c.AllowOrigins, ", "))
	buf.WriteString("\nAllow Headers: ")
	buf.WriteString(strings.Join(c.AllowHeaders, ", "))
	buf.WriteString("\nAllow Methods: ")
	buf.WriteString(strings.Join(c.AllowMethods, ", "))
	buf.WriteString("\nExpose Headers: ")
	buf.WriteString(strings.Join(c.ExposeHeaders, ", "))
	buf.WriteString(fmt.Sprintf("\nAllow Credentials: %v", c.AllowCredentials))
	buf.WriteString(fmt.Sprintf("\nMax Age: %v", c.maxAgeStr))
	return buf.String()
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Unexported CORS methods
//___________________________________

func (c *CORS) addHeaders(dst []string, src []string) []string {
	for _, h := range src {
		if h == allowAll {
			return []string{allowAll}
		}
		h = http.CanonicalHeaderKey(strings.TrimSpace(h))
		if !ess.IsStrEmpty(h) && !ess.IsSliceContainsString(dst, h) {
			dst = append(dst, h)
		}
	}
	return dst
}

func processBaseCORSSection(cfg *config.Config) *CORS {
	cors := &CORS{}

	// Access-Control-Allow-Origin
	if origins, found := cfg.StringList("allow_origins"); found {
		cors.AddOrigins(origins)
	} else {
		cors.AddOrigins([]string{allowAll})
	}

	// Access-Control-Allow-Headers
	if hdrs, found := cfg.StringList("allow_headers"); found {
		cors.AddAllowHeaders(hdrs)
	} else {
		cors.AddAllowHeaders(defaultAllowHeaders)
	}

	// Access-Control-Allow-Methods
	if methods, found := cfg.StringList("allow_methods"); found {
		cors.AddAllowMethods(methods)
	} else {
		cors.AddAllowMethods(defaultAllowMethods)
	}
	cors.AddAllowMethods([]string{ahttp.MethodOptions})

	// Access-Control-Allow-Credentials
	cors.SetAllowCredentials(cfg.BoolDefault("allow_credentials", false))

	// Access-Control-Expose-Headers
	if hdrs, found := cfg.StringList("expose_headers"); found {
		cors.AddExposeHeaders(hdrs)
	}

	// Access-Control-Max-Age
	cors.maxAgeStr = cfg.StringDefault("max_age", "24h")
	cors.SetMaxAge(cors.maxAgeStr)

	return cors
}

func processCORSSection(cfg *config.Config, parent *CORS) *CORS {
	cors := &CORS{}

	// Access-Control-Allow-Origin
	if origins, found := cfg.StringList("allow_origins"); found {
		cors.AddOrigins(origins)
	} else {
		cors.AddOrigins(parent.AllowOrigins)
	}

	// Access-Control-Allow-Headers
	if hdrs, found := cfg.StringList("allow_headers"); found {
		cors.AddAllowHeaders(hdrs)
	} else {
		cors.AddAllowHeaders(parent.AllowHeaders)
	}

	// Access-Control-Allow-Methods
	if methods, found := cfg.StringList("allow_methods"); found {
		cors.AddAllowMethods(methods)
	} else {
		cors.AddAllowMethods(parent.AllowMethods)
	}

	// Access-Control-Allow-Credentials
	cors.SetAllowCredentials(cfg.BoolDefault("allow_credentials", parent.AllowCredentials))

	// Access-Control-Expose-Headers
	if hdrs, found := cfg.StringList("expose_headers"); found {
		cors.AddExposeHeaders(hdrs)
	} else {
		cors.AddExposeHeaders(parent.ExposeHeaders)
	}

	// Access-Control-Max-Age
	cors.maxAgeStr = cfg.StringDefault("max_age", parent.maxAgeStr)
	cors.SetMaxAge(cors.maxAgeStr)

	return cors
}
<|endoftext|>


<commit_before>package cram

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/satori/go.uuid"
)

const (
	commandPrefix = "  $ "
	continuationPrefix = "  > "
	outputPrefix  = "  "

	reSuffix   = " (re)"
	globSuffix = " (glob)"
)

type InvalidTestError struct {
	Path   string // Path to test file.
	Lineno int    // Line number of failure
	Msg    string // Error message
}

func (e *InvalidTestError) Error() string {
	return fmt.Sprintf("%s:%d: %s", e.Path, e.Lineno, e.Msg)
}

type Test struct {
	Path string    // Path to test file.
	Cmds []Command // Commands.
}

type Command struct {
	CmdLine          string   // Command line passed to the shell.
	ExpectedOutput   []string // Expected output lines.
	ExpectedExitCode int      // Expected exit code.
	Lineno           int      // Line number of first output line.
}

type ExecutedCommand struct {
	*Command                // Command responsible for the output.
	ActualOutput   []string // Actual output read from stdout and stderr.
	ActualExitCode int      // Exit code.
}

// ExecutedTest captures the executed commands, the script sent to the
// shell and the failed commands. The Test struct is embedded as a
// value instead of pointer to make the zero value for ExecutedTest
// immediatedly useful.
type ExecutedTest struct {
	Test
	ExecutedCmds []ExecutedCommand // All executed commands.
	Script       string            // The script passed to the shell.
	Failures     []ExecutedCommand // Failed commands.
}

// matchEntireLine returns true exactly when pattern can be compiled
// and matches all of line.
func matchEntireLine(pattern, line string) bool {
	pattern = "^(?:" + pattern + ")$"
	matched, err := regexp.MatchString(pattern, line)
	return err == nil && matched
}

// globToRegexp translates a glob pattern into the corresponding
// regular expression. We cannnot simply use filepath.Match since we
// want "*" to match a sequence of any character instead of stopping
// at "/" (or "\\" on Windows). Also, filepath.Match has a quirk where
// there is no escaping on Windows.
func globToRegexp(pattern string) string {
	regexpMeta := `\.+*?()|[]{}^$`
	buf := make([]byte, 2*len(pattern))
	j := 0
Loop:
	for i := 0; i < len(pattern); i++ {
		switch pattern[i] {
		case '?':
			buf[j] = '.'
		case '*':
			buf[j], buf[j+1] = '.', '*'
			j++
		case '\\':
			// Skip over the backslash
			i++
			if i == len(pattern) {
				break Loop
			}
			// If we didn't break, add the next character to buf as a
			// literal.
			fallthrough
		default:
			// Escape character is necessary
			if strings.IndexByte(regexpMeta, pattern[i]) >= 0 {
				buf[j] = '\\'
				j++
			}
			buf[j] = pattern[i]
		}
		j++
	}
	return string(buf[:j])
}

// failed indicates if the actual exit code or output differed from
// what was expected.
func (cmd *ExecutedCommand) failed() bool {
	if cmd.ActualExitCode != cmd.ExpectedExitCode {
		return true
	}
	if len(cmd.ActualOutput) != len(cmd.ExpectedOutput) {
		return true
	}
	for i, actual := range cmd.ActualOutput {
		expected := cmd.ExpectedOutput[i]
		// Always accept an exact match, even if the line might end
		// with (re). This means that such lines need no escaping in
		// the test file and are quick to match.
		if actual == expected {
			continue
		}

		// The following tests ignore EOLs.
		actual = DropEol(actual)
		expected = DropEol(expected)

		switch {
		case strings.HasSuffix(expected, reSuffix):
			pattern := expected[:len(expected)-len(reSuffix)]
			if !matchEntireLine(pattern, actual) {
				return true
			}
		case strings.HasSuffix(expected, globSuffix):
			pattern := expected[:len(expected)-len(globSuffix)]
			if !matchEntireLine(globToRegexp(pattern), actual) {
				return true
			}
		default:
			// No special suffix, not equal by the check above => we
			// found a change in the output.
			return true
		}
	}
	return false
}

// DropEol removes a final end-of-line from s. It removes both Unix ("\n")
// and DOS ("\r\n") end-of-line characters.
func DropEol(s string) string {
	l := len(s)
	if l == 0 || s[l-1] != '\n' {
		return s
	}
	drop := 1
	if l > 1 && s[l-2] == '\r' {
		drop = 2
	}
	return s[:l-drop]
}

// updateExitCode looks at the last line of output and updates exit
// code if it is of the form [n]. The exit code line is only required
// for non-zero exit codes.
func updateExitCode(cmd *Command) {
	lines := len(cmd.ExpectedOutput)
	if lines == 0 {
		return
	}
	line := DropEol(cmd.ExpectedOutput[lines-1])

	l := len(line)
	if l == 0 || line[0] != '[' || line[l-1] != ']' {
		return
	}

	exitCode, err := strconv.Atoi(line[1 : l-1])
	if err != nil {
		// Not an exit code, just normal output.
		return
	}
	cmd.ExpectedOutput = cmd.ExpectedOutput[:lines-1]
	cmd.ExpectedExitCode = exitCode
}

// Parse splits an input test file into Commands.
func ParseTest(r io.Reader, path string) (test Test, err error) {
	const (
		inCommentary = iota
		inCommand
		inOutput
	)

	test.Path = path
	reader := bufio.NewReader(r)
	state := inCommentary
	lineno := 0
	line := ""
	for err == nil {
		line, err = reader.ReadString('\n')
		switch {
		case strings.HasPrefix(line, commandPrefix):
			if state == inOutput {
				updateExitCode(&test.Cmds[len(test.Cmds)-1])
			}
			line = line[len(commandPrefix):]
			cmd := Command{
				CmdLine: line,
				Lineno:  lineno + 1,
			}
			test.Cmds = append(test.Cmds, cmd)
			state = inCommand
		case strings.HasPrefix(line, continuationPrefix):
			if state != inCommand {
				err = &InvalidTestError{path, lineno,
					fmt.Sprintf("Continuation line %q has no command", line)}
				return
			}
			line = line[len(continuationPrefix):]
			cmd := &test.Cmds[len(test.Cmds)-1]
			cmd.CmdLine = cmd.CmdLine + line
			cmd.Lineno++
		case strings.HasPrefix(line, outputPrefix):
			if state == inCommentary {
				err = &InvalidTestError{path, lineno,
					fmt.Sprintf("Output line %q has no command", line)}
				return
			}
			line = line[len(outputPrefix):]
			cmd := &test.Cmds[len(test.Cmds)-1]
			cmd.ExpectedOutput = append(cmd.ExpectedOutput, line)
			state = inOutput
		default:
			if state == inOutput {
				updateExitCode(&test.Cmds[len(test.Cmds)-1])
			}
			state = inCommentary
		}
		lineno++
	}
	if state == inOutput {
		updateExitCode(&test.Cmds[len(test.Cmds)-1])
	}
	if err == io.EOF {
		err = nil
	}
	return
}

// MakeBanner turns a UUID into a nice banner we can recognize later
// in the output.
func MakeBanner(u uuid.UUID) string {
	return "--- CRAM " + u.String() + " ---"
}

// MakeScript produces a script ready to be sent to a shell. The
// banner should be a random string. It will be inserted in the output
// together with the exit status of each command.
func MakeScript(cmds []Command, banner string) (lines []string) {
	echo := fmt.Sprintf("echo \"%s $?\"\n", banner)
	for _, cmd := range cmds {
		lines = append(lines, cmd.CmdLine, echo)
	}
	return
}

// ParseOutput finds the actual output and exit codes for a slice of
// commands. The result is a slice of executed commands.
func ParseOutput(cmds []Command, output []byte, banner string) (
	executed []ExecutedCommand, err error) {
	r := bytes.NewReader(output)
	reader := bufio.NewReader(r)

	i := 0
	actualOutput := []string{}
	line := ""
	for err == nil {
		line, err = reader.ReadString('\n')
		if strings.HasPrefix(line, banner) {
			number := DropEol(line[len(banner)+1:])
			exitCode, e := strconv.Atoi(number)
			if e != nil {
				err = e
				return
			}
			executed = append(executed, ExecutedCommand{
				Command:        &cmds[i],
				ActualExitCode: exitCode,
				ActualOutput:   actualOutput,
			})
			actualOutput = nil
			i++
		} else {
			actualOutput = append(actualOutput, line)
		}
	}
	if err == io.EOF {
		err = nil
	}
	return
}

// Execute a script in the specified working directory.
func ExecuteScript(workdir string, lines []string) ([]byte, error) {
	script := strings.Join(lines, "")
	cmd := exec.Command("/bin/sh", "-")
	cmd.Dir = workdir
	cmd.Stdin = strings.NewReader(script)
	return cmd.CombinedOutput()
}

func filterFailures(executed []ExecutedCommand) (failures []ExecutedCommand) {
	for _, cmd := range executed {
		if cmd.failed() {
			failures = append(failures, cmd)
		}
	}
	return
}

// Patch takes an ExecutedTest, a slice ExecutedCommands and returns
// the patched output where ActualOutput from each ExecutedCommand
// replaces the ExpectedOutput.
func Patch(r io.Reader, w io.Writer, cmds []ExecutedCommand) (err error) {
	reader := bufio.NewReader(r)
	writer := bufio.NewWriter(w)

	lines := []string{}
	line := ""
	for err == nil {
		line, err = reader.ReadString('\n')
		lines = append(lines, line)
	}
	if err == io.EOF {
		err = nil
	}

	output := []string{}
	lastLineno := 0

	for _, cmd := range cmds {
		pre := lines[lastLineno:cmd.Lineno]
		output = append(output, pre...)
		for _, outputLine := range cmd.ActualOutput {
			output = append(output, "  "+outputLine)
		}
		lastLineno = cmd.Lineno + len(cmd.ExpectedOutput)

		if cmd.ActualExitCode != cmd.ExpectedExitCode {
			// Add a line with the actual exit code, but only if it is
			// non-zero since [0] is implied.
			if cmd.ActualExitCode != 0 {
				line := fmt.Sprintf("  [%d]\n", cmd.ActualExitCode)
				output = append(output, line)
			}

			// Figure out if we should skip a line in the input file.
			// We should only skip a line if we are overwriting the
			// expected exit code -- we should not skip a line if we
			// merely added the actual exit code.
			if lastLineno < len(lines) {
				lastLine := lines[lastLineno]
				line := fmt.Sprintf("  [%d]", cmd.ExpectedExitCode)
				// Extra whitespace on the exit code line is okay.
				if strings.HasPrefix(lastLine, line) {
					lastLineno++
				}
			}
		}
	}
	post := lines[lastLineno:]
	output = append(output, post...)

	for _, line := range output {
		_, err = writer.WriteString(line)
	}
	err = writer.Flush()
	return
}

// Process parses a .t file, executes the test commands and compares
// the actual output to the expected output. The idx passed is used to
// make the working directory unique inside tempdir and must be
// different for each test file.
func Process(tempdir, path string, idx int) (result ExecutedTest, err error) {
	fp, err := os.Open(path)
	if err != nil {
		return
	}
	defer fp.Close()
	test, err := ParseTest(fp, path)
	if err != nil {
		return
	}

	// Create unique base inside the tempdir
	base := fmt.Sprintf("%03d-%s", idx, filepath.Base(path))
	// Remove file extension (often, but not necessarily ".t")
	base = base[:len(base)-len(filepath.Ext(base))]
	workdir := filepath.Join(tempdir, base)
	err = os.Mkdir(workdir, 0700)
	if err != nil {
		return
	}

	u := uuid.NewV4()
	banner := MakeBanner(u)
	lines := MakeScript(test.Cmds, banner)

	output, err := ExecuteScript(workdir, lines)
	if err != nil {
		return
	}

	executed, err := ParseOutput(test.Cmds, output, banner)
	if err != nil {
		return
	}

	failures := filterFailures(executed)
	result = ExecutedTest{test, executed, strings.Join(lines, ""),
		failures}
	return
}
<commit_msg>cram: run gofmt on file<commit_after>package cram

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/satori/go.uuid"
)

const (
	commandPrefix      = "  $ "
	continuationPrefix = "  > "
	outputPrefix       = "  "

	reSuffix   = " (re)"
	globSuffix = " (glob)"
)

type InvalidTestError struct {
	Path   string // Path to test file.
	Lineno int    // Line number of failure
	Msg    string // Error message
}

func (e *InvalidTestError) Error() string {
	return fmt.Sprintf("%s:%d: %s", e.Path, e.Lineno, e.Msg)
}

type Test struct {
	Path string    // Path to test file.
	Cmds []Command // Commands.
}

type Command struct {
	CmdLine          string   // Command line passed to the shell.
	ExpectedOutput   []string // Expected output lines.
	ExpectedExitCode int      // Expected exit code.
	Lineno           int      // Line number of first output line.
}

type ExecutedCommand struct {
	*Command                // Command responsible for the output.
	ActualOutput   []string // Actual output read from stdout and stderr.
	ActualExitCode int      // Exit code.
}

// ExecutedTest captures the executed commands, the script sent to the
// shell and the failed commands. The Test struct is embedded as a
// value instead of pointer to make the zero value for ExecutedTest
// immediatedly useful.
type ExecutedTest struct {
	Test
	ExecutedCmds []ExecutedCommand // All executed commands.
	Script       string            // The script passed to the shell.
	Failures     []ExecutedCommand // Failed commands.
}

// matchEntireLine returns true exactly when pattern can be compiled
// and matches all of line.
func matchEntireLine(pattern, line string) bool {
	pattern = "^(?:" + pattern + ")$"
	matched, err := regexp.MatchString(pattern, line)
	return err == nil && matched
}

// globToRegexp translates a glob pattern into the corresponding
// regular expression. We cannnot simply use filepath.Match since we
// want "*" to match a sequence of any character instead of stopping
// at "/" (or "\\" on Windows). Also, filepath.Match has a quirk where
// there is no escaping on Windows.
func globToRegexp(pattern string) string {
	regexpMeta := `\.+*?()|[]{}^$`
	buf := make([]byte, 2*len(pattern))
	j := 0
Loop:
	for i := 0; i < len(pattern); i++ {
		switch pattern[i] {
		case '?':
			buf[j] = '.'
		case '*':
			buf[j], buf[j+1] = '.', '*'
			j++
		case '\\':
			// Skip over the backslash
			i++
			if i == len(pattern) {
				break Loop
			}
			// If we didn't break, add the next character to buf as a
			// literal.
			fallthrough
		default:
			// Escape character is necessary
			if strings.IndexByte(regexpMeta, pattern[i]) >= 0 {
				buf[j] = '\\'
				j++
			}
			buf[j] = pattern[i]
		}
		j++
	}
	return string(buf[:j])
}

// failed indicates if the actual exit code or output differed from
// what was expected.
func (cmd *ExecutedCommand) failed() bool {
	if cmd.ActualExitCode != cmd.ExpectedExitCode {
		return true
	}
	if len(cmd.ActualOutput) != len(cmd.ExpectedOutput) {
		return true
	}
	for i, actual := range cmd.ActualOutput {
		expected := cmd.ExpectedOutput[i]
		// Always accept an exact match, even if the line might end
		// with (re). This means that such lines need no escaping in
		// the test file and are quick to match.
		if actual == expected {
			continue
		}

		// The following tests ignore EOLs.
		actual = DropEol(actual)
		expected = DropEol(expected)

		switch {
		case strings.HasSuffix(expected, reSuffix):
			pattern := expected[:len(expected)-len(reSuffix)]
			if !matchEntireLine(pattern, actual) {
				return true
			}
		case strings.HasSuffix(expected, globSuffix):
			pattern := expected[:len(expected)-len(globSuffix)]
			if !matchEntireLine(globToRegexp(pattern), actual) {
				return true
			}
		default:
			// No special suffix, not equal by the check above => we
			// found a change in the output.
			return true
		}
	}
	return false
}

// DropEol removes a final end-of-line from s. It removes both Unix ("\n")
// and DOS ("\r\n") end-of-line characters.
func DropEol(s string) string {
	l := len(s)
	if l == 0 || s[l-1] != '\n' {
		return s
	}
	drop := 1
	if l > 1 && s[l-2] == '\r' {
		drop = 2
	}
	return s[:l-drop]
}

// updateExitCode looks at the last line of output and updates exit
// code if it is of the form [n]. The exit code line is only required
// for non-zero exit codes.
func updateExitCode(cmd *Command) {
	lines := len(cmd.ExpectedOutput)
	if lines == 0 {
		return
	}
	line := DropEol(cmd.ExpectedOutput[lines-1])

	l := len(line)
	if l == 0 || line[0] != '[' || line[l-1] != ']' {
		return
	}

	exitCode, err := strconv.Atoi(line[1 : l-1])
	if err != nil {
		// Not an exit code, just normal output.
		return
	}
	cmd.ExpectedOutput = cmd.ExpectedOutput[:lines-1]
	cmd.ExpectedExitCode = exitCode
}

// Parse splits an input test file into Commands.
func ParseTest(r io.Reader, path string) (test Test, err error) {
	const (
		inCommentary = iota
		inCommand
		inOutput
	)

	test.Path = path
	reader := bufio.NewReader(r)
	state := inCommentary
	lineno := 0
	line := ""
	for err == nil {
		line, err = reader.ReadString('\n')
		switch {
		case strings.HasPrefix(line, commandPrefix):
			if state == inOutput {
				updateExitCode(&test.Cmds[len(test.Cmds)-1])
			}
			line = line[len(commandPrefix):]
			cmd := Command{
				CmdLine: line,
				Lineno:  lineno + 1,
			}
			test.Cmds = append(test.Cmds, cmd)
			state = inCommand
		case strings.HasPrefix(line, continuationPrefix):
			if state != inCommand {
				err = &InvalidTestError{path, lineno,
					fmt.Sprintf("Continuation line %q has no command", line)}
				return
			}
			line = line[len(continuationPrefix):]
			cmd := &test.Cmds[len(test.Cmds)-1]
			cmd.CmdLine = cmd.CmdLine + line
			cmd.Lineno++
		case strings.HasPrefix(line, outputPrefix):
			if state == inCommentary {
				err = &InvalidTestError{path, lineno,
					fmt.Sprintf("Output line %q has no command", line)}
				return
			}
			line = line[len(outputPrefix):]
			cmd := &test.Cmds[len(test.Cmds)-1]
			cmd.ExpectedOutput = append(cmd.ExpectedOutput, line)
			state = inOutput
		default:
			if state == inOutput {
				updateExitCode(&test.Cmds[len(test.Cmds)-1])
			}
			state = inCommentary
		}
		lineno++
	}
	if state == inOutput {
		updateExitCode(&test.Cmds[len(test.Cmds)-1])
	}
	if err == io.EOF {
		err = nil
	}
	return
}

// MakeBanner turns a UUID into a nice banner we can recognize later
// in the output.
func MakeBanner(u uuid.UUID) string {
	return "--- CRAM " + u.String() + " ---"
}

// MakeScript produces a script ready to be sent to a shell. The
// banner should be a random string. It will be inserted in the output
// together with the exit status of each command.
func MakeScript(cmds []Command, banner string) (lines []string) {
	echo := fmt.Sprintf("echo \"%s $?\"\n", banner)
	for _, cmd := range cmds {
		lines = append(lines, cmd.CmdLine, echo)
	}
	return
}

// ParseOutput finds the actual output and exit codes for a slice of
// commands. The result is a slice of executed commands.
func ParseOutput(cmds []Command, output []byte, banner string) (
	executed []ExecutedCommand, err error) {
	r := bytes.NewReader(output)
	reader := bufio.NewReader(r)

	i := 0
	actualOutput := []string{}
	line := ""
	for err == nil {
		line, err = reader.ReadString('\n')
		if strings.HasPrefix(line, banner) {
			number := DropEol(line[len(banner)+1:])
			exitCode, e := strconv.Atoi(number)
			if e != nil {
				err = e
				return
			}
			executed = append(executed, ExecutedCommand{
				Command:        &cmds[i],
				ActualExitCode: exitCode,
				ActualOutput:   actualOutput,
			})
			actualOutput = nil
			i++
		} else {
			actualOutput = append(actualOutput, line)
		}
	}
	if err == io.EOF {
		err = nil
	}
	return
}

// Execute a script in the specified working directory.
func ExecuteScript(workdir string, lines []string) ([]byte, error) {
	script := strings.Join(lines, "")
	cmd := exec.Command("/bin/sh", "-")
	cmd.Dir = workdir
	cmd.Stdin = strings.NewReader(script)
	return cmd.CombinedOutput()
}

func filterFailures(executed []ExecutedCommand) (failures []ExecutedCommand) {
	for _, cmd := range executed {
		if cmd.failed() {
			failures = append(failures, cmd)
		}
	}
	return
}

// Patch takes an ExecutedTest, a slice ExecutedCommands and returns
// the patched output where ActualOutput from each ExecutedCommand
// replaces the ExpectedOutput.
func Patch(r io.Reader, w io.Writer, cmds []ExecutedCommand) (err error) {
	reader := bufio.NewReader(r)
	writer := bufio.NewWriter(w)

	lines := []string{}
	line := ""
	for err == nil {
		line, err = reader.ReadString('\n')
		lines = append(lines, line)
	}
	if err == io.EOF {
		err = nil
	}

	output := []string{}
	lastLineno := 0

	for _, cmd := range cmds {
		pre := lines[lastLineno:cmd.Lineno]
		output = append(output, pre...)
		for _, outputLine := range cmd.ActualOutput {
			output = append(output, "  "+outputLine)
		}
		lastLineno = cmd.Lineno + len(cmd.ExpectedOutput)

		if cmd.ActualExitCode != cmd.ExpectedExitCode {
			// Add a line with the actual exit code, but only if it is
			// non-zero since [0] is implied.
			if cmd.ActualExitCode != 0 {
				line := fmt.Sprintf("  [%d]\n", cmd.ActualExitCode)
				output = append(output, line)
			}

			// Figure out if we should skip a line in the input file.
			// We should only skip a line if we are overwriting the
			// expected exit code -- we should not skip a line if we
			// merely added the actual exit code.
			if lastLineno < len(lines) {
				lastLine := lines[lastLineno]
				line := fmt.Sprintf("  [%d]", cmd.ExpectedExitCode)
				// Extra whitespace on the exit code line is okay.
				if strings.HasPrefix(lastLine, line) {
					lastLineno++
				}
			}
		}
	}
	post := lines[lastLineno:]
	output = append(output, post...)

	for _, line := range output {
		_, err = writer.WriteString(line)
	}
	err = writer.Flush()
	return
}

// Process parses a .t file, executes the test commands and compares
// the actual output to the expected output. The idx passed is used to
// make the working directory unique inside tempdir and must be
// different for each test file.
func Process(tempdir, path string, idx int) (result ExecutedTest, err error) {
	fp, err := os.Open(path)
	if err != nil {
		return
	}
	defer fp.Close()
	test, err := ParseTest(fp, path)
	if err != nil {
		return
	}

	// Create unique base inside the tempdir
	base := fmt.Sprintf("%03d-%s", idx, filepath.Base(path))
	// Remove file extension (often, but not necessarily ".t")
	base = base[:len(base)-len(filepath.Ext(base))]
	workdir := filepath.Join(tempdir, base)
	err = os.Mkdir(workdir, 0700)
	if err != nil {
		return
	}

	u := uuid.NewV4()
	banner := MakeBanner(u)
	lines := MakeScript(test.Cmds, banner)

	output, err := ExecuteScript(workdir, lines)
	if err != nil {
		return
	}

	executed, err := ParseOutput(test.Cmds, output, banner)
	if err != nil {
		return
	}

	failures := filterFailures(executed)
	result = ExecutedTest{test, executed, strings.Join(lines, ""),
		failures}
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package v1beta1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/pkg/api/v1"
)

// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.
type ServerAddressByClientCIDR struct {
	// The CIDR with which clients can match their IP to figure out the server address that they should use.
	ClientCIDR string `json:"clientCIDR" protobuf:"bytes,1,opt,name=clientCIDR"`
	// Address of this server, suitable for a client that matches the above CIDR.
	// This can be a hostname, hostname:port, IP or IP:port.
	ServerAddress string `json:"serverAddress" protobuf:"bytes,2,opt,name=serverAddress"`
}

// ClusterSpec describes the attributes of a kubernetes cluster.
type ClusterSpec struct {
	// A map of client CIDR to server address.
	// This is to help clients reach servers in the most network-efficient way possible.
	// Clients can use the appropriate server address as per the CIDR that they match.
	// In case of multiple matches, clients should use the longest matching CIDR.
	ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" patchStrategy:"merge" patchMergeKey:"clientCIDR" protobuf:"bytes,1,rep,name=serverAddressByClientCIDRs"`
	// Name of the secret containing kubeconfig to access this cluster.
	// The secret is read from the kubernetes cluster that is hosting federation control plane.
	// Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key "kubeconfig".
	// This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets.
	// This can be left empty if the cluster allows insecure access.
	// +optional
	SecretRef *v1.LocalObjectReference `json:"secretRef,omitempty" protobuf:"bytes,2,opt,name=secretRef"`
}

type ClusterConditionType string

// These are valid conditions of a cluster.
const (
	// ClusterReady means the cluster is ready to accept workloads.
	ClusterReady ClusterConditionType = "Ready"
	// ClusterOffline means the cluster is temporarily down or not reachable
	ClusterOffline ClusterConditionType = "Offline"
)

// ClusterCondition describes current state of a cluster.
type ClusterCondition struct {
	// Type of cluster condition, Complete or Failed.
	Type ClusterConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=ClusterConditionType"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/kubernetes/pkg/api/v1.ConditionStatus"`
	// Last time the condition was checked.
	// +optional
	LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"`
	// Last time the condition transit from one status to another.
	// +optional
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"`
	// (brief) reason for the condition's last transition.
	// +optional
	Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`
	// Human readable message indicating details about last transition.
	// +optional
	Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}

// ClusterStatus is information about the current status of a cluster updated by cluster controller periodically.
type ClusterStatus struct {
	// Conditions is an array of current cluster conditions.
	// +optional
	Conditions []ClusterCondition `json:"conditions,omitempty" protobuf:"bytes,1,rep,name=conditions"`
	// Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'.
	// These will always be in the same region.
	// +optional
	Zones []string `json:"zones,omitempty" protobuf:"bytes,5,rep,name=zones"`
	// Region is the name of the region in which all of the nodes in the cluster exist.  e.g. 'us-east1'.
	// +optional
	Region string `json:"region,omitempty" protobuf:"bytes,6,opt,name=region"`
}

// +genclient=true
// +nonNamespaced=true

// Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation.
type Cluster struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Spec defines the behavior of the Cluster.
	// +optional
	Spec ClusterSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
	// Status describes the current status of a Cluster
	// +optional
	Status ClusterStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

// A list of all the kubernetes clusters registered to the federation
type ClusterList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// List of Cluster objects.
	Items []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"`
}

const (
	// FederationNamespaceSystem is the system namespace where we place federation control plane components.
	FederationNamespaceSystem string = "federation-system"
)
<commit_msg>add patch comment tags in open api<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package v1beta1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/pkg/api/v1"
)

// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.
type ServerAddressByClientCIDR struct {
	// The CIDR with which clients can match their IP to figure out the server address that they should use.
	ClientCIDR string `json:"clientCIDR" protobuf:"bytes,1,opt,name=clientCIDR"`
	// Address of this server, suitable for a client that matches the above CIDR.
	// This can be a hostname, hostname:port, IP or IP:port.
	ServerAddress string `json:"serverAddress" protobuf:"bytes,2,opt,name=serverAddress"`
}

// ClusterSpec describes the attributes of a kubernetes cluster.
type ClusterSpec struct {
	// A map of client CIDR to server address.
	// This is to help clients reach servers in the most network-efficient way possible.
	// Clients can use the appropriate server address as per the CIDR that they match.
	// In case of multiple matches, clients should use the longest matching CIDR.
	// +patchMergeKey=clientCIDR
	// +patchStrategy=merge
	ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" patchStrategy:"merge" patchMergeKey:"clientCIDR" protobuf:"bytes,1,rep,name=serverAddressByClientCIDRs"`
	// Name of the secret containing kubeconfig to access this cluster.
	// The secret is read from the kubernetes cluster that is hosting federation control plane.
	// Admin needs to ensure that the required secret exists. Secret should be in the same namespace where federation control plane is hosted and it should have kubeconfig in its data with key "kubeconfig".
	// This will later be changed to a reference to secret in federation control plane when the federation control plane supports secrets.
	// This can be left empty if the cluster allows insecure access.
	// +optional
	SecretRef *v1.LocalObjectReference `json:"secretRef,omitempty" protobuf:"bytes,2,opt,name=secretRef"`
}

type ClusterConditionType string

// These are valid conditions of a cluster.
const (
	// ClusterReady means the cluster is ready to accept workloads.
	ClusterReady ClusterConditionType = "Ready"
	// ClusterOffline means the cluster is temporarily down or not reachable
	ClusterOffline ClusterConditionType = "Offline"
)

// ClusterCondition describes current state of a cluster.
type ClusterCondition struct {
	// Type of cluster condition, Complete or Failed.
	Type ClusterConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=ClusterConditionType"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/kubernetes/pkg/api/v1.ConditionStatus"`
	// Last time the condition was checked.
	// +optional
	LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"`
	// Last time the condition transit from one status to another.
	// +optional
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"`
	// (brief) reason for the condition's last transition.
	// +optional
	Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`
	// Human readable message indicating details about last transition.
	// +optional
	Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}

// ClusterStatus is information about the current status of a cluster updated by cluster controller periodically.
type ClusterStatus struct {
	// Conditions is an array of current cluster conditions.
	// +optional
	Conditions []ClusterCondition `json:"conditions,omitempty" protobuf:"bytes,1,rep,name=conditions"`
	// Zones is the list of availability zones in which the nodes of the cluster exist, e.g. 'us-east1-a'.
	// These will always be in the same region.
	// +optional
	Zones []string `json:"zones,omitempty" protobuf:"bytes,5,rep,name=zones"`
	// Region is the name of the region in which all of the nodes in the cluster exist.  e.g. 'us-east1'.
	// +optional
	Region string `json:"region,omitempty" protobuf:"bytes,6,opt,name=region"`
}

// +genclient=true
// +nonNamespaced=true

// Information about a registered cluster in a federated kubernetes setup. Clusters are not namespaced and have unique names in the federation.
type Cluster struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Spec defines the behavior of the Cluster.
	// +optional
	Spec ClusterSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
	// Status describes the current status of a Cluster
	// +optional
	Status ClusterStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

// A list of all the kubernetes clusters registered to the federation
type ClusterList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
	// +optional
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// List of Cluster objects.
	Items []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"`
}

const (
	// FederationNamespaceSystem is the system namespace where we place federation control plane components.
	FederationNamespaceSystem string = "federation-system"
)
<|endoftext|>