<commit_before>// Copyright 2015 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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 grpc supports network connections to GRPC servers.
// This package is not intended for use by end developers. Use the
// google.golang.org/api/option package to configure API clients.
package grpc

import (
	"context"
	"errors"
	"log"
	"os"
	"strings"

	"go.opencensus.io/plugin/ocgrpc"
	"golang.org/x/oauth2"
	"google.golang.org/api/internal"
	"google.golang.org/api/option"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	grpcgoogle "google.golang.org/grpc/credentials/google"
	"google.golang.org/grpc/credentials/oauth"

	// Install grpclb, which is required for direct path.
	_ "google.golang.org/grpc/balancer/grpclb"
)

// Set at init time by dial_appengine.go. If nil, we're not on App Engine.
var appengineDialerHook func(context.Context) grpc.DialOption

// Dial returns a GRPC connection for use communicating with a Google cloud
// service, configured with the given ClientOptions.
func Dial(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	return dial(ctx, false, opts)
}

// DialInsecure returns an insecure GRPC connection for use communicating
// with fake or mock Google cloud service implementations, such as emulators.
// The connection is configured with the given ClientOptions.
func DialInsecure(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	return dial(ctx, true, opts)
}

func dial(ctx context.Context, insecure bool, opts []option.ClientOption) (*grpc.ClientConn, error) {
	var o internal.DialSettings
	for _, opt := range opts {
		opt.Apply(&o)
	}
	if err := o.Validate(); err != nil {
		return nil, err
	}
	if o.HTTPClient != nil {
		return nil, errors.New("unsupported HTTP client specified")
	}
	if o.GRPCConn != nil {
		return o.GRPCConn, nil
	}
	var grpcOpts []grpc.DialOption
	if insecure {
		grpcOpts = []grpc.DialOption{grpc.WithInsecure()}
	} else if !o.NoAuth {
		if o.APIKey != "" {
			log.Print("API keys are not supported for gRPC APIs. Remove the WithAPIKey option from your client-creating call.")
		}
		creds, err := internal.Creds(ctx, &o)
		if err != nil {
			return nil, err
		}
		// Attempt Direct Path only if:
		// * The endpoint is a host:port (or dns:///host:port).
		// * Credentials are obtained via GCE metadata server, using the default
		//   service account.
		// * Opted in via GOOGLE_CLOUD_ENABLE_DIRECT_PATH environment variable.
		//   For example, GOOGLE_CLOUD_ENABLE_DIRECT_PATH=spanner,pubsub
		if isDirectPathEnabled(o.Endpoint) && isTokenSourceDirectPathCompatible(creds.TokenSource) {
			if !strings.HasPrefix(o.Endpoint, "dns:///") {
				o.Endpoint = "dns:///" + o.Endpoint
			}
			grpcOpts = []grpc.DialOption{
				grpc.WithCredentialsBundle(
					grpcgoogle.NewComputeEngineCredentials(),
				),
			}
			// TODO(cbro): add support for system parameters (quota project, request reason) via chained interceptor.
		} else {
			grpcOpts = []grpc.DialOption{
				grpc.WithPerRPCCredentials(grpcTokenSource{
					TokenSource:   oauth.TokenSource{creds.TokenSource},
					quotaProject:  o.QuotaProject,
					requestReason: o.RequestReason,
				}),
				grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
			}
		}
	}
	if appengineDialerHook != nil {
		// Use the Socket API on App Engine.
		grpcOpts = append(grpcOpts, appengineDialerHook(ctx))
	}
	// Add tracing, but before the other options, so that clients can override the
	// gRPC stats handler.
	// This assumes that gRPC options are processed in order, left to right.
	grpcOpts = addOCStatsHandler(grpcOpts)
	grpcOpts = append(grpcOpts, o.GRPCDialOpts...)
	if o.UserAgent != "" {
		grpcOpts = append(grpcOpts, grpc.WithUserAgent(o.UserAgent))
	}
	return grpc.DialContext(ctx, o.Endpoint, grpcOpts...)
}

func addOCStatsHandler(opts []grpc.DialOption) []grpc.DialOption {
	return append(opts, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
}

// grpcTokenSource supplies PerRPCCredentials from an oauth.TokenSource.
type grpcTokenSource struct {
	oauth.TokenSource

	// Additional metadata attached as headers.
	quotaProject  string
	requestReason string
}

// GetRequestMetadata gets the request metadata as a map from a grpcTokenSource.
func (ts grpcTokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (
	map[string]string, error) {
	metadata, err := ts.TokenSource.GetRequestMetadata(ctx, uri...)
	if err != nil {
		return nil, err
	}

	// Attach system parameters into the metadata
	if ts.quotaProject != "" {
		metadata["X-goog-user-project"] = ts.quotaProject
	}
	if ts.requestReason != "" {
		metadata["X-goog-request-reason"] = ts.requestReason
	}
	return metadata, nil
}

func isTokenSourceDirectPathCompatible(ts oauth2.TokenSource) bool {
	if ts == nil {
		return false
	}
	tok, err := ts.Token()
	if err != nil {
		return false
	}
	if tok == nil {
		return false
	}
	if source, _ := tok.Extra("oauth2.google.tokenSource").(string); source != "compute-metadata" {
		return false
	}
	if acct, _ := tok.Extra("oauth2.google.serviceAccount").(string); acct != "default" {
		return false
	}
	return true
}

func isDirectPathEnabled(endpoint string) bool {
	// Only host:port is supported, not other schemes (e.g., "tcp://" or "unix://").
	// Also don't try direct path if the user has chosen an alternate name resolver
	// (i.e., via ":///" prefix).
	//
	// TODO(cbro): once gRPC has introspectible options, check the user hasn't
	// provided a custom dialer in gRPC options.
	if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") {
		return false
	}

	// Only try direct path if the user has opted in via the environment variable.
	whitelist := strings.Split(os.Getenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH"), ",")
	for _, api := range whitelist {
		if strings.Contains(endpoint, api) {
			return true
		}
	}
	return false
}
<commit_msg>transport/grpc: Fix check of GOOGLE_CLOUD_ENABLE_DIRECT_PATH<commit_after>// Copyright 2015 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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 grpc supports network connections to GRPC servers.
// This package is not intended for use by end developers. Use the
// google.golang.org/api/option package to configure API clients.
package grpc

import (
	"context"
	"errors"
	"log"
	"os"
	"strings"

	"go.opencensus.io/plugin/ocgrpc"
	"golang.org/x/oauth2"
	"google.golang.org/api/internal"
	"google.golang.org/api/option"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	grpcgoogle "google.golang.org/grpc/credentials/google"
	"google.golang.org/grpc/credentials/oauth"

	// Install grpclb, which is required for direct path.
	_ "google.golang.org/grpc/balancer/grpclb"
)

// Set at init time by dial_appengine.go. If nil, we're not on App Engine.
var appengineDialerHook func(context.Context) grpc.DialOption

// Dial returns a GRPC connection for use communicating with a Google cloud
// service, configured with the given ClientOptions.
func Dial(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	return dial(ctx, false, opts)
}

// DialInsecure returns an insecure GRPC connection for use communicating
// with fake or mock Google cloud service implementations, such as emulators.
// The connection is configured with the given ClientOptions.
func DialInsecure(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	return dial(ctx, true, opts)
}

func dial(ctx context.Context, insecure bool, opts []option.ClientOption) (*grpc.ClientConn, error) {
	var o internal.DialSettings
	for _, opt := range opts {
		opt.Apply(&o)
	}
	if err := o.Validate(); err != nil {
		return nil, err
	}
	if o.HTTPClient != nil {
		return nil, errors.New("unsupported HTTP client specified")
	}
	if o.GRPCConn != nil {
		return o.GRPCConn, nil
	}
	var grpcOpts []grpc.DialOption
	if insecure {
		grpcOpts = []grpc.DialOption{grpc.WithInsecure()}
	} else if !o.NoAuth {
		if o.APIKey != "" {
			log.Print("API keys are not supported for gRPC APIs. Remove the WithAPIKey option from your client-creating call.")
		}
		creds, err := internal.Creds(ctx, &o)
		if err != nil {
			return nil, err
		}
		// Attempt Direct Path only if:
		// * The endpoint is a host:port (or dns:///host:port).
		// * Credentials are obtained via GCE metadata server, using the default
		//   service account.
		// * Opted in via GOOGLE_CLOUD_ENABLE_DIRECT_PATH environment variable.
		//   For example, GOOGLE_CLOUD_ENABLE_DIRECT_PATH=spanner,pubsub
		if isDirectPathEnabled(o.Endpoint) && isTokenSourceDirectPathCompatible(creds.TokenSource) {
			if !strings.HasPrefix(o.Endpoint, "dns:///") {
				o.Endpoint = "dns:///" + o.Endpoint
			}
			grpcOpts = []grpc.DialOption{
				grpc.WithCredentialsBundle(
					grpcgoogle.NewComputeEngineCredentials(),
				),
			}
			// TODO(cbro): add support for system parameters (quota project, request reason) via chained interceptor.
		} else {
			grpcOpts = []grpc.DialOption{
				grpc.WithPerRPCCredentials(grpcTokenSource{
					TokenSource:   oauth.TokenSource{creds.TokenSource},
					quotaProject:  o.QuotaProject,
					requestReason: o.RequestReason,
				}),
				grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
			}
		}
	}
	if appengineDialerHook != nil {
		// Use the Socket API on App Engine.
		grpcOpts = append(grpcOpts, appengineDialerHook(ctx))
	}
	// Add tracing, but before the other options, so that clients can override the
	// gRPC stats handler.
	// This assumes that gRPC options are processed in order, left to right.
	grpcOpts = addOCStatsHandler(grpcOpts)
	grpcOpts = append(grpcOpts, o.GRPCDialOpts...)
	if o.UserAgent != "" {
		grpcOpts = append(grpcOpts, grpc.WithUserAgent(o.UserAgent))
	}
	return grpc.DialContext(ctx, o.Endpoint, grpcOpts...)
}

func addOCStatsHandler(opts []grpc.DialOption) []grpc.DialOption {
	return append(opts, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
}

// grpcTokenSource supplies PerRPCCredentials from an oauth.TokenSource.
type grpcTokenSource struct {
	oauth.TokenSource

	// Additional metadata attached as headers.
	quotaProject  string
	requestReason string
}

// GetRequestMetadata gets the request metadata as a map from a grpcTokenSource.
func (ts grpcTokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (
	map[string]string, error) {
	metadata, err := ts.TokenSource.GetRequestMetadata(ctx, uri...)
	if err != nil {
		return nil, err
	}

	// Attach system parameters into the metadata
	if ts.quotaProject != "" {
		metadata["X-goog-user-project"] = ts.quotaProject
	}
	if ts.requestReason != "" {
		metadata["X-goog-request-reason"] = ts.requestReason
	}
	return metadata, nil
}

func isTokenSourceDirectPathCompatible(ts oauth2.TokenSource) bool {
	if ts == nil {
		return false
	}
	tok, err := ts.Token()
	if err != nil {
		return false
	}
	if tok == nil {
		return false
	}
	if source, _ := tok.Extra("oauth2.google.tokenSource").(string); source != "compute-metadata" {
		return false
	}
	if acct, _ := tok.Extra("oauth2.google.serviceAccount").(string); acct != "default" {
		return false
	}
	return true
}

func isDirectPathEnabled(endpoint string) bool {
	// Only host:port is supported, not other schemes (e.g., "tcp://" or "unix://").
	// Also don't try direct path if the user has chosen an alternate name resolver
	// (i.e., via ":///" prefix).
	//
	// TODO(cbro): once gRPC has introspectible options, check the user hasn't
	// provided a custom dialer in gRPC options.
	if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") {
		return false
	}

	// Only try direct path if the user has opted in via the environment variable.
	whitelist := strings.Split(os.Getenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH"), ",")
	for _, api := range whitelist {
		// Ignore empty string since an empty env variable splits into [""]
		if api != "" && strings.Contains(endpoint, api) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2017 Monax Industries Limited
//
// 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 client

import (
	"bytes"
	"fmt"
	"time"

	"github.com/tendermint/go-rpc/client"
	"github.com/tendermint/go-wire"

	"github.com/monax/eris-db/logging"
	"github.com/monax/eris-db/logging/loggers"
	ctypes "github.com/monax/eris-db/rpc/tendermint/core/types"
	"github.com/monax/eris-db/txs"
)

const (
	MaxCommitWaitTimeSeconds = 10
)

type Confirmation struct {
	BlockHash []byte
	Event     txs.EventData
	Exception error
	Error     error
}

// NOTE [ben] Compiler check to ensure erisNodeClient successfully implements
// eris-db/client.NodeClient
var _ NodeWebsocketClient = (*erisNodeWebsocketClient)(nil)

type erisNodeWebsocketClient struct {
	// TODO: assert no memory leak on closing with open websocket
	tendermintWebsocket *rpcclient.WSClient
	logger              loggers.InfoTraceLogger
}

// Subscribe to an eventid
func (erisNodeWebsocketClient *erisNodeWebsocketClient) Subscribe(eventid string) error {
	// TODO we can in the background listen to the subscription id and remember it to ease unsubscribing later.
	return erisNodeWebsocketClient.tendermintWebsocket.Subscribe(eventid)
}

// Unsubscribe from an eventid
func (erisNodeWebsocketClient *erisNodeWebsocketClient) Unsubscribe(subscriptionId string) error {
	return erisNodeWebsocketClient.tendermintWebsocket.Unsubscribe(subscriptionId)
}

// Returns a channel that will receive a confirmation with a result or the exception that
// has been confirmed; or an error is returned and the confirmation channel is nil.
func (erisNodeWebsocketClient *erisNodeWebsocketClient) WaitForConfirmation(tx txs.Tx, chainId string, inputAddr []byte) (chan Confirmation, error) {
	// check no errors are reported on the websocket
	if err := erisNodeWebsocketClient.assertNoErrors(); err != nil {
		return nil, err
	}

	// Setup the confirmation channel to be returned
	confirmationChannel := make(chan Confirmation, 1)
	var latestBlockHash []byte

	eid := txs.EventStringAccInput(inputAddr)
	if err := erisNodeWebsocketClient.tendermintWebsocket.Subscribe(eid); err != nil {
		return nil, fmt.Errorf("Error subscribing to AccInput event (%s): %v", eid, err)
	}
	if err := erisNodeWebsocketClient.tendermintWebsocket.Subscribe(txs.EventStringNewBlock()); err != nil {
		return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err)
	}
	// Read the incoming events
	go func() {
		var err error
		for {
			resultBytes := <-erisNodeWebsocketClient.tendermintWebsocket.ResultsCh
			result := new(ctypes.ErisDBResult)
			if wire.ReadJSONPtr(result, resultBytes, &err); err != nil {
				// keep calm and carry on
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Failed to unmarshal json bytes for websocket event",
					"error", err)
				continue
			}

			subscription, ok := (*result).(*ctypes.ResultSubscribe)
			if ok {
				// Received confirmation of subscription to event streams
				// TODO: collect subscription IDs, push into channel and on completion
				// unsubscribe
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Received confirmation for event",
					"event", subscription.Event,
					"subscription_id", subscription.SubscriptionId)
				continue
			}

			event, ok := (*result).(*ctypes.ResultEvent)
			if !ok {
				// keep calm and carry on
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Failed to cast to ResultEvent for websocket event",
					"event", event.Event)
				continue
			}

			// NOTE: [ben] hotfix on 0.16.1 because NewBlock events looks to arrive late
			// both in tests; so we now miss events;  This check is safely removed because
			// for CallTx on checking the transaction the EVM is not run and no false positive event
			// is sent; neither is this check a good check for that.

			// blockData, ok := event.Data.(txs.EventDataNewBlock)
			// if ok {
			// 	latestBlockHash = blockData.Block.Hash()
			// 	logging.TraceMsg(erisNodeWebsocketClient.logger, "Registered new block",
			// 		"block", blockData.Block,
			// 		"latest_block_hash", latestBlockHash,
			// 	)
			// 	continue
			// }

			// we don't accept events unless they came after a new block (ie. in)
			if latestBlockHash == nil {
				logging.InfoMsg(erisNodeWebsocketClient.logger, "First block has not been registered so ignoring event",
					"event", event.Event)
				continue
			}

			if event.Event != eid {
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Received unsolicited event",
					"event_received", event.Event,
					"event_expected", eid)
				continue
			}

			data, ok := event.Data.(txs.EventDataTx)
			if !ok {
				// We are on the lookout for EventDataTx
				confirmationChannel <- Confirmation{
					BlockHash: latestBlockHash,
					Event:     nil,
					Exception: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx"),
					Error:     nil,
				}
				return
			}

			if !bytes.Equal(txs.TxHash(chainId, data.Tx), txs.TxHash(chainId, tx)) {
				logging.TraceMsg(erisNodeWebsocketClient.logger, "Received different event",
					// TODO: consider re-implementing TxID again, or other more clear debug
					"received transaction event", txs.TxHash(chainId, data.Tx))
				continue
			}

			if data.Exception != "" {
				confirmationChannel <- Confirmation{
					BlockHash: latestBlockHash,
					Event:     &data,
					Exception: fmt.Errorf("Transaction confirmed with exception:", data.Exception),
					Error:     nil,
				}
				return
			}
			// success, return the full event and blockhash and exit go-routine
			confirmationChannel <- Confirmation{
				BlockHash: latestBlockHash,
				Event:     &data,
				Exception: nil,
				Error:     nil,
			}
			return
		}

	}()

	// TODO: [ben] this is a draft implementation as resources on time.After can not be
	// recovered before the timeout.  Close-down timeout at success properly.
	timeout := time.After(time.Duration(MaxCommitWaitTimeSeconds) * time.Second)

	go func() {
		<-timeout
		confirmationChannel <- Confirmation{
			BlockHash: nil,
			Event:     nil,
			Exception: nil,
			Error:     fmt.Errorf("timed out waiting for event"),
		}
		return
	}()
	return confirmationChannel, nil
}

func (erisNodeWebsocketClient *erisNodeWebsocketClient) Close() {
	if erisNodeWebsocketClient.tendermintWebsocket != nil {
		erisNodeWebsocketClient.tendermintWebsocket.Stop()
	}
}

func (erisNodeWebsocketClient *erisNodeWebsocketClient) assertNoErrors() error {
	if erisNodeWebsocketClient.tendermintWebsocket != nil {
		select {
		case err := <-erisNodeWebsocketClient.tendermintWebsocket.ErrorsCh:
			return err
		default:
			return nil
		}
	} else {
		return fmt.Errorf("Eris-client has no websocket initialised.")
	}
}
<commit_msg>client: correct yesterdays hotfix for event confirmation<commit_after>// Copyright 2017 Monax Industries Limited
//
// 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 client

import (
	"bytes"
	"fmt"
	"time"

	"github.com/tendermint/go-rpc/client"
	"github.com/tendermint/go-wire"

	"github.com/monax/eris-db/logging"
	"github.com/monax/eris-db/logging/loggers"
	ctypes "github.com/monax/eris-db/rpc/tendermint/core/types"
	"github.com/monax/eris-db/txs"
)

const (
	MaxCommitWaitTimeSeconds = 10
)

type Confirmation struct {
	BlockHash []byte
	Event     txs.EventData
	Exception error
	Error     error
}

// NOTE [ben] Compiler check to ensure erisNodeClient successfully implements
// eris-db/client.NodeClient
var _ NodeWebsocketClient = (*erisNodeWebsocketClient)(nil)

type erisNodeWebsocketClient struct {
	// TODO: assert no memory leak on closing with open websocket
	tendermintWebsocket *rpcclient.WSClient
	logger              loggers.InfoTraceLogger
}

// Subscribe to an eventid
func (erisNodeWebsocketClient *erisNodeWebsocketClient) Subscribe(eventid string) error {
	// TODO we can in the background listen to the subscription id and remember it to ease unsubscribing later.
	return erisNodeWebsocketClient.tendermintWebsocket.Subscribe(eventid)
}

// Unsubscribe from an eventid
func (erisNodeWebsocketClient *erisNodeWebsocketClient) Unsubscribe(subscriptionId string) error {
	return erisNodeWebsocketClient.tendermintWebsocket.Unsubscribe(subscriptionId)
}

// Returns a channel that will receive a confirmation with a result or the exception that
// has been confirmed; or an error is returned and the confirmation channel is nil.
func (erisNodeWebsocketClient *erisNodeWebsocketClient) WaitForConfirmation(tx txs.Tx, chainId string, inputAddr []byte) (chan Confirmation, error) {
	// check no errors are reported on the websocket
	if err := erisNodeWebsocketClient.assertNoErrors(); err != nil {
		return nil, err
	}

	// Setup the confirmation channel to be returned
	confirmationChannel := make(chan Confirmation, 1)
	var latestBlockHash []byte

	eid := txs.EventStringAccInput(inputAddr)
	if err := erisNodeWebsocketClient.tendermintWebsocket.Subscribe(eid); err != nil {
		return nil, fmt.Errorf("Error subscribing to AccInput event (%s): %v", eid, err)
	}
	if err := erisNodeWebsocketClient.tendermintWebsocket.Subscribe(txs.EventStringNewBlock()); err != nil {
		return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err)
	}
	// Read the incoming events
	go func() {
		var err error
		for {
			resultBytes := <-erisNodeWebsocketClient.tendermintWebsocket.ResultsCh
			result := new(ctypes.ErisDBResult)
			if wire.ReadJSONPtr(result, resultBytes, &err); err != nil {
				// keep calm and carry on
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Failed to unmarshal json bytes for websocket event",
					"error", err)
				continue
			}

			subscription, ok := (*result).(*ctypes.ResultSubscribe)
			if ok {
				// Received confirmation of subscription to event streams
				// TODO: collect subscription IDs, push into channel and on completion
				// unsubscribe
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Received confirmation for event",
					"event", subscription.Event,
					"subscription_id", subscription.SubscriptionId)
				continue
			}

			event, ok := (*result).(*ctypes.ResultEvent)
			if !ok {
				// keep calm and carry on
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Failed to cast to ResultEvent for websocket event",
					"event", event.Event)
				continue
			}

			blockData, ok := event.Data.(txs.EventDataNewBlock)
			if ok {
				latestBlockHash = blockData.Block.Hash()
				logging.TraceMsg(erisNodeWebsocketClient.logger, "Registered new block",
					"block", blockData.Block,
					"latest_block_hash", latestBlockHash,
				)
				continue
			}

			// NOTE: [ben] hotfix on 0.16.1 because NewBlock events to arrive seemingly late
			// we now miss events because of this redundant check;  This check is safely removed
			// because for CallTx on checking the transaction is not run in the EVM and no false
			// positive event can be sent; neither is this check a good check for that.

			// we don't accept events unless they came after a new block (ie. in)
			// if latestBlockHash == nil {
			// 	logging.InfoMsg(erisNodeWebsocketClient.logger, "First block has not been registered so ignoring event",
			// 		"event", event.Event)
			// 	continue
			// }

			if event.Event != eid {
				logging.InfoMsg(erisNodeWebsocketClient.logger, "Received unsolicited event",
					"event_received", event.Event,
					"event_expected", eid)
				continue
			}

			data, ok := event.Data.(txs.EventDataTx)
			if !ok {
				// We are on the lookout for EventDataTx
				confirmationChannel <- Confirmation{
					BlockHash: latestBlockHash,
					Event:     nil,
					Exception: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx"),
					Error:     nil,
				}
				return
			}

			if !bytes.Equal(txs.TxHash(chainId, data.Tx), txs.TxHash(chainId, tx)) {
				logging.TraceMsg(erisNodeWebsocketClient.logger, "Received different event",
					// TODO: consider re-implementing TxID again, or other more clear debug
					"received transaction event", txs.TxHash(chainId, data.Tx))
				continue
			}

			if data.Exception != "" {
				confirmationChannel <- Confirmation{
					BlockHash: latestBlockHash,
					Event:     &data,
					Exception: fmt.Errorf("Transaction confirmed with exception:", data.Exception),
					Error:     nil,
				}
				return
			}
			// success, return the full event and blockhash and exit go-routine
			confirmationChannel <- Confirmation{
				BlockHash: latestBlockHash,
				Event:     &data,
				Exception: nil,
				Error:     nil,
			}
			return
		}

	}()

	// TODO: [ben] this is a draft implementation as resources on time.After can not be
	// recovered before the timeout.  Close-down timeout at success properly.
	timeout := time.After(time.Duration(MaxCommitWaitTimeSeconds) * time.Second)

	go func() {
		<-timeout
		confirmationChannel <- Confirmation{
			BlockHash: nil,
			Event:     nil,
			Exception: nil,
			Error:     fmt.Errorf("timed out waiting for event"),
		}
		return
	}()
	return confirmationChannel, nil
}

func (erisNodeWebsocketClient *erisNodeWebsocketClient) Close() {
	if erisNodeWebsocketClient.tendermintWebsocket != nil {
		erisNodeWebsocketClient.tendermintWebsocket.Stop()
	}
}

func (erisNodeWebsocketClient *erisNodeWebsocketClient) assertNoErrors() error {
	if erisNodeWebsocketClient.tendermintWebsocket != nil {
		select {
		case err := <-erisNodeWebsocketClient.tendermintWebsocket.ErrorsCh:
			return err
		default:
			return nil
		}
	} else {
		return fmt.Errorf("Eris-client has no websocket initialised.")
	}
}
<|endoftext|>


<commit_before>package accessors

import (
	"encoding/json"
	"fmt"
	"log"
	"math"
	"math/rand"
	"strconv"
	"time"

	"github.com/kellydunn/golang-geo"
)

const nearbyEnemyCap = 100

// Returns an array of all loot locations and values to plot on the map in iOS
func (ag *AccessorGroup) DumpDatabase(userLatitude float64, userLongitude float64, radius float64) (string, error) {
	currentEnemyCount, err := ag.CountNearbyEnemies(userLatitude, userLongitude, radius)
	if err != nil {
		log.Panic(err)
	}

	if currentEnemyCount < nearbyEnemyCap {
		// Add enemies
		ag.AddEnemies(userLatitude, userLongitude, radius, currentEnemyCount, nearbyEnemyCap)
	}

	rows, err := ag.DB.Query("SELECT * FROM enemies")
	if err != nil {
		log.Panic(err)
	}

	defer rows.Close()
	columns, err := rows.Columns()
	if err != nil {
		log.Panic(err)
	}

	count := len(columns)
	tableData := make([]map[string]string, 0)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)

	for rows.Next() {
		for i := 0; i < count; i++ {
			valuePtrs[i] = &values[i]
		}

		rows.Scan(valuePtrs...)
		entry := make(map[string]string)

		for i, col := range columns {
			val := values[i]
			if val != nil {
				entry[col] = fmt.Sprintf("%s", string(val.([]byte))) // Save the data as a string
			}
		}

		if len(entry["latitude"]) > 0 && len(entry["latitude"]) > 0 { // Make sure we don't have bad data
			latitude, err := strconv.ParseFloat(entry["latitude"], 64)
			if err == nil {
				longitude, err := strconv.ParseFloat(entry["longitude"], 64)
				if err == nil {
					if WithinRadius(latitude, longitude, userLatitude, userLongitude, radius) { // Only return enemies that are close to the player
						tableData = append(tableData, entry)
					}
				} else {
					log.Panic(err)
				}
			} else {
				log.Panic(err)
			}
		}
	}

	jsonData, err := json.Marshal(tableData)
	if err != nil {
		log.Panic(err)
	}

	return string(jsonData), nil
}

func (ag *AccessorGroup) CountNearbyEnemies(userLatitude float64, userLongitude float64, radius float64) (int, error) {
	enemyCount := 0

	rows, err := ag.DB.Query("SELECT * FROM enemies")
	if err != nil {
		log.Panic(err)
	}

	defer rows.Close()
	columns, err := rows.Columns()
	if err != nil {
		log.Panic(err)
	}

	count := len(columns)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)

	for rows.Next() {
		for i := 0; i < count; i++ {
			valuePtrs[i] = &values[i]
		}

		rows.Scan(valuePtrs...)
		entry := make(map[string]string)

		for i, col := range columns {
			val := values[i]
			if val != nil {
				entry[col] = fmt.Sprintf("%s", string(val.([]byte))) // Save the data as a string
			}
		}

		if len(entry["latitude"]) > 0 && len(entry["latitude"]) > 0 { // Make sure we don't have bad data
			latitude, err := strconv.ParseFloat(entry["latitude"], 64)
			if err == nil {
				longitude, err := strconv.ParseFloat(entry["longitude"], 64)
				if err == nil {
					if WithinRadius(latitude, longitude, userLatitude, userLongitude, radius) { // Only return enemies that are close to the player
						enemyCount++
					}
				} else {
					log.Panic(err)
				}
			} else {
				log.Panic(err)
			}
		}
	}

	return enemyCount, nil
}

func WithinRadius(lat1 float64, lon1 float64, lat2 float64, lon2 float64, radius float64) bool {
	p := geo.NewPoint(lat1, lon1)
	p2 := geo.NewPoint(lat2, lon2)

	dist := p.GreatCircleDistance(p2) // Find the great circle distance between points

	if dist < radius { // Return whether we're inside the radius or not
		return true
	} else {
		return false
	}
}

func (ag *AccessorGroup) AddEnemies(userLatitude float64, userLongitude float64, radius float64, currentEnemyCount int, enemyCap int) {
	iterations := enemyCap - currentEnemyCount
	rand.Seed(time.Now().UTC().UnixNano())

	for i := 0; i < iterations; i++ {
		w := radius / 111 * math.Sqrt(rand.Float64())
		t := 2 * math.Pi * rand.Float64()
		x := w * math.Cos(t)
		y := w * math.Sin(t)

		randomLatitude := userLatitude + x
		randomLongitude := userLongitude + y

		_, err := ag.DB.Exec("INSERT INTO enemies (latitude, longitude) VALUES (?,?)", randomLatitude, randomLongitude)
		if err != nil {
			log.Panic(err)
		}
	}
}
<commit_msg>Increasing the enemy limit<commit_after>package accessors

import (
	"encoding/json"
	"fmt"
	"log"
	"math"
	"math/rand"
	"strconv"
	"time"

	"github.com/kellydunn/golang-geo"
)

const nearbyEnemyCap = 300

// Returns an array of all loot locations and values to plot on the map in iOS
func (ag *AccessorGroup) DumpDatabase(userLatitude float64, userLongitude float64, radius float64) (string, error) {
	currentEnemyCount, err := ag.CountNearbyEnemies(userLatitude, userLongitude, radius)
	if err != nil {
		log.Panic(err)
	}

	if currentEnemyCount < nearbyEnemyCap {
		// Add enemies
		ag.AddEnemies(userLatitude, userLongitude, radius, currentEnemyCount, nearbyEnemyCap)
	}

	rows, err := ag.DB.Query("SELECT * FROM enemies")
	if err != nil {
		log.Panic(err)
	}

	defer rows.Close()
	columns, err := rows.Columns()
	if err != nil {
		log.Panic(err)
	}

	count := len(columns)
	tableData := make([]map[string]string, 0)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)

	for rows.Next() {
		for i := 0; i < count; i++ {
			valuePtrs[i] = &values[i]
		}

		rows.Scan(valuePtrs...)
		entry := make(map[string]string)

		for i, col := range columns {
			val := values[i]
			if val != nil {
				entry[col] = fmt.Sprintf("%s", string(val.([]byte))) // Save the data as a string
			}
		}

		if len(entry["latitude"]) > 0 && len(entry["latitude"]) > 0 { // Make sure we don't have bad data
			latitude, err := strconv.ParseFloat(entry["latitude"], 64)
			if err == nil {
				longitude, err := strconv.ParseFloat(entry["longitude"], 64)
				if err == nil {
					if WithinRadius(latitude, longitude, userLatitude, userLongitude, radius) { // Only return enemies that are close to the player
						tableData = append(tableData, entry)
					}
				} else {
					log.Panic(err)
				}
			} else {
				log.Panic(err)
			}
		}
	}

	jsonData, err := json.Marshal(tableData)
	if err != nil {
		log.Panic(err)
	}

	return string(jsonData), nil
}

func (ag *AccessorGroup) CountNearbyEnemies(userLatitude float64, userLongitude float64, radius float64) (int, error) {
	enemyCount := 0

	rows, err := ag.DB.Query("SELECT * FROM enemies")
	if err != nil {
		log.Panic(err)
	}

	defer rows.Close()
	columns, err := rows.Columns()
	if err != nil {
		log.Panic(err)
	}

	count := len(columns)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)

	for rows.Next() {
		for i := 0; i < count; i++ {
			valuePtrs[i] = &values[i]
		}

		rows.Scan(valuePtrs...)
		entry := make(map[string]string)

		for i, col := range columns {
			val := values[i]
			if val != nil {
				entry[col] = fmt.Sprintf("%s", string(val.([]byte))) // Save the data as a string
			}
		}

		if len(entry["latitude"]) > 0 && len(entry["latitude"]) > 0 { // Make sure we don't have bad data
			latitude, err := strconv.ParseFloat(entry["latitude"], 64)
			if err == nil {
				longitude, err := strconv.ParseFloat(entry["longitude"], 64)
				if err == nil {
					if WithinRadius(latitude, longitude, userLatitude, userLongitude, radius) { // Only return enemies that are close to the player
						enemyCount++
					}
				} else {
					log.Panic(err)
				}
			} else {
				log.Panic(err)
			}
		}
	}

	return enemyCount, nil
}

func WithinRadius(lat1 float64, lon1 float64, lat2 float64, lon2 float64, radius float64) bool {
	p := geo.NewPoint(lat1, lon1)
	p2 := geo.NewPoint(lat2, lon2)

	dist := p.GreatCircleDistance(p2) // Find the great circle distance between points

	if dist < radius { // Return whether we're inside the radius or not
		return true
	} else {
		return false
	}
}

func (ag *AccessorGroup) AddEnemies(userLatitude float64, userLongitude float64, radius float64, currentEnemyCount int, enemyCap int) {
	iterations := enemyCap - currentEnemyCount
	rand.Seed(time.Now().UTC().UnixNano())

	for i := 0; i < iterations; i++ {
		w := radius / 111 * math.Sqrt(rand.Float64())
		t := 2 * math.Pi * rand.Float64()
		x := w * math.Cos(t)
		y := w * math.Sin(t)

		randomLatitude := userLatitude + x
		randomLongitude := userLongitude + y

		_, err := ag.DB.Exec("INSERT INTO enemies (latitude, longitude) VALUES (?,?)", randomLatitude, randomLongitude)
		if err != nil {
			log.Panic(err)
		}
	}
}
<|endoftext|>


<commit_before>package action_test

import (
	"errors"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry/bosh-softlayer-cpi/action"

	fakevm "github.com/cloudfoundry/bosh-softlayer-cpi/softlayer/vm/fakes"
)

var _ = Describe("HasVM", func() {
	var (
		vmFinder *fakevm.FakeFinder
		action   HasVMAction
	)

	BeforeEach(func() {
		vmFinder = &fakevm.FakeFinder{}
		action = NewHasVM(vmFinder)
	})

	Describe("Run", func() {
		Context("when VM is found with given CID", func() {
			It("returns true without error", func() {
				vmFinder.FindFound = true
				vmFinder.FindVM = fakevm.NewFakeVM(1234)

				found, err := action.Run(1234)
				Expect(err).ToNot(HaveOccurred())
				Expect(found).To(BeTrue())
			})
		})

		Context("when VM is not found with given CID", func() {
			It("returns false without error", func() {
				vmFinder.FindFound = false

				found, err := action.Run(1234)
				Expect(err).ToNot(HaveOccurred())
				Expect(found).To(BeFalse())
			})
		})

		Context("when VM finding fails", func() {
			It("returns error", func() {
				vmFinder.FindFound = false
				vmFinder.FindErr = errors.New("fake-find-err")

				found, err := action.Run(1234)
				Expect(err).ToNot(HaveOccurred())
				Expect(found).To(BeFalse())
			})
		})
	})
})
<commit_msg>updated test context of has_vm_test.go<commit_after>package action_test

import (
	"errors"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry/bosh-softlayer-cpi/action"

	fakevm "github.com/cloudfoundry/bosh-softlayer-cpi/softlayer/vm/fakes"
)

var _ = Describe("HasVM", func() {
	var (
		vmFinder *fakevm.FakeFinder
		action   HasVMAction
	)

	BeforeEach(func() {
		vmFinder = &fakevm.FakeFinder{}
		action = NewHasVM(vmFinder)
	})

	Describe("Run", func() {
		Context("when VM is found with given CID", func() {
			It("returns true without error", func() {
				vmFinder.FindFound = true
				vmFinder.FindVM = fakevm.NewFakeVM(1234)

				found, err := action.Run(1234)
				Expect(err).ToNot(HaveOccurred())
				Expect(found).To(BeTrue())
			})
		})

		Context("when VM is not found with given CID", func() {
			It("returns false without error", func() {
				vmFinder.FindFound = false

				found, err := action.Run(1234)
				Expect(err).ToNot(HaveOccurred())
				Expect(found).To(BeFalse())
			})
		})

		Context("when VM finding fails", func() {
			It("returns false without error", func() {
				vmFinder.FindFound = false
				vmFinder.FindErr = errors.New("fake-find-err")

				found, err := action.Run(1234)
				Expect(err).ToNot(HaveOccurred())
				Expect(found).To(BeFalse())
			})
		})
	})
})
<|endoftext|>


<commit_before>/*
Copyright 2017 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 scheduling

import (
	"fmt"
	"strconv"
	"sync"

	"k8s.io/klog/v2"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/client-go/tools/cache"
)

// AssumeCache is a cache on top of the informer that allows for updating
// objects outside of informer events and also restoring the informer
// cache's version of the object.  Objects are assumed to be
// Kubernetes API objects that implement meta.Interface
type AssumeCache interface {
	// Assume updates the object in-memory only
	Assume(obj interface{}) error

	// Restore the informer cache's version of the object
	Restore(objName string)

	// Get the object by name
	Get(objName string) (interface{}, error)

	// Get the API object by name
	GetAPIObj(objName string) (interface{}, error)

	// List all the objects in the cache
	List(indexObj interface{}) []interface{}
}

type errWrongType struct {
	typeName string
	object   interface{}
}

func (e *errWrongType) Error() string {
	return fmt.Sprintf("could not convert object to type %v: %+v", e.typeName, e.object)
}

type errNotFound struct {
	typeName   string
	objectName string
}

func (e *errNotFound) Error() string {
	return fmt.Sprintf("could not find %v %q", e.typeName, e.objectName)
}

type errObjectName struct {
	detailedErr error
}

func (e *errObjectName) Error() string {
	return fmt.Sprintf("failed to get object name: %v", e.detailedErr)
}

// assumeCache stores two pointers to represent a single object:
// * The pointer to the informer object.
// * The pointer to the latest object, which could be the same as
//   the informer object, or an in-memory object.
//
// An informer update always overrides the latest object pointer.
//
// Assume() only updates the latest object pointer.
// Restore() sets the latest object pointer back to the informer object.
// Get/List() always returns the latest object pointer.
type assumeCache struct {
	// Synchronizes updates to store
	rwMutex sync.RWMutex

	// describes the object stored
	description string

	// Stores objInfo pointers
	store cache.Indexer

	// Index function for object
	indexFunc cache.IndexFunc
	indexName string
}

type objInfo struct {
	// name of the object
	name string

	// Latest version of object could be cached-only or from informer
	latestObj interface{}

	// Latest object from informer
	apiObj interface{}
}

func objInfoKeyFunc(obj interface{}) (string, error) {
	objInfo, ok := obj.(*objInfo)
	if !ok {
		return "", &errWrongType{"objInfo", obj}
	}
	return objInfo.name, nil
}

func (c *assumeCache) objInfoIndexFunc(obj interface{}) ([]string, error) {
	objInfo, ok := obj.(*objInfo)
	if !ok {
		return []string{""}, &errWrongType{"objInfo", obj}
	}
	return c.indexFunc(objInfo.latestObj)
}

// NewAssumeCache creates an assume cache for general objects.
func NewAssumeCache(informer cache.SharedIndexInformer, description, indexName string, indexFunc cache.IndexFunc) AssumeCache {
	c := &assumeCache{
		description: description,
		indexFunc:   indexFunc,
		indexName:   indexName,
	}
	indexers := cache.Indexers{}
	if indexName != "" && indexFunc != nil {
		indexers[indexName] = c.objInfoIndexFunc
	}
	c.store = cache.NewIndexer(objInfoKeyFunc, indexers)

	// Unit tests don't use informers
	if informer != nil {
		informer.AddEventHandler(
			cache.ResourceEventHandlerFuncs{
				AddFunc:    c.add,
				UpdateFunc: c.update,
				DeleteFunc: c.delete,
			},
		)
	}
	return c
}

func (c *assumeCache) add(obj interface{}) {
	if obj == nil {
		return
	}

	name, err := cache.MetaNamespaceKeyFunc(obj)
	if err != nil {
		klog.Errorf("add failed: %v", &errObjectName{err})
		return
	}

	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	if objInfo, _ := c.getObjInfo(name); objInfo != nil {
		newVersion, err := c.getObjVersion(name, obj)
		if err != nil {
			klog.Errorf("add: couldn't get object version: %v", err)
			return
		}

		storedVersion, err := c.getObjVersion(name, objInfo.latestObj)
		if err != nil {
			klog.Errorf("add: couldn't get stored object version: %v", err)
			return
		}

		// Only update object if version is newer.
		// This is so we don't override assumed objects due to informer resync.
		if newVersion <= storedVersion {
			klog.V(10).Infof("Skip adding %v %v to assume cache because version %v is not newer than %v", c.description, name, newVersion, storedVersion)
			return
		}
	}

	objInfo := &objInfo{name: name, latestObj: obj, apiObj: obj}
	if err = c.store.Update(objInfo); err != nil {
		klog.Warningf("got error when updating stored object : %v", err)
	} else {
		klog.V(10).Infof("Adding %v %v to assume cache: %+v ", c.description, name, obj)
	}
}

func (c *assumeCache) update(oldObj interface{}, newObj interface{}) {
	c.add(newObj)
}

func (c *assumeCache) delete(obj interface{}) {
	if obj == nil {
		return
	}

	name, err := cache.MetaNamespaceKeyFunc(obj)
	if err != nil {
		klog.Errorf("delete failed: %v", &errObjectName{err})
		return
	}

	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	objInfo := &objInfo{name: name}
	err = c.store.Delete(objInfo)
	if err != nil {
		klog.Errorf("delete: failed to delete %v %v: %v", c.description, name, err)
	}
}

func (c *assumeCache) getObjVersion(name string, obj interface{}) (int64, error) {
	objAccessor, err := meta.Accessor(obj)
	if err != nil {
		return -1, err
	}

	objResourceVersion, err := strconv.ParseInt(objAccessor.GetResourceVersion(), 10, 64)
	if err != nil {
		return -1, fmt.Errorf("error parsing ResourceVersion %q for %v %q: %s", objAccessor.GetResourceVersion(), c.description, name, err)
	}
	return objResourceVersion, nil
}

func (c *assumeCache) getObjInfo(name string) (*objInfo, error) {
	obj, ok, err := c.store.GetByKey(name)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, &errNotFound{c.description, name}
	}

	objInfo, ok := obj.(*objInfo)
	if !ok {
		return nil, &errWrongType{"objInfo", obj}
	}
	return objInfo, nil
}

func (c *assumeCache) Get(objName string) (interface{}, error) {
	c.rwMutex.RLock()
	defer c.rwMutex.RUnlock()

	objInfo, err := c.getObjInfo(objName)
	if err != nil {
		return nil, err
	}
	return objInfo.latestObj, nil
}

func (c *assumeCache) GetAPIObj(objName string) (interface{}, error) {
	c.rwMutex.RLock()
	defer c.rwMutex.RUnlock()

	objInfo, err := c.getObjInfo(objName)
	if err != nil {
		return nil, err
	}
	return objInfo.apiObj, nil
}

func (c *assumeCache) List(indexObj interface{}) []interface{} {
	c.rwMutex.RLock()
	defer c.rwMutex.RUnlock()

	allObjs := []interface{}{}
	objs, err := c.store.Index(c.indexName, &objInfo{latestObj: indexObj})
	if err != nil {
		klog.Errorf("list index error: %v", err)
		return nil
	}

	for _, obj := range objs {
		objInfo, ok := obj.(*objInfo)
		if !ok {
			klog.Errorf("list error: %v", &errWrongType{"objInfo", obj})
			continue
		}
		allObjs = append(allObjs, objInfo.latestObj)
	}
	return allObjs
}

func (c *assumeCache) Assume(obj interface{}) error {
	name, err := cache.MetaNamespaceKeyFunc(obj)
	if err != nil {
		return &errObjectName{err}
	}

	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	objInfo, err := c.getObjInfo(name)
	if err != nil {
		return err
	}

	newVersion, err := c.getObjVersion(name, obj)
	if err != nil {
		return err
	}

	storedVersion, err := c.getObjVersion(name, objInfo.latestObj)
	if err != nil {
		return err
	}

	if newVersion < storedVersion {
		return fmt.Errorf("%v %q is out of sync (stored: %d, assume: %d)", c.description, name, storedVersion, newVersion)
	}

	// Only update the cached object
	objInfo.latestObj = obj
	klog.V(4).Infof("Assumed %v %q, version %v", c.description, name, newVersion)
	return nil
}

func (c *assumeCache) Restore(objName string) {
	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	objInfo, err := c.getObjInfo(objName)
	if err != nil {
		// This could be expected if object got deleted
		klog.V(5).Infof("Restore %v %q warning: %v", c.description, objName, err)
	} else {
		objInfo.latestObj = objInfo.apiObj
		klog.V(4).Infof("Restored %v %q", c.description, objName)
	}
}

// PVAssumeCache is a AssumeCache for PersistentVolume objects
type PVAssumeCache interface {
	AssumeCache

	GetPV(pvName string) (*v1.PersistentVolume, error)
	GetAPIPV(pvName string) (*v1.PersistentVolume, error)
	ListPVs(storageClassName string) []*v1.PersistentVolume
}

type pvAssumeCache struct {
	AssumeCache
}

func pvStorageClassIndexFunc(obj interface{}) ([]string, error) {
	if pv, ok := obj.(*v1.PersistentVolume); ok {
		return []string{pv.Spec.StorageClassName}, nil
	}
	return []string{""}, fmt.Errorf("object is not a v1.PersistentVolume: %v", obj)
}

// NewPVAssumeCache creates a PV assume cache.
func NewPVAssumeCache(informer cache.SharedIndexInformer) PVAssumeCache {
	return &pvAssumeCache{NewAssumeCache(informer, "v1.PersistentVolume", "storageclass", pvStorageClassIndexFunc)}
}

func (c *pvAssumeCache) GetPV(pvName string) (*v1.PersistentVolume, error) {
	obj, err := c.Get(pvName)
	if err != nil {
		return nil, err
	}

	pv, ok := obj.(*v1.PersistentVolume)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolume", obj}
	}
	return pv, nil
}

func (c *pvAssumeCache) GetAPIPV(pvName string) (*v1.PersistentVolume, error) {
	obj, err := c.GetAPIObj(pvName)
	if err != nil {
		return nil, err
	}
	pv, ok := obj.(*v1.PersistentVolume)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolume", obj}
	}
	return pv, nil
}

func (c *pvAssumeCache) ListPVs(storageClassName string) []*v1.PersistentVolume {
	objs := c.List(&v1.PersistentVolume{
		Spec: v1.PersistentVolumeSpec{
			StorageClassName: storageClassName,
		},
	})
	pvs := []*v1.PersistentVolume{}
	for _, obj := range objs {
		pv, ok := obj.(*v1.PersistentVolume)
		if !ok {
			klog.Errorf("ListPVs: %v", &errWrongType{"v1.PersistentVolume", obj})
			continue
		}
		pvs = append(pvs, pv)
	}
	return pvs
}

// PVCAssumeCache is a AssumeCache for PersistentVolumeClaim objects
type PVCAssumeCache interface {
	AssumeCache

	// GetPVC returns the PVC from the cache with given pvcKey.
	// pvcKey is the result of MetaNamespaceKeyFunc on PVC obj
	GetPVC(pvcKey string) (*v1.PersistentVolumeClaim, error)
	GetAPIPVC(pvcKey string) (*v1.PersistentVolumeClaim, error)
}

type pvcAssumeCache struct {
	AssumeCache
}

// NewPVCAssumeCache creates a PVC assume cache.
func NewPVCAssumeCache(informer cache.SharedIndexInformer) PVCAssumeCache {
	return &pvcAssumeCache{NewAssumeCache(informer, "v1.PersistentVolumeClaim", "", nil)}
}

func (c *pvcAssumeCache) GetPVC(pvcKey string) (*v1.PersistentVolumeClaim, error) {
	obj, err := c.Get(pvcKey)
	if err != nil {
		return nil, err
	}

	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolumeClaim", obj}
	}
	return pvc, nil
}

func (c *pvcAssumeCache) GetAPIPVC(pvcKey string) (*v1.PersistentVolumeClaim, error) {
	obj, err := c.GetAPIObj(pvcKey)
	if err != nil {
		return nil, err
	}
	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolumeClaim", obj}
	}
	return pvc, nil
}
<commit_msg>Use v1helper.GetPersistentVolumeClass for compatibility<commit_after>/*
Copyright 2017 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 scheduling

import (
	"fmt"
	"strconv"
	"sync"

	"k8s.io/klog/v2"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/client-go/tools/cache"
	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
)

// AssumeCache is a cache on top of the informer that allows for updating
// objects outside of informer events and also restoring the informer
// cache's version of the object.  Objects are assumed to be
// Kubernetes API objects that implement meta.Interface
type AssumeCache interface {
	// Assume updates the object in-memory only
	Assume(obj interface{}) error

	// Restore the informer cache's version of the object
	Restore(objName string)

	// Get the object by name
	Get(objName string) (interface{}, error)

	// Get the API object by name
	GetAPIObj(objName string) (interface{}, error)

	// List all the objects in the cache
	List(indexObj interface{}) []interface{}
}

type errWrongType struct {
	typeName string
	object   interface{}
}

func (e *errWrongType) Error() string {
	return fmt.Sprintf("could not convert object to type %v: %+v", e.typeName, e.object)
}

type errNotFound struct {
	typeName   string
	objectName string
}

func (e *errNotFound) Error() string {
	return fmt.Sprintf("could not find %v %q", e.typeName, e.objectName)
}

type errObjectName struct {
	detailedErr error
}

func (e *errObjectName) Error() string {
	return fmt.Sprintf("failed to get object name: %v", e.detailedErr)
}

// assumeCache stores two pointers to represent a single object:
// * The pointer to the informer object.
// * The pointer to the latest object, which could be the same as
//   the informer object, or an in-memory object.
//
// An informer update always overrides the latest object pointer.
//
// Assume() only updates the latest object pointer.
// Restore() sets the latest object pointer back to the informer object.
// Get/List() always returns the latest object pointer.
type assumeCache struct {
	// Synchronizes updates to store
	rwMutex sync.RWMutex

	// describes the object stored
	description string

	// Stores objInfo pointers
	store cache.Indexer

	// Index function for object
	indexFunc cache.IndexFunc
	indexName string
}

type objInfo struct {
	// name of the object
	name string

	// Latest version of object could be cached-only or from informer
	latestObj interface{}

	// Latest object from informer
	apiObj interface{}
}

func objInfoKeyFunc(obj interface{}) (string, error) {
	objInfo, ok := obj.(*objInfo)
	if !ok {
		return "", &errWrongType{"objInfo", obj}
	}
	return objInfo.name, nil
}

func (c *assumeCache) objInfoIndexFunc(obj interface{}) ([]string, error) {
	objInfo, ok := obj.(*objInfo)
	if !ok {
		return []string{""}, &errWrongType{"objInfo", obj}
	}
	return c.indexFunc(objInfo.latestObj)
}

// NewAssumeCache creates an assume cache for general objects.
func NewAssumeCache(informer cache.SharedIndexInformer, description, indexName string, indexFunc cache.IndexFunc) AssumeCache {
	c := &assumeCache{
		description: description,
		indexFunc:   indexFunc,
		indexName:   indexName,
	}
	indexers := cache.Indexers{}
	if indexName != "" && indexFunc != nil {
		indexers[indexName] = c.objInfoIndexFunc
	}
	c.store = cache.NewIndexer(objInfoKeyFunc, indexers)

	// Unit tests don't use informers
	if informer != nil {
		informer.AddEventHandler(
			cache.ResourceEventHandlerFuncs{
				AddFunc:    c.add,
				UpdateFunc: c.update,
				DeleteFunc: c.delete,
			},
		)
	}
	return c
}

func (c *assumeCache) add(obj interface{}) {
	if obj == nil {
		return
	}

	name, err := cache.MetaNamespaceKeyFunc(obj)
	if err != nil {
		klog.Errorf("add failed: %v", &errObjectName{err})
		return
	}

	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	if objInfo, _ := c.getObjInfo(name); objInfo != nil {
		newVersion, err := c.getObjVersion(name, obj)
		if err != nil {
			klog.Errorf("add: couldn't get object version: %v", err)
			return
		}

		storedVersion, err := c.getObjVersion(name, objInfo.latestObj)
		if err != nil {
			klog.Errorf("add: couldn't get stored object version: %v", err)
			return
		}

		// Only update object if version is newer.
		// This is so we don't override assumed objects due to informer resync.
		if newVersion <= storedVersion {
			klog.V(10).Infof("Skip adding %v %v to assume cache because version %v is not newer than %v", c.description, name, newVersion, storedVersion)
			return
		}
	}

	objInfo := &objInfo{name: name, latestObj: obj, apiObj: obj}
	if err = c.store.Update(objInfo); err != nil {
		klog.Warningf("got error when updating stored object : %v", err)
	} else {
		klog.V(10).Infof("Adding %v %v to assume cache: %+v ", c.description, name, obj)
	}
}

func (c *assumeCache) update(oldObj interface{}, newObj interface{}) {
	c.add(newObj)
}

func (c *assumeCache) delete(obj interface{}) {
	if obj == nil {
		return
	}

	name, err := cache.MetaNamespaceKeyFunc(obj)
	if err != nil {
		klog.Errorf("delete failed: %v", &errObjectName{err})
		return
	}

	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	objInfo := &objInfo{name: name}
	err = c.store.Delete(objInfo)
	if err != nil {
		klog.Errorf("delete: failed to delete %v %v: %v", c.description, name, err)
	}
}

func (c *assumeCache) getObjVersion(name string, obj interface{}) (int64, error) {
	objAccessor, err := meta.Accessor(obj)
	if err != nil {
		return -1, err
	}

	objResourceVersion, err := strconv.ParseInt(objAccessor.GetResourceVersion(), 10, 64)
	if err != nil {
		return -1, fmt.Errorf("error parsing ResourceVersion %q for %v %q: %s", objAccessor.GetResourceVersion(), c.description, name, err)
	}
	return objResourceVersion, nil
}

func (c *assumeCache) getObjInfo(name string) (*objInfo, error) {
	obj, ok, err := c.store.GetByKey(name)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, &errNotFound{c.description, name}
	}

	objInfo, ok := obj.(*objInfo)
	if !ok {
		return nil, &errWrongType{"objInfo", obj}
	}
	return objInfo, nil
}

func (c *assumeCache) Get(objName string) (interface{}, error) {
	c.rwMutex.RLock()
	defer c.rwMutex.RUnlock()

	objInfo, err := c.getObjInfo(objName)
	if err != nil {
		return nil, err
	}
	return objInfo.latestObj, nil
}

func (c *assumeCache) GetAPIObj(objName string) (interface{}, error) {
	c.rwMutex.RLock()
	defer c.rwMutex.RUnlock()

	objInfo, err := c.getObjInfo(objName)
	if err != nil {
		return nil, err
	}
	return objInfo.apiObj, nil
}

func (c *assumeCache) List(indexObj interface{}) []interface{} {
	c.rwMutex.RLock()
	defer c.rwMutex.RUnlock()

	allObjs := []interface{}{}
	objs, err := c.store.Index(c.indexName, &objInfo{latestObj: indexObj})
	if err != nil {
		klog.Errorf("list index error: %v", err)
		return nil
	}

	for _, obj := range objs {
		objInfo, ok := obj.(*objInfo)
		if !ok {
			klog.Errorf("list error: %v", &errWrongType{"objInfo", obj})
			continue
		}
		allObjs = append(allObjs, objInfo.latestObj)
	}
	return allObjs
}

func (c *assumeCache) Assume(obj interface{}) error {
	name, err := cache.MetaNamespaceKeyFunc(obj)
	if err != nil {
		return &errObjectName{err}
	}

	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	objInfo, err := c.getObjInfo(name)
	if err != nil {
		return err
	}

	newVersion, err := c.getObjVersion(name, obj)
	if err != nil {
		return err
	}

	storedVersion, err := c.getObjVersion(name, objInfo.latestObj)
	if err != nil {
		return err
	}

	if newVersion < storedVersion {
		return fmt.Errorf("%v %q is out of sync (stored: %d, assume: %d)", c.description, name, storedVersion, newVersion)
	}

	// Only update the cached object
	objInfo.latestObj = obj
	klog.V(4).Infof("Assumed %v %q, version %v", c.description, name, newVersion)
	return nil
}

func (c *assumeCache) Restore(objName string) {
	c.rwMutex.Lock()
	defer c.rwMutex.Unlock()

	objInfo, err := c.getObjInfo(objName)
	if err != nil {
		// This could be expected if object got deleted
		klog.V(5).Infof("Restore %v %q warning: %v", c.description, objName, err)
	} else {
		objInfo.latestObj = objInfo.apiObj
		klog.V(4).Infof("Restored %v %q", c.description, objName)
	}
}

// PVAssumeCache is a AssumeCache for PersistentVolume objects
type PVAssumeCache interface {
	AssumeCache

	GetPV(pvName string) (*v1.PersistentVolume, error)
	GetAPIPV(pvName string) (*v1.PersistentVolume, error)
	ListPVs(storageClassName string) []*v1.PersistentVolume
}

type pvAssumeCache struct {
	AssumeCache
}

func pvStorageClassIndexFunc(obj interface{}) ([]string, error) {
	if pv, ok := obj.(*v1.PersistentVolume); ok {
		return []string{v1helper.GetPersistentVolumeClass(pv)}, nil
	}
	return []string{""}, fmt.Errorf("object is not a v1.PersistentVolume: %v", obj)
}

// NewPVAssumeCache creates a PV assume cache.
func NewPVAssumeCache(informer cache.SharedIndexInformer) PVAssumeCache {
	return &pvAssumeCache{NewAssumeCache(informer, "v1.PersistentVolume", "storageclass", pvStorageClassIndexFunc)}
}

func (c *pvAssumeCache) GetPV(pvName string) (*v1.PersistentVolume, error) {
	obj, err := c.Get(pvName)
	if err != nil {
		return nil, err
	}

	pv, ok := obj.(*v1.PersistentVolume)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolume", obj}
	}
	return pv, nil
}

func (c *pvAssumeCache) GetAPIPV(pvName string) (*v1.PersistentVolume, error) {
	obj, err := c.GetAPIObj(pvName)
	if err != nil {
		return nil, err
	}
	pv, ok := obj.(*v1.PersistentVolume)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolume", obj}
	}
	return pv, nil
}

func (c *pvAssumeCache) ListPVs(storageClassName string) []*v1.PersistentVolume {
	objs := c.List(&v1.PersistentVolume{
		Spec: v1.PersistentVolumeSpec{
			StorageClassName: storageClassName,
		},
	})
	pvs := []*v1.PersistentVolume{}
	for _, obj := range objs {
		pv, ok := obj.(*v1.PersistentVolume)
		if !ok {
			klog.Errorf("ListPVs: %v", &errWrongType{"v1.PersistentVolume", obj})
			continue
		}
		pvs = append(pvs, pv)
	}
	return pvs
}

// PVCAssumeCache is a AssumeCache for PersistentVolumeClaim objects
type PVCAssumeCache interface {
	AssumeCache

	// GetPVC returns the PVC from the cache with given pvcKey.
	// pvcKey is the result of MetaNamespaceKeyFunc on PVC obj
	GetPVC(pvcKey string) (*v1.PersistentVolumeClaim, error)
	GetAPIPVC(pvcKey string) (*v1.PersistentVolumeClaim, error)
}

type pvcAssumeCache struct {
	AssumeCache
}

// NewPVCAssumeCache creates a PVC assume cache.
func NewPVCAssumeCache(informer cache.SharedIndexInformer) PVCAssumeCache {
	return &pvcAssumeCache{NewAssumeCache(informer, "v1.PersistentVolumeClaim", "", nil)}
}

func (c *pvcAssumeCache) GetPVC(pvcKey string) (*v1.PersistentVolumeClaim, error) {
	obj, err := c.Get(pvcKey)
	if err != nil {
		return nil, err
	}

	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolumeClaim", obj}
	}
	return pvc, nil
}

func (c *pvcAssumeCache) GetAPIPVC(pvcKey string) (*v1.PersistentVolumeClaim, error) {
	obj, err := c.GetAPIObj(pvcKey)
	if err != nil {
		return nil, err
	}
	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		return nil, &errWrongType{"v1.PersistentVolumeClaim", obj}
	}
	return pvc, nil
}
<|endoftext|>


<commit_before>package view

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"strings"

	"github.com/ywzjackal/goweb"
)

var (
	// TemplateSuffix is template suffix -.-
	TemplateSuffix = ".html"
	// TemplatePosition is template position path -.-
	TemplatePosition = "" //"./templates"
	// html.template delims attributes of left
	DelimsLeft = "{{"
	// html.template delims attributes of right
	DelimsRight = "}}"
	// views map container
	views = make(map[string]goweb.View)
	// never mind! -.-
	rootTemplate = template.New("")
	//
	TemplateFuncs = template.FuncMap{
		"httpStatusText": http.StatusText,
	}
)

// Initialize buildin view components
func init() {
	RegisterView("html", &viewHtml{})
	RegisterView("json", &viewJson{})
	RegisterView("", &view{})
	//
	//	ReloadTemplates()
}

// ReloadTemplates, you know what it will do. `.`
func ReloadTemplates() {
	if TemplatePosition == "" {
		panic("Please set `goweb.TemplatePosition to path of templates directory!`")
	}
	rootTemplate = template.New("").Funcs(TemplateFuncs)
	rootTemplate = template.Must(rootTemplate.Delims(DelimsLeft, DelimsRight).
		ParseGlob(TemplatePosition + "/*"))
}

// RegisterView should be called by custom view component in 'package file init() function'
// will panic when register with duplicate name.
//
// RegisterView 应该在用户引用的自定义视图组件的包文件的init（）函数中调用以注册新的视图组件
// 如果出现panic，说明视图组件的名字被重复注册
func RegisterView(name string, view goweb.View) {
	if _, ok := views[strings.ToLower(name)]; ok {
		panic("Register view `" + name + "` duplicate!")
	}
	views[strings.ToLower(name)] = view
}

func GetView(name string) goweb.View {
	v, exist := views[name]
	if exist {
		return v
	}
	return nil
}

type view struct {
	goweb.View
}

type ViewHtml interface {
	goweb.View
}

type ViewJson interface {
	goweb.View
}

type viewHtml struct {
	ViewHtml
	*view
	//
	req *http.Request
}

type viewJson struct {
	ViewJson
	*view
}

func (v *view) Render(c goweb.Controller, args ...interface{}) goweb.WebError {
	//	raw := []byte(fmt.Sprintf("% +v, % +v", c, args))
	//	_, err := c.Context().ResponseWriter().Write(raw)
	//	if err != nil {
	//		return goweb.NewWebError(500, err.Error())
	//	}
	return nil
}

func (v *viewHtml) Render(c goweb.Controller, args ...interface{}) (err goweb.WebError) {
	defer func() {
		if r := recover(); r != nil {
			c.Context().ResponseWriter().Write([]byte(fmt.Sprintf("%v", r)))
		}
	}()
	var (
		name = strings.ToLower(c.Context().Request().URL.Path)
	)
	if goweb.Debug {
		ReloadTemplates()
	}
	buffer := bytes.Buffer{}
	writer := bufio.NewWriter(&buffer)
	switch len(args) {
	case 0:
		e := rootTemplate.ExecuteTemplate(writer, name, c)
		if e != nil {
			return goweb.NewWebError(500, e.Error())
		}
	case 1:
		name, ok := args[0].(string)
		if !ok {
			return goweb.NewWebError(500, "invalid view template name:%+v,need string", args[0])
		}
		e := rootTemplate.ExecuteTemplate(writer, name, c)
		if e != nil {
			return goweb.NewWebError(500, e.Error())
		}
	default:
		e := rootTemplate.ExecuteTemplate(writer, name, c)
		if e != nil {
			return goweb.NewWebError(500, e.Error())
		}
	}
	writer.Flush()
	c.Context().ResponseWriter().Header().Add("Cache-Control", "no-store, must-revalidate")
	c.Context().ResponseWriter().Header().Add("Pragma", "no-cache")
	c.Context().ResponseWriter().Write(buffer.Bytes())
	return err
}

func (v *viewJson) Render(c goweb.Controller, args ...interface{}) goweb.WebError {
	if len(args) == 0 {
		b, err := json.MarshalIndent(c, "", " ")
		if err != nil {
			return goweb.NewWebError(500, err.Error())
		}
		c.Context().ResponseWriter().Header().Add("Cache-Control", "no-store, must-revalidate")
		c.Context().ResponseWriter().Header().Add("Pragma", "no-cache")
		_, err = c.Context().ResponseWriter().Write(b)
	} else {
		b, err := json.MarshalIndent(args, "", " ")
		if err != nil {
			return goweb.NewWebError(500, err.Error())
		}
		c.Context().ResponseWriter().Header().Add("Cache-Control", "no-store, must-revalidate")
		c.Context().ResponseWriter().Header().Add("Pragma", "no-cache")
		_, err = c.Context().ResponseWriter().Write(b)
	}
	return nil
}
<commit_msg>Fix json view render always as array Signed-off-by: jackal <ywzjackal@163.com><commit_after>package view

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"strings"

	"github.com/ywzjackal/goweb"
)

var (
	// TemplateSuffix is template suffix -.-
	TemplateSuffix = ".html"
	// TemplatePosition is template position path -.-
	TemplatePosition = "" //"./templates"
	// html.template delims attributes of left
	DelimsLeft = "{{"
	// html.template delims attributes of right
	DelimsRight = "}}"
	// views map container
	views = make(map[string]goweb.View)
	// never mind! -.-
	rootTemplate = template.New("")
	//
	TemplateFuncs = template.FuncMap{
		"httpStatusText": http.StatusText,
	}
)

// Initialize buildin view components
func init() {
	RegisterView("html", &viewHtml{})
	RegisterView("json", &viewJson{})
	RegisterView("", &view{})
	//
	//	ReloadTemplates()
}

// ReloadTemplates, you know what it will do. `.`
func ReloadTemplates() {
	if TemplatePosition == "" {
		panic("Please set `goweb.TemplatePosition to path of templates directory!`")
	}
	rootTemplate = template.New("").Funcs(TemplateFuncs)
	rootTemplate = template.Must(rootTemplate.Delims(DelimsLeft, DelimsRight).
		ParseGlob(TemplatePosition + "/*"))
}

// RegisterView should be called by custom view component in 'package file init() function'
// will panic when register with duplicate name.
//
// RegisterView 应该在用户引用的自定义视图组件的包文件的init（）函数中调用以注册新的视图组件
// 如果出现panic，说明视图组件的名字被重复注册
func RegisterView(name string, view goweb.View) {
	if _, ok := views[strings.ToLower(name)]; ok {
		panic("Register view `" + name + "` duplicate!")
	}
	views[strings.ToLower(name)] = view
}

func GetView(name string) goweb.View {
	v, exist := views[name]
	if exist {
		return v
	}
	return nil
}

type view struct {
	goweb.View
}

type ViewHtml interface {
	goweb.View
}

type ViewJson interface {
	goweb.View
}

type viewHtml struct {
	ViewHtml
	*view
	//
	req *http.Request
}

type viewJson struct {
	ViewJson
	*view
}

func (v *view) Render(c goweb.Controller, args ...interface{}) goweb.WebError {
	//	raw := []byte(fmt.Sprintf("% +v, % +v", c, args))
	//	_, err := c.Context().ResponseWriter().Write(raw)
	//	if err != nil {
	//		return goweb.NewWebError(500, err.Error())
	//	}
	return nil
}

func (v *viewHtml) Render(c goweb.Controller, args ...interface{}) (err goweb.WebError) {
	defer func() {
		if r := recover(); r != nil {
			c.Context().ResponseWriter().Write([]byte(fmt.Sprintf("%v", r)))
		}
	}()
	var (
		name = strings.ToLower(c.Context().Request().URL.Path)
	)
	if goweb.Debug {
		ReloadTemplates()
	}
	buffer := bytes.Buffer{}
	writer := bufio.NewWriter(&buffer)
	switch len(args) {
	case 0:
		e := rootTemplate.ExecuteTemplate(writer, name, c)
		if e != nil {
			return goweb.NewWebError(500, e.Error())
		}
	case 1:
		name, ok := args[0].(string)
		if !ok {
			return goweb.NewWebError(500, "invalid view template name:%+v,need string", args[0])
		}
		e := rootTemplate.ExecuteTemplate(writer, name, c)
		if e != nil {
			return goweb.NewWebError(500, e.Error())
		}
	default:
		e := rootTemplate.ExecuteTemplate(writer, name, c)
		if e != nil {
			return goweb.NewWebError(500, e.Error())
		}
	}
	writer.Flush()
	c.Context().ResponseWriter().Header().Add("Cache-Control", "no-store, must-revalidate")
	c.Context().ResponseWriter().Header().Add("Pragma", "no-cache")
	c.Context().ResponseWriter().Write(buffer.Bytes())
	return err
}

func (v *viewJson) Render(c goweb.Controller, args ...interface{}) goweb.WebError {
	var (
		b   []byte = nil
		err error  = nil
	)
	switch len(args) {
	case 0:
		b, err = json.MarshalIndent(c, "", " ")
	case 1:
		b, err = json.MarshalIndent(args[0], "", " ")
	default:
		b, err = json.MarshalIndent(args, "", " ")
	}
	if err != nil {
		return goweb.NewWebError(500, err.Error())
	}
	c.Context().ResponseWriter().Header().Add("Cache-Control", "no-store, must-revalidate")
	c.Context().ResponseWriter().Header().Add("Pragma", "no-cache")
	_, err = c.Context().ResponseWriter().Write(b)
	return nil
}
<|endoftext|>


<commit_before>package actor

import (
	"sync"
)

type eventStream struct {
	sync.RWMutex
	subscriptions []*Subscription
}

var (
	EventStream = &eventStream{}
)

// SubscriberFunc is the signature of an EventStream subscriber function
type SubscriberFunc func(msg interface{})

type Predicate func(msg interface{}) bool

// Subscription is returned from the Subscribe function.
//
// This value and can be passed to Unsubscribe when the observer is no longer interested in receiving messages
type Subscription struct {
	i  int
	fn SubscriberFunc
	p  Predicate
}

// WithPredicate sets a predicate to filter messages passed to the subscriber
func (s *Subscription) WithPredicate(p Predicate) *Subscription {
	s.p = p
	return s
}

func (es *eventStream) Subscribe(fn SubscriberFunc) *Subscription {
	es.Lock()
	sub := &Subscription{
		i:  len(es.subscriptions),
		fn: fn,
	}
	es.subscriptions = append(es.subscriptions, sub)
	es.Unlock()
	return sub
}

func (es *eventStream) SubscribePID(pid *PID) *Subscription {
	return es.Subscribe(pid.Tell)
}

func (es *eventStream) Unsubscribe(sub *Subscription) {
	if sub.i == -1 {
		return
	}

	es.Lock()
	i := sub.i
	l := len(es.subscriptions) - 1

	es.subscriptions[i] = es.subscriptions[l]
	es.subscriptions[i].i = i
	es.subscriptions[l] = nil
	es.subscriptions = es.subscriptions[:l]
	sub.i = -1

	// TODO(SGC): implement resizing
	if len(es.subscriptions) == 0 {
		es.subscriptions = nil
	}

	es.Unlock()
}

func (es *eventStream) Publish(message interface{}) {
	es.RLock()
	defer es.RUnlock()

	for _, s := range es.subscriptions {
		if s.p == nil || s.p(message) {
			s.fn(message)
		}
	}
}
<commit_msg>serialize modification of predicate<commit_after>package actor

import (
	"sync"
)

type eventStream struct {
	sync.RWMutex
	subscriptions []*Subscription
}

var (
	EventStream = &eventStream{}
)

// SubscriberFunc is the signature of an EventStream subscriber function
type SubscriberFunc func(msg interface{})

// Predicate is a function used to filter messages before being forwarded to a SubscriberFunc
type Predicate func(msg interface{}) bool

// Subscription is returned from the Subscribe function.
//
// This value and can be passed to Unsubscribe when the observer is no longer interested in receiving messages
type Subscription struct {
	es *eventStream
	i  int
	fn SubscriberFunc
	p  Predicate
}

// WithPredicate sets a predicate to filter messages passed to the subscriber
func (s *Subscription) WithPredicate(p Predicate) *Subscription {
	s.es.Lock()
	s.p = p
	s.es.Unlock()
	return s
}

func (es *eventStream) Subscribe(fn SubscriberFunc) *Subscription {
	es.Lock()
	sub := &Subscription{
		es: es,
		i:  len(es.subscriptions),
		fn: fn,
	}
	es.subscriptions = append(es.subscriptions, sub)
	es.Unlock()
	return sub
}

func (es *eventStream) SubscribePID(pid *PID) *Subscription {
	return es.Subscribe(pid.Tell)
}

func (es *eventStream) Unsubscribe(sub *Subscription) {
	if sub.i == -1 {
		return
	}

	es.Lock()
	i := sub.i
	l := len(es.subscriptions) - 1

	es.subscriptions[i] = es.subscriptions[l]
	es.subscriptions[i].i = i
	es.subscriptions[l] = nil
	es.subscriptions = es.subscriptions[:l]
	sub.i = -1

	// TODO(SGC): implement resizing
	if len(es.subscriptions) == 0 {
		es.subscriptions = nil
	}

	es.Unlock()
}

func (es *eventStream) Publish(message interface{}) {
	es.RLock()
	defer es.RUnlock()

	for _, s := range es.subscriptions {
		if s.p == nil || s.p(message) {
			s.fn(message)
		}
	}
}
<|endoftext|>


<commit_before>package root

import (
	"fmt"
	"github.com/Aptomi/aptomi/cmd/aptomictl/endpoints"
	"github.com/Aptomi/aptomi/cmd/aptomictl/policy"
	"github.com/Aptomi/aptomi/cmd/aptomictl/revision"
	"github.com/Aptomi/aptomi/cmd/common"
	"github.com/Aptomi/aptomi/pkg/config"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"path"
	"time"
)

const (
	// EnvPrefix is the prefix for all environment variables used by aptomictl
	EnvPrefix = "APTOMICTL"
)

var (
	// Config is the global instance of the client config
	Config = &config.Client{}

	// Command is the main (root) cobra command for aptomictl
	Command = &cobra.Command{
		Use:   "aptomictl",
		Short: "aptomictl controls Aptomi",
		Long:  "aptomictl controls Aptomi",

		PersistentPreRun: preRun,

		Run: func(cmd *cobra.Command, args []string) {
			// fall back on default help if no args/flags are passed
			cmd.HelpFunc()(cmd, args)
		},
	}
)

func init() {
	viper.SetEnvPrefix(EnvPrefix)

	common.AddDefaultFlags(Command, EnvPrefix)

	common.AddStringFlag(Command, "auth.username", "username", "u", "", EnvPrefix+"_USERNAME", "Username")
	common.AddDurationFlag(Command, "http.timeout", "timeout", "", 15*time.Second, EnvPrefix+"_TIMEOUT", "HTTP Timeout")

	// Add sub commands
	Command.AddCommand(
		common.Version,
		endpoints.NewCommand(Config),
		policy.NewCommand(Config),
		revision.NewCommand(Config),
	)
}

func preRun(command *cobra.Command, args []string) {
	err := common.ReadConfig(viper.GetViper(), Config, defaultConfigDir())
	if err != nil {
		panic(fmt.Sprintf("error while loading config: %s", err))
	}
}

func defaultConfigDir() string {
	home, err := homedir.Dir()
	if err != nil {
		panic(fmt.Sprintf("can't find home dir: %s", err))
	}

	return path.Join(home, ".aptomi")
}
<commit_msg>Add --output/-o flag for setting desired output format for aptomictl<commit_after>package root

import (
	"fmt"
	"github.com/Aptomi/aptomi/cmd/aptomictl/endpoints"
	"github.com/Aptomi/aptomi/cmd/aptomictl/policy"
	"github.com/Aptomi/aptomi/cmd/aptomictl/revision"
	"github.com/Aptomi/aptomi/cmd/common"
	"github.com/Aptomi/aptomi/pkg/config"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"path"
	"time"
)

const (
	// EnvPrefix is the prefix for all environment variables used by aptomictl
	EnvPrefix = "APTOMICTL"
)

var (
	// Config is the global instance of the client config
	Config = &config.Client{}

	// Command is the main (root) cobra command for aptomictl
	Command = &cobra.Command{
		Use:   "aptomictl",
		Short: "aptomictl controls Aptomi",
		Long:  "aptomictl controls Aptomi",

		PersistentPreRun: preRun,

		Run: func(cmd *cobra.Command, args []string) {
			// fall back on default help if no args/flags are passed
			cmd.HelpFunc()(cmd, args)
		},
	}
)

func init() {
	viper.SetEnvPrefix(EnvPrefix)

	common.AddDefaultFlags(Command, EnvPrefix)

	common.AddStringFlag(Command, "output", "output", "o", "text", EnvPrefix+"_OUTPUT", "Output format. One of: text (default), json, yaml")

	common.AddStringFlag(Command, "auth.username", "username", "u", "", EnvPrefix+"_USERNAME", "Username")
	common.AddDurationFlag(Command, "http.timeout", "timeout", "", 15*time.Second, EnvPrefix+"_TIMEOUT", "HTTP Timeout")

	// Add sub commands
	Command.AddCommand(
		common.Version,
		endpoints.NewCommand(Config),
		policy.NewCommand(Config),
		revision.NewCommand(Config),
	)
}

func preRun(command *cobra.Command, args []string) {
	err := common.ReadConfig(viper.GetViper(), Config, defaultConfigDir())
	if err != nil {
		panic(fmt.Sprintf("error while loading config: %s", err))
	}
}

func defaultConfigDir() string {
	home, err := homedir.Dir()
	if err != nil {
		panic(fmt.Sprintf("can't find home dir: %s", err))
	}

	return path.Join(home, ".aptomi")
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Jetstack cert-manager contributors.

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 renew

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/spf13/cobra"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
)

// Options is a struct to support version command
type Options struct {
	// The Namespace that the Certificate to be renewed resided in
	Namespace string

	LabelSelector string

	All  bool
	Wait bool

	AllNamespaces bool

	PollTime time.Duration
	Timeout  time.Duration

	genericclioptions.IOStreams
}

// NewOptions returns initialized Options
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
	return &Options{
		IOStreams: ioStreams,
	}
}

// NewCmdRenew returns a cobra command for renewing Certificates
func NewCmdRenew(ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
	o := NewOptions(ioStreams)

	cmd := &cobra.Command{
		Use:   "renew",
		Short: "Mark a Certificate for manual renewal",
		Long:  "Mark a Certificate for manual renewal",
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(factory, cmd, args))
			cmdutil.CheckErr(o.Validate(cmd, args))
			cmdutil.CheckErr(o.Run(factory, cmd, args))
		},
	}

	cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
	cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, wait for Certificates across all namespaces to become ready. Namespace in current context is ignored even if specified with --namespace.")
	cmd.Flags().BoolVar(&o.All, "all", o.All, "Renew all Certificates in the given Namespace, or all namespaces with --all-namespaces enabled.")
	cmd.Flags().BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait for all Certificates to become ready once being marked for renewal.")
	cmd.Flags().DurationVar(&o.PollTime, "poll-time", time.Second*2, "Poll period between checking Certificates to become ready. Used in conjunction with --wait.")
	cmd.Flags().DurationVar(&o.Timeout, "timeout", 0, "The length of time to wait before ending watch, zero means never. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")

	return cmd
}

// Validate validates the provided options
func (o *Options) Validate(cmd *cobra.Command, args []string) error {
	if len(o.LabelSelector) > 0 && len(args) > 0 {
		return errors.New("cannot specify Certificate arguments as well as label selectors")
	}

	if o.All && len(args) > 0 {
		return errors.New("cannot specify Certificate arguments as well as --all flag")
	}

	if o.All && len(o.LabelSelector) > 0 {
		return errors.New("cannot specify label selector as well as --all flag")
	}

	return nil
}

// Complete takes the command arguments and factory and infers any remaining options.
func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	return nil
}

// Run executes version command
func (o *Options) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	restConfig, err := f.ToRESTConfig()
	if err != nil {
		return err
	}

	cmClient, err := cmclient.NewForConfig(restConfig)
	if err != nil {
		return err
	}

	nss := []corev1.Namespace{{ObjectMeta: metav1.ObjectMeta{Name: o.Namespace}}}

	// TODO: handle network context

	if o.AllNamespaces {
		kubeClient, err := kubernetes.NewForConfig(restConfig)
		if err != nil {
			return err
		}

		nsList, err := kubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			return err
		}

		nss = nsList.Items
	}

	var crts []cmapi.Certificate
	for _, ns := range nss {
		switch {
		case o.All:
			crtsList, err := cmClient.CertmanagerV1alpha2().Certificates(ns.Name).List(context.TODO(), metav1.ListOptions{})
			if err != nil {
				return err
			}

			crts = append(crts, crtsList.Items...)

		case len(o.LabelSelector) > 0:
			crtsList, err := cmClient.CertmanagerV1alpha2().Certificates(ns.Name).List(context.TODO(), metav1.ListOptions{
				LabelSelector: o.LabelSelector,
			})
			if err != nil {
				return err
			}

			crts = append(crts, crtsList.Items...)

		default:
			for _, crtName := range args {
				crt, err := cmClient.CertmanagerV1alpha2().Certificates(ns.Name).Get(context.TODO(), crtName, metav1.GetOptions{})
				if err != nil {
					return err
				}

				crts = append(crts, *crt)
			}
		}
	}

	if len(crts) == 0 {
		if o.AllNamespaces {
			fmt.Fprintln(o.ErrOut, "No Certificates found")
		} else {
			fmt.Fprintf(o.ErrOut, "No Certificates found in %s namespace.\n", o.Namespace)
		}

		return nil
	}

	for _, crt := range crts {
		if err := o.renewCertificate(cmClient, &crt); err != nil {
			return err
		}
	}

	if o.Wait {
		if err := o.waitCertificatesReady(cmClient, crts); err != nil {
			return err
		}

		fmt.Fprintf(o.Out, "%d Certificates successfully renewed\n", len(crts))
	}

	return nil
}

func (o *Options) renewCertificate(cmClient *cmclient.Clientset, crt *cmapi.Certificate) error {
	apiutil.SetCertificateCondition(crt, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "ManuallyTriggered", "Certificate re-issuance manually triggered")
	_, err := cmClient.CertmanagerV1alpha2().Certificates(crt.Namespace).UpdateStatus(context.TODO(), crt, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to trigger issuance of Certificate %s/%s: %v", crt.Namespace, crt.Name, err)
	}
	fmt.Fprintf(o.Out, "Manually triggered issuance of Certificate %s/%s\n", crt.Namespace, crt.Name)
	return nil
}

func (o *Options) waitCertificatesReady(cmClient *cmclient.Clientset, crts []cmapi.Certificate) error {
	// TODO: start poll time after all get requests?
	ticker := time.NewTicker(o.PollTime)
	defer ticker.Stop()

	ctx := context.TODO()

	if o.Timeout > 0 {
		var cancel func()
		ctx, cancel = context.WithTimeout(ctx, o.Timeout)
		defer cancel()
	}

	for {
		lenReady := 0

		for _, crt := range crts {
			crt, err := cmClient.CertmanagerV1alpha2().Certificates(crt.Namespace).Get(context.TODO(), crt.Name, metav1.GetOptions{})
			if err != nil {
				// TODO: handle certificate no longer existing?
				return err
			}

			if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing); cond != nil && cond.Status == cmmeta.ConditionTrue {
				continue
			}

			if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionReady); cond != nil && cond.Status == cmmeta.ConditionTrue {
				lenReady++
			}
		}

		if lenReady == len(crts) {
			return nil
		}

		fmt.Fprintf(o.Out, "Currently %d Certificates out of %d are ready...\n", lenReady, len(crts))

		select {
		case <-ticker.C:
			continue
		case <-ctx.Done():
			return fmt.Errorf("%d Certificates failed to become ready in time", len(crts)-lenReady)
		}
	}
}
<commit_msg>All ctl renew -A and -l options together<commit_after>/*
Copyright 2020 The Jetstack cert-manager contributors.

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 renew

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/spf13/cobra"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
)

// Options is a struct to support renew command
type Options struct {
	// The Namespace that the Certificate to be renewed resided in
	Namespace string

	LabelSelector string

	All  bool
	Wait bool

	AllNamespaces bool

	PollTime time.Duration
	Timeout  time.Duration

	genericclioptions.IOStreams
}

// NewOptions returns initialized Options
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
	return &Options{
		IOStreams: ioStreams,
	}
}

// NewCmdRenew returns a cobra command for renewing Certificates
func NewCmdRenew(ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
	o := NewOptions(ioStreams)

	cmd := &cobra.Command{
		Use:   "renew",
		Short: "Mark a Certificate for manual renewal",
		Long:  "Mark a Certificate for manual renewal",
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(factory, cmd, args))
			cmdutil.CheckErr(o.Validate(cmd, args))
			cmdutil.CheckErr(o.Run(factory, cmd, args))
		},
	}

	cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
	cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, wait for Certificates across all namespaces to become ready. Namespace in current context is ignored even if specified with --namespace.")
	cmd.Flags().BoolVar(&o.All, "all", o.All, "Renew all Certificates in the given Namespace, or all namespaces with --all-namespaces enabled.")
	cmd.Flags().BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait for all Certificates to become ready once being marked for renewal.")
	cmd.Flags().DurationVar(&o.PollTime, "poll-time", time.Second*2, "Poll period between checking Certificates to become ready. Used in conjunction with --wait.")
	cmd.Flags().DurationVar(&o.Timeout, "timeout", 0, "The length of time to wait before ending watch, zero means never. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")

	return cmd
}

// Validate validates the provided options
func (o *Options) Validate(cmd *cobra.Command, args []string) error {
	if len(o.LabelSelector) > 0 && len(args) > 0 {
		return errors.New("cannot specify Certificate arguments as well as label selectors")
	}

	if o.All && len(args) > 0 {
		return errors.New("cannot specify Certificate arguments as well as --all flag")
	}

	return nil
}

// Complete takes the command arguments and factory and infers any remaining options.
func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	return nil
}

// Run executes renew command
func (o *Options) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	restConfig, err := f.ToRESTConfig()
	if err != nil {
		return err
	}

	cmClient, err := cmclient.NewForConfig(restConfig)
	if err != nil {
		return err
	}

	nss := []corev1.Namespace{{ObjectMeta: metav1.ObjectMeta{Name: o.Namespace}}}

	// TODO: handle network context

	if o.AllNamespaces {
		kubeClient, err := kubernetes.NewForConfig(restConfig)
		if err != nil {
			return err
		}

		nsList, err := kubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			return err
		}

		nss = nsList.Items
	}

	var crts []cmapi.Certificate
	for _, ns := range nss {
		switch {
		case o.All, len(o.LabelSelector) > 0:
			crtsList, err := cmClient.CertmanagerV1alpha2().Certificates(ns.Name).List(context.TODO(), metav1.ListOptions{
				LabelSelector: o.LabelSelector,
			})
			if err != nil {
				return err
			}

			crts = append(crts, crtsList.Items...)

		default:
			for _, crtName := range args {
				crt, err := cmClient.CertmanagerV1alpha2().Certificates(ns.Name).Get(context.TODO(), crtName, metav1.GetOptions{})
				if err != nil {
					return err
				}

				crts = append(crts, *crt)
			}
		}
	}

	if len(crts) == 0 {
		if o.AllNamespaces {
			fmt.Fprintln(o.ErrOut, "No Certificates found")
		} else {
			fmt.Fprintf(o.ErrOut, "No Certificates found in %s namespace.\n", o.Namespace)
		}

		return nil
	}

	for _, crt := range crts {
		if err := o.renewCertificate(cmClient, &crt); err != nil {
			return err
		}
	}

	if o.Wait {
		if err := o.waitCertificatesReady(cmClient, crts); err != nil {
			return err
		}

		fmt.Fprintf(o.Out, "%d Certificates successfully renewed\n", len(crts))
	}

	return nil
}

func (o *Options) renewCertificate(cmClient *cmclient.Clientset, crt *cmapi.Certificate) error {
	apiutil.SetCertificateCondition(crt, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "ManuallyTriggered", "Certificate re-issuance manually triggered")
	_, err := cmClient.CertmanagerV1alpha2().Certificates(crt.Namespace).UpdateStatus(context.TODO(), crt, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to trigger issuance of Certificate %s/%s: %v", crt.Namespace, crt.Name, err)
	}
	fmt.Fprintf(o.Out, "Manually triggered issuance of Certificate %s/%s\n", crt.Namespace, crt.Name)
	return nil
}

func (o *Options) waitCertificatesReady(cmClient *cmclient.Clientset, crts []cmapi.Certificate) error {
	// TODO: start poll time after all get requests?
	ticker := time.NewTicker(o.PollTime)
	defer ticker.Stop()

	ctx := context.TODO()

	if o.Timeout > 0 {
		var cancel func()
		ctx, cancel = context.WithTimeout(ctx, o.Timeout)
		defer cancel()
	}

	for {
		lenReady := 0

		for _, crt := range crts {
			crt, err := cmClient.CertmanagerV1alpha2().Certificates(crt.Namespace).Get(context.TODO(), crt.Name, metav1.GetOptions{})
			if err != nil {
				// TODO: handle certificate no longer existing?
				return err
			}

			if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing); cond != nil && cond.Status == cmmeta.ConditionTrue {
				continue
			}

			if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionReady); cond != nil && cond.Status == cmmeta.ConditionTrue {
				lenReady++
			}
		}

		if lenReady == len(crts) {
			return nil
		}

		fmt.Fprintf(o.Out, "Currently %d Certificates out of %d are ready...\n", lenReady, len(crts))

		select {
		case <-ticker.C:
			continue
		case <-ctx.Done():
			return fmt.Errorf("%d Certificates failed to become ready in time", len(crts)-lenReady)
		}
	}
}
<|endoftext|>