<commit_before>package redis_backend

import (
	"github.com/go-errors/errors"
	"github.com/leanovate/microzon-auth-go/common"
	"github.com/leanovate/microzon-auth-go/logging"
	"gopkg.in/redis.v3"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const keyRevocationsPublish = "revocations"
const keyRevocationVersionCounter = "revocations:version"

type redisRevocationsListener struct {
	currentVersion uint64
	connector      redisConnector
	listener       common.RevocationsListener
	logger         logging.Logger
}

func newRedisRevocationsListener(connector redisConnector, listener common.RevocationsListener, logger logging.Logger) (*redisRevocationsListener, error) {
	redisListener := &redisRevocationsListener{
		currentVersion: 0,
		connector:      connector,
		listener:       listener,
		logger:         logger,
	}

	if err := redisListener.updateCurrentVersion(); err != nil {
		return nil, err
	}

	go redisListener.startListenRevocationUpdates()

	if err := redisListener.scanRevocations(); err != nil {
		return nil, err
	}

	return redisListener, nil
}

func (r *redisRevocationsListener) updateCurrentVersion() error {
	client, err := r.connector.getClient("")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	value, err := client.Get(keyRevocationVersionCounter).Result()
	if err == redis.Nil {
		return nil
	} else if err != nil {
		return errors.Wrap(err, 0)
	}
	version, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return errors.Wrap(err, 0)
	}
	r.currentVersion = version
	return nil
}

func (r *redisRevocationsListener) scanRevocations() error {
	client, err := r.connector.getClient("")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	var cursor int64 = 0
	first := true
	for first || cursor != 0 {
		first = false
		nextCursor, keys, err := client.Scan(cursor, revocationKey("*"), 0).Result()
		if err != nil {
			return errors.Wrap(err, 0)
		}
		cursor = nextCursor
		if len(keys) > 0 {
			encodedRevocations, err := client.MGet(keys...).Result()
			if err != nil {
				return errors.Wrap(err, 0)
			}
			for _, encodedRevocation := range encodedRevocations {
				if err := r.decodeAndAddRevocation(encodedRevocation.(string)); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

func (r *redisRevocationsListener) fetchUpdates(newVersion uint64) error {
	client, err := r.connector.getClient(keyRevocationVersionCounter)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	r.logger.Debugf("Fetch updates from %d to %d", r.currentVersion, newVersion)

	for version := atomic.LoadUint64(&r.currentVersion) + 1; version <= newVersion; version++ {
		encoded, err := client.Get(revocationKey(strconv.FormatUint(version, 10))).Result()
		if err == redis.Nil {
			r.logger.Debugf("Version %d does not exists in redis", version)
		} else if err != nil {
			r.logger.ErrorErr(err)
		} else {
			if err := r.decodeAndAddRevocation(encoded); err != nil {
				r.logger.ErrorErr(err)
			}
		}
	}
	return nil
}

func (r *redisRevocationsListener) decodeAndAddRevocation(encoded string) error {
	parts := strings.Split(encoded, ";")
	if len(parts) != 3 {
		return errors.Errorf("Invalid entry: %s", encoded)
	}
	sha256, err := common.RawSha256FromBase64(parts[0])
	if err != nil {
		return err
	}
	expiresAt, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return err
	}
	version, err := strconv.ParseUint(parts[2], 10, 64)
	if err != nil {
		return err
	}

	currentVersion := atomic.LoadUint64(&r.currentVersion)
	if version > currentVersion {
		atomic.CompareAndSwapUint64(&r.currentVersion, currentVersion, version)
	}
	r.listener(version, sha256, time.Unix(expiresAt, 0))
	return nil
}

func (r *redisRevocationsListener) listenRevocationUpdates() error {
	client, err := r.connector.getClient(keyRevocationVersionCounter)
	if err != nil {
		return err
	}
	subscription, err := client.Subscribe(keyRevocationsPublish)
	if err != nil {
		return err
	}

	for {
		if message, err := subscription.ReceiveMessage(); err == nil {
			r.logger.Debugf("Received revocation update: %s", message.Payload)
			if version, err := strconv.ParseUint(message.Payload, 10, 64); err == nil {
				if err := r.fetchUpdates(version); err != nil {
					r.logger.ErrorErr(err)
				}
			} else {
				r.logger.ErrorErr(err)
			}
		} else {
			return err
		}
	}
}

func (r *redisRevocationsListener) startListenRevocationUpdates() {
	for {
		r.logger.Info("Connect to revocation subscription")

		if err := r.listenRevocationUpdates(); err != nil {
			r.logger.ErrorErr(err)
		}
		r.logger.Info("Wait 1 second before reconnecting")
		time.Sleep(1 * time.Second)
	}
}
<commit_msg>Fix scan<commit_after>package redis_backend

import (
	"github.com/go-errors/errors"
	"github.com/leanovate/microzon-auth-go/common"
	"github.com/leanovate/microzon-auth-go/logging"
	"gopkg.in/redis.v3"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const keyRevocationsPublish = "revocations"
const keyRevocationVersionCounter = "revocations:version"

type redisRevocationsListener struct {
	currentVersion uint64
	connector      redisConnector
	listener       common.RevocationsListener
	logger         logging.Logger
}

func newRedisRevocationsListener(connector redisConnector, listener common.RevocationsListener, logger logging.Logger) (*redisRevocationsListener, error) {
	redisListener := &redisRevocationsListener{
		currentVersion: 0,
		connector:      connector,
		listener:       listener,
		logger:         logger,
	}

	if err := redisListener.updateCurrentVersion(); err != nil {
		return nil, err
	}

	go redisListener.startListenRevocationUpdates()

	if err := redisListener.scanRevocations(); err != nil {
		return nil, err
	}

	return redisListener, nil
}

func (r *redisRevocationsListener) updateCurrentVersion() error {
	client, err := r.connector.getClient("")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	value, err := client.Get(keyRevocationVersionCounter).Result()
	if err == redis.Nil {
		return nil
	} else if err != nil {
		return errors.Wrap(err, 0)
	}
	version, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return errors.Wrap(err, 0)
	}
	r.currentVersion = version
	return nil
}

func (r *redisRevocationsListener) scanRevocations() error {
	client, err := r.connector.getClient("")
	if err != nil {
		return errors.Wrap(err, 0)
	}
	var cursor int64 = 0
	first := true
	for first || cursor != 0 {
		first = false
		nextCursor, keys, err := client.Scan(cursor, revocationKey("*"), 0).Result()
		if err != nil {
			return errors.Wrap(err, 0)
		}
		cursor = nextCursor
		if len(keys) > 0 {
			values, err := client.MGet(keys...).Result()
			if err != nil {
				return errors.Wrap(err, 0)
			}
			for _, value := range values {
				if encodedRevocation, ok := value.(string); ok {
					if err := r.decodeAndAddRevocation(encodedRevocation); err != nil {
						return err
					}
				}
			}
		}
	}
	return nil
}

func (r *redisRevocationsListener) fetchUpdates(newVersion uint64) error {
	client, err := r.connector.getClient(keyRevocationVersionCounter)
	if err != nil {
		return errors.Wrap(err, 0)
	}

	r.logger.Debugf("Fetch updates from %d to %d", r.currentVersion, newVersion)

	for version := atomic.LoadUint64(&r.currentVersion) + 1; version <= newVersion; version++ {
		encoded, err := client.Get(revocationKey(strconv.FormatUint(version, 10))).Result()
		if err == redis.Nil {
			r.logger.Debugf("Version %d does not exists in redis", version)
		} else if err != nil {
			r.logger.ErrorErr(err)
		} else {
			if err := r.decodeAndAddRevocation(encoded); err != nil {
				r.logger.ErrorErr(err)
			}
		}
	}
	return nil
}

func (r *redisRevocationsListener) decodeAndAddRevocation(encoded string) error {
	parts := strings.Split(encoded, ";")
	if len(parts) != 3 {
		return errors.Errorf("Invalid entry: %s", encoded)
	}
	sha256, err := common.RawSha256FromBase64(parts[0])
	if err != nil {
		return err
	}
	expiresAt, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return err
	}
	version, err := strconv.ParseUint(parts[2], 10, 64)
	if err != nil {
		return err
	}

	currentVersion := atomic.LoadUint64(&r.currentVersion)
	if version > currentVersion {
		atomic.CompareAndSwapUint64(&r.currentVersion, currentVersion, version)
	}
	r.listener(version, sha256, time.Unix(expiresAt, 0))
	return nil
}

func (r *redisRevocationsListener) listenRevocationUpdates() error {
	client, err := r.connector.getClient(keyRevocationVersionCounter)
	if err != nil {
		return err
	}
	subscription, err := client.Subscribe(keyRevocationsPublish)
	if err != nil {
		return err
	}

	for {
		if message, err := subscription.ReceiveMessage(); err == nil {
			r.logger.Debugf("Received revocation update: %s", message.Payload)
			if version, err := strconv.ParseUint(message.Payload, 10, 64); err == nil {
				if err := r.fetchUpdates(version); err != nil {
					r.logger.ErrorErr(err)
				}
			} else {
				r.logger.ErrorErr(err)
			}
		} else {
			return err
		}
	}
}

func (r *redisRevocationsListener) startListenRevocationUpdates() {
	for {
		r.logger.Info("Connect to revocation subscription")

		if err := r.listenRevocationUpdates(); err != nil {
			r.logger.ErrorErr(err)
		}
		r.logger.Info("Wait 1 second before reconnecting")
		time.Sleep(1 * time.Second)
	}
}
<|endoftext|>


<commit_before>package data

type LedgerEntrySlice []LedgerEntry

type leBase struct {
	LedgerEntryType   LedgerEntryType
	LedgerIndex       *Hash256 `json:"index,omitempty"`
	PreviousTxnID     *Hash256 `json:",omitempty"`
	PreviousTxnLgrSeq *uint32  `json:",omitempty"`
	Hash              Hash256  `json:"-"`
	Id                Hash256  `json:"-"`
}

type AccountRoot struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	Account        *Account         `json:",omitempty"`
	Sequence       *uint32          `json:",omitempty"`
	Balance        *Value           `json:",omitempty"`
	OwnerCount     *uint32          `json:",omitempty"`
	AccountTxnID   *Hash256         `json:",omitempty"`
	RegularKey     *RegularKey      `json:",omitempty"`
	EmailHash      *Hash128         `json:",omitempty"`
	WalletLocator  *Hash256         `json:",omitempty"`
	WalletSize     *uint32          `json:",omitempty"`
	MessageKey     *VariableLength  `json:",omitempty"`
	TransferRate   *uint32          `json:",omitempty"`
	Domain         *VariableLength  `json:",omitempty"`
	TickSize       *uint8           `json:",omitempty"`
	TicketCount    *uint32          `json:",omitempty"`
	NFTokenMinter  *Account         `json:",omitempty"`
	MintedNFTokens *uint32          `json:",omitempty"`
	BurnedNFTokens *uint32          `json:",omitempty"`
}

type RippleState struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	LowLimit       *Amount          `json:",omitempty"`
	HighLimit      *Amount          `json:",omitempty"`
	Balance        *Amount          `json:",omitempty"`
	LowNode        *NodeIndex       `json:",omitempty"`
	HighNode       *NodeIndex       `json:",omitempty"`
	LowQualityIn   *uint32          `json:",omitempty"`
	LowQualityOut  *uint32          `json:",omitempty"`
	HighQualityIn  *uint32          `json:",omitempty"`
	HighQualityOut *uint32          `json:",omitempty"`
}

type Offer struct {
	leBase
	Flags         *LedgerEntryFlag `json:",omitempty"`
	Account       *Account         `json:",omitempty"`
	Sequence      *uint32          `json:",omitempty"`
	TakerPays     *Amount          `json:",omitempty"`
	TakerGets     *Amount          `json:",omitempty"`
	BookDirectory *Hash256         `json:",omitempty"`
	BookNode      *NodeIndex       `json:",omitempty"`
	OwnerNode     *NodeIndex       `json:",omitempty"`
	Expiration    *uint32          `json:",omitempty"`
}

type Directory struct {
	leBase
	Flags             *LedgerEntryFlag `json:",omitempty"`
	RootIndex         *Hash256         `json:",omitempty"`
	Indexes           *Vector256       `json:",omitempty"`
	Owner             *Account         `json:",omitempty"`
	TakerPaysCurrency *Hash160         `json:",omitempty"`
	TakerPaysIssuer   *Hash160         `json:",omitempty"`
	TakerGetsCurrency *Hash160         `json:",omitempty"`
	TakerGetsIssuer   *Hash160         `json:",omitempty"`
	ExchangeRate      *ExchangeRate    `json:",omitempty"`
	IndexNext         *NodeIndex       `json:",omitempty"`
	IndexPrevious     *NodeIndex       `json:",omitempty"`
	NFTokenID         *Hash256         `json:",omitempty"`
}

type LedgerHashes struct {
	leBase
	Flags               *LedgerEntryFlag `json:",omitempty"`
	FirstLedgerSequence *uint32          `json:",omitempty"`
	LastLedgerSequence  *uint32          `json:",omitempty"`
	Hashes              *Vector256       `json:",omitempty"`
}

type Majority struct {
	Amendment *Hash256 `json:",omitempty"`
	CloseTime *uint32  `json:",omitempty"`
}

type Amendments struct {
	leBase
	Flags      *LedgerEntryFlag `json:",omitempty"`
	Amendments *Vector256       `json:",omitempty"`
	Majorities []Majority       `json:",omitempty"`
}

type FeeSettings struct {
	leBase
	Flags             *LedgerEntryFlag `json:",omitempty"`
	BaseFee           *Uint64Hex       `json:",omitempty"`
	ReferenceFeeUnits *uint32          `json:",omitempty"`
	ReserveBase       *uint32          `json:",omitempty"`
	ReserveIncrement  *uint32          `json:",omitempty"`
}

type DisabledValidator struct {
	FirstLedgerSequence *uint32    `json:",omitempty"`
	PublicKey           *PublicKey `json:",omitempty"`
}

type NegativeUNL struct {
	leBase
	Flags               *LedgerEntryFlag    `json:",omitempty"`
	DisabledValidators  []DisabledValidator `json:",omitempty"`
	ValidatorToDisable  *VariableLength     `json:",omitempty"`
	ValidatorToReEnable *VariableLength     `json:",omitempty"`
}

type Escrow struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	Account         Account          `json:",omitempty"`
	Destination     Account          `json:",omitempty"`
	Amount          Amount           `json:",omitempty"`
	Condition       *VariableLength  `json:",omitempty"`
	CancelAfter     *uint32          `json:",omitempty"`
	FinishAfter     *uint32          `json:",omitempty"`
	SourceTag       *uint32          `json:",omitempty"`
	DestinationTag  *uint32          `json:",omitempty"`
	OwnerNode       *NodeIndex       `json:",omitempty"`
	DestinationNode *NodeIndex       `json:",omitempty"`
}

type SignerEntry struct {
	Account      *Account `json:",omitempty"`
	SignerWeight *uint16  `json:",omitempty"`
}

type SignerList struct {
	leBase
	Flags         *LedgerEntryFlag `json:",omitempty"`
	OwnerNode     *NodeIndex       `json:",omitempty"`
	SignerQuorum  *uint32          `json:",omitempty"`
	SignerEntries []SignerEntry    `json:",omitempty"`
	SignerListID  *uint32          `json:",omitempty"`
}

type Ticket struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	Account        *Account         `json:",omitempty"`
	TicketSequence *uint32          `json:",omitempty"`
	OwnerNode      *NodeIndex       `json:",omitempty"`
}

type PayChannel struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	Account         *Account         `json:",omitempty"`
	Destination     *Account         `json:",omitempty"`
	Amount          *Amount          `json:",omitempty"`
	Balance         *Amount          `json:",omitempty"`
	PublicKey       *PublicKey       `json:",omitempty"`
	SettleDelay     *uint32          `json:",omitempty"`
	OwnerNode       *NodeIndex       `json:",omitempty"`
	DestinationNode *NodeIndex       `json:",omitempty"`
	Expiration      *uint32          `json:",omitempty"`
	CancelAfter     *uint32          `json:",omitempty"`
	DestinationTag  *uint32          `json:",omitempty"`
	SourceTag       *uint32          `json:",omitempty"`
}

type Check struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	Account         *Account         `json:",omitempty"`
	Destination     *Account         `json:",omitempty"`
	OwnerNode       *NodeIndex       `json:",omitempty"`
	SendMax         *Amount          `json:",omitempty"`
	Sequence        *uint32          `json:",omitempty"`
	DestinationNode *NodeIndex       `json:",omitempty"`
	DestinationTag  *uint32          `json:",omitempty"`
	Expiration      *uint32          `json:",omitempty"`
	SourceTag       *uint32          `json:",omitempty"`
	InvoiceID       *Hash256         `json:",omitempty"`
}

type DepositPreAuth struct {
	leBase
	Flags     *LedgerEntryFlag `json:",omitempty"`
	Account   *Account         `json:",omitempty"`
	Authorize *Account         `json:",omitempty"`
	Flags     *LedgerEntryFlag `json:",omitempty"`
	OwnerNode *NodeIndex       `json:",omitempty"`
}

type NFTokenPage struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	PreviousPageMin *Hash256         `json:",omitempty"`
	NextPageMin     *Hash256         `json:",omitempty"`
	NFTokens        []NFToken        `json:",omitempty"`
}

type NFTokenOffer struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	Owner          *Account         `json:",omitempty"`
	NFTokenID      *Hash256         `json:",omitempty"`
	Amount         *Amount          `json:",omitempty"`
	OwnerNode      *NodeIndex       `json:",omitempty"`
	TokenOfferNode *NodeIndex       `json:",omitempty"`
	Destination    *Account         `json:",omitempty"`
	Expiration     *uint32          `json:",omitempty"`
}

func (a *AccountRoot) Affects(account Account) bool {
	return a.Account != nil && a.Account.Equals(account)
}
func (r *RippleState) Affects(account Account) bool {
	return r.LowLimit.Issuer.Equals(account) || r.HighLimit.Issuer.Equals(account)
}
func (o *Offer) Affects(account Account) bool        { return o.Account.Equals(account) }
func (d *Directory) Affects(account Account) bool    { return false }
func (l *LedgerHashes) Affects(account Account) bool { return false }
func (a *Amendments) Affects(account Account) bool   { return false }
func (f *FeeSettings) Affects(account Account) bool  { return false }
func (n *NegativeUNL) Affects(account Account) bool  { return false }
func (s *Escrow) Affects(account Account) bool {
	return s.Account.Equals(account) || s.Destination.Equals(account)
}
func (s *SignerList) Affects(account Account) bool {
	for _, entry := range s.SignerEntries {
		if entry.Account != nil && entry.Account.Equals(account) {
			return true
		}
	}
	return false
}
func (t *Ticket) Affects(account Account) bool { return t.Account != nil && t.Account.Equals(account) }
func (p *PayChannel) Affects(account Account) bool {
	return (p.Account != nil && p.Account.Equals(account)) || (p.Destination != nil && p.Destination.Equals(account))
}
func (p *Check) Affects(account Account) bool {
	return (p.Account != nil && p.Account.Equals(account)) || (p.Destination != nil && p.Destination.Equals(account))
}

func (d *DepositPreAuth) Affects(account Account) bool {
	return (d.Account != nil && d.Account.Equals(account)) || (d.Authorize != nil && d.Authorize.Equals(account))
}

func (tp *NFTokenPage) Affects(account Account) bool {
	return false
}

func (to *NFTokenOffer) Affects(account Account) bool {
	return (to.Owner != nil && to.Owner.Equals(account)) || (to.Destination != nil && to.Destination.Equals(account))
}

func (le *leBase) GetType() string                     { return ledgerEntryNames[le.LedgerEntryType] }
func (le *leBase) GetLedgerEntryType() LedgerEntryType { return le.LedgerEntryType }
func (le *leBase) Prefix() HashPrefix                  { return HP_LEAF_NODE }
func (le *leBase) NodeType() NodeType                  { return NT_ACCOUNT_NODE }
func (le *leBase) Ledger() uint32                      { return 0 }
func (le *leBase) GetHash() *Hash256                   { return &le.Hash }
func (le *leBase) NodeId() *Hash256                    { return &le.Id }
func (le *leBase) GetLedgerIndex() *Hash256            { return le.LedgerIndex }
func (le *leBase) GetPreviousTxnId() *Hash256          { return le.PreviousTxnID }

func (o *Offer) Ratio() *Value {
	return o.TakerPays.Ratio(*o.TakerGets)
}
<commit_msg>Update ledgerentry.go<commit_after>package data

type LedgerEntrySlice []LedgerEntry

type leBase struct {
	LedgerEntryType   LedgerEntryType
	LedgerIndex       *Hash256 `json:"index,omitempty"`
	PreviousTxnID     *Hash256 `json:",omitempty"`
	PreviousTxnLgrSeq *uint32  `json:",omitempty"`
	Hash              Hash256  `json:"-"`
	Id                Hash256  `json:"-"`
}

type AccountRoot struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	Account        *Account         `json:",omitempty"`
	Sequence       *uint32          `json:",omitempty"`
	Balance        *Value           `json:",omitempty"`
	OwnerCount     *uint32          `json:",omitempty"`
	AccountTxnID   *Hash256         `json:",omitempty"`
	RegularKey     *RegularKey      `json:",omitempty"`
	EmailHash      *Hash128         `json:",omitempty"`
	WalletLocator  *Hash256         `json:",omitempty"`
	WalletSize     *uint32          `json:",omitempty"`
	MessageKey     *VariableLength  `json:",omitempty"`
	TransferRate   *uint32          `json:",omitempty"`
	Domain         *VariableLength  `json:",omitempty"`
	TickSize       *uint8           `json:",omitempty"`
	TicketCount    *uint32          `json:",omitempty"`
	NFTokenMinter  *Account         `json:",omitempty"`
	MintedNFTokens *uint32          `json:",omitempty"`
	BurnedNFTokens *uint32          `json:",omitempty"`
}

type RippleState struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	LowLimit       *Amount          `json:",omitempty"`
	HighLimit      *Amount          `json:",omitempty"`
	Balance        *Amount          `json:",omitempty"`
	LowNode        *NodeIndex       `json:",omitempty"`
	HighNode       *NodeIndex       `json:",omitempty"`
	LowQualityIn   *uint32          `json:",omitempty"`
	LowQualityOut  *uint32          `json:",omitempty"`
	HighQualityIn  *uint32          `json:",omitempty"`
	HighQualityOut *uint32          `json:",omitempty"`
}

type Offer struct {
	leBase
	Flags         *LedgerEntryFlag `json:",omitempty"`
	Account       *Account         `json:",omitempty"`
	Sequence      *uint32          `json:",omitempty"`
	TakerPays     *Amount          `json:",omitempty"`
	TakerGets     *Amount          `json:",omitempty"`
	BookDirectory *Hash256         `json:",omitempty"`
	BookNode      *NodeIndex       `json:",omitempty"`
	OwnerNode     *NodeIndex       `json:",omitempty"`
	Expiration    *uint32          `json:",omitempty"`
}

type Directory struct {
	leBase
	Flags             *LedgerEntryFlag `json:",omitempty"`
	RootIndex         *Hash256         `json:",omitempty"`
	Indexes           *Vector256       `json:",omitempty"`
	Owner             *Account         `json:",omitempty"`
	TakerPaysCurrency *Hash160         `json:",omitempty"`
	TakerPaysIssuer   *Hash160         `json:",omitempty"`
	TakerGetsCurrency *Hash160         `json:",omitempty"`
	TakerGetsIssuer   *Hash160         `json:",omitempty"`
	ExchangeRate      *ExchangeRate    `json:",omitempty"`
	IndexNext         *NodeIndex       `json:",omitempty"`
	IndexPrevious     *NodeIndex       `json:",omitempty"`
	NFTokenID         *Hash256         `json:",omitempty"`
}

type LedgerHashes struct {
	leBase
	Flags               *LedgerEntryFlag `json:",omitempty"`
	FirstLedgerSequence *uint32          `json:",omitempty"`
	LastLedgerSequence  *uint32          `json:",omitempty"`
	Hashes              *Vector256       `json:",omitempty"`
}

type Majority struct {
	Amendment *Hash256 `json:",omitempty"`
	CloseTime *uint32  `json:",omitempty"`
}

type Amendments struct {
	leBase
	Flags      *LedgerEntryFlag `json:",omitempty"`
	Amendments *Vector256       `json:",omitempty"`
	Majorities []Majority       `json:",omitempty"`
}

type FeeSettings struct {
	leBase
	Flags             *LedgerEntryFlag `json:",omitempty"`
	BaseFee           *Uint64Hex       `json:",omitempty"`
	ReferenceFeeUnits *uint32          `json:",omitempty"`
	ReserveBase       *uint32          `json:",omitempty"`
	ReserveIncrement  *uint32          `json:",omitempty"`
}

type DisabledValidator struct {
	FirstLedgerSequence *uint32    `json:",omitempty"`
	PublicKey           *PublicKey `json:",omitempty"`
}

type NegativeUNL struct {
	leBase
	Flags               *LedgerEntryFlag    `json:",omitempty"`
	DisabledValidators  []DisabledValidator `json:",omitempty"`
	ValidatorToDisable  *VariableLength     `json:",omitempty"`
	ValidatorToReEnable *VariableLength     `json:",omitempty"`
}

type Escrow struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	Account         Account          `json:",omitempty"`
	Destination     Account          `json:",omitempty"`
	Amount          Amount           `json:",omitempty"`
	Condition       *VariableLength  `json:",omitempty"`
	CancelAfter     *uint32          `json:",omitempty"`
	FinishAfter     *uint32          `json:",omitempty"`
	SourceTag       *uint32          `json:",omitempty"`
	DestinationTag  *uint32          `json:",omitempty"`
	OwnerNode       *NodeIndex       `json:",omitempty"`
	DestinationNode *NodeIndex       `json:",omitempty"`
}

type SignerEntry struct {
	Account      *Account `json:",omitempty"`
	SignerWeight *uint16  `json:",omitempty"`
}

type SignerList struct {
	leBase
	Flags         *LedgerEntryFlag `json:",omitempty"`
	OwnerNode     *NodeIndex       `json:",omitempty"`
	SignerQuorum  *uint32          `json:",omitempty"`
	SignerEntries []SignerEntry    `json:",omitempty"`
	SignerListID  *uint32          `json:",omitempty"`
}

type Ticket struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	Account        *Account         `json:",omitempty"`
	TicketSequence *uint32          `json:",omitempty"`
	OwnerNode      *NodeIndex       `json:",omitempty"`
}

type PayChannel struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	Account         *Account         `json:",omitempty"`
	Destination     *Account         `json:",omitempty"`
	Amount          *Amount          `json:",omitempty"`
	Balance         *Amount          `json:",omitempty"`
	PublicKey       *PublicKey       `json:",omitempty"`
	SettleDelay     *uint32          `json:",omitempty"`
	OwnerNode       *NodeIndex       `json:",omitempty"`
	DestinationNode *NodeIndex       `json:",omitempty"`
	Expiration      *uint32          `json:",omitempty"`
	CancelAfter     *uint32          `json:",omitempty"`
	DestinationTag  *uint32          `json:",omitempty"`
	SourceTag       *uint32          `json:",omitempty"`
}

type Check struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	Account         *Account         `json:",omitempty"`
	Destination     *Account         `json:",omitempty"`
	OwnerNode       *NodeIndex       `json:",omitempty"`
	SendMax         *Amount          `json:",omitempty"`
	Sequence        *uint32          `json:",omitempty"`
	DestinationNode *NodeIndex       `json:",omitempty"`
	DestinationTag  *uint32          `json:",omitempty"`
	Expiration      *uint32          `json:",omitempty"`
	SourceTag       *uint32          `json:",omitempty"`
	InvoiceID       *Hash256         `json:",omitempty"`
}

type DepositPreAuth struct {
	leBase
	Flags     *LedgerEntryFlag `json:",omitempty"`
	Account   *Account         `json:",omitempty"`
	Authorize *Account         `json:",omitempty"`
	OwnerNode *NodeIndex       `json:",omitempty"`
}

type NFTokenPage struct {
	leBase
	Flags           *LedgerEntryFlag `json:",omitempty"`
	PreviousPageMin *Hash256         `json:",omitempty"`
	NextPageMin     *Hash256         `json:",omitempty"`
	NFTokens        []NFToken        `json:",omitempty"`
}

type NFTokenOffer struct {
	leBase
	Flags          *LedgerEntryFlag `json:",omitempty"`
	Owner          *Account         `json:",omitempty"`
	NFTokenID      *Hash256         `json:",omitempty"`
	Amount         *Amount          `json:",omitempty"`
	OwnerNode      *NodeIndex       `json:",omitempty"`
	TokenOfferNode *NodeIndex       `json:",omitempty"`
	Destination    *Account         `json:",omitempty"`
	Expiration     *uint32          `json:",omitempty"`
}

func (a *AccountRoot) Affects(account Account) bool {
	return a.Account != nil && a.Account.Equals(account)
}
func (r *RippleState) Affects(account Account) bool {
	return r.LowLimit.Issuer.Equals(account) || r.HighLimit.Issuer.Equals(account)
}
func (o *Offer) Affects(account Account) bool        { return o.Account.Equals(account) }
func (d *Directory) Affects(account Account) bool    { return false }
func (l *LedgerHashes) Affects(account Account) bool { return false }
func (a *Amendments) Affects(account Account) bool   { return false }
func (f *FeeSettings) Affects(account Account) bool  { return false }
func (n *NegativeUNL) Affects(account Account) bool  { return false }
func (s *Escrow) Affects(account Account) bool {
	return s.Account.Equals(account) || s.Destination.Equals(account)
}
func (s *SignerList) Affects(account Account) bool {
	for _, entry := range s.SignerEntries {
		if entry.Account != nil && entry.Account.Equals(account) {
			return true
		}
	}
	return false
}
func (t *Ticket) Affects(account Account) bool { return t.Account != nil && t.Account.Equals(account) }
func (p *PayChannel) Affects(account Account) bool {
	return (p.Account != nil && p.Account.Equals(account)) || (p.Destination != nil && p.Destination.Equals(account))
}
func (p *Check) Affects(account Account) bool {
	return (p.Account != nil && p.Account.Equals(account)) || (p.Destination != nil && p.Destination.Equals(account))
}

func (d *DepositPreAuth) Affects(account Account) bool {
	return (d.Account != nil && d.Account.Equals(account)) || (d.Authorize != nil && d.Authorize.Equals(account))
}

func (tp *NFTokenPage) Affects(account Account) bool {
	return false
}

func (to *NFTokenOffer) Affects(account Account) bool {
	return (to.Owner != nil && to.Owner.Equals(account)) || (to.Destination != nil && to.Destination.Equals(account))
}

func (le *leBase) GetType() string                     { return ledgerEntryNames[le.LedgerEntryType] }
func (le *leBase) GetLedgerEntryType() LedgerEntryType { return le.LedgerEntryType }
func (le *leBase) Prefix() HashPrefix                  { return HP_LEAF_NODE }
func (le *leBase) NodeType() NodeType                  { return NT_ACCOUNT_NODE }
func (le *leBase) Ledger() uint32                      { return 0 }
func (le *leBase) GetHash() *Hash256                   { return &le.Hash }
func (le *leBase) NodeId() *Hash256                    { return &le.Id }
func (le *leBase) GetLedgerIndex() *Hash256            { return le.LedgerIndex }
func (le *leBase) GetPreviousTxnId() *Hash256          { return le.PreviousTxnID }

func (o *Offer) Ratio() *Value {
	return o.TakerPays.Ratio(*o.TakerGets)
}
<|endoftext|>


<commit_before>package brightbox

import (
	"context"
	"errors"
	"log"

	brightbox "github.com/brightbox/gobrightbox/v2"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceBrightboxCreate[O, I any](
	creator func(*brightbox.Client, context.Context, I) (*O, error),
	objectName string,
	updater func(*schema.ResourceData, *I) diag.Diagnostics,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.CreateContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[INFO] Creating %s", objectName)
		var objectOptions I
		errs := updater(d, &objectOptions)
		if errs.HasError() {
			return errs
		}
		log.Printf("[INFO] %s create configuration: %+v", objectName, objectOptions)
		object, err := creator(client, ctx, objectOptions)
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func resourceBrightboxRead[O any](
	reader func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s resource read called for %s", objectName, d.Id())

		object, err := reader(client, ctx, d.Id())
		if err != nil {
			var apierror *brightbox.APIError
			if errors.As(err, &apierror) {
				if apierror.StatusCode == 404 {
					log.Printf("[WARN] %s not found, removing from state: %s", objectName, d.Id())
					d.SetId("")
					return nil
				}
			}
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func resourceBrightboxReadStatus[O any](
	reader func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
	missing func(*O) bool,
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s resource read called for %s", objectName, d.Id())

		object, err := reader(client, ctx, d.Id())
		if err != nil {
			var apierror *brightbox.APIError
			if errors.As(err, &apierror) {
				if apierror.StatusCode == 404 {
					log.Printf("[WARN] %s not found, removing from state: %s", objectName, d.Id())
					d.SetId("")
					return nil
				}
			}
			return diag.FromErr(err)
		}
		if missing(object) {
			log.Printf("[WARN] %s not found, removing from state: %s", objectName, d.Id())
			d.SetId("")
			return nil
		}

		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func datasourceBrightboxRead[O any](
	reader func(*brightbox.Client, context.Context) ([]O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
	finderGenerator func(*schema.ResourceData) (func(O) bool, diag.Diagnostics),
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s data read called. Retrieving object list", objectName)

		objects, err := reader(client, ctx)
		if err != nil {
			return diag.FromErr(err)
		}

		findFunc, errs := finderGenerator(d)
		if errs.HasError() {
			return errs
		}

		results := filter(objects, findFunc)

		if len(results) > 1 {
			return diag.Errorf("Your query returned more than one result (found %d entries). Please try a more "+
				"specific search criteria.", len(results))
		}
		if len(results) < 1 {
			return diag.Errorf("Your query returned no results. " +
				"Please change your search criteria and try again.")
		}

		result := &results[0]
		log.Printf("[DEBUG] Single %s found", objectName)
		log.Printf("[DEBUG] %+v", *result)
		return setter(d, result)

	}
}

func datasourceBrightboxRecentRead[O brightbox.CreateDated](
	reader func(*brightbox.Client, context.Context) ([]O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
	finderGenerator func(*schema.ResourceData) (func(O) bool, diag.Diagnostics),
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s data read called. Retrieving object list", objectName)

		objects, err := reader(client, ctx)
		if err != nil {
			return diag.FromErr(err)
		}

		findFunc, errs := finderGenerator(d)
		if errs.HasError() {
			return errs
		}

		results := filter(objects, findFunc)

		var result *O

		if len(results) > 1 {
			recent, ok := d.GetOk("most_recent")
			if ok {
				if recent.(bool) {
					log.Printf("[DEBUG] Multiple results found and `most_recent` is set")
					result = mostRecent(results)
				}
			}
			return diag.Errorf("Your query returned more than one result (found %d entries). Please try a more "+
				"specific search criteria.", len(results))
		} else if len(results) < 1 {
			return diag.Errorf("Your query returned no results. " +
				"Please change your search criteria and try again.")
		} else {
			result = &results[0]
		}
		log.Printf("[DEBUG] Single %s found", objectName)
		log.Printf("[DEBUG] %+v", *result)
		return setter(d, result)

	}
}

func resourceBrightboxUpdate[O, I any](
	putter func(*brightbox.Client, context.Context, I) (*O, error),
	objectName string,
	newFromID func(string) *I,
	updater func(*schema.ResourceData, *I) diag.Diagnostics,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.UpdateContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		objectOpts := newFromID(d.Id())
		errs := updater(d, objectOpts)
		if errs.HasError() {
			return errs
		}
		log.Printf("[DEBUG] %s update configuration: %+v", objectName, objectOpts)

		object, err := putter(client, ctx, *objectOpts)
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func resourceBrightboxDelete[O any](
	deleter func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
) schema.DeleteContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[INFO] Deleting %s %s", objectName, d.Id())
		_, err := deleter(client, ctx, d.Id())
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] Deleted cleanly")
		return nil
	}
}

func resourceBrightboxDeleteAndWait[O any](
	deleter func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
	pending []string,
	target []string,
	refresher func(*brightbox.Client, context.Context, string) resource.StateRefreshFunc,
) schema.DeleteContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		diags := resourceBrightboxDelete(deleter, objectName)(ctx, d, meta)
		if diags.HasError() {
			return diags
		}

		client := meta.(*CompositeClient).APIClient
		stateConf := resource.StateChangeConf{
			Pending:    pending,
			Target:     target,
			Refresh:    refresher(client, ctx, d.Id()),
			Timeout:    d.Timeout(schema.TimeoutDelete),
			Delay:      checkDelay,
			MinTimeout: minimumRefreshWait,
		}
		_, err := stateConf.WaitForStateContext(ctx)
		if err != nil {
			return diag.FromErr(err)
		}
		d.SetId("")
		return nil
	}
}

func resourceBrightboxSetLockState[O any](
	locker func(*brightbox.Client, context.Context, string) (*O, error),
	unlocker func(*brightbox.Client, context.Context, string) (*O, error),
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.UpdateContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{},
	) diag.Diagnostics {
		locked := d.Get("locked").(bool)
		log.Printf("[INFO] Setting lock state to %v", locked)
		client := meta.(*CompositeClient).APIClient
		var object *O
		var err error
		if locked {
			object, err = locker(client, ctx, d.Id())
		} else {
			object, err = unlocker(client, ctx, d.Id())
		}
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}
<commit_msg>Refactor generic read function<commit_after>package brightbox

import (
	"context"
	"errors"
	"log"

	brightbox "github.com/brightbox/gobrightbox/v2"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceBrightboxCreate[O, I any](
	creator func(*brightbox.Client, context.Context, I) (*O, error),
	objectName string,
	updater func(*schema.ResourceData, *I) diag.Diagnostics,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.CreateContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[INFO] Creating %s", objectName)
		var objectOptions I
		errs := updater(d, &objectOptions)
		if errs.HasError() {
			return errs
		}
		log.Printf("[INFO] %s create configuration: %+v", objectName, objectOptions)
		object, err := creator(client, ctx, objectOptions)
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func resourceBrightboxRead[O any](
	reader func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.ReadContextFunc {
	return resourceBrightboxReadStatus(
		reader,
		objectName,
		setter,
		func(_*O) bool {
			return false
		},
	)
}

func resourceBrightboxReadStatus[O any](
	reader func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
	missing func(*O) bool,
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s resource read called for %s", objectName, d.Id())

		object, err := reader(client, ctx, d.Id())
		if err != nil {
			var apierror *brightbox.APIError
			if errors.As(err, &apierror) {
				if apierror.StatusCode == 404 {
					log.Printf("[WARN] %s not found, removing from state: %s", objectName, d.Id())
					d.SetId("")
					return nil
				}
			}
			return diag.FromErr(err)
		}
		if missing(object) {
			log.Printf("[WARN] %s not found, removing from state: %s", objectName, d.Id())
			d.SetId("")
			return nil
		}

		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func datasourceBrightboxRead[O any](
	reader func(*brightbox.Client, context.Context) ([]O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
	finderGenerator func(*schema.ResourceData) (func(O) bool, diag.Diagnostics),
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s data read called. Retrieving object list", objectName)

		objects, err := reader(client, ctx)
		if err != nil {
			return diag.FromErr(err)
		}

		findFunc, errs := finderGenerator(d)
		if errs.HasError() {
			return errs
		}

		results := filter(objects, findFunc)

		if len(results) > 1 {
			return diag.Errorf("Your query returned more than one result (found %d entries). Please try a more "+
				"specific search criteria.", len(results))
		}
		if len(results) < 1 {
			return diag.Errorf("Your query returned no results. " +
				"Please change your search criteria and try again.")
		}

		result := &results[0]
		log.Printf("[DEBUG] Single %s found", objectName)
		log.Printf("[DEBUG] %+v", *result)
		return setter(d, result)

	}
}

func datasourceBrightboxRecentRead[O brightbox.CreateDated](
	reader func(*brightbox.Client, context.Context) ([]O, error),
	objectName string,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
	finderGenerator func(*schema.ResourceData) (func(O) bool, diag.Diagnostics),
) schema.ReadContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[DEBUG] %s data read called. Retrieving object list", objectName)

		objects, err := reader(client, ctx)
		if err != nil {
			return diag.FromErr(err)
		}

		findFunc, errs := finderGenerator(d)
		if errs.HasError() {
			return errs
		}

		results := filter(objects, findFunc)

		var result *O

		if len(results) > 1 {
			recent, ok := d.GetOk("most_recent")
			if ok {
				if recent.(bool) {
					log.Printf("[DEBUG] Multiple results found and `most_recent` is set")
					result = mostRecent(results)
				}
			}
			return diag.Errorf("Your query returned more than one result (found %d entries). Please try a more "+
				"specific search criteria.", len(results))
		} else if len(results) < 1 {
			return diag.Errorf("Your query returned no results. " +
				"Please change your search criteria and try again.")
		} else {
			result = &results[0]
		}
		log.Printf("[DEBUG] Single %s found", objectName)
		log.Printf("[DEBUG] %+v", *result)
		return setter(d, result)

	}
}

func resourceBrightboxUpdate[O, I any](
	putter func(*brightbox.Client, context.Context, I) (*O, error),
	objectName string,
	newFromID func(string) *I,
	updater func(*schema.ResourceData, *I) diag.Diagnostics,
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.UpdateContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		objectOpts := newFromID(d.Id())
		errs := updater(d, objectOpts)
		if errs.HasError() {
			return errs
		}
		log.Printf("[DEBUG] %s update configuration: %+v", objectName, objectOpts)

		object, err := putter(client, ctx, *objectOpts)
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}

func resourceBrightboxDelete[O any](
	deleter func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
) schema.DeleteContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		client := meta.(*CompositeClient).APIClient

		log.Printf("[INFO] Deleting %s %s", objectName, d.Id())
		_, err := deleter(client, ctx, d.Id())
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] Deleted cleanly")
		return nil
	}
}

func resourceBrightboxDeleteAndWait[O any](
	deleter func(*brightbox.Client, context.Context, string) (*O, error),
	objectName string,
	pending []string,
	target []string,
	refresher func(*brightbox.Client, context.Context, string) resource.StateRefreshFunc,
) schema.DeleteContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
		diags := resourceBrightboxDelete(deleter, objectName)(ctx, d, meta)
		if diags.HasError() {
			return diags
		}

		client := meta.(*CompositeClient).APIClient
		stateConf := resource.StateChangeConf{
			Pending:    pending,
			Target:     target,
			Refresh:    refresher(client, ctx, d.Id()),
			Timeout:    d.Timeout(schema.TimeoutDelete),
			Delay:      checkDelay,
			MinTimeout: minimumRefreshWait,
		}
		_, err := stateConf.WaitForStateContext(ctx)
		if err != nil {
			return diag.FromErr(err)
		}
		d.SetId("")
		return nil
	}
}

func resourceBrightboxSetLockState[O any](
	locker func(*brightbox.Client, context.Context, string) (*O, error),
	unlocker func(*brightbox.Client, context.Context, string) (*O, error),
	setter func(*schema.ResourceData, *O) diag.Diagnostics,
) schema.UpdateContextFunc {
	return func(ctx context.Context, d *schema.ResourceData, meta interface{},
	) diag.Diagnostics {
		locked := d.Get("locked").(bool)
		log.Printf("[INFO] Setting lock state to %v", locked)
		client := meta.(*CompositeClient).APIClient
		var object *O
		var err error
		if locked {
			object, err = locker(client, ctx, d.Id())
		} else {
			object, err = unlocker(client, ctx, d.Id())
		}
		if err != nil {
			return diag.FromErr(err)
		}
		log.Printf("[DEBUG] setting details from returned object: %+v", *object)
		return setter(d, object)
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/diff"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/goamz/ec2"
)

// resourceMap is the mapping of resources we support to their basic
// operations. This makes it easy to implement new resource types.
var resourceMap *resource.Map

func init() {
	resourceMap = &resource.Map{
		Mapping: map[string]resource.Resource{
			"aws_instance": resource.Resource{
				Create:  resource_aws_instance_create,
				Destroy: resource_aws_instance_destroy,
				Diff:    resource_aws_instance_diff,
				Refresh: resource_aws_instance_refresh,
			},
		},
	}
}

func resource_aws_instance_create(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Merge the diff into the state so that we have all the attributes
	// properly.
	rs := s.MergeDiff(d)

	runOpts := &ec2.RunInstances{
		ImageId:      rs.Attributes["ami"],
		InstanceType: rs.Attributes["instance_type"],
	}

	log.Printf("[DEBUG] Run configuration: %#v", runOpts)
	runResp, err := ec2conn.RunInstances(runOpts)
	if err != nil {
		return nil, fmt.Errorf("Error launching source instance: %s", err)
	}

	instance := &runResp.Instances[0]
	log.Printf("[INFO] Instance ID: %s", instance.InstanceId)

	// Store the resource state now so that we can return it in the case
	// of any errors.
	rs.ID = instance.InstanceId

	// Wait for the instance to become running so we can get some attributes
	// that aren't available until later.
	log.Printf(
		"[DEBUG] Waiting for instance (%s) to become running",
		instance.InstanceId)
	instanceRaw, err := WaitForState(&StateChangeConf{
		Pending: []string{"pending"},
		Target:  "running",
		Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId),
	})
	if err != nil {
		return rs, fmt.Errorf(
			"Error waiting for instance (%s) to become ready: %s",
			instance.InstanceId, err)
	}
	instance = instanceRaw.(*ec2.Instance)

	// Set our attributes
	return resource_aws_instance_update_state(rs, instance)
}

func resource_aws_instance_destroy(
	s *terraform.ResourceState,
	meta interface{}) error {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	log.Printf("[INFO] Terminating instance: %s", s.ID)
	if _, err := ec2conn.TerminateInstances([]string{s.ID}); err != nil {
		return fmt.Errorf("Error terminating instance: %s", err)
	}

	log.Printf(
		"[DEBUG] Waiting for instance (%s) to become terminated",
		s.ID)
	_, err := WaitForState(&StateChangeConf{
		Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
		Target:  "terminated",
		Refresh: InstanceStateRefreshFunc(ec2conn, s.ID),
	})
	if err != nil {
		return fmt.Errorf(
			"Error waiting for instance (%s) to terminate: %s",
			s.ID, err)
	}

	return nil
}

func resource_aws_instance_diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig,
	meta interface{}) (*terraform.ResourceDiff, error) {
	b := &diff.ResourceBuilder{
		CreateComputedAttrs: []string{
			"public_dns",
			"public_ip",
			"private_dns",
			"private_ip",
		},

		RequiresNewAttrs: []string{
			"ami",
			"availability_zone",
			"instance_type",
		},
	}

	return b.Diff(s, c)
}

func resource_aws_instance_refresh(
	s *terraform.ResourceState,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	resp, err := ec2conn.Instances([]string{s.ID}, ec2.NewFilter())
	if err != nil {
		// If the instance was not found, return nil so that we can show
		// that the instance is gone.
		if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
			return nil, nil
		}

		// Some other error, report it
		return s, err
	}

	// If nothing was found, then return no state
	if len(resp.Reservations) == 0 {
		return nil, nil
	}

	instance := &resp.Reservations[0].Instances[0]

	// If the instance is terminated, then it is gone
	if instance.State.Name == "terminated" {
		return nil, nil
	}

	return resource_aws_instance_update_state(s, instance)
}

func resource_aws_instance_update_state(
	s *terraform.ResourceState,
	instance *ec2.Instance) (*terraform.ResourceState, error) {
	s.Attributes["public_dns"] = instance.DNSName
	s.Attributes["public_ip"] = instance.PublicIpAddress
	s.Attributes["private_dns"] = instance.PrivateDNSName
	s.Attributes["private_ip"] = instance.PrivateIpAddress
	return s, nil
}
<commit_msg>main: fix the help output to show up<commit_after>package aws

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/diff"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/goamz/ec2"
)

// resourceMap is the mapping of resources we support to their basic
// operations. This makes it easy to implement new resource types.
var resourceMap *resource.Map

func init() {
	resourceMap = &resource.Map{
		Mapping: map[string]resource.Resource{
			"aws_instance": resource.Resource{
				Create:  resource_aws_instance_create,
				Destroy: resource_aws_instance_destroy,
				Diff:    resource_aws_instance_diff,
				Refresh: resource_aws_instance_refresh,
			},
		},
	}
}

func resource_aws_instance_create(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Merge the diff into the state so that we have all the attributes
	// properly.
	rs := s.MergeDiff(d)

	// Create the instance
	runOpts := &ec2.RunInstances{
		ImageId:      rs.Attributes["ami"],
		InstanceType: rs.Attributes["instance_type"],
	}
	log.Printf("[DEBUG] Run configuration: %#v", runOpts)
	runResp, err := ec2conn.RunInstances(runOpts)
	if err != nil {
		return nil, fmt.Errorf("Error launching source instance: %s", err)
	}

	instance := &runResp.Instances[0]
	log.Printf("[INFO] Instance ID: %s", instance.InstanceId)

	// Store the resulting ID so we can look this up later
	rs.ID = instance.InstanceId

	// Wait for the instance to become running so we can get some attributes
	// that aren't available until later.
	log.Printf(
		"[DEBUG] Waiting for instance (%s) to become running",
		instance.InstanceId)
	instanceRaw, err := WaitForState(&StateChangeConf{
		Pending: []string{"pending"},
		Target:  "running",
		Refresh: InstanceStateRefreshFunc(ec2conn, instance.InstanceId),
	})
	if err != nil {
		return rs, fmt.Errorf(
			"Error waiting for instance (%s) to become ready: %s",
			instance.InstanceId, err)
	}
	instance = instanceRaw.(*ec2.Instance)

	// Set our attributes
	return resource_aws_instance_update_state(rs, instance)
}

func resource_aws_instance_destroy(
	s *terraform.ResourceState,
	meta interface{}) error {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	log.Printf("[INFO] Terminating instance: %s", s.ID)
	if _, err := ec2conn.TerminateInstances([]string{s.ID}); err != nil {
		return fmt.Errorf("Error terminating instance: %s", err)
	}

	log.Printf(
		"[DEBUG] Waiting for instance (%s) to become terminated",
		s.ID)
	_, err := WaitForState(&StateChangeConf{
		Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
		Target:  "terminated",
		Refresh: InstanceStateRefreshFunc(ec2conn, s.ID),
	})
	if err != nil {
		return fmt.Errorf(
			"Error waiting for instance (%s) to terminate: %s",
			s.ID, err)
	}

	return nil
}

func resource_aws_instance_diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig,
	meta interface{}) (*terraform.ResourceDiff, error) {
	b := &diff.ResourceBuilder{
		CreateComputedAttrs: []string{
			"public_dns",
			"public_ip",
			"private_dns",
			"private_ip",
		},

		RequiresNewAttrs: []string{
			"ami",
			"availability_zone",
			"instance_type",
		},
	}

	return b.Diff(s, c)
}

func resource_aws_instance_refresh(
	s *terraform.ResourceState,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	resp, err := ec2conn.Instances([]string{s.ID}, ec2.NewFilter())
	if err != nil {
		// If the instance was not found, return nil so that we can show
		// that the instance is gone.
		if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
			return nil, nil
		}

		// Some other error, report it
		return s, err
	}

	// If nothing was found, then return no state
	if len(resp.Reservations) == 0 {
		return nil, nil
	}

	instance := &resp.Reservations[0].Instances[0]

	// If the instance is terminated, then it is gone
	if instance.State.Name == "terminated" {
		return nil, nil
	}

	return resource_aws_instance_update_state(s, instance)
}

func resource_aws_instance_update_state(
	s *terraform.ResourceState,
	instance *ec2.Instance) (*terraform.ResourceState, error) {
	s.Attributes["public_dns"] = instance.DNSName
	s.Attributes["public_ip"] = instance.PublicIpAddress
	s.Attributes["private_dns"] = instance.PrivateDNSName
	s.Attributes["private_ip"] = instance.PrivateIpAddress
	return s, nil
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"sync"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
	libDatabox "github.com/me-box/lib-go-databox"
)

func ShowLogs() {

	cli, _ := client.NewEnvClient()

	libDatabox.Info("Outputting logs")

	//filters := filters.NewArgs()
	//filters.Add("label", "databox.type")
	//services, err := d.cli.ServiceList(context.Background(), types.ServiceListOptions{Filters: filters})
	services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
	libDatabox.ChkErr(err)

	logChan := make(chan string)
	var wg sync.WaitGroup

	for _, service := range services {
		ioLogReader, err := cli.ServiceLogs(context.Background(), service.ID,
			types.ContainerLogsOptions{
				Follow:     true,
				ShowStderr: true,
				ShowStdout: true,
				Tail:       "all",
				Timestamps: true,
			},
		)

		if err != nil {
			libDatabox.Err("Error reading logs for " + service.Spec.Name)
			continue
		}
		wg.Add(1)
		go func(name string) {
			for {
				message := make([]byte, 128)
				n, err := ioLogReader.Read(message)
				if err != nil {
					wg.Done()
					ioLogReader.Close()
					break
				}
				if n > 0 {
					logChan <- name + "\t: " + string(message)
				}
			}
		}(service.Spec.Name)
	}

	go func() {
		for {
			select {
			case msg := <-logChan:
				fmt.Print(msg)
			}
		}
	}()

	wg.Wait()
}
<commit_msg>Tidy up log output <commit_after>package main

import (
	"bufio"
	"context"
	"fmt"
	"sync"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
	libDatabox "github.com/me-box/lib-go-databox"
)

func ShowLogs() {

	cli, _ := client.NewEnvClient()

	libDatabox.Info("Outputting logs")

	//filters := filters.NewArgs()
	//filters.Add("label", "databox.type")
	//services, err := d.cli.ServiceList(context.Background(), types.ServiceListOptions{Filters: filters})
	services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
	libDatabox.ChkErr(err)

	logChan := make(chan string)
	var wg sync.WaitGroup

	for _, service := range services {
		ioLogReader, err := cli.ServiceLogs(context.Background(), service.ID,
			types.ContainerLogsOptions{
				Follow:     true,
				ShowStderr: true,
				ShowStdout: true,
				Tail:       "all",
				Timestamps: false,
			},
		)

		if err != nil {
			libDatabox.Err("Error reading logs for " + service.Spec.Name)
			continue
		}
		wg.Add(1)
		go func(name string) {
			scanner := bufio.NewScanner(ioLogReader)
			for scanner.Scan() {
				message := scanner.Text()
				if err := scanner.Err(); err != nil {
					wg.Done()
					ioLogReader.Close()
					break
				}
				if len(message) > 8 {
					message = message[8:]
				}
				logChan <- fmt.Sprintf("%-25s: %s", name, message)
			}
		}(service.Spec.Name)
	}

	go func() {
		for {
			select {
			case msg := <-logChan:
				fmt.Println(msg)
			}
		}
	}()

	wg.Wait()
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"net/smtp"
	"strconv"

	"github.com/ian-kent/Go-MailHog/MailHog-Server/config"
	"github.com/ian-kent/Go-MailHog/data"
	"github.com/ian-kent/Go-MailHog/storage"
	"github.com/ian-kent/go-log/log"
	gotcha "github.com/ian-kent/gotcha/app"
	"github.com/ian-kent/gotcha/http"
)

type APIv1 struct {
	config         *config.Config
	eventlisteners []*EventListener
	app            *gotcha.App
}

type EventListener struct {
	session *http.Session
	ch      chan []byte
}

type ReleaseConfig struct {
	Email string
	Host  string
	Port  string
}

func CreateAPIv1(conf *config.Config, app *gotcha.App) *APIv1 {
	log.Println("Creating API v1")
	apiv1 := &APIv1{
		config:         conf,
		eventlisteners: make([]*EventListener, 0),
		app:            app,
	}

	r := app.Router

	r.Get("/api/v1/messages/?", apiv1.messages)
	r.Delete("/api/v1/messages/?", apiv1.delete_all)
	r.Get("/api/v1/messages/(?P<id>[^/]+)/?", apiv1.message)
	r.Delete("/api/v1/messages/(?P<id>[^/]+)/?", apiv1.delete_one)
	r.Get("/api/v1/messages/(?P<id>[^/]+)/download/?", apiv1.download)
	r.Get("/api/v1/messages/(?P<id>[^/]+)/mime/part/(\\d+)/download/?", apiv1.download_part)
	r.Post("/api/v1/messages/(?P<id>[^/]+)/release/?", apiv1.release_one)
	r.Get("/api/v1/events/?", apiv1.eventstream)

	go func() {
		for {
			select {
			case msg := <-apiv1.config.MessageChan:
				log.Println("Got message in APIv1 event stream")
				bytes, _ := json.MarshalIndent(msg, "", "  ")
				json := string(bytes)
				log.Printf("Sending content: %s\n", json)
				apiv1.broadcast(json)
			}
		}
	}()

	return apiv1
}

func (apiv1 *APIv1) broadcast(json string) {
	log.Println("[APIv1] BROADCAST /api/v1/events")
	b := []byte(json)
	for _, l := range apiv1.eventlisteners {
		log.Printf("Sending to connection: %s\n", l.session.Request.RemoteAddr)
		l.ch <- b
	}
}

func (apiv1 *APIv1) eventstream(session *http.Session) {
	log.Println("[APIv1] GET /api/v1/events")

	apiv1.eventlisteners = append(apiv1.eventlisteners, &EventListener{
		session,
		session.Response.EventStream(),
	})
}

func (apiv1 *APIv1) messages(session *http.Session) {
	log.Println("[APIv1] GET /api/v1/messages")

	// TODO start, limit
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		messages, _ := apiv1.config.Storage.(*storage.MongoDB).List(0, 1000)
		bytes, _ := json.Marshal(messages)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	case *storage.InMemory:
		messages, _ := apiv1.config.Storage.(*storage.InMemory).List(0, 1000)
		bytes, _ := json.Marshal(messages)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) message(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		bytes, _ := json.Marshal(message)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		bytes, _ := json.Marshal(message)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) download(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	session.Response.Headers.Add("Content-Type", "message/rfc822")
	session.Response.Headers.Add("Content-Disposition", "attachment; filename=\""+id+".eml\"")

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		for h, l := range message.Content.Headers {
			for _, v := range l {
				session.Response.Write([]byte(h + ": " + v + "\r\n"))
			}
		}
		session.Response.Write([]byte("\r\n" + message.Content.Body))
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		for h, l := range message.Content.Headers {
			for _, v := range l {
				session.Response.Write([]byte(h + ": " + v + "\r\n"))
			}
		}
		session.Response.Write([]byte("\r\n" + message.Content.Body))
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) download_part(session *http.Session) {
	id := session.Stash["id"].(string)
	part, _ := strconv.Atoi(session.Stash["part"].(string))
	log.Printf("[APIv1] GET /api/v1/messages/%s/mime/part/%d/download\n", id, part)

	// TODO extension from content-type?

	session.Response.Headers.Add("Content-Disposition", "attachment; filename=\""+id+"-part-"+strconv.Itoa(part)+"\"")

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		for h, l := range message.MIME.Parts[part].Headers {
			for _, v := range l {
				session.Response.Headers.Add(h, v)
			}
		}
		session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		for h, l := range message.MIME.Parts[part].Headers {
			for _, v := range l {
				session.Response.Headers.Add(h, v)
			}
		}
		session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) delete_all(session *http.Session) {
	log.Println("[APIv1] POST /api/v1/messages")

	session.Response.Headers.Add("Content-Type", "text/json")
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		apiv1.config.Storage.(*storage.MongoDB).DeleteAll()
	case *storage.InMemory:
		apiv1.config.Storage.(*storage.InMemory).DeleteAll()
	default:
		session.Response.Status = 500
		return
	}
}

func (apiv1 *APIv1) release_one(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] POST /api/v1/messages/%s/release\n", id)

	session.Response.Headers.Add("Content-Type", "text/json")
	var msg = &data.Message{}
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		msg, _ = apiv1.config.Storage.(*storage.MongoDB).Load(id)
	case *storage.InMemory:
		msg, _ = apiv1.config.Storage.(*storage.InMemory).Load(id)
	default:
		session.Response.Status = 500
		return
	}

	decoder := json.NewDecoder(session.Request.Body())
	var cfg ReleaseConfig
	err := decoder.Decode(&cfg)
	if err != nil {
		log.Printf("Error decoding request body: %s", err)
		session.Response.Status = 500
		session.Response.Write([]byte("Error decoding request body"))
		return
	}

	log.Printf("Releasing to %s (via %s:%s)", cfg.Email, cfg.Host, cfg.Port)
	log.Printf("Got message: %s", msg.ID)

	bytes := make([]byte, 0)
	for h, l := range msg.Content.Headers {
		for _, v := range l {
			bytes = append(bytes, []byte(h+": "+v+"\r\n")...)
		}
	}
	bytes = append(bytes, []byte("\r\n"+msg.Content.Body)...)

	err = smtp.SendMail(cfg.Host+":"+cfg.Port, nil, "nobody@"+apiv1.config.Hostname, []string{cfg.Email}, bytes)
	if err != nil {
		log.Printf("Failed to release message: %s", err)
		session.Response.Status = 500
		return
	}
	log.Printf("Message released successfully")
}

func (apiv1 *APIv1) delete_one(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] POST /api/v1/messages/%s/delete\n", id)

	session.Response.Headers.Add("Content-Type", "text/json")
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		apiv1.config.Storage.(*storage.MongoDB).DeleteOne(id)
	case *storage.InMemory:
		apiv1.config.Storage.(*storage.InMemory).DeleteOne(id)
	default:
		session.Response.Status = 500
	}
}
<commit_msg>Fix #18<commit_after>package api

import (
	"encoding/json"
	"net/smtp"
	"strconv"

	"github.com/ian-kent/Go-MailHog/MailHog-Server/config"
	"github.com/ian-kent/Go-MailHog/data"
	"github.com/ian-kent/Go-MailHog/storage"
	"github.com/ian-kent/go-log/log"
	gotcha "github.com/ian-kent/gotcha/app"
	"github.com/ian-kent/gotcha/http"

	"github.com/ian-kent/goose"
)

type APIv1 struct {
	config *config.Config
	app    *gotcha.App
}

var stream *goose.EventStream

type ReleaseConfig struct {
	Email string
	Host  string
	Port  string
}

func CreateAPIv1(conf *config.Config, app *gotcha.App) *APIv1 {
	log.Println("Creating API v1")
	apiv1 := &APIv1{
		config: conf,
		app:    app,
	}

	stream = goose.NewEventStream()
	r := app.Router

	r.Get("/api/v1/messages/?", apiv1.messages)
	r.Delete("/api/v1/messages/?", apiv1.delete_all)
	r.Get("/api/v1/messages/(?P<id>[^/]+)/?", apiv1.message)
	r.Delete("/api/v1/messages/(?P<id>[^/]+)/?", apiv1.delete_one)
	r.Get("/api/v1/messages/(?P<id>[^/]+)/download/?", apiv1.download)
	r.Get("/api/v1/messages/(?P<id>[^/]+)/mime/part/(\\d+)/download/?", apiv1.download_part)
	r.Post("/api/v1/messages/(?P<id>[^/]+)/release/?", apiv1.release_one)
	r.Get("/api/v1/events/?", apiv1.eventstream)

	go func() {
		for {
			select {
			case msg := <-apiv1.config.MessageChan:
				log.Println("Got message in APIv1 event stream")
				bytes, _ := json.MarshalIndent(msg, "", "  ")
				json := string(bytes)
				log.Printf("Sending content: %s\n", json)
				apiv1.broadcast(json)
			}
		}
	}()

	return apiv1
}

func (apiv1 *APIv1) broadcast(json string) {
	log.Println("[APIv1] BROADCAST /api/v1/events")
	b := []byte(json)
	stream.Notify("data", b)
}

func (apiv1 *APIv1) eventstream(session *http.Session) {
	log.Println("[APIv1] GET /api/v1/events")

	stream.AddReceiver(session.Response.GetWriter())
}

func (apiv1 *APIv1) messages(session *http.Session) {
	log.Println("[APIv1] GET /api/v1/messages")

	// TODO start, limit
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		messages, _ := apiv1.config.Storage.(*storage.MongoDB).List(0, 1000)
		bytes, _ := json.Marshal(messages)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	case *storage.InMemory:
		messages, _ := apiv1.config.Storage.(*storage.InMemory).List(0, 1000)
		bytes, _ := json.Marshal(messages)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) message(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		bytes, _ := json.Marshal(message)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		bytes, _ := json.Marshal(message)
		session.Response.Headers.Add("Content-Type", "text/json")
		session.Response.Write(bytes)
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) download(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	session.Response.Headers.Add("Content-Type", "message/rfc822")
	session.Response.Headers.Add("Content-Disposition", "attachment; filename=\""+id+".eml\"")

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		for h, l := range message.Content.Headers {
			for _, v := range l {
				session.Response.Write([]byte(h + ": " + v + "\r\n"))
			}
		}
		session.Response.Write([]byte("\r\n" + message.Content.Body))
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		for h, l := range message.Content.Headers {
			for _, v := range l {
				session.Response.Write([]byte(h + ": " + v + "\r\n"))
			}
		}
		session.Response.Write([]byte("\r\n" + message.Content.Body))
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) download_part(session *http.Session) {
	id := session.Stash["id"].(string)
	part, _ := strconv.Atoi(session.Stash["part"].(string))
	log.Printf("[APIv1] GET /api/v1/messages/%s/mime/part/%d/download\n", id, part)

	// TODO extension from content-type?

	session.Response.Headers.Add("Content-Disposition", "attachment; filename=\""+id+"-part-"+strconv.Itoa(part)+"\"")

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		for h, l := range message.MIME.Parts[part].Headers {
			for _, v := range l {
				session.Response.Headers.Add(h, v)
			}
		}
		session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		for h, l := range message.MIME.Parts[part].Headers {
			for _, v := range l {
				session.Response.Headers.Add(h, v)
			}
		}
		session.Response.Write([]byte("\r\n" + message.MIME.Parts[part].Body))
	default:
		session.Response.Status = 500
	}
}

func (apiv1 *APIv1) delete_all(session *http.Session) {
	log.Println("[APIv1] POST /api/v1/messages")

	session.Response.Headers.Add("Content-Type", "text/json")
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		apiv1.config.Storage.(*storage.MongoDB).DeleteAll()
	case *storage.InMemory:
		apiv1.config.Storage.(*storage.InMemory).DeleteAll()
	default:
		session.Response.Status = 500
		return
	}
}

func (apiv1 *APIv1) release_one(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] POST /api/v1/messages/%s/release\n", id)

	session.Response.Headers.Add("Content-Type", "text/json")
	var msg = &data.Message{}
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		msg, _ = apiv1.config.Storage.(*storage.MongoDB).Load(id)
	case *storage.InMemory:
		msg, _ = apiv1.config.Storage.(*storage.InMemory).Load(id)
	default:
		session.Response.Status = 500
		return
	}

	decoder := json.NewDecoder(session.Request.Body())
	var cfg ReleaseConfig
	err := decoder.Decode(&cfg)
	if err != nil {
		log.Printf("Error decoding request body: %s", err)
		session.Response.Status = 500
		session.Response.Write([]byte("Error decoding request body"))
		return
	}

	log.Printf("Releasing to %s (via %s:%s)", cfg.Email, cfg.Host, cfg.Port)
	log.Printf("Got message: %s", msg.ID)

	bytes := make([]byte, 0)
	for h, l := range msg.Content.Headers {
		for _, v := range l {
			bytes = append(bytes, []byte(h+": "+v+"\r\n")...)
		}
	}
	bytes = append(bytes, []byte("\r\n"+msg.Content.Body)...)

	err = smtp.SendMail(cfg.Host+":"+cfg.Port, nil, "nobody@"+apiv1.config.Hostname, []string{cfg.Email}, bytes)
	if err != nil {
		log.Printf("Failed to release message: %s", err)
		session.Response.Status = 500
		return
	}
	log.Printf("Message released successfully")
}

func (apiv1 *APIv1) delete_one(session *http.Session) {
	id := session.Stash["id"].(string)
	log.Printf("[APIv1] POST /api/v1/messages/%s/delete\n", id)

	session.Response.Headers.Add("Content-Type", "text/json")
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		apiv1.config.Storage.(*storage.MongoDB).DeleteOne(id)
	case *storage.InMemory:
		apiv1.config.Storage.(*storage.InMemory).DeleteOne(id)
	default:
		session.Response.Status = 500
	}
}
<|endoftext|>


<commit_before>package fastly

import (
	"fmt"
)

// BigQuery represents a BigQuery logging response from the Fastly API.
type BigQuery struct {
	ServiceID         string `mapstructure:"service_id"`
	Name              string `mapstructure:"name"`
	Format            string `mapstructure:"format"`
	User              string `mapstructure:"user"`
	ProjectID         string `mapstructure:"project_id"`
	Dataset           string `mapstructure:"dataset"`
	Table             string `mapstructure:"table"`
	SecretKey         string `mapstructure:"secret_key"`
	CreatedAt         string `mapstructure:"created_at"`
	UpdatedAt         string `mapstructure:"updated_at"`
	DeletedAt         string `mapstructure:"deleted_at"`
	ResponseCondition string `mapstructure:"response_condition"`
}

// GetBigQueryInput is used as input to the GetBigQuery function.
type GetBigQueryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version int
}

// GetBigQuery lists all BigQuerys associated with a service version.
func (c *Client) GetBigQuery(i *GetBigQueryInput) ([]*BigQuery, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == 0 {
		return nil, ErrMissingVersion
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery", i.Service, i.Version)
	resp, err := c.Get(path, nil)
	if err != nil {
		return nil, err
	}

	var bs []*BigQuery
	if err := decodeJSON(&bs, resp.Body); err != nil {
		return nil, err
	}
	return bs, nil
}

// CreateBigQueryInput is used as input to the CreateBigQuery function.
type CreateBigQueryInput struct {
	// All fields other than format are required.
	// Service is the ID of the service.
	Service string

	//Version is the specific configuration version.
	Version int

	// Name is the name if your bigquery logging endpoint.
	Name string

	// Project ID your GCP project ID.
	ProjectID string

	// Dataset is your BigQuery dataset.
	Dataset string

	// Table is your BigQuery table.
	Table string

	// User is the user with access to write to your BigQuery dataset.
	User string

	// Secret key is the user's secret key.
	SecretKey string

	// Format is the log formatting desired for your BigQuery dataset.
	// Optional.
	Format string
}

// CreateBigQuery creates a new Fastly BigQuery logging endpoint.
func (c *Client) CreateBigQuery(i *CreateBigQueryInput) (*BigQuery, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == 0 {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	if i.ProjectID == "" {
		return nil, ErrMissingProjectID
	}

	if i.Dataset == "" {
		return nil, ErrMissingDataset
	}

	if i.Table == "" {
		return nil, ErrMissingTable
	}

	if i.User == "" {
		return nil, ErrMissingUser
	}

	if i.SecretKey == "" {
		return nil, ErrMissingSecretKey
	}

	params := make(map[string]string)
	params["name"] = i.Name
	params["project_id"] = i.ProjectID
	params["dataset"] = i.Dataset
	params["table"] = i.Table
	params["user"] = i.User
	params["secret_key"] = i.SecretKey
	if i.Format != "" {
		params["format"] = i.Format
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery", i.Service, i.Version)
	resp, err := c.PostForm(path, i, &RequestOptions{
		Params: params,
	})
	if err != nil {
		return nil, err
	}

	var b *BigQuery
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// UpdateBigQueryInput is used as input to the UpdateBigQuery function.
type UpdateBigQueryInput struct {
	// Service is the ID of the service.
	// This field is required.
	Service string

	//Version is the specific configuration version.
	// This field is required.
	Version int

	// Name is the old name if your bigquery logging endpoint.
	// Used to identify the correct BigQuery logging endpoint if there
	// is a name change.
	// This field is required.
	Name string

	// NewName is the new name of your BigQuery logging endpoint.
	// This field is required.
	NewName string

	// Project ID your GCP project ID.
	ProjectID string

	// Dataset is your BigQuery dataset.
	Dataset string

	// Table is your BigQuery table.
	Table string

	// User is the user with access to write to your BigQuery dataset.
	User string

	// Secret key is the user's secret key.
	SecretKey string

	// Format is the log formatting desired for your BigQuery dataset.
	// Optional.
	Format string
}

// UpdateBigQuery updates a BigQuery logging endpoint.
func (c *Client) UpdateBigQuery(i *UpdateBigQueryInput) (*BigQuery, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == 0 {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	if i.NewName == "" {
		return nil, ErrMissingNewName
	}

	params := make(map[string]string)
	params["name"] = i.NewName
	if i.ProjectID != "" {
		params["project_id"] = i.ProjectID
	}
	if i.Dataset != "" {
		params["dataset"] = i.Dataset
	}
	if i.Table != "" {
		params["table"] = i.Table
	}
	if i.User != "" {
		params["user"] = i.User
	}
	if i.SecretKey != "" {
		params["secret_key"] = i.SecretKey
	}
	if i.Format != "" {
		params["format"] = i.Format
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery/%s", i.Service, i.Version, i.Name)
	resp, err := c.PutForm(path, i, &RequestOptions{
		Params: params,
	})
	if err != nil {
		return nil, err
	}

	var b *BigQuery
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// DeleteBigQueryInput is the input parameter to DeleteBigQuery.
// All fields are required.
type DeleteBigQueryInput struct {
	// Service is the ID of the service.
	Service string

	// Version is the specific configuration.
	Version int

	// Name is the name of the BigQuery logging endpoint to delete.
	Name string
}

// DeleteBigQuery deletes the given BigQuery logging endpoint.
func (c *Client) DeleteBigQuery(i *DeleteBigQueryInput) error {
	if i.Service == "" {
		return ErrMissingService
	}

	if i.Version == 0 {
		return ErrMissingVersion
	}

	if i.Name == "" {
		return ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery/%s", i.Service, i.Version, i.Name)
	resp, err := c.Delete(path, nil)
	if err != nil {
		return err
	}

	var r *statusResp
	if err := decodeJSON(&r, resp.Body); err != nil {
		return err
	}
	if !r.Ok() {
		return fmt.Errorf("Not Ok")
	}
	return nil
}
<commit_msg>Adding request_logging as a parameter<commit_after>package fastly

import (
	"fmt"
)

// BigQuery represents a BigQuery logging response from the Fastly API.
type BigQuery struct {
	ServiceID         string `mapstructure:"service_id"`
	Name              string `mapstructure:"name"`
	Format            string `mapstructure:"format"`
	User              string `mapstructure:"user"`
	ProjectID         string `mapstructure:"project_id"`
	Dataset           string `mapstructure:"dataset"`
	Table             string `mapstructure:"table"`
	SecretKey         string `mapstructure:"secret_key"`
	CreatedAt         string `mapstructure:"created_at"`
	UpdatedAt         string `mapstructure:"updated_at"`
	DeletedAt         string `mapstructure:"deleted_at"`
	ResponseCondition string `mapstructure:"response_condition"`
}

// GetBigQueryInput is used as input to the GetBigQuery function.
type GetBigQueryInput struct {
	// Service is the ID of the service. Version is the specific configuration
	// version. Both fields are required.
	Service string
	Version int
}

// GetBigQuery lists all BigQuerys associated with a service version.
func (c *Client) GetBigQuery(i *GetBigQueryInput) ([]*BigQuery, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == 0 {
		return nil, ErrMissingVersion
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery", i.Service, i.Version)
	resp, err := c.Get(path, nil)
	if err != nil {
		return nil, err
	}

	var bs []*BigQuery
	if err := decodeJSON(&bs, resp.Body); err != nil {
		return nil, err
	}
	return bs, nil
}

// CreateBigQueryInput is used as input to the CreateBigQuery function.
type CreateBigQueryInput struct {
	// All fields other than format are required.
	// Service is the ID of the service.
	Service string

	//Version is the specific configuration version.
	Version int

	// Name is the name if your bigquery logging endpoint.
	Name string

	// Project ID your GCP project ID.
	ProjectID string

	// Dataset is your BigQuery dataset.
	Dataset string

	// Table is your BigQuery table.
	Table string

	// User is the user with access to write to your BigQuery dataset.
	User string

	// Secret key is the user's secret key.
	SecretKey string

	// Format is the log formatting desired for your BigQuery dataset.
	// Optional.
	Format string

	// ResponseCondition allows you to attach a response condition to your BigQuery logging endpoint.
	// Optional.
	ResponseCondition string
}

// CreateBigQuery creates a new Fastly BigQuery logging endpoint.
func (c *Client) CreateBigQuery(i *CreateBigQueryInput) (*BigQuery, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == 0 {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	if i.ProjectID == "" {
		return nil, ErrMissingProjectID
	}

	if i.Dataset == "" {
		return nil, ErrMissingDataset
	}

	if i.Table == "" {
		return nil, ErrMissingTable
	}

	if i.User == "" {
		return nil, ErrMissingUser
	}

	if i.SecretKey == "" {
		return nil, ErrMissingSecretKey
	}

	params := make(map[string]string)
	params["name"] = i.Name
	params["project_id"] = i.ProjectID
	params["dataset"] = i.Dataset
	params["table"] = i.Table
	params["user"] = i.User
	params["secret_key"] = i.SecretKey
	if i.Format != "" {
		params["format"] = i.Format
	}
	if i.ResponseCondition != "" {
		params["response_condition"] = i.ResponseCondition
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery", i.Service, i.Version)
	resp, err := c.PostForm(path, i, &RequestOptions{
		Params: params,
	})
	if err != nil {
		return nil, err
	}

	var b *BigQuery
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// UpdateBigQueryInput is used as input to the UpdateBigQuery function.
type UpdateBigQueryInput struct {
	// Service is the ID of the service.
	// This field is required.
	Service string

	//Version is the specific configuration version.
	// This field is required.
	Version int

	// Name is the old name if your bigquery logging endpoint.
	// Used to identify the correct BigQuery logging endpoint if there
	// is a name change.
	// This field is required.
	Name string

	// NewName is the new name of your BigQuery logging endpoint.
	// This field is required.
	NewName string

	// Project ID your GCP project ID.
	ProjectID string

	// Dataset is your BigQuery dataset.
	Dataset string

	// Table is your BigQuery table.
	Table string

	// User is the user with access to write to your BigQuery dataset.
	User string

	// Secret key is the user's secret key.
	SecretKey string

	// Format is the log formatting desired for your BigQuery dataset.
	// Optional.
	Format string

	// ResponseCondition allows you to attach a response condition to your BigQuery logging endpoint.
	// Optional.
	ResponseCondition string
}

// UpdateBigQuery updates a BigQuery logging endpoint.
func (c *Client) UpdateBigQuery(i *UpdateBigQueryInput) (*BigQuery, error) {
	if i.Service == "" {
		return nil, ErrMissingService
	}

	if i.Version == 0 {
		return nil, ErrMissingVersion
	}

	if i.Name == "" {
		return nil, ErrMissingName
	}

	if i.NewName == "" {
		return nil, ErrMissingNewName
	}

	params := make(map[string]string)
	params["name"] = i.NewName
	if i.ProjectID != "" {
		params["project_id"] = i.ProjectID
	}
	if i.Dataset != "" {
		params["dataset"] = i.Dataset
	}
	if i.Table != "" {
		params["table"] = i.Table
	}
	if i.User != "" {
		params["user"] = i.User
	}
	if i.SecretKey != "" {
		params["secret_key"] = i.SecretKey
	}
	if i.Format != "" {
		params["format"] = i.Format
	}
	if i.ResponseCondition != "" {
		params["response_condition"] = i.ResponseCondition
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery/%s", i.Service, i.Version, i.Name)
	resp, err := c.PutForm(path, i, &RequestOptions{
		Params: params,
	})
	if err != nil {
		return nil, err
	}

	var b *BigQuery
	if err := decodeJSON(&b, resp.Body); err != nil {
		return nil, err
	}
	return b, nil
}

// DeleteBigQueryInput is the input parameter to DeleteBigQuery.
// All fields are required.
type DeleteBigQueryInput struct {
	// Service is the ID of the service.
	Service string

	// Version is the specific configuration.
	Version int

	// Name is the name of the BigQuery logging endpoint to delete.
	Name string
}

// DeleteBigQuery deletes the given BigQuery logging endpoint.
func (c *Client) DeleteBigQuery(i *DeleteBigQueryInput) error {
	if i.Service == "" {
		return ErrMissingService
	}

	if i.Version == 0 {
		return ErrMissingVersion
	}

	if i.Name == "" {
		return ErrMissingName
	}

	path := fmt.Sprintf("/service/%s/version/%d/logging/bigquery/%s", i.Service, i.Version, i.Name)
	resp, err := c.Delete(path, nil)
	if err != nil {
		return err
	}

	var r *statusResp
	if err := decodeJSON(&r, resp.Body); err != nil {
		return err
	}
	if !r.Ok() {
		return fmt.Errorf("Not Ok")
	}
	return nil
}
<|endoftext|>


<commit_before>package sub

import "testing"

func TestPrintMeSomething(t *testing.T) {
	PrintMeSomething()
}
<commit_msg>Break test!<commit_after>package sub

import "testing"

func TestPrintMeSomething(t *testing.T) {
	PrintMeSomething()
	t.FailNow()
}
<|endoftext|>