<commit_before>// Copyright 2016-2018 Authors of Cilium
//
// 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 proxy

import (
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/completion"
	"github.com/cilium/cilium/pkg/envoy"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/maps/proxymap"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/policy"
	"github.com/cilium/cilium/pkg/proxy/logger"

	"github.com/sirupsen/logrus"
)

var (
	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "proxy")
)

// field names used while logging
const (
	fieldMarker          = "marker"
	fieldSocket          = "socket"
	fieldFd              = "fd"
	fieldProxyRedirectID = "id"

	// portReuseDelay is the delay until a port is being reused
	portReuseDelay = 5 * time.Minute

	// redirectCreationAttempts is the number of attempts to create a redirect
	redirectCreationAttempts = 5
)

// Proxy maintains state about redirects
type Proxy struct {
	*envoy.XDSServer

	// stateDir is the path of the directory where the state of L7 proxies is
	// stored.
	stateDir string

	// mutex is the lock required when modifying any proxy datastructure
	mutex lock.RWMutex

	// rangeMin is the minimum port used for proxy port allocation
	rangeMin uint16

	// rangeMax is the maximum port used for proxy port allocation.
	// If port is unspecified, the proxy will automatically allocate
	// ports out of the rangeMin-rangeMax range.
	rangeMax uint16

	// allocatedPorts is a map of all allocated proxy ports pointing
	// to the redirect rules attached to that port
	allocatedPorts map[uint16]struct{}

	// redirects is a map of all redirect configurations indexed by
	// the redirect identifier. Redirects may be implemented by different
	// proxies.
	redirects map[string]*Redirect
}

// StartProxySupport starts the servers to support L7 proxies: xDS GRPC server
// and access log server.
func StartProxySupport(minPort uint16, maxPort uint16, stateDir string,
	accessLogFile string, accessLogNotifier logger.LogRecordNotifier, accessLogMetadata []string) *Proxy {
	xdsServer := envoy.StartXDSServer(stateDir)

	if accessLogFile != "" {
		if err := logger.OpenLogfile(accessLogFile); err != nil {
			log.WithError(err).WithField(logger.FieldFilePath, accessLogFile).
				Warn("Cannot open L7 access log")
		}
	}

	if accessLogNotifier != nil {
		logger.SetNotifier(accessLogNotifier)
	}

	if len(accessLogMetadata) > 0 {
		logger.SetMetadata(accessLogMetadata)
	}

	envoy.StartAccessLogServer(stateDir, xdsServer, DefaultEndpointInfoRegistry)

	return &Proxy{
		XDSServer:      xdsServer,
		stateDir:       stateDir,
		rangeMin:       minPort,
		rangeMax:       maxPort,
		redirects:      make(map[string]*Redirect),
		allocatedPorts: make(map[uint16]struct{}),
	}
}

var (
	portRandomizer      = rand.New(rand.NewSource(time.Now().UnixNano()))
	portRandomizerMutex lock.Mutex
)

func isPortBindable(port uint16) bool {
	socket, err := listenSocket(fmt.Sprintf(":%d", port), 0)
	if err != nil {
		log.WithError(err).Infof("Skipping port %d already in use", port)
		return false
	}
	socket.Close()
	return true
}

func (p *Proxy) allocatePort() (uint16, error) {
	portRandomizerMutex.Lock()
	defer portRandomizerMutex.Unlock()

	for _, r := range portRandomizer.Perm(int(p.rangeMax - p.rangeMin + 1)) {
		resPort := uint16(r) + p.rangeMin

		if _, ok := p.allocatedPorts[resPort]; !ok {
			if isPortBindable(resPort) {
				return resPort, nil
			}
		}

	}

	return 0, fmt.Errorf("no available proxy ports")
}

var gcOnce sync.Once

// CreateOrUpdateRedirect creates or updates a L4 redirect with corresponding
// proxy configuration. This will allocate a proxy port as required and launch
// a proxy instance. If the redirect is already in place, only the rules will be
// updated.
func (p *Proxy) CreateOrUpdateRedirect(l4 *policy.L4Filter, id string, localEndpoint logger.EndpointUpdater,
	wg *completion.WaitGroup) (*Redirect, error) {
	gcOnce.Do(func() {
		go func() {
			for {
				time.Sleep(10 * time.Second)
				if deleted := proxymap.GC(); deleted > 0 {
					log.WithField("count", deleted).
						Debug("Evicted entries from proxy table")
				}
			}
		}()
	})

	p.mutex.Lock()
	defer p.mutex.Unlock()

	scopedLog := log.WithField(fieldProxyRedirectID, id)

	if r, ok := p.redirects[id]; ok {
		r.mutex.Lock()
		defer r.mutex.Unlock()

		if r.parserType != l4.L7Parser {
			if err := p.removeRedirect(id, r, wg); err != nil {
				return nil, fmt.Errorf("unable to remove old redirect: %s", err)
			}

			goto create
		}

		r.updateRules(l4)
		err := r.implementation.UpdateRules(wg)
		if err != nil {
			scopedLog.WithError(err).Error("Unable to update ", l4.L7Parser, " proxy")
			return nil, err
		}

		r.lastUpdated = time.Now()

		scopedLog.WithField(logfields.Object, logfields.Repr(r)).
			Debug("updated existing ", l4.L7Parser, " proxy instance")

		return r, nil
	}

create:
	redir := newRedirect(localEndpoint, id)
	redir.endpointID = localEndpoint.GetID()
	redir.ingress = l4.Ingress
	redir.parserType = l4.L7Parser
	redir.updateRules(l4)

retryCreatePort:
	for nRetry := 0; ; nRetry++ {
		to, err := p.allocatePort()
		if err != nil {
			return nil, err
		}

		redir.ProxyPort = to

		switch l4.L7Parser {
		case policy.ParserTypeKafka:
			redir.implementation, err = createKafkaRedirect(redir, kafkaConfiguration{}, DefaultEndpointInfoRegistry)

		case policy.ParserTypeHTTP:
			redir.implementation, err = createEnvoyRedirect(redir, p.stateDir, p.XDSServer, wg)

		default:
			return nil, fmt.Errorf("unsupported L7 parser type: %s", l4.L7Parser)
		}

		switch {
		case err == nil:
			scopedLog.WithField(logfields.Object, logfields.Repr(redir)).
				Debug("Created new ", l4.L7Parser, " proxy instance")

			p.allocatedPorts[to] = struct{}{}
			p.redirects[id] = redir

			break retryCreatePort

		// an error occurred, and we have no more retries
		case nRetry >= redirectCreationAttempts:
			scopedLog.WithError(err).Error("Unable to create ", l4.L7Parser, " proxy")
			return nil, err

		// an error occurred and we can retry
		default:
			scopedLog.WithError(err).Warning("Unable to create ", l4.L7Parser, " proxy, will retry")
		}
	}

	return redir, nil
}

// RemoveRedirect removes an existing redirect.
func (p *Proxy) RemoveRedirect(id string, wg *completion.WaitGroup) error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	r, ok := p.redirects[id]
	if !ok {
		return fmt.Errorf("unable to find redirect %s", id)
	}

	return p.removeRedirect(id, r, wg)
}

// removeRedirect removes an existing redirect. p.mutex must be held
func (p *Proxy) removeRedirect(id string, r *Redirect, wg *completion.WaitGroup) error {
	log.WithField(fieldProxyRedirectID, id).
		Debug("removing proxy redirect")
	r.implementation.Close(wg)

	delete(p.redirects, id)

	// delay the release and reuse of the port number so it is guaranteed
	// to be safe to listen on the port again
	go func() {
		time.Sleep(portReuseDelay)

		// The cleanup of the proxymap is delayed a bit to ensure that
		// the datapath has implemented the redirect change and we
		// cleanup the map before we release the port and allow reuse
		proxymap.CleanupOnRedirectClose(r.ProxyPort)

		p.mutex.Lock()
		delete(p.allocatedPorts, r.ProxyPort)
		p.mutex.Unlock()

		log.WithField(fieldProxyRedirectID, id).Debugf("Delayed release of proxy port %d", r.ProxyPort)
	}()

	return nil
}

// ChangeLogLevel changes proxy log level to correspond to the logrus log level 'level'.
func ChangeLogLevel(level logrus.Level) {
	if envoyProxy != nil {
		envoyProxy.ChangeLogLevel(level)
	}
}

// GetStatusModel returns the proxy status as API model
func (p *Proxy) GetStatusModel() *models.ProxyStatus {
	p.mutex.RLock()
	defer p.mutex.RUnlock()

	return &models.ProxyStatus{
		IP:        node.GetInternalIPv4().String(),
		PortRange: fmt.Sprintf("%d-%d", p.rangeMin, p.rangeMax),
	}
}
<commit_msg>proxy: Remove port binding check on redirect creation<commit_after>// Copyright 2016-2018 Authors of Cilium
//
// 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 proxy

import (
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/completion"
	"github.com/cilium/cilium/pkg/envoy"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/maps/proxymap"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/policy"
	"github.com/cilium/cilium/pkg/proxy/logger"

	"github.com/sirupsen/logrus"
)

var (
	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "proxy")
)

// field names used while logging
const (
	fieldMarker          = "marker"
	fieldSocket          = "socket"
	fieldFd              = "fd"
	fieldProxyRedirectID = "id"

	// portReuseDelay is the delay until a port is being reused
	portReuseDelay = 5 * time.Minute

	// redirectCreationAttempts is the number of attempts to create a redirect
	redirectCreationAttempts = 5
)

// Proxy maintains state about redirects
type Proxy struct {
	*envoy.XDSServer

	// stateDir is the path of the directory where the state of L7 proxies is
	// stored.
	stateDir string

	// mutex is the lock required when modifying any proxy datastructure
	mutex lock.RWMutex

	// rangeMin is the minimum port used for proxy port allocation
	rangeMin uint16

	// rangeMax is the maximum port used for proxy port allocation.
	// If port is unspecified, the proxy will automatically allocate
	// ports out of the rangeMin-rangeMax range.
	rangeMax uint16

	// allocatedPorts is a map of all allocated proxy ports pointing
	// to the redirect rules attached to that port
	allocatedPorts map[uint16]struct{}

	// redirects is a map of all redirect configurations indexed by
	// the redirect identifier. Redirects may be implemented by different
	// proxies.
	redirects map[string]*Redirect
}

// StartProxySupport starts the servers to support L7 proxies: xDS GRPC server
// and access log server.
func StartProxySupport(minPort uint16, maxPort uint16, stateDir string,
	accessLogFile string, accessLogNotifier logger.LogRecordNotifier, accessLogMetadata []string) *Proxy {
	xdsServer := envoy.StartXDSServer(stateDir)

	if accessLogFile != "" {
		if err := logger.OpenLogfile(accessLogFile); err != nil {
			log.WithError(err).WithField(logger.FieldFilePath, accessLogFile).
				Warn("Cannot open L7 access log")
		}
	}

	if accessLogNotifier != nil {
		logger.SetNotifier(accessLogNotifier)
	}

	if len(accessLogMetadata) > 0 {
		logger.SetMetadata(accessLogMetadata)
	}

	envoy.StartAccessLogServer(stateDir, xdsServer, DefaultEndpointInfoRegistry)

	return &Proxy{
		XDSServer:      xdsServer,
		stateDir:       stateDir,
		rangeMin:       minPort,
		rangeMax:       maxPort,
		redirects:      make(map[string]*Redirect),
		allocatedPorts: make(map[uint16]struct{}),
	}
}

var (
	portRandomizer      = rand.New(rand.NewSource(time.Now().UnixNano()))
	portRandomizerMutex lock.Mutex
)

func (p *Proxy) allocatePort() (uint16, error) {
	portRandomizerMutex.Lock()
	defer portRandomizerMutex.Unlock()

	for _, r := range portRandomizer.Perm(int(p.rangeMax - p.rangeMin + 1)) {
		resPort := uint16(r) + p.rangeMin

		if _, ok := p.allocatedPorts[resPort]; !ok {
			return resPort, nil
		}

	}

	return 0, fmt.Errorf("no available proxy ports")
}

var gcOnce sync.Once

// CreateOrUpdateRedirect creates or updates a L4 redirect with corresponding
// proxy configuration. This will allocate a proxy port as required and launch
// a proxy instance. If the redirect is already in place, only the rules will be
// updated.
func (p *Proxy) CreateOrUpdateRedirect(l4 *policy.L4Filter, id string, localEndpoint logger.EndpointUpdater,
	wg *completion.WaitGroup) (*Redirect, error) {
	gcOnce.Do(func() {
		go func() {
			for {
				time.Sleep(10 * time.Second)
				if deleted := proxymap.GC(); deleted > 0 {
					log.WithField("count", deleted).
						Debug("Evicted entries from proxy table")
				}
			}
		}()
	})

	p.mutex.Lock()
	defer p.mutex.Unlock()

	scopedLog := log.WithField(fieldProxyRedirectID, id)

	if r, ok := p.redirects[id]; ok {
		r.mutex.Lock()
		defer r.mutex.Unlock()

		if r.parserType != l4.L7Parser {
			if err := p.removeRedirect(id, r, wg); err != nil {
				return nil, fmt.Errorf("unable to remove old redirect: %s", err)
			}

			goto create
		}

		r.updateRules(l4)
		err := r.implementation.UpdateRules(wg)
		if err != nil {
			scopedLog.WithError(err).Error("Unable to update ", l4.L7Parser, " proxy")
			return nil, err
		}

		r.lastUpdated = time.Now()

		scopedLog.WithField(logfields.Object, logfields.Repr(r)).
			Debug("updated existing ", l4.L7Parser, " proxy instance")

		return r, nil
	}

create:
	redir := newRedirect(localEndpoint, id)
	redir.endpointID = localEndpoint.GetID()
	redir.ingress = l4.Ingress
	redir.parserType = l4.L7Parser
	redir.updateRules(l4)

retryCreatePort:
	for nRetry := 0; ; nRetry++ {
		to, err := p.allocatePort()
		if err != nil {
			return nil, err
		}

		redir.ProxyPort = to

		switch l4.L7Parser {
		case policy.ParserTypeKafka:
			redir.implementation, err = createKafkaRedirect(redir, kafkaConfiguration{}, DefaultEndpointInfoRegistry)

		case policy.ParserTypeHTTP:
			redir.implementation, err = createEnvoyRedirect(redir, p.stateDir, p.XDSServer, wg)

		default:
			return nil, fmt.Errorf("unsupported L7 parser type: %s", l4.L7Parser)
		}

		switch {
		case err == nil:
			scopedLog.WithField(logfields.Object, logfields.Repr(redir)).
				Debug("Created new ", l4.L7Parser, " proxy instance")

			p.allocatedPorts[to] = struct{}{}
			p.redirects[id] = redir

			break retryCreatePort

		// an error occurred, and we have no more retries
		case nRetry >= redirectCreationAttempts:
			scopedLog.WithError(err).Error("Unable to create ", l4.L7Parser, " proxy")
			return nil, err

		// an error occurred and we can retry
		default:
			scopedLog.WithError(err).Warning("Unable to create ", l4.L7Parser, " proxy, will retry")
		}
	}

	return redir, nil
}

// RemoveRedirect removes an existing redirect.
func (p *Proxy) RemoveRedirect(id string, wg *completion.WaitGroup) error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	r, ok := p.redirects[id]
	if !ok {
		return fmt.Errorf("unable to find redirect %s", id)
	}

	return p.removeRedirect(id, r, wg)
}

// removeRedirect removes an existing redirect. p.mutex must be held
func (p *Proxy) removeRedirect(id string, r *Redirect, wg *completion.WaitGroup) error {
	log.WithField(fieldProxyRedirectID, id).
		Debug("removing proxy redirect")
	r.implementation.Close(wg)

	delete(p.redirects, id)

	// delay the release and reuse of the port number so it is guaranteed
	// to be safe to listen on the port again
	go func() {
		time.Sleep(portReuseDelay)

		// The cleanup of the proxymap is delayed a bit to ensure that
		// the datapath has implemented the redirect change and we
		// cleanup the map before we release the port and allow reuse
		proxymap.CleanupOnRedirectClose(r.ProxyPort)

		p.mutex.Lock()
		delete(p.allocatedPorts, r.ProxyPort)
		p.mutex.Unlock()

		log.WithField(fieldProxyRedirectID, id).Debugf("Delayed release of proxy port %d", r.ProxyPort)
	}()

	return nil
}

// ChangeLogLevel changes proxy log level to correspond to the logrus log level 'level'.
func ChangeLogLevel(level logrus.Level) {
	if envoyProxy != nil {
		envoyProxy.ChangeLogLevel(level)
	}
}

// GetStatusModel returns the proxy status as API model
func (p *Proxy) GetStatusModel() *models.ProxyStatus {
	p.mutex.RLock()
	defer p.mutex.RUnlock()

	return &models.ProxyStatus{
		IP:        node.GetInternalIPv4().String(),
		PortRange: fmt.Sprintf("%d-%d", p.rangeMin, p.rangeMax),
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

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 server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os/user"
	"sort"
	"sync"
	"time"

	"camlistore.org/pkg/auth"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/search"
)

// RootHandler handles serving the about/splash page.
type RootHandler struct {
	// Stealth determines whether we hide from non-authenticated
	// clients.
	Stealth bool

	OwnerName string // for display purposes only

	// URL prefixes (path or full URL) to the primary blob and
	// search root.
	BlobRoot   string
	SearchRoot string
	statusRoot string

	Storage blobserver.Storage // of BlobRoot, or nil

	searchInitOnce sync.Once // runs searchInit, which populates searchHandler
	searchInit     func()
	searchHandler  *search.Handler // of SearchRoot, or nil

	ui   *UIHandler     // or nil, if none configured
	sync []*SyncHandler // list of configured sync handlers, for discovery.
}

func (rh *RootHandler) SearchHandler() (h *search.Handler, ok bool) {
	rh.searchInitOnce.Do(rh.searchInit)
	return rh.searchHandler, rh.searchHandler != nil
}

func init() {
	blobserver.RegisterHandlerConstructor("root", newRootFromConfig)
}

func newRootFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
	u, err := user.Current()
	if err != nil {
		return
	}
	root := &RootHandler{
		BlobRoot:   conf.OptionalString("blobRoot", ""),
		SearchRoot: conf.OptionalString("searchRoot", ""),
		OwnerName:  conf.OptionalString("ownerName", u.Name),
	}
	root.Stealth = conf.OptionalBool("stealth", false)
	root.statusRoot = conf.OptionalString("statusRoot", "")
	if err = conf.Validate(); err != nil {
		return
	}

	if root.BlobRoot != "" {
		bs, err := ld.GetStorage(root.BlobRoot)
		if err != nil {
			return nil, fmt.Errorf("Root handler's blobRoot of %q error: %v", root.BlobRoot, err)
		}
		root.Storage = bs
	}

	root.searchInit = func() {}
	if root.SearchRoot != "" {
		prefix := root.SearchRoot
		if t := ld.GetHandlerType(prefix); t != "search" {
			if t == "" {
				return nil, fmt.Errorf("root handler's searchRoot of %q is invalid and doesn't refer to a declared handler", prefix)
			}
			return nil, fmt.Errorf("root handler's searchRoot of %q is of type %q, not %q", prefix, t, "search")
		}
		root.searchInit = func() {
			h, err := ld.GetHandler(prefix)
			if err != nil {
				log.Fatalf("Error fetching SearchRoot at %q: %v", prefix, err)
			}
			root.searchHandler = h.(*search.Handler)
			root.searchInit = nil
		}
	}

	return root, nil
}

func (rh *RootHandler) registerUIHandler(h *UIHandler) {
	rh.ui = h
}

func (rh *RootHandler) registerSyncHandler(h *SyncHandler) {
	rh.sync = append(rh.sync, h)
	sort.Sort(byFromTo(rh.sync))
}

func (rh *RootHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if wantsDiscovery(req) {
		if auth.Allowed(req, auth.OpDiscovery) {
			rh.serveDiscovery(rw, req)
			return
		}
		if !rh.Stealth {
			http.Error(rw, "Unauthorized", http.StatusUnauthorized)
		}
		return
	}

	if rh.Stealth {
		return
	}
	if req.URL.Path == "/favicon.ico" {
		serveStaticFile(rw, req, Files, "favicon.ico")
		return
	}

	configLink := ""
	if auth.IsLocalhost(req) {
		configLink = "
If you're coming from localhost, hit /setup.
"
	}
	fmt.Fprintf(rw, "This is camlistored, a "+
		"Camlistore server."+
		"%s\n", configLink)
}

type byFromTo []*SyncHandler

func (b byFromTo) Len() int      { return len(b) }
func (b byFromTo) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byFromTo) Less(i, j int) bool {
	if b[i].fromName < b[j].fromName {
		return true
	}
	return b[i].fromName == b[j].fromName && b[i].toName < b[j].toName
}

func (rh *RootHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) {
	m := map[string]interface{}{
		"blobRoot":   rh.BlobRoot,
		"searchRoot": rh.SearchRoot,
		"ownerName":  rh.OwnerName,
		"statusRoot": rh.statusRoot,
	}
	if gener, ok := rh.Storage.(blobserver.Generationer); ok {
		initTime, gen, err := gener.StorageGeneration()
		if err != nil {
			m["storageGenerationError"] = err.Error()
		} else {
			m["storageInitTime"] = initTime.UTC().Format(time.RFC3339)
			m["storageGeneration"] = gen
		}
	}
	if rh.ui != nil {
		rh.ui.populateDiscoveryMap(m)
	}
	if len(rh.sync) > 0 {
		var syncHandlers []map[string]interface{}
		for _, sh := range rh.sync {
			syncHandlers = append(syncHandlers, sh.discoveryMap())
		}
		m["syncHandlers"] = syncHandlers
	}
	discoveryHelper(rw, req, m)
}

func discoveryHelper(rw http.ResponseWriter, req *http.Request, m map[string]interface{}) {
	rw.Header().Set("Content-Type", "text/javascript")
	if cb := req.FormValue("cb"); identOrDotPattern.MatchString(cb) {
		fmt.Fprintf(rw, "%s(", cb)
		defer rw.Write([]byte(");\n"))
	} else if v := req.FormValue("var"); identOrDotPattern.MatchString(v) {
		fmt.Fprintf(rw, "%s = ", v)
		defer rw.Write([]byte(";\n"))
	}
	bytes, _ := json.MarshalIndent(m, "", "  ")
	rw.Write(bytes)
}
<commit_msg>root page: added link to ui<commit_after>/*
Copyright 2011 Google Inc.

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 server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os/user"
	"sort"
	"sync"
	"time"

	"camlistore.org/pkg/auth"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/search"
)

// RootHandler handles serving the about/splash page.
type RootHandler struct {
	// Stealth determines whether we hide from non-authenticated
	// clients.
	Stealth bool

	OwnerName string // for display purposes only

	// URL prefixes (path or full URL) to the primary blob and
	// search root.
	BlobRoot   string
	SearchRoot string
	statusRoot string

	Storage blobserver.Storage // of BlobRoot, or nil

	searchInitOnce sync.Once // runs searchInit, which populates searchHandler
	searchInit     func()
	searchHandler  *search.Handler // of SearchRoot, or nil

	ui   *UIHandler     // or nil, if none configured
	sync []*SyncHandler // list of configured sync handlers, for discovery.
}

func (rh *RootHandler) SearchHandler() (h *search.Handler, ok bool) {
	rh.searchInitOnce.Do(rh.searchInit)
	return rh.searchHandler, rh.searchHandler != nil
}

func init() {
	blobserver.RegisterHandlerConstructor("root", newRootFromConfig)
}

func newRootFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
	u, err := user.Current()
	if err != nil {
		return
	}
	root := &RootHandler{
		BlobRoot:   conf.OptionalString("blobRoot", ""),
		SearchRoot: conf.OptionalString("searchRoot", ""),
		OwnerName:  conf.OptionalString("ownerName", u.Name),
	}
	root.Stealth = conf.OptionalBool("stealth", false)
	root.statusRoot = conf.OptionalString("statusRoot", "")
	if err = conf.Validate(); err != nil {
		return
	}

	if root.BlobRoot != "" {
		bs, err := ld.GetStorage(root.BlobRoot)
		if err != nil {
			return nil, fmt.Errorf("Root handler's blobRoot of %q error: %v", root.BlobRoot, err)
		}
		root.Storage = bs
	}

	root.searchInit = func() {}
	if root.SearchRoot != "" {
		prefix := root.SearchRoot
		if t := ld.GetHandlerType(prefix); t != "search" {
			if t == "" {
				return nil, fmt.Errorf("root handler's searchRoot of %q is invalid and doesn't refer to a declared handler", prefix)
			}
			return nil, fmt.Errorf("root handler's searchRoot of %q is of type %q, not %q", prefix, t, "search")
		}
		root.searchInit = func() {
			h, err := ld.GetHandler(prefix)
			if err != nil {
				log.Fatalf("Error fetching SearchRoot at %q: %v", prefix, err)
			}
			root.searchHandler = h.(*search.Handler)
			root.searchInit = nil
		}
	}

	return root, nil
}

func (rh *RootHandler) registerUIHandler(h *UIHandler) {
	rh.ui = h
}

func (rh *RootHandler) registerSyncHandler(h *SyncHandler) {
	rh.sync = append(rh.sync, h)
	sort.Sort(byFromTo(rh.sync))
}

func (rh *RootHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if wantsDiscovery(req) {
		if auth.Allowed(req, auth.OpDiscovery) {
			rh.serveDiscovery(rw, req)
			return
		}
		if !rh.Stealth {
			http.Error(rw, "Unauthorized", http.StatusUnauthorized)
		}
		return
	}

	if rh.Stealth {
		return
	}
	if req.URL.Path == "/favicon.ico" {
		serveStaticFile(rw, req, Files, "favicon.ico")
		return
	}

	configLink := ""
	if auth.IsLocalhost(req) {
		configLink = "
If you're coming from localhost, configure your Camlistore server at /setup.
"
	}
	fmt.Fprintf(rw, "This is camlistored, a "+
		"Camlistore server."+
		"%s"+
		"
To manage your content, access the /ui.
\n",
		configLink)
}

type byFromTo []*SyncHandler

func (b byFromTo) Len() int      { return len(b) }
func (b byFromTo) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byFromTo) Less(i, j int) bool {
	if b[i].fromName < b[j].fromName {
		return true
	}
	return b[i].fromName == b[j].fromName && b[i].toName < b[j].toName
}

func (rh *RootHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) {
	m := map[string]interface{}{
		"blobRoot":   rh.BlobRoot,
		"searchRoot": rh.SearchRoot,
		"ownerName":  rh.OwnerName,
		"statusRoot": rh.statusRoot,
	}
	if gener, ok := rh.Storage.(blobserver.Generationer); ok {
		initTime, gen, err := gener.StorageGeneration()
		if err != nil {
			m["storageGenerationError"] = err.Error()
		} else {
			m["storageInitTime"] = initTime.UTC().Format(time.RFC3339)
			m["storageGeneration"] = gen
		}
	}
	if rh.ui != nil {
		rh.ui.populateDiscoveryMap(m)
	}
	if len(rh.sync) > 0 {
		var syncHandlers []map[string]interface{}
		for _, sh := range rh.sync {
			syncHandlers = append(syncHandlers, sh.discoveryMap())
		}
		m["syncHandlers"] = syncHandlers
	}
	discoveryHelper(rw, req, m)
}

func discoveryHelper(rw http.ResponseWriter, req *http.Request, m map[string]interface{}) {
	rw.Header().Set("Content-Type", "text/javascript")
	if cb := req.FormValue("cb"); identOrDotPattern.MatchString(cb) {
		fmt.Fprintf(rw, "%s(", cb)
		defer rw.Write([]byte(");\n"))
	} else if v := req.FormValue("var"); identOrDotPattern.MatchString(v) {
		fmt.Fprintf(rw, "%s = ", v)
		defer rw.Write([]byte(";\n"))
	}
	bytes, _ := json.MarshalIndent(m, "", "  ")
	rw.Write(bytes)
}
<|endoftext|>


<commit_before>package utils

import (
	"strconv"
	"sync/atomic"
	"time"
)

// v2 types

type NonceGenerator interface {
	GetNonce() string
}

type EpochNonceGenerator struct {
	nonce uint64
}

// GetNonce is a naive nonce producer that takes the current Unix nano epoch
// and counts upwards.
// This is a naive approach because the nonce bound to the currently used API
// key and as such needs to be synchronised with other instances using the same
// key in order to avoid race conditions.
func (u *EpochNonceGenerator) GetNonce() string {
	return strconv.FormatUint(atomic.AddUint64(&u.nonce, 1), 10)
}

func NewEpochNonceGenerator() *EpochNonceGenerator {
	return &EpochNonceGenerator{
		nonce: uint64(time.Now().Unix()) * 1000,
	}
}

// v1 support

var nonce uint64

func init() {
	nonce = uint64(time.Now().UnixNano()) * 1000
}

// GetNonce is a naive nonce producer that takes the current Unix nano epoch
// and counts upwards.
// This is a naive approach because the nonce bound to the currently used API
// key and as such needs to be synchronised with other instances using the same
// key in order to avoid race conditions.
func GetNonce() string {
	return strconv.FormatUint(atomic.AddUint64(&nonce, 1), 10)
}
<commit_msg>updating nonce<commit_after>package utils

import (
	"strconv"
	"sync/atomic"
	"time"
)

// v2 types

type NonceGenerator interface {
	GetNonce() string
}

type EpochNonceGenerator struct {
	nonce uint64
}

// GetNonce is a naive nonce producer that takes the current Unix nano epoch
// and counts upwards.
// This is a naive approach because the nonce bound to the currently used API
// key and as such needs to be synchronised with other instances using the same
// key in order to avoid race conditions.
func (u *EpochNonceGenerator) GetNonce() string {
	return strconv.FormatUint(atomic.AddUint64(&u.nonce, 1), 10)
}

func NewEpochNonceGenerator() *EpochNonceGenerator {
	return &EpochNonceGenerator{
		nonce: uint64(time.Now().Unix()) * 1000000,
	}
}

// v1 support

var nonce uint64

func init() {
	nonce = uint64(time.Now().UnixNano()) * 1000000
}

// GetNonce is a naive nonce producer that takes the current Unix nano epoch
// and counts upwards.
// This is a naive approach because the nonce bound to the currently used API
// key and as such needs to be synchronised with other instances using the same
// key in order to avoid race conditions.
func GetNonce() string {
	return strconv.FormatUint(atomic.AddUint64(&nonce, 1), 10)
}
<|endoftext|>


<commit_before>// Copyright 2015 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package plans

import (
	"bytes"
	"fmt"
	"sort"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/column"
	"github.com/pingcap/tidb/context"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/field"
	"github.com/pingcap/tidb/model"
	mysql "github.com/pingcap/tidb/mysqldef"
	"github.com/pingcap/tidb/plan"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/sessionctx/variable"
	"github.com/pingcap/tidb/stmt"
	"github.com/pingcap/tidb/table"
	"github.com/pingcap/tidb/util/charset"
	"github.com/pingcap/tidb/util/format"
)

var (
	_ plan.Plan = (*ShowPlan)(nil)
)

// ShowPlan is used for show statements
type ShowPlan struct {
	Target     int
	DBName     string
	TableName  string
	ColumnName string
	Flag       int
	Full       bool
	// Used by SHOW VARIABLES
	GlobalScope bool
	Pattern     *expression.PatternLike
	Where       expression.Expression
	rows        []*plan.Row
	cursor      int
}

func (s *ShowPlan) isColOK(c *column.Col) bool {
	// support `desc tableName columnName`
	// TODO: columnName can be a regular
	if s.ColumnName == "" {
		return true
	}

	if strings.EqualFold(s.ColumnName, c.Name.L) {
		return true
	}

	return false
}

// Explain implements plan.Plan Explain interface.
func (s *ShowPlan) Explain(w format.Formatter) {
	// TODO: finish this
}

// GetFields implements plan.Plan GetFields interface.
func (s *ShowPlan) GetFields() []*field.ResultField {
	var (
		names []string
		types []byte
	)

	switch s.Target {
	case stmt.ShowEngines:
		names = []string{"Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"}
	case stmt.ShowDatabases:
		names = []string{"Database"}
	case stmt.ShowTables:
		names = []string{fmt.Sprintf("Tables_in_%s", s.DBName)}
		if s.Full {
			names = append(names, "Table_type")
		}
	case stmt.ShowColumns:
		names = column.ColDescFieldNames(s.Full)
	case stmt.ShowWarnings:
		names = []string{"Level", "Code", "Message"}
		types = []byte{mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar}
	case stmt.ShowCharset:
		names = []string{"Charset", "Description", "Default collation", "Maxlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	case stmt.ShowVariables:
		names = []string{"Variable_name", "Value"}
	case stmt.ShowCollation:
		names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong,
			mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	case stmt.ShowCreateTable:
		names = []string{"Table", "Create Table"}
	}
	fields := make([]*field.ResultField, 0, len(names))
	for i, name := range names {
		f := &field.ResultField{Name: name}
		if types == nil || types[i] == 0 {
			// use varchar as the default return column type
			f.Col.Tp = mysql.TypeVarchar
		} else {
			f.Col.Tp = types[i]
		}

		fields = append(fields, f)
	}

	return fields
}

// Filter implements plan.Plan Filter interface.
func (s *ShowPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) {
	return s, false, nil
}

// Next implements plan.Plan Next interface.
func (s *ShowPlan) Next(ctx context.Context) (row *plan.Row, err error) {
	if s.rows == nil {
		s.fetchAll(ctx)
	}
	if s.cursor == len(s.rows) {
		return
	}
	row = s.rows[s.cursor]
	s.cursor++
	return
}

func (s *ShowPlan) fetchAll(ctx context.Context) error {
	switch s.Target {
	case stmt.ShowEngines:
		return s.fetchShowEngines(ctx)
	case stmt.ShowDatabases:
		return s.fetchShowDatabases(ctx)
	case stmt.ShowTables:
		return s.fetchShowTables(ctx)
	case stmt.ShowColumns:
		return s.fetchShowColumns(ctx)
	case stmt.ShowWarnings:
		// empty result
	case stmt.ShowCharset:
		return s.fetchShowCharset(ctx)
	case stmt.ShowVariables:
		return s.fetchShowVariables(ctx)
	case stmt.ShowCollation:
		return s.fetchShowCollation(ctx)
	case stmt.ShowCreateTable:
		return s.fetchShowCreateTable(ctx)
	}
	return nil
}

// Close implements plan.Plan Close interface.
func (s *ShowPlan) Close() error {
	s.rows = nil
	s.cursor = 0
	return nil
}

func (s *ShowPlan) evalCondition(ctx context.Context, m map[interface{}]interface{}) (bool, error) {
	var cond expression.Expression
	if s.Pattern != nil {
		cond = s.Pattern
	} else if s.Where != nil {
		cond = s.Where
	}

	if cond == nil {
		return true, nil
	}

	return expression.EvalBoolExpr(ctx, cond, m)
}

func (s *ShowPlan) getTable(ctx context.Context) (table.Table, error) {
	is := sessionctx.GetDomain(ctx).InfoSchema()
	dbName := model.NewCIStr(s.DBName)
	if !is.SchemaExists(dbName) {
		return nil, errors.Errorf("Can not find DB: %s", dbName)
	}
	tbName := model.NewCIStr(s.TableName)
	tb, err := is.TableByName(dbName, tbName)
	if err != nil {
		return nil, errors.Errorf("Can not find table: %s", s.TableName)
	}
	return tb, nil
}

func (s *ShowPlan) fetchShowColumns(ctx context.Context) error {
	tb, err := s.getTable(ctx)
	if err != nil {
		return errors.Trace(err)
	}

	cols := tb.Cols()

	for _, col := range cols {
		if !s.isColOK(col) {
			continue
		}

		desc := column.NewColDesc(col)

		// The FULL keyword causes the output to include the column collation and comments,
		// as well as the privileges you have for each column.
		row := &plan.Row{}
		if s.Full {
			row.Data = []interface{}{
				desc.Field,
				desc.Type,
				desc.Collation,
				desc.Null,
				desc.Key,
				desc.DefaultValue,
				desc.Extra,
				desc.Privileges,
				desc.Comment,
			}
		} else {
			row.Data = []interface{}{
				desc.Field,
				desc.Type,
				desc.Null,
				desc.Key,
				desc.DefaultValue,
				desc.Extra,
			}
		}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowCollation(ctx context.Context) error {
	collations := charset.GetCollations()
	m := map[interface{}]interface{}{}

	for _, v := range collations {
		if s.Pattern != nil {
			s.Pattern.Expr = expression.Value{Val: v.Name}
		} else if s.Where != nil {
			m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
				switch {
				case strings.EqualFold(name, "Collation"):
					return v.Name, nil
				case strings.EqualFold(name, "Charset"):
					return v.CharsetName, nil
				case strings.EqualFold(name, "Id"):
					return v.ID, nil
				case strings.EqualFold(name, "Default"):
					if v.IsDefault {
						return "Yes", nil
					}
					return "", nil
				case strings.EqualFold(name, "Compiled"):
					return "Yes", nil
				case strings.EqualFold(name, "Sortlen"):
					// TODO: add sort length in Collation
					return 1, nil
				default:
					return nil, errors.Errorf("unknown field %s", name)
				}
			}
		}

		match, err := s.evalCondition(ctx, m)
		if err != nil {
			return errors.Trace(err)
		}
		if !match {
			continue
		}

		isDefault := ""
		if v.IsDefault {
			isDefault = "Yes"
		}
		row := &plan.Row{Data: []interface{}{v.Name, v.CharsetName, v.ID, isDefault, "Yes", 1}}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowTables(ctx context.Context) error {
	is := sessionctx.GetDomain(ctx).InfoSchema()
	dbName := model.NewCIStr(s.DBName)
	if !is.SchemaExists(dbName) {
		return errors.Errorf("Can not find DB: %s", dbName)
	}

	// sort for tables
	var tableNames []string
	for _, v := range is.SchemaTables(dbName) {
		tableNames = append(tableNames, v.TableName().L)
	}

	sort.Strings(tableNames)

	for _, v := range tableNames {
		data := []interface{}{v}
		if s.Full {
			// TODO: support "VIEW" later if we have supported view feature.
			// now, just use "BASE TABLE".
			data = append(data, "BASE TABLE")
		}
		s.rows = append(s.rows, &plan.Row{Data: data})
	}
	return nil
}

func (s *ShowPlan) fetchShowVariables(ctx context.Context) error {
	sessionVars := variable.GetSessionVars(ctx)
	m := map[interface{}]interface{}{}

	for _, v := range variable.SysVars {
		if s.Pattern != nil {
			s.Pattern.Expr = expression.Value{Val: v.Name}
		} else if s.Where != nil {
			m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
				if strings.EqualFold(name, "Variable_name") {
					return v.Name, nil
				}

				return nil, errors.Errorf("unknown field %s", name)
			}
		}

		match, err := s.evalCondition(ctx, m)
		if err != nil {
			return errors.Trace(err)
		}
		if !match {
			continue
		}

		value := v.Value
		if !s.GlobalScope {
			// Try to get Session Scope variable value
			sv, ok := sessionVars.Systems[v.Name]
			if ok {
				value = sv
			}
		}
		row := &plan.Row{Data: []interface{}{v.Name, value}}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowCharset(ctx context.Context) error {
	// See: http://dev.mysql.com/doc/refman/5.7/en/show-character-set.html
	descs := charset.GetAllCharsets()
	for _, desc := range descs {
		row := &plan.Row{
			Data: []interface{}{desc.Name, desc.Desc, desc.DefaultCollation, desc.Maxlen},
		}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowEngines(ctx context.Context) error {
	row := &plan.Row{
		Data: []interface{}{"InnoDB", "DEFAULT", "Supports transactions, row-level locking, and foreign keys", "YES", "YES", "YES"},
	}
	s.rows = append(s.rows, row)
	return nil
}

func (s *ShowPlan) fetchShowDatabases(ctx context.Context) error {
	dbs := sessionctx.GetDomain(ctx).InfoSchema().AllSchemaNames()

	// TODO: let information_schema be the first database
	sort.Strings(dbs)

	for _, d := range dbs {
		s.rows = append(s.rows, &plan.Row{Data: []interface{}{d}})
	}
	return nil
}

func (s *ShowPlan) fetchShowCreateTable(ctx context.Context) error {
	tb, err := s.getTable(ctx)
	if err != nil {
		return errors.Trace(err)
	}

	// TODO: let the result more like MySQL.
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("CREATE TABLE `%s` (\n", tb.TableName().O))
	for i, col := range tb.Cols() {
		buf.WriteString(fmt.Sprintf("  `%s` %s", col.Name.O, col.GetTypeDesc()))
		if mysql.HasAutoIncrementFlag(col.Flag) {
			buf.WriteString(" NOT NULL AUTO_INCREMENT")
		} else {
			if col.DefaultValue == nil {
				buf.WriteString(" DEFAULT NULL")
			} else {
				buf.WriteString(fmt.Sprintf(" DEFAULT %v", col.DefaultValue))
			}
		}
		if i != len(tb.Cols())-1 {
			buf.WriteString(",\n")
		}
	}

	if len(tb.Indices()) > 0 {
		buf.WriteString(",\n")
	}

	for i, idx := range tb.Indices() {
		if idx.Primary {
			buf.WriteString("  PRIMARY KEY ")
		} else if idx.Unique {
			buf.WriteString(fmt.Sprintf("  UNIQUE KEY `%s` ", idx.Name.O))
		} else {
			buf.WriteString(fmt.Sprintf("  KEY `%s` ", idx.Name.O))
		}

		cols := make([]string, 0, len(idx.Columns))
		for _, c := range idx.Columns {
			cols = append(cols, c.Name.O)
		}
		buf.WriteString(fmt.Sprintf("(`%s`)", strings.Join(cols, "`,")))
		if i != len(tb.Indices())-1 {
			buf.WriteString(",\n")
		}
	}

	buf.WriteString("\n")

	buf.WriteString(") ENGINE=InnoDB")
	if s := tb.Meta().Charset; len(s) > 0 {
		buf.WriteString(fmt.Sprintf(" DEFAULT CHARSET=%s", s))
	} else {
		buf.WriteString(" DEFAULT CHARSET=latin1")
	}

	data := []interface{}{
		tb.TableName().O,
		buf.String(),
	}

	s.rows = append(s.rows, &plan.Row{Data: data})

	return nil
}
<commit_msg>plans: handle CURRENT_TIMESTAMP specially<commit_after>// Copyright 2015 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package plans

import (
	"bytes"
	"fmt"
	"sort"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/column"
	"github.com/pingcap/tidb/context"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/field"
	"github.com/pingcap/tidb/model"
	mysql "github.com/pingcap/tidb/mysqldef"
	"github.com/pingcap/tidb/plan"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/sessionctx/variable"
	"github.com/pingcap/tidb/stmt"
	"github.com/pingcap/tidb/table"
	"github.com/pingcap/tidb/util/charset"
	"github.com/pingcap/tidb/util/format"
)

var (
	_ plan.Plan = (*ShowPlan)(nil)
)

// ShowPlan is used for show statements
type ShowPlan struct {
	Target     int
	DBName     string
	TableName  string
	ColumnName string
	Flag       int
	Full       bool
	// Used by SHOW VARIABLES
	GlobalScope bool
	Pattern     *expression.PatternLike
	Where       expression.Expression
	rows        []*plan.Row
	cursor      int
}

func (s *ShowPlan) isColOK(c *column.Col) bool {
	// support `desc tableName columnName`
	// TODO: columnName can be a regular
	if s.ColumnName == "" {
		return true
	}

	if strings.EqualFold(s.ColumnName, c.Name.L) {
		return true
	}

	return false
}

// Explain implements plan.Plan Explain interface.
func (s *ShowPlan) Explain(w format.Formatter) {
	// TODO: finish this
}

// GetFields implements plan.Plan GetFields interface.
func (s *ShowPlan) GetFields() []*field.ResultField {
	var (
		names []string
		types []byte
	)

	switch s.Target {
	case stmt.ShowEngines:
		names = []string{"Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"}
	case stmt.ShowDatabases:
		names = []string{"Database"}
	case stmt.ShowTables:
		names = []string{fmt.Sprintf("Tables_in_%s", s.DBName)}
		if s.Full {
			names = append(names, "Table_type")
		}
	case stmt.ShowColumns:
		names = column.ColDescFieldNames(s.Full)
	case stmt.ShowWarnings:
		names = []string{"Level", "Code", "Message"}
		types = []byte{mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar}
	case stmt.ShowCharset:
		names = []string{"Charset", "Description", "Default collation", "Maxlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	case stmt.ShowVariables:
		names = []string{"Variable_name", "Value"}
	case stmt.ShowCollation:
		names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong,
			mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	case stmt.ShowCreateTable:
		names = []string{"Table", "Create Table"}
	}
	fields := make([]*field.ResultField, 0, len(names))
	for i, name := range names {
		f := &field.ResultField{Name: name}
		if types == nil || types[i] == 0 {
			// use varchar as the default return column type
			f.Col.Tp = mysql.TypeVarchar
		} else {
			f.Col.Tp = types[i]
		}

		fields = append(fields, f)
	}

	return fields
}

// Filter implements plan.Plan Filter interface.
func (s *ShowPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) {
	return s, false, nil
}

// Next implements plan.Plan Next interface.
func (s *ShowPlan) Next(ctx context.Context) (row *plan.Row, err error) {
	if s.rows == nil {
		s.fetchAll(ctx)
	}
	if s.cursor == len(s.rows) {
		return
	}
	row = s.rows[s.cursor]
	s.cursor++
	return
}

func (s *ShowPlan) fetchAll(ctx context.Context) error {
	switch s.Target {
	case stmt.ShowEngines:
		return s.fetchShowEngines(ctx)
	case stmt.ShowDatabases:
		return s.fetchShowDatabases(ctx)
	case stmt.ShowTables:
		return s.fetchShowTables(ctx)
	case stmt.ShowColumns:
		return s.fetchShowColumns(ctx)
	case stmt.ShowWarnings:
		// empty result
	case stmt.ShowCharset:
		return s.fetchShowCharset(ctx)
	case stmt.ShowVariables:
		return s.fetchShowVariables(ctx)
	case stmt.ShowCollation:
		return s.fetchShowCollation(ctx)
	case stmt.ShowCreateTable:
		return s.fetchShowCreateTable(ctx)
	}
	return nil
}

// Close implements plan.Plan Close interface.
func (s *ShowPlan) Close() error {
	s.rows = nil
	s.cursor = 0
	return nil
}

func (s *ShowPlan) evalCondition(ctx context.Context, m map[interface{}]interface{}) (bool, error) {
	var cond expression.Expression
	if s.Pattern != nil {
		cond = s.Pattern
	} else if s.Where != nil {
		cond = s.Where
	}

	if cond == nil {
		return true, nil
	}

	return expression.EvalBoolExpr(ctx, cond, m)
}

func (s *ShowPlan) getTable(ctx context.Context) (table.Table, error) {
	is := sessionctx.GetDomain(ctx).InfoSchema()
	dbName := model.NewCIStr(s.DBName)
	if !is.SchemaExists(dbName) {
		return nil, errors.Errorf("Can not find DB: %s", dbName)
	}
	tbName := model.NewCIStr(s.TableName)
	tb, err := is.TableByName(dbName, tbName)
	if err != nil {
		return nil, errors.Errorf("Can not find table: %s", s.TableName)
	}
	return tb, nil
}

func (s *ShowPlan) fetchShowColumns(ctx context.Context) error {
	tb, err := s.getTable(ctx)
	if err != nil {
		return errors.Trace(err)
	}

	cols := tb.Cols()

	for _, col := range cols {
		if !s.isColOK(col) {
			continue
		}

		desc := column.NewColDesc(col)

		// The FULL keyword causes the output to include the column collation and comments,
		// as well as the privileges you have for each column.
		row := &plan.Row{}
		if s.Full {
			row.Data = []interface{}{
				desc.Field,
				desc.Type,
				desc.Collation,
				desc.Null,
				desc.Key,
				desc.DefaultValue,
				desc.Extra,
				desc.Privileges,
				desc.Comment,
			}
		} else {
			row.Data = []interface{}{
				desc.Field,
				desc.Type,
				desc.Null,
				desc.Key,
				desc.DefaultValue,
				desc.Extra,
			}
		}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowCollation(ctx context.Context) error {
	collations := charset.GetCollations()
	m := map[interface{}]interface{}{}

	for _, v := range collations {
		if s.Pattern != nil {
			s.Pattern.Expr = expression.Value{Val: v.Name}
		} else if s.Where != nil {
			m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
				switch {
				case strings.EqualFold(name, "Collation"):
					return v.Name, nil
				case strings.EqualFold(name, "Charset"):
					return v.CharsetName, nil
				case strings.EqualFold(name, "Id"):
					return v.ID, nil
				case strings.EqualFold(name, "Default"):
					if v.IsDefault {
						return "Yes", nil
					}
					return "", nil
				case strings.EqualFold(name, "Compiled"):
					return "Yes", nil
				case strings.EqualFold(name, "Sortlen"):
					// TODO: add sort length in Collation
					return 1, nil
				default:
					return nil, errors.Errorf("unknown field %s", name)
				}
			}
		}

		match, err := s.evalCondition(ctx, m)
		if err != nil {
			return errors.Trace(err)
		}
		if !match {
			continue
		}

		isDefault := ""
		if v.IsDefault {
			isDefault = "Yes"
		}
		row := &plan.Row{Data: []interface{}{v.Name, v.CharsetName, v.ID, isDefault, "Yes", 1}}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowTables(ctx context.Context) error {
	is := sessionctx.GetDomain(ctx).InfoSchema()
	dbName := model.NewCIStr(s.DBName)
	if !is.SchemaExists(dbName) {
		return errors.Errorf("Can not find DB: %s", dbName)
	}

	// sort for tables
	var tableNames []string
	for _, v := range is.SchemaTables(dbName) {
		tableNames = append(tableNames, v.TableName().L)
	}

	sort.Strings(tableNames)

	for _, v := range tableNames {
		data := []interface{}{v}
		if s.Full {
			// TODO: support "VIEW" later if we have supported view feature.
			// now, just use "BASE TABLE".
			data = append(data, "BASE TABLE")
		}
		s.rows = append(s.rows, &plan.Row{Data: data})
	}
	return nil
}

func (s *ShowPlan) fetchShowVariables(ctx context.Context) error {
	sessionVars := variable.GetSessionVars(ctx)
	m := map[interface{}]interface{}{}

	for _, v := range variable.SysVars {
		if s.Pattern != nil {
			s.Pattern.Expr = expression.Value{Val: v.Name}
		} else if s.Where != nil {
			m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
				if strings.EqualFold(name, "Variable_name") {
					return v.Name, nil
				}

				return nil, errors.Errorf("unknown field %s", name)
			}
		}

		match, err := s.evalCondition(ctx, m)
		if err != nil {
			return errors.Trace(err)
		}
		if !match {
			continue
		}

		value := v.Value
		if !s.GlobalScope {
			// Try to get Session Scope variable value
			sv, ok := sessionVars.Systems[v.Name]
			if ok {
				value = sv
			}
		}
		row := &plan.Row{Data: []interface{}{v.Name, value}}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowCharset(ctx context.Context) error {
	// See: http://dev.mysql.com/doc/refman/5.7/en/show-character-set.html
	descs := charset.GetAllCharsets()
	for _, desc := range descs {
		row := &plan.Row{
			Data: []interface{}{desc.Name, desc.Desc, desc.DefaultCollation, desc.Maxlen},
		}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowEngines(ctx context.Context) error {
	row := &plan.Row{
		Data: []interface{}{"InnoDB", "DEFAULT", "Supports transactions, row-level locking, and foreign keys", "YES", "YES", "YES"},
	}
	s.rows = append(s.rows, row)
	return nil
}

func (s *ShowPlan) fetchShowDatabases(ctx context.Context) error {
	dbs := sessionctx.GetDomain(ctx).InfoSchema().AllSchemaNames()

	// TODO: let information_schema be the first database
	sort.Strings(dbs)

	for _, d := range dbs {
		s.rows = append(s.rows, &plan.Row{Data: []interface{}{d}})
	}
	return nil
}

func (s *ShowPlan) fetchShowCreateTable(ctx context.Context) error {
	tb, err := s.getTable(ctx)
	if err != nil {
		return errors.Trace(err)
	}

	// TODO: let the result more like MySQL.
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("CREATE TABLE `%s` (\n", tb.TableName().O))
	for i, col := range tb.Cols() {
		buf.WriteString(fmt.Sprintf("  `%s` %s", col.Name.O, col.GetTypeDesc()))
		if mysql.HasAutoIncrementFlag(col.Flag) {
			buf.WriteString(" NOT NULL AUTO_INCREMENT")
		} else {
			switch col.DefaultValue {
			case nil:
				buf.WriteString(" DEFAULT NULL")
			case "CURRENT_TIMESTAMP":
				buf.WriteString(" DEFAULT CURRENT_TIMESTAMP")
			default:
				buf.WriteString(fmt.Sprintf(" DEFAULT '%v'", col.DefaultValue))
			}

			if mysql.HasOnUpdateNowFlag(col.Flag) {
				buf.WriteString(" ON UPDATE CURRENT_TIMESTAMP")
			}
		}
		if i != len(tb.Cols())-1 {
			buf.WriteString(",\n")
		}
	}

	if len(tb.Indices()) > 0 {
		buf.WriteString(",\n")
	}

	for i, idx := range tb.Indices() {
		if idx.Primary {
			buf.WriteString("  PRIMARY KEY ")
		} else if idx.Unique {
			buf.WriteString(fmt.Sprintf("  UNIQUE KEY `%s` ", idx.Name.O))
		} else {
			buf.WriteString(fmt.Sprintf("  KEY `%s` ", idx.Name.O))
		}

		cols := make([]string, 0, len(idx.Columns))
		for _, c := range idx.Columns {
			cols = append(cols, c.Name.O)
		}
		buf.WriteString(fmt.Sprintf("(`%s`)", strings.Join(cols, "`,")))
		if i != len(tb.Indices())-1 {
			buf.WriteString(",\n")
		}
	}

	buf.WriteString("\n")

	buf.WriteString(") ENGINE=InnoDB")
	if s := tb.Meta().Charset; len(s) > 0 {
		buf.WriteString(fmt.Sprintf(" DEFAULT CHARSET=%s", s))
	} else {
		buf.WriteString(" DEFAULT CHARSET=latin1")
	}

	data := []interface{}{
		tb.TableName().O,
		buf.String(),
	}

	s.rows = append(s.rows, &plan.Row{Data: data})

	return nil
}
<|endoftext|>


<commit_before>package govuk_crawler_worker_test

import (
	. "github.com/alphagov/govuk_crawler_worker"

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

	"github.com/streadway/amqp"
)

var _ = Describe("QueueConnection", func() {
	It("fails if it can't connect to an AMQP server", func() {
		connection, err := NewQueueConnection("amqp://guest:guest@localhost:50000/")

		Expect(err).ToNot(BeNil())
		Expect(connection).To(BeNil())
	})

	Describe("Connecting to a running AMQP service", func() {
		var (
			connection    *QueueConnection
			connectionErr error
		)

		BeforeEach(func() {
			connection, connectionErr = NewQueueConnection("amqp://guest:guest@localhost:5672/")
		})

		AfterEach(func() {
			defer connection.Close()
		})

		It("successfully connects to an AMQP service", func() {
			Expect(connectionErr).To(BeNil())
			Expect(connection).ToNot(BeNil())
		})

		It("can close the connection without errors", func() {
			Expect(connection.Close()).To(BeNil())
		})

		It("can declare an exchange", func() {
			var err error
			exchange := "some-exchange"

			err = connection.ExchangeDeclare(exchange, "direct")
			Expect(err).To(BeNil())

			err = connection.Channel.ExchangeDelete(exchange, false, true)
			Expect(err).To(BeNil())
		})

		It("can declare a queue", func() {
			var (
				err   error
				queue amqp.Queue
				name  = "some-queue"
			)

			queue, err = connection.QueueDeclare(name)
			Expect(err).To(BeNil())
			Expect(queue.Name).To(Equal(name))

			deleted, err := connection.Channel.QueueDelete(name, false, false, true)
			Expect(err).To(BeNil())
			Expect(deleted).To(Equal(0))
		})

		It("can bind a queue to an exchange", func() {
			var err error

			exchangeName := "some-binding-exchange"
			queueName := "some-binding-queue"

			err = connection.ExchangeDeclare(exchangeName, "direct")
			Expect(err).To(BeNil())

			_, err = connection.QueueDeclare(queueName)
			Expect(err).To(BeNil())

			err = connection.BindQueueToExchange(queueName, exchangeName)
			Expect(err).To(BeNil())

			deleted, err := connection.Channel.QueueDelete(queueName, false, false, true)
			Expect(err).To(BeNil())
			Expect(deleted).To(Equal(0))

			err = connection.Channel.ExchangeDelete(exchangeName, false, true)
			Expect(err).To(BeNil())
		})
	})

	Describe("working with messages on the queue", func() {
		var (
			publisher *QueueConnection
			consumer  *QueueConnection
			err       error
		)

		exchangeName := "test-crawler-exchange"
		queueName := "test-crawler-queue"

		BeforeEach(func() {
			publisher, err = NewQueueConnection("amqp://guest:guest@localhost:5672/")
			Expect(err).To(BeNil())
			Expect(publisher).ToNot(BeNil())

			consumer, err = NewQueueConnection("amqp://guest:guest@localhost:5672/")
			Expect(err).To(BeNil())
			Expect(consumer).ToNot(BeNil())
		})

		AfterEach(func() {
			deleted, err := consumer.Channel.QueueDelete(queueName, false, false, true)
			Expect(err).To(BeNil())
			Expect(deleted).To(Equal(0))

			err = consumer.Channel.ExchangeDelete(exchangeName, false, true)
			Expect(err).To(BeNil())

			Expect(publisher.Close()).To(BeNil())
			Expect(consumer.Close()).To(BeNil())
		})

		It("should consume and publish messages onto the provided queue and exchange", func() {
			err = consumer.ExchangeDeclare(exchangeName, "direct")
			Expect(err).To(BeNil())

			_, err = consumer.QueueDeclare(queueName)
			Expect(err).To(BeNil())

			err = consumer.BindQueueToExchange(queueName, exchangeName)
			Expect(err).To(BeNil())

			deliveries, err := consumer.Consume(queueName)
			Expect(err).To(BeNil())

			err = publisher.Publish(exchangeName, "#", "text/plain", "foo")
			Expect(err).To(BeNil())

			for d := range deliveries {
				Expect(string(d.Body)).To(Equal("foo"))
				break
			}
		})
	})
})
<commit_msg>Acknowledge all deliveries we work with, even during testing<commit_after>package govuk_crawler_worker_test

import (
	. "github.com/alphagov/govuk_crawler_worker"

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

	"github.com/streadway/amqp"
)

var _ = Describe("QueueConnection", func() {
	It("fails if it can't connect to an AMQP server", func() {
		connection, err := NewQueueConnection("amqp://guest:guest@localhost:50000/")

		Expect(err).ToNot(BeNil())
		Expect(connection).To(BeNil())
	})

	Describe("Connecting to a running AMQP service", func() {
		var (
			connection    *QueueConnection
			connectionErr error
		)

		BeforeEach(func() {
			connection, connectionErr = NewQueueConnection("amqp://guest:guest@localhost:5672/")
		})

		AfterEach(func() {
			defer connection.Close()
		})

		It("successfully connects to an AMQP service", func() {
			Expect(connectionErr).To(BeNil())
			Expect(connection).ToNot(BeNil())
		})

		It("can close the connection without errors", func() {
			Expect(connection.Close()).To(BeNil())
		})

		It("can declare an exchange", func() {
			var err error
			exchange := "some-exchange"

			err = connection.ExchangeDeclare(exchange, "direct")
			Expect(err).To(BeNil())

			err = connection.Channel.ExchangeDelete(exchange, false, true)
			Expect(err).To(BeNil())
		})

		It("can declare a queue", func() {
			var (
				err   error
				queue amqp.Queue
				name  = "some-queue"
			)

			queue, err = connection.QueueDeclare(name)
			Expect(err).To(BeNil())
			Expect(queue.Name).To(Equal(name))

			deleted, err := connection.Channel.QueueDelete(name, false, false, true)
			Expect(err).To(BeNil())
			Expect(deleted).To(Equal(0))
		})

		It("can bind a queue to an exchange", func() {
			var err error

			exchangeName := "some-binding-exchange"
			queueName := "some-binding-queue"

			err = connection.ExchangeDeclare(exchangeName, "direct")
			Expect(err).To(BeNil())

			_, err = connection.QueueDeclare(queueName)
			Expect(err).To(BeNil())

			err = connection.BindQueueToExchange(queueName, exchangeName)
			Expect(err).To(BeNil())

			deleted, err := connection.Channel.QueueDelete(queueName, false, false, true)
			Expect(err).To(BeNil())
			Expect(deleted).To(Equal(0))

			err = connection.Channel.ExchangeDelete(exchangeName, false, true)
			Expect(err).To(BeNil())
		})
	})

	Describe("working with messages on the queue", func() {
		var (
			publisher *QueueConnection
			consumer  *QueueConnection
			err       error
		)

		exchangeName := "test-crawler-exchange"
		queueName := "test-crawler-queue"

		BeforeEach(func() {
			publisher, err = NewQueueConnection("amqp://guest:guest@localhost:5672/")
			Expect(err).To(BeNil())
			Expect(publisher).ToNot(BeNil())

			consumer, err = NewQueueConnection("amqp://guest:guest@localhost:5672/")
			Expect(err).To(BeNil())
			Expect(consumer).ToNot(BeNil())
		})

		AfterEach(func() {
			deleted, err := consumer.Channel.QueueDelete(queueName, false, false, true)
			Expect(err).To(BeNil())
			Expect(deleted).To(Equal(0))

			err = consumer.Channel.ExchangeDelete(exchangeName, false, true)
			Expect(err).To(BeNil())

			Expect(publisher.Close()).To(BeNil())
			Expect(consumer.Close()).To(BeNil())
		})

		It("should consume and publish messages onto the provided queue and exchange", func() {
			err = consumer.ExchangeDeclare(exchangeName, "direct")
			Expect(err).To(BeNil())

			_, err = consumer.QueueDeclare(queueName)
			Expect(err).To(BeNil())

			err = consumer.BindQueueToExchange(queueName, exchangeName)
			Expect(err).To(BeNil())

			deliveries, err := consumer.Consume(queueName)
			Expect(err).To(BeNil())

			err = publisher.Publish(exchangeName, "#", "text/plain", "foo")
			Expect(err).To(BeNil())

			for d := range deliveries {
				Expect(string(d.Body)).To(Equal("foo"))
				d.Ack(false)
				break
			}
		})
	})
})
<|endoftext|>


<commit_before>/*
Package plotter abstracts the details of other chart packages into a simple
interface composed of Figures, Charts, and Data
*/
package plotter

import (
	"fmt"
	"image"
	"image/color"
	"image/png"
	"io"
	"os"
	"path"
	"time"

	// third-party
	"code.google.com/p/freetype-go/freetype"
	"code.google.com/p/freetype-go/freetype/truetype"
	//"github.com/vdobler/chart"
	//"github.com/vdobler/chart/imgg"
	//"github.com/vdobler/chart/svgg"
	"github.com/ajstarks/svgo"
	"github.com/stephen-soltesz/chart"
	"github.com/stephen-soltesz/chart/imgg"
	"github.com/stephen-soltesz/chart/svgg"
)

type Style chart.Style

type Figure struct {
	Charts   []*Chart
	fontName string
	usetime  bool
}

var fontCache map[string]*truetype.Font

type Chart struct {
	chart.ScatterChart
}

type Data struct {
	chart.ScatterChartData
}

func autoStyle(s int) func() Style {
	i := s
	l := len(chart.StandardColors)
	return func() Style {
		i = (i + 1) % l
		return Style{Symbol: '.',
			SymbolSize:  1,
			SymbolColor: chart.StandardColors[i],
			LineColor:   chart.StandardColors[i],
			LineStyle:   chart.SolidLine,
			LineWidth:   2,
			FillColor:   chart.StandardColors[i]}
	}
}

var NextStyle = autoStyle(0)

func loadFont(fontfile string) *truetype.Font {
	var font *truetype.Font
	var ok bool
	var err error
	var data []byte

	// Read the font data once.
	if fontCache == nil {
		fontCache = make(map[string]*truetype.Font)
	}
	fontpath := "res/" + fontfile
	if font, ok = fontCache[fontpath]; !ok {
		data, err = Asset(fontpath)
		if err != nil {
			return nil
		}
		font, err = freetype.ParseFont(data)
		if err != nil {
			return nil
		}
		fontCache[fontpath] = font
	}
	return font
}

func NewFigure(usetime bool) *Figure {
	f := Figure{}
	f.fontName = "FreeSans.ttf"
	f.usetime = usetime
	return &f
}

func (f *Figure) RenderFile(filename string, width, height int) error {
	var err error
	var imgFile *os.File
	dir, file := path.Split(filename)
	tmpname := dir + "/.tmp_" + file

	imgFile, err = os.Create(tmpname)
	if err != nil {
		return err
	}
	err = f.Render(imgFile, width, height)
	if err != nil {
		return err
	}
	err = imgFile.Close()
	if err != nil {
		return err
	}
	err = os.Rename(tmpname, filename)
	if err != nil {
		return err
	}
	return nil
}

func (f *Figure) RenderSVG(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	count := len(f.Charts)

	svgdata := svg.New(writer)
	svgdata.Start(width+10, height+10)
	svgdata.Rect(0, 0, width, height, "fill: #ffffff")

	for i, ax := range f.Charts {
		igr := svgg.AddTo(svgdata, 0, i*(height/count), width, (height / count), "", 12, whiteBG)
		ax.Plot(igr)
	}

	svgdata.End()
	return nil
}

func (f *Figure) Render(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	image := image.NewRGBA(image.Rect(0, 0, width, height))
	count := len(f.Charts)
	font := loadFont(f.fontName)

	for i, ax := range f.Charts {
		igr := imgg.AddTo(image, 0, i*(height/count), width, (height / count), whiteBG, font, nil)
		ax.Plot(igr)
	}

	return png.Encode(writer, image)
}

func (f *Figure) AddChart(title, xlabel, ylabel string, xmin, xmax float64) *Chart {
	axis := Chart{chart.ScatterChart{}}

	axis.Title = title
	axis.XRange.Label = xlabel
	axis.YRange.Label = ylabel
	axis.XRange.ShowZero = false
	axis.XRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.YRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.XRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Format = func(val float64) string {
		return fmt.Sprintf("%.1f", val)
	}
	axis.XRange.Time = f.usetime
	if f.usetime {
		axis.XRange.TFixed(time.Unix(int64(xmin), 0), time.Unix(int64(xmax), 0), nil)
	} else {
		axis.XRange.Fixed(xmin, xmax, 0)
	}
	axis.XRange.Init()

	fSmall := chart.Font{Size: chart.SmallFontSize}
	fNormal := chart.Font{Size: chart.NormalFontSize}
	sKey := chart.Style{LineColor: color.Gray{0x88},
		LineStyle: chart.SolidLine,
		LineWidth: 1,
		FillColor: color.NRGBA{0xf8, 0xf8, 0xf8, 0x66},
		Font:      fSmall}
	sGrid := chart.Style{LineStyle: chart.DashedLine,
		LineColor: color.Gray{0xbb},
		LineWidth: 1}
	sZero := chart.Style{LineStyle: chart.DashedLine,
		LineColor: color.Gray{0xbb}}
	sMajor := chart.Style{LineColor: color.Gray{0x88},
		LineWidth: 1,
		Font:      fNormal}
	sTic := chart.Style{LineWidth: 1,
		LineColor: color.Gray{0x88},
		Font:      fNormal}
	sRange := chart.Style{LineWidth: 1, Font: fNormal}
	sTitle := chart.Style{LineWidth: 1, Font: fNormal}

	//sBg := chart.Style{LineColor: color.NRGBA{0xff, 0xff, 0xee, 0x88},
	//                   FillColor: color.NRGBA{0xff, 0xff, 0xee, 0x88}}
	//                   chart.PlotBackgroundElement: sBg,

	axis.Options = chart.PlotOptions{
		chart.GridLineElement:   sGrid,
		chart.ZeroAxisElement:   sZero,
		chart.MajorAxisElement:  sMajor,
		chart.MinorAxisElement:  sMajor,
		chart.MajorTicElement:   sTic,
		chart.MinorTicElement:   sTic,
		chart.KeyElement:        sKey,
		chart.RangeLimitElement: sRange,
		chart.TitleElement:      sTitle}
	axis.Key.Cols = 1
	axis.Key.Pos = "itl"

	f.Charts = append(f.Charts, &axis)
	return &axis
}

func (ax *Chart) ShowXAxis(s bool) {
	ax.XRange.TicSetting.HideLabels = !s
	return
}

func (ax *Chart) AddData(name string, x, y []float64, style Style) *Data {
	ax.AddDataPair(name, x, y, chart.PlotStyleLinesPoints, chart.Style(style))
	line := Data{ax.Data[len(ax.Data)-1]}
	return &line
}

/*
// must be called after a plot
func (ax *Chart) PlotArea() (int, int, int, int) {
  x1 := ax.XRange.Data2Screen(ax.XRange.Min)
  y1 := ax.YRange.Data2Screen(ax.YRange.Min)
  w1 := ax.XRange.Data2Screen(ax.XRange.Max) - ax.XRange.Data2Screen(ax.XRange.Min)
  h1 := ax.YRange.Data2Screen(ax.YRange.Max) - ax.YRange.Data2Screen(ax.YRange.Min)
  return x1, y1, w1, h1
}
*/
<commit_msg>retire PNG support, export Figure interface rather than struct.<commit_after>/*
Package plotter abstracts the details of other chart packages into a simple
interface composed of Figures, Charts, and Data
*/
package plotter

import (
	"fmt"
	//"image"
	"image/color"
	//"image/png"
	"io"
	"os"
	"path"
	"time"

	// third-party
	//"code.google.com/p/freetype-go/freetype"
	//"code.google.com/p/freetype-go/freetype/truetype"
	//"github.com/vdobler/chart"
	//"github.com/vdobler/chart/imgg"
	//"github.com/vdobler/chart/svgg"
	"github.com/ajstarks/svgo"
	"github.com/stephen-soltesz/chart"
	//"github.com/stephen-soltesz/chart/imgg"
	"github.com/stephen-soltesz/chart/svgg"
)

type Figure interface {
	Render(writer io.Writer, width, height int) error
	AddChart(title, xlabel, ylabel string, xmin, xmax float64) *Chart
}

type implFigure struct {
	Charts   []*Chart
	fontName string
	usetime  bool
}

type Style chart.Style

type Chart struct {
	chart.ScatterChart
}

type Data struct {
	chart.ScatterChartData
}

func autoStyle(s int) func() Style {
	i := s
	l := len(chart.StandardColors)
	return func() Style {
		i = (i + 1) % l
		return Style{Symbol: '.',
			SymbolSize:  1,
			SymbolColor: chart.StandardColors[i],
			LineColor:   chart.StandardColors[i],
			LineStyle:   chart.SolidLine,
			LineWidth:   2,
			FillColor:   chart.StandardColors[i]}
	}
}

var NextStyle = autoStyle(0)

func NewFigure(usetime bool) Figure {
	f := implFigure{}
	f.fontName = "FreeSans.ttf"
	f.usetime = usetime
	return &f
}

func RenderToFile(f Figure, filename string, width, height int) error {
	var err error
	var imgFile *os.File
	dir, file := path.Split(filename)
	tmpname := dir + "/.tmp_" + file

	imgFile, err = os.Create(tmpname)
	if err != nil {
		return err
	}
	err = f.Render(imgFile, width, height)
	if err != nil {
		return err
	}
	err = imgFile.Close()
	if err != nil {
		return err
	}
	err = os.Rename(tmpname, filename)
	if err != nil {
		return err
	}
	return nil
}

func (f *implFigure) Render(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	count := len(f.Charts)

	svgdata := svg.New(writer)
	svgdata.Start(width+10, height+10)
	svgdata.Rect(0, 0, width, height, "fill: #ffffff")

	for i, ax := range f.Charts {
		igr := svgg.AddTo(svgdata, 0, i*(height/count), width, (height / count), "", 12, whiteBG)
		ax.Plot(igr)
	}

	svgdata.End()
	return nil
}

func (f *implFigure) AddChart(title, xlabel, ylabel string, xmin, xmax float64) *Chart {
	axis := Chart{chart.ScatterChart{}}

	axis.Title = title
	axis.XRange.Label = xlabel
	axis.YRange.Label = ylabel
	axis.XRange.ShowZero = false
	axis.XRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.YRange.TicSetting.Mirror = chart.MirrorAxisOnly
	axis.XRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Grid = chart.GridLines
	axis.YRange.TicSetting.Format = func(val float64) string {
		return fmt.Sprintf("%.1f", val)
	}
	axis.XRange.Time = f.usetime
	if f.usetime {
		axis.XRange.TFixed(time.Unix(int64(xmin), 0), time.Unix(int64(xmax), 0), nil)
	} else {
		axis.XRange.Fixed(xmin, xmax, 0)
	}
	axis.XRange.Init()

	fSmall := chart.Font{Size: chart.SmallFontSize}
	fNormal := chart.Font{Size: chart.NormalFontSize}
	sKey := chart.Style{LineColor: color.Gray{0x88},
		LineStyle: chart.SolidLine,
		LineWidth: 1,
		FillColor: color.NRGBA{0xf8, 0xf8, 0xf8, 0x66},
		Font:      fSmall}
	sGrid := chart.Style{LineStyle: chart.DashedLine,
		LineColor: color.Gray{0xbb},
		LineWidth: 1}
	sZero := chart.Style{LineStyle: chart.DashedLine,
		LineColor: color.Gray{0xbb}}
	sMajor := chart.Style{LineColor: color.Gray{0x88},
		LineWidth: 1,
		Font:      fNormal}
	sTic := chart.Style{LineWidth: 1,
		LineColor: color.Gray{0x88},
		Font:      fNormal}
	sRange := chart.Style{LineWidth: 1, Font: fNormal}
	sTitle := chart.Style{LineWidth: 1, Font: fNormal}

	//sBg := chart.Style{LineColor: color.NRGBA{0xff, 0xff, 0xee, 0x88},
	//                   FillColor: color.NRGBA{0xff, 0xff, 0xee, 0x88}}
	//                   chart.PlotBackgroundElement: sBg,

	axis.Options = chart.PlotOptions{
		chart.GridLineElement:   sGrid,
		chart.ZeroAxisElement:   sZero,
		chart.MajorAxisElement:  sMajor,
		chart.MinorAxisElement:  sMajor,
		chart.MajorTicElement:   sTic,
		chart.MinorTicElement:   sTic,
		chart.KeyElement:        sKey,
		chart.RangeLimitElement: sRange,
		chart.TitleElement:      sTitle}
	axis.Key.Cols = 1
	axis.Key.Pos = "itl"

	f.Charts = append(f.Charts, &axis)
	return &axis
}

func (ax *Chart) ShowXAxis(s bool) {
	ax.XRange.TicSetting.HideLabels = !s
	return
}

func (ax *Chart) AddData(name string, x, y []float64, style Style) *Data {
	ax.AddDataPair(name, x, y, chart.PlotStyleLinesPoints, chart.Style(style))
	line := Data{ax.Data[len(ax.Data)-1]}
	return &line
}

/*
// must be called after a plot
func (ax *Chart) PlotArea() (int, int, int, int) {
  x1 := ax.XRange.Data2Screen(ax.XRange.Min)
  y1 := ax.YRange.Data2Screen(ax.YRange.Min)
  w1 := ax.XRange.Data2Screen(ax.XRange.Max) - ax.XRange.Data2Screen(ax.XRange.Min)
  h1 := ax.YRange.Data2Screen(ax.YRange.Max) - ax.YRange.Data2Screen(ax.YRange.Min)
  return x1, y1, w1, h1
}
*/
/*
func (f *implFigure) RenderPNG(writer io.Writer, width, height int) error {
	whiteBG := color.RGBA{0xee, 0xee, 0xee, 0xff}
	image := image.NewRGBA(image.Rect(0, 0, width, height))
	count := len(f.Charts)
	font := loadFont(f.fontName)

	for i, ax := range f.Charts {
		igr := imgg.AddTo(image, 0, i*(height/count), width, (height / count), whiteBG, font, nil)
		ax.Plot(igr)
	}

	return png.Encode(writer, image)
}

var fontCache map[string]*truetype.Font

func loadFont(fontfile string) *truetype.Font {
	var font *truetype.Font
	var ok bool
	var err error
	var data []byte

	// Read the font data once.
	if fontCache == nil {
		fontCache = make(map[string]*truetype.Font)
	}
	fontpath := "res/" + fontfile
	if font, ok = fontCache[fontpath]; !ok {
		data, err = Asset(fontpath)
		if err != nil {
			return nil
		}
		font, err = freetype.ParseFont(data)
		if err != nil {
			return nil
		}
		fontCache[fontpath] = font
	}
	return font
}
*/
<|endoftext|>


<commit_before>// Package rainforest is a golang client for the Rainforest QA API
package rainforest

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/rainforestapp/rainforest-cli/gittrigger"
	"github.com/ukd1/go.detectci"
	"github.com/whilp/git-urls"
)

const (
	// Version of the lib in SemVer
	libVersion = "2.0.1"

	currentBaseURL  = "https://app.rainforestqa.com/api/1/"
	authTokenHeader = "CLIENT_TOKEN"
)

// Client is responsible for communicating with Rainforest API
type Client struct {
	// http client used to connect to the Rainforest API
	client *http.Client

	// URL of a Rainforest API endpoint to be used by the client
	BaseURL *url.URL

	// String that will be set as an user agent with current library version appended to it
	UserAgent string

	// Save HTTP Response Headers
	LastResponseHeaders http.Header

	//Set debug flag to decide whether to return headers or not
	DebugFlag bool

	// Client token used for authenticating requests made to the RF
	clientToken string

	// Send telemetry with each API request using the user agent
	// this is used by Rainforest (and not shared or sold) to make
	// integrations better. See README for more details.
	SendTelemetry bool
}

// NewClient constructs a new rainforest API Client. As a parameter takes client token
// which is used for authentication and is available in the rainforest web app.
func NewClient(token string, debug bool) *Client {
	var baseURL *url.URL
	var err error
	if envURL := os.Getenv("RAINFOREST_API_URL"); envURL != "" {
		baseURL, err = url.Parse(envURL)
		if err != nil {
			log.Fatalf("Invalid URL set in $RAINFOREST_API_URL=%v", envURL)
		}
	} else {
		baseURL, _ = url.Parse(currentBaseURL)
	}

	return &Client{
		client:              http.DefaultClient,
		BaseURL:             baseURL,
		clientToken:         token,
		LastResponseHeaders: http.Header{},
		DebugFlag:           debug,
	}
}

// ClientToken returns the API authentication token for the client.
func (c *Client) ClientToken() string {
	return c.clientToken
}

// NewRequest creates an API request. Provided url will be resolved using ResolveReference,
// which works in a similar way to the hrefs in a browser (most important takeaway is to
// not add preceeding slash to the link as it resolves to a root path of domain).
// The body argument is JSON endoded and attached as a request body.
// This function also attaches auth token from the client to the request.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	// Resolve the relative URL path
	relPath, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}
	endpointURL := c.BaseURL.ResolveReference(relPath)

	// Create buffer and fill it with body data encoded in JSON
	var b io.ReadWriter
	if body != nil {
		b = new(bytes.Buffer)
		err = json.NewEncoder(b).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	// Create new http request and set the headers
	req, err := http.NewRequest(method, endpointURL.String(), b)
	if err != nil {
		return nil, err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	// Set the auth token to the one specified in the client
	if c.clientToken != "" {
		req.Header.Set(authTokenHeader, c.clientToken)
	} else {
		return nil, errors.New("Please provide your API Token with the RAINFOREST_API_TOKEN environment variable or --token global flag")
	}

	// Set UserAgent header with appended library version, will look like:
	// "rainforest-cli/2.1.0 [rainforest golang lib/2.0.0]"
	userAgent := []string{"rainforest", "golang", "lib/" + libVersion}

	if c.SendTelemetry {
		found, ci_name := detectci.WhichCI()
		if found {
			userAgent = append(userAgent, "ci/"+ci_name)
		}

		var remote string
		git, err := gitTrigger.NewGitTrigger()
		if err == nil {
			remote, err = git.GetRemote()
			if err == nil {
				u, err := giturls.Parse(remote)
				if err == nil {
					// Strip the user details, if any
					u.User = nil
					userAgent = append(userAgent, "repo/"+u.String())
				}
			}
		}
	}

	composedUserAgent := c.UserAgent + " [" + strings.Join(userAgent[:], " ") + "]"
	req.Header.Set("User-Agent", composedUserAgent)

	return req, nil
}

// checkResponse checks if we received vaild response with code 200,
// returns error otherwise
func checkResponse(res *http.Response, debugFlag bool) error {
	// Log any deprecation warnings returned by the backend
	if deprecations := res.Header.Get("X-RF-Deprecation"); deprecations != "" {
		for _, deprecation := range strings.Split(deprecations, "\n") {
			fmt.Println("RF API Deprecation:", deprecation)
		}
	}

	// If we are on a happy path just return nil
	if res.StatusCode >= 200 && res.StatusCode < 300 {
		return nil
	}

	errPrefix := fmt.Sprintf("RF API Error (%v)", res.StatusCode)

	// Otherwise we return error from the API or general one if we can't decode it
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return errors.New(errPrefix + " - Unable to read response: " + err.Error())
	}

	if contentType := res.Header.Get("Content-Type"); contentType != "application/json" {
		// Just print out the response body as a string
		return errors.New(errPrefix + ":\n" + string(body))
	}

	var out struct {
		Err string `json:"error"`
	}
	err = json.Unmarshal(body, &out)
	if err != nil {
		if debugFlag {
			fmt.Println("Cannot parse response:\n" + string(body))
		}

		return errors.New(errPrefix + " - Unable to parse response JSON: " + err.Error())
	}

	return errors.New(errPrefix + ": " + out.Err)
}

// Do sends out the request to the API and unpacks JSON response to the out variable.
func (c *Client) Do(req *http.Request, out interface{}) (*http.Response, error) {
	// Send out http request
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	if c.DebugFlag {
		log.Print("Trying ", res.Request.URL, "...")
	}

	// We check response for potential errors and return them to the caller.
	// We do not nil the response, as a caller might want to inspect the response in case of an error.
	err = checkResponse(res, c.DebugFlag)
	if err != nil {
		return res, err
	}

	// Here we check for the out pointer, and if it exists we unmarshall JSON there and return any
	// potential errors to the caller.
	if out != nil {
		// Close the body after we're done with it, to allow connection reuse.
		defer func() {
			io.Copy(ioutil.Discard, res.Body)
			res.Body.Close()
		}()

		err = json.NewDecoder(res.Body).Decode(out)

		if err != nil {
			log.Println("ERROR for ", req.Method, req.URL)
			log.Printf("ERROR PARSING JSON : %v\n\n", err.Error())
			return res, err
		}
	}

	c.LastResponseHeaders = res.Header

	if c.DebugFlag {
		log.Println("connected")
		printRequestHeaders(res)
	}

	return res, err
}

func printRequestHeaders(res *http.Response) {
	log.Println(res.Request.Method, res.Request.Proto)
	log.Println("User Agent:", res.Request.UserAgent())
	log.Println("Host:", res.Request.Host)
	log.Println("")
}
<commit_msg>RF-24889 Fix typo<commit_after>// Package rainforest is a golang client for the Rainforest QA API
package rainforest

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/rainforestapp/rainforest-cli/gittrigger"
	"github.com/ukd1/go.detectci"
	"github.com/whilp/git-urls"
)

const (
	// Version of the lib in SemVer
	libVersion = "2.0.1"

	currentBaseURL  = "https://app.rainforestqa.com/api/1/"
	authTokenHeader = "CLIENT_TOKEN"
)

// Client is responsible for communicating with Rainforest API
type Client struct {
	// http client used to connect to the Rainforest API
	client *http.Client

	// URL of a Rainforest API endpoint to be used by the client
	BaseURL *url.URL

	// String that will be set as an user agent with current library version appended to it
	UserAgent string

	// Save HTTP Response Headers
	LastResponseHeaders http.Header

	//Set debug flag to decide whether to return headers or not
	DebugFlag bool

	// Client token used for authenticating requests made to the RF
	clientToken string

	// Send telemetry with each API request using the user agent
	// this is used by Rainforest (and not shared or sold) to make
	// integrations better. See README for more details.
	SendTelemetry bool
}

// NewClient constructs a new rainforest API Client. As a parameter takes client token
// which is used for authentication and is available in the rainforest web app.
func NewClient(token string, debug bool) *Client {
	var baseURL *url.URL
	var err error
	if envURL := os.Getenv("RAINFOREST_API_URL"); envURL != "" {
		baseURL, err = url.Parse(envURL)
		if err != nil {
			log.Fatalf("Invalid URL set in $RAINFOREST_API_URL=%v", envURL)
		}
	} else {
		baseURL, _ = url.Parse(currentBaseURL)
	}

	return &Client{
		client:              http.DefaultClient,
		BaseURL:             baseURL,
		clientToken:         token,
		LastResponseHeaders: http.Header{},
		DebugFlag:           debug,
	}
}

// ClientToken returns the API authentication token for the client.
func (c *Client) ClientToken() string {
	return c.clientToken
}

// NewRequest creates an API request. Provided url will be resolved using ResolveReference,
// which works in a similar way to the hrefs in a browser (most important takeaway is to
// not add preceeding slash to the link as it resolves to a root path of domain).
// The body argument is JSON encoded and attached as a request body.
// This function also attaches auth token from the client to the request.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	// Resolve the relative URL path
	relPath, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}
	endpointURL := c.BaseURL.ResolveReference(relPath)

	// Create buffer and fill it with body data encoded in JSON
	var b io.ReadWriter
	if body != nil {
		b = new(bytes.Buffer)
		err = json.NewEncoder(b).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	// Create new http request and set the headers
	req, err := http.NewRequest(method, endpointURL.String(), b)
	if err != nil {
		return nil, err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	// Set the auth token to the one specified in the client
	if c.clientToken != "" {
		req.Header.Set(authTokenHeader, c.clientToken)
	} else {
		return nil, errors.New("Please provide your API Token with the RAINFOREST_API_TOKEN environment variable or --token global flag")
	}

	// Set UserAgent header with appended library version, will look like:
	// "rainforest-cli/2.1.0 [rainforest golang lib/2.0.0]"
	userAgent := []string{"rainforest", "golang", "lib/" + libVersion}

	if c.SendTelemetry {
		found, ci_name := detectci.WhichCI()
		if found {
			userAgent = append(userAgent, "ci/"+ci_name)
		}

		var remote string
		git, err := gitTrigger.NewGitTrigger()
		if err == nil {
			remote, err = git.GetRemote()
			if err == nil {
				u, err := giturls.Parse(remote)
				if err == nil {
					// Strip the user details, if any
					u.User = nil
					userAgent = append(userAgent, "repo/"+u.String())
				}
			}
		}
	}

	composedUserAgent := c.UserAgent + " [" + strings.Join(userAgent[:], " ") + "]"
	req.Header.Set("User-Agent", composedUserAgent)

	return req, nil
}

// checkResponse checks if we received vaild response with code 200,
// returns error otherwise
func checkResponse(res *http.Response, debugFlag bool) error {
	// Log any deprecation warnings returned by the backend
	if deprecations := res.Header.Get("X-RF-Deprecation"); deprecations != "" {
		for _, deprecation := range strings.Split(deprecations, "\n") {
			fmt.Println("RF API Deprecation:", deprecation)
		}
	}

	// If we are on a happy path just return nil
	if res.StatusCode >= 200 && res.StatusCode < 300 {
		return nil
	}

	errPrefix := fmt.Sprintf("RF API Error (%v)", res.StatusCode)

	// Otherwise we return error from the API or general one if we can't decode it
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return errors.New(errPrefix + " - Unable to read response: " + err.Error())
	}

	if contentType := res.Header.Get("Content-Type"); contentType != "application/json" {
		// Just print out the response body as a string
		return errors.New(errPrefix + ":\n" + string(body))
	}

	var out struct {
		Err string `json:"error"`
	}
	err = json.Unmarshal(body, &out)
	if err != nil {
		if debugFlag {
			fmt.Println("Cannot parse response:\n" + string(body))
		}

		return errors.New(errPrefix + " - Unable to parse response JSON: " + err.Error())
	}

	return errors.New(errPrefix + ": " + out.Err)
}

// Do sends out the request to the API and unpacks JSON response to the out variable.
func (c *Client) Do(req *http.Request, out interface{}) (*http.Response, error) {
	// Send out http request
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	if c.DebugFlag {
		log.Print("Trying ", res.Request.URL, "...")
	}

	// We check response for potential errors and return them to the caller.
	// We do not nil the response, as a caller might want to inspect the response in case of an error.
	err = checkResponse(res, c.DebugFlag)
	if err != nil {
		return res, err
	}

	// Here we check for the out pointer, and if it exists we unmarshall JSON there and return any
	// potential errors to the caller.
	if out != nil {
		// Close the body after we're done with it, to allow connection reuse.
		defer func() {
			io.Copy(ioutil.Discard, res.Body)
			res.Body.Close()
		}()

		err = json.NewDecoder(res.Body).Decode(out)

		if err != nil {
			log.Println("ERROR for ", req.Method, req.URL)
			log.Printf("ERROR PARSING JSON : %v\n\n", err.Error())
			return res, err
		}
	}

	c.LastResponseHeaders = res.Header

	if c.DebugFlag {
		log.Println("connected")
		printRequestHeaders(res)
	}

	return res, err
}

func printRequestHeaders(res *http.Response) {
	log.Println(res.Request.Method, res.Request.Proto)
	log.Println("User Agent:", res.Request.UserAgent())
	log.Println("Host:", res.Request.Host)
	log.Println("")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math"
)

func main() {

	num := 16.0
	upperBound := 10.0

	// The syntax is also slightly different: there are no parentheses and
	// the bodies must always be brace-delimited.
	if num > upperBound {
		fmt.Printf("number is %f\n", num)
	}

	// If with a short statement.
	// NOTE: the scope of variable sqr is limited the enclosing if statement
	// body
	if sqr := math.Sqrt(num); sqr < upperBound {
		fmt.Printf("Square root of %f is %f\n", num, sqr)
	}

	// uncomment this line to verify that teh variable sqr is not visible
	// outside the previous defined if block
	//fmt.Printf("square root of .2f is %.2f\n", sqr, num)

	// if and else
	if num < upperBound {
		fmt.Printf("%.2f < %.2f\n", num, upperBound)
	} else if num == upperBound {
		fmt.Printf("%.2f == %.2f\n", num, upperBound)
	} else {
		fmt.Printf("%.2f > %.2f\n", num, upperBound)
	}
}
<commit_msg>update the if sample<commit_after>package main

import (
	"fmt"
	"math"
)

func main() {

	num := 16.0
	upperBound := 10.0

	// The syntax is also slightly different: there are no parentheses and
	// the bodies must always be brace-delimited.
	if num > upperBound {
		fmt.Printf("number is %f\n", num)
	}

	// If with a short statement.
	// NOTE: the scope of variable sqr is limited the enclosing if statement
	// body
	if sqr := math.Sqrt(num); sqr < upperBound {
		fmt.Printf("Square root of %f is %f\n", num, sqr)
	}

	// uncomment this line to verify that teh variable sqr is not visible
	// outside the previous defined if block
	//fmt.Printf("square root of .2f is %.2f\n", sqr, num)

	// NOTE that its idiomatic to write an if-else-if-else chain as a
	// switch instead.
	if num < upperBound {
		fmt.Printf("%.2f < %.2f\n", num, upperBound)
	} else if num == upperBound {
		fmt.Printf("%.2f == %.2f\n", num, upperBound)
	} else {
		fmt.Printf("%.2f > %.2f\n", num, upperBound)
	}
}
<|endoftext|>